move session to module

This commit is contained in:
Evert Prants 2022-09-17 10:13:52 +03:00
parent ff431c958c
commit 9c7820dc94
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
15 changed files with 227 additions and 142 deletions

View File

@ -1,9 +1,6 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as dotenv from 'dotenv';
import * as session from 'express-session';
import * as connectRedis from 'connect-redis';
import * as redis from 'redis';
import * as cookieParser from 'cookie-parser';
import { join } from 'path';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
@ -11,6 +8,7 @@ import { NestExpressApplication } from '@nestjs/platform-express';
import { AdminApiModule } from './modules/api/admin/admin.module';
import { OAuth2RouterModule } from './modules/static-front-end/oauth2-router/oauth2-router.module';
import { ConfigurationService } from './modules/config/config.service';
import { ApiModule } from './modules/api/api.module';
dotenv.config();
@ -23,20 +21,33 @@ async function bootstrap() {
.setTitle('Icy Network Authentication Server')
.setDescription('Central authentication and management server')
.setVersion('1.0')
.addTag('api')
.addTag('admin')
.addTag('oauth2')
.addBearerAuth()
.addOAuth2({
type: 'oauth2',
flows: {
authorizationCode: {
authorizationUrl: '/oauth2/authorize',
tokenUrl: '/oauth2/token',
scopes: {
email: 'Access email address',
image: 'Access profile picture',
privileges: 'List of user privileges',
management: 'Administrative access',
openid: 'Get ID token',
},
},
},
})
.build();
const document = SwaggerModule.createDocument(app, docBuilder, {
include: [AdminApiModule, OAuth2RouterModule],
});
SwaggerModule.setup('api', app, document);
const RedisStore = connectRedis(session);
const redisClient = redis.createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379',
legacyMode: true,
const document = SwaggerModule.createDocument(app, docBuilder, {
include: [ApiModule, AdminApiModule, OAuth2RouterModule],
});
redisClient.connect();
SwaggerModule.setup('api/openapi', app, document);
// app.use(express.urlencoded());
app.use(cookieParser());
@ -47,21 +58,6 @@ async function bootstrap() {
app.disable('x-powered-by');
}
app.use(
/\/((?!api).)*/,
session({
name: config.get<string>('app.session_name'),
secret: config.get<string>('app.session_secret'),
resave: true,
saveUninitialized: false,
store: new RedisStore({ client: redisClient }),
cookie: {
sameSite: 'strict',
secure: process.env.NODE_ENV === 'production',
},
}),
);
app.useStaticAssets(join(__dirname, '..', 'public'), {
prefix: '/public/',
});

View File

@ -3,7 +3,6 @@ import { MulterModule } from '@nestjs/platform-express';
import * as multer from 'multer';
import * as mime from 'mime-types';
import { join } from 'path';
import { ConfigurationService } from '../../config/config.service';
import { OAuth2Module } from 'src/modules/oauth2/oauth2.module';
import { ObjectsModule } from 'src/modules/objects/objects.module';
import { OAuth2AdminController } from './oauth2-admin.controller';
@ -23,37 +22,34 @@ import { AuditAdminController } from './audit-admin.controller';
imports: [
ObjectsModule,
OAuth2Module,
ConfigurationModule,
MulterModule.registerAsync({
imports: [ConfigurationModule],
useFactory: async () => {
return {
storage: multer.diskStorage({
destination: (req, file, cb) => {
cb(null, join(__dirname, '..', '..', '..', '..', 'uploads'));
},
filename: (req, file, cb) => {
const hashTruncate = req.user.uuid.split('-')[0];
const timestamp = Math.floor(Date.now() / 1000);
const ext = mime.extension(file.mimetype);
cb(null, `app-${hashTruncate}-${timestamp}.${ext}`);
},
}),
limits: {
fileSize: 1.049e7, // 10 MiB
useFactory: async () => ({
storage: multer.diskStorage({
destination: (req, file, cb) => {
cb(null, join(__dirname, '..', '..', '..', '..', 'uploads'));
},
fileFilter: (req, file, cb) => {
if (
!file.mimetype.startsWith('image/') ||
file.mimetype.includes('svg')
) {
return cb(new Error('Invalid file type.'), false);
}
filename: (req, file, cb) => {
const hashTruncate = req.user.uuid.split('-')[0];
const timestamp = Math.floor(Date.now() / 1000);
const ext = mime.extension(file.mimetype);
cb(null, `app-${hashTruncate}-${timestamp}.${ext}`);
},
}),
limits: {
fileSize: 1.049e7, // 10 MiB
},
fileFilter: (req, file, cb) => {
if (
!file.mimetype.startsWith('image/') ||
file.mimetype.includes('svg')
) {
return cb(new Error('Invalid file type.'), false);
}
cb(null, true);
},
};
},
inject: [ConfigurationService],
cb(null, true);
},
}),
}),
],
providers: [AdminService],

View File

@ -1,5 +1,5 @@
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiOAuth2, ApiTags } from '@nestjs/swagger';
import { Privileges } from 'src/decorators/privileges.decorator';
import { Scopes } from 'src/decorators/scopes.decorator';
import { OAuth2Guard } from 'src/guards/oauth2.guard';
@ -14,6 +14,7 @@ import { PageOptions } from 'src/types/pagination.interfaces';
@ApiBearerAuth()
@ApiTags('admin')
@ApiOAuth2(['management'])
@Controller('/api/admin/audit')
@UseGuards(OAuth2Guard, PrivilegesGuard, ScopesGuard)
export class AuditAdminController {

View File

@ -15,7 +15,7 @@ import {
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiOAuth2, ApiTags } from '@nestjs/swagger';
import { FileInterceptor } from '@nestjs/platform-express';
import { unlink } from 'fs/promises';
import { Privileges } from 'src/decorators/privileges.decorator';
@ -57,6 +57,7 @@ const REQUIRED_CLIENT_FIELDS = ['title', 'grants', 'activated'];
@ApiBearerAuth()
@ApiTags('admin')
@ApiOAuth2(['management'])
@Controller('/api/admin/oauth2')
@UseGuards(OAuth2Guard, PrivilegesGuard, ScopesGuard)
export class OAuth2AdminController {

View File

@ -6,7 +6,7 @@ import {
Post,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiOAuth2, ApiTags } from '@nestjs/swagger';
import { Privileges } from 'src/decorators/privileges.decorator';
import { Scopes } from 'src/decorators/scopes.decorator';
import { OAuth2Guard } from 'src/guards/oauth2.guard';
@ -16,6 +16,7 @@ import { PrivilegeService } from 'src/modules/objects/privilege/privilege.servic
@ApiBearerAuth()
@ApiTags('admin')
@ApiOAuth2(['management'])
@Controller('/api/admin/privileges')
@UseGuards(OAuth2Guard, PrivilegesGuard, ScopesGuard)
export class PrivilegeAdminController {

View File

@ -12,7 +12,7 @@ import {
Query,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiOAuth2, ApiTags } from '@nestjs/swagger';
import { Privileges } from 'src/decorators/privileges.decorator';
import { Scopes } from 'src/decorators/scopes.decorator';
import { OAuth2Guard } from 'src/guards/oauth2.guard';
@ -29,6 +29,7 @@ const RELATIONS = ['picture', 'privileges'];
@ApiBearerAuth()
@ApiTags('admin')
@ApiOAuth2(['management'])
@Controller('/api/admin/users')
@UseGuards(OAuth2Guard, PrivilegesGuard, ScopesGuard)
export class UserAdminController {

View File

@ -19,7 +19,9 @@ import { OAuth2ClientService } from '../objects/oauth2-client/oauth2-client.serv
import { User } from '../objects/user/user.entity';
import { FormUtilityService } from '../utility/services/form-utility.service';
import { UploadService } from '../objects/upload/upload.service';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('api')
@Controller({
path: '/api',
})
@ -31,7 +33,22 @@ export class ApiController {
private _upload: UploadService,
) {}
@Get()
welcome() {
return {
welcome: { to: 'Icy Network!' },
api: true,
oauth2: {
authorizationUrl: '/oauth2/authorize',
tokenUrl: '/oauth2/token',
userUrl: '/api/user',
},
swaggerdoc: '/api/openapi',
};
}
@Get('/user')
@ApiBearerAuth()
@UseGuards(OAuth2Guard)
getCurrentUser(@CurrentUser() user: User, @Scope() scopeStr: string) {
const scope = scopeStr.split(' ');
@ -73,6 +90,7 @@ export class ApiController {
}
@Get('/client')
@ApiBearerAuth()
@UseGuards(OAuth2Guard, ScopesGuard)
async getCurrentClient(@Bearer() token: OAuth2AccessToken) {
const client = await this._oaClient.getById(token.client_id, [
@ -90,6 +108,7 @@ export class ApiController {
}
@Get('/upload/:file')
@ApiBearerAuth()
@UseGuards(OAuth2Guard)
async sendFile(@Param('file') fileName: string) {
const cleanFile = decodeURI(fileName).replace(/(\&(.*))/, '');

View File

@ -1,9 +1,5 @@
import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from '@nestjs/common';
import { Inject, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { RequestHandler } from 'express';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { FlashMiddleware } from 'src/middleware/flash.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
@ -11,19 +7,25 @@ import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware'
import { AuditModule } from 'src/modules/objects/audit/audit.module';
import { UserTokenModule } from 'src/modules/objects/user-token/user-token.module';
import { UserModule } from 'src/modules/objects/user/user.module';
import { SessionModule } from '../session/session.module';
import { LoginController } from './login.controller';
@Module({
imports: [UserModule, UserTokenModule, AuditModule],
imports: [UserModule, UserTokenModule, AuditModule, SessionModule],
controllers: [LoginController],
})
export class LoginModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(CSRFMiddleware, UserMiddleware).forRoutes(LoginController);
consumer
.apply(ValidateCSRFMiddleware)
.forRoutes({ path: 'login*', method: RequestMethod.POST });
constructor(@Inject('SESSION') private _session: RequestHandler) {}
consumer.apply(FlashMiddleware).forRoutes('login*');
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
this._session,
CSRFMiddleware,
FlashMiddleware,
UserMiddleware,
ValidateCSRFMiddleware,
)
.forRoutes(LoginController);
}
}

View File

@ -1,4 +1,5 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { Inject, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { RequestHandler } from 'express';
// import * as cors from 'cors';
import { AuthMiddleware } from 'src/middleware/auth.middleware';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
@ -7,26 +8,31 @@ import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware'
import { OAuth2Module } from 'src/modules/oauth2/oauth2.module';
import { OAuth2Service } from 'src/modules/oauth2/oauth2.service';
import { UserModule } from 'src/modules/objects/user/user.module';
import { SessionModule } from '../session/session.module';
import { OAuth2Controller } from './oauth2-router.controller';
@Module({
controllers: [OAuth2Controller],
imports: [OAuth2Module, UserModule],
imports: [OAuth2Module, UserModule, SessionModule],
})
export class OAuth2RouterModule implements NestModule {
// private corsOpts = cors({ origin: true, credentials: true });
constructor(private _service: OAuth2Service) {}
constructor(
private _service: OAuth2Service,
@Inject('SESSION') private _session: RequestHandler,
) {}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
this._session,
CSRFMiddleware,
UserMiddleware,
AuthMiddleware,
ValidateCSRFMiddleware,
)
.forRoutes('oauth2/authorize');
.forRoutes('/oauth2/authorize');
// consumer
// .apply(this.corsOpts)

View File

@ -1,31 +1,30 @@
import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from '@nestjs/common';
import { Inject, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { RequestHandler } from 'express';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { FlashMiddleware } from 'src/middleware/flash.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware';
import { AuditModule } from 'src/modules/objects/audit/audit.module';
import { UserModule } from 'src/modules/objects/user/user.module';
import { SessionModule } from '../session/session.module';
import { RegisterController } from './register.controller';
@Module({
imports: [UserModule, AuditModule],
imports: [UserModule, AuditModule, SessionModule],
controllers: [RegisterController],
})
export class RegisterModule implements NestModule {
constructor(@Inject('SESSION') private _session: RequestHandler) {}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(CSRFMiddleware, UserMiddleware)
.apply(
this._session,
CSRFMiddleware,
FlashMiddleware,
UserMiddleware,
ValidateCSRFMiddleware,
)
.forRoutes(RegisterController);
consumer
.apply(ValidateCSRFMiddleware)
.forRoutes({ path: 'register*', method: RequestMethod.POST });
consumer.apply(FlashMiddleware).forRoutes('register*');
}
}

View File

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ConfigurationModule } from 'src/modules/config/config.module';
import { sessionProviders } from './session.providers';
@Module({
providers: [...sessionProviders],
exports: [...sessionProviders],
imports: [ConfigurationModule],
})
export class SessionModule {}

View File

@ -0,0 +1,49 @@
import { FactoryProvider } from '@nestjs/common';
import { ConfigurationService } from 'src/modules/config/config.service';
import * as session from 'express-session';
import * as connectRedis from 'connect-redis';
import * as redis from 'redis';
import { RequestHandler } from 'express';
export const sessionProviders = [
{
provide: 'SESSION_STORE',
useFactory: async (
config: ConfigurationService,
): Promise<connectRedis.RedisStore> => {
const RedisStore = connectRedis(session);
const redisClient = redis.createClient({
url:
process.env.REDIS_URL ||
config.get<string>('app.redis_url') ||
'redis://localhost:6379',
legacyMode: true,
});
await redisClient.connect();
return new RedisStore({ client: redisClient });
},
inject: [ConfigurationService],
} as FactoryProvider<connectRedis.RedisStore>,
{
provide: 'SESSION',
useFactory: (
store: connectRedis.RedisStore,
config: ConfigurationService,
) =>
session({
name: config.get<string>('app.session_name'),
secret: config.get<string>('app.session_secret'),
resave: true,
saveUninitialized: false,
store,
cookie: {
sameSite: 'strict',
secure: process.env.NODE_ENV === 'production',
},
}),
inject: ['SESSION_STORE', ConfigurationService],
} as FactoryProvider<RequestHandler>,
];

View File

@ -1,9 +1,4 @@
import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from '@nestjs/common';
import { Inject, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import * as multer from 'multer';
import * as mime from 'mime-types';
@ -12,7 +7,6 @@ import { AuthMiddleware } from 'src/middleware/auth.middleware';
import { FlashMiddleware } from 'src/middleware/flash.middleware';
import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware';
import { ConfigurationModule } from 'src/modules/config/config.module';
import { ConfigurationService } from 'src/modules/config/config.service';
import { UploadModule } from 'src/modules/objects/upload/upload.module';
import { UserModule } from 'src/modules/objects/user/user.module';
import { OAuth2TokenModule } from 'src/modules/objects/oauth2-token/oauth2-token.module';
@ -24,6 +18,8 @@ import { SettingsService } from './settings.service';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
import { AuditModule } from 'src/modules/objects/audit/audit.module';
import { RequestHandler } from 'express';
import { SessionModule } from '../session/session.module';
@Module({
controllers: [SettingsController],
@ -32,59 +28,55 @@ import { AuditModule } from 'src/modules/objects/audit/audit.module';
UploadModule,
UserModule,
UserTokenModule,
SessionModule,
AuditModule,
OAuth2Module,
OAuth2ClientModule,
OAuth2TokenModule,
MulterModule.registerAsync({
imports: [ConfigurationModule],
useFactory: async (config: ConfigurationService) => {
return {
storage: multer.diskStorage({
destination: (req, file, cb) => {
cb(null, join(__dirname, '..', '..', '..', '..', 'uploads'));
},
filename: (req, file, cb) => {
const hashTruncate = req.user.uuid.split('-')[0];
const timestamp = Math.floor(Date.now() / 1000);
const ext = mime.extension(file.mimetype);
cb(null, `user-${hashTruncate}-${timestamp}.${ext}`);
},
}),
limits: {
fileSize: 1.049e7, // 10 MiB
useFactory: async () => ({
storage: multer.diskStorage({
destination: (req, file, cb) => {
cb(null, join(__dirname, '..', '..', '..', '..', 'uploads'));
},
fileFilter: (req, file, cb) => {
if (
!file.mimetype.startsWith('image/') ||
file.mimetype.includes('svg')
) {
return cb(new Error('Invalid file type.'), false);
}
filename: (req, file, cb) => {
const hashTruncate = req.user.uuid.split('-')[0];
const timestamp = Math.floor(Date.now() / 1000);
const ext = mime.extension(file.mimetype);
cb(null, `user-${hashTruncate}-${timestamp}.${ext}`);
},
}),
limits: {
fileSize: 1.049e7, // 10 MiB
},
fileFilter: (req, file, cb) => {
if (
!file.mimetype.startsWith('image/') ||
file.mimetype.includes('svg')
) {
return cb(new Error('Invalid file type.'), false);
}
cb(null, true);
},
};
},
inject: [ConfigurationService],
cb(null, true);
},
}),
}),
],
providers: [SettingsService],
})
export class SettingsModule implements NestModule {
constructor(@Inject('SESSION') private _session: RequestHandler) {}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(CSRFMiddleware, UserMiddleware)
.apply(
this._session,
CSRFMiddleware,
FlashMiddleware,
UserMiddleware,
AuthMiddleware,
ValidateCSRFMiddleware,
)
.forRoutes(SettingsController);
consumer
.apply(ValidateCSRFMiddleware)
.forRoutes(
{ path: '/account*', method: RequestMethod.POST },
{ path: '/account*', method: RequestMethod.PATCH },
{ path: '/account*', method: RequestMethod.DELETE },
);
consumer.apply(AuthMiddleware).forRoutes('account*');
consumer.apply(FlashMiddleware).forRoutes('account*');
}
}

View File

@ -6,11 +6,13 @@ import { ObjectsModule } from '../objects/objects.module';
import { LoginModule } from './login/login.module';
import { OAuth2RouterModule } from './oauth2-router/oauth2-router.module';
import { RegisterModule } from './register/register.module';
import { SessionModule } from './session/session.module';
import { SettingsModule } from './settings/settings.module';
import { TwoFactorModule } from './two-factor/two-factor.module';
@Module({
imports: [
SessionModule,
ConfigurationModule,
JWTModule,

View File

@ -1,23 +1,33 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { Inject, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { RequestHandler } from 'express';
import { AuthMiddleware } from 'src/middleware/auth.middleware';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { FlashMiddleware } from 'src/middleware/flash.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware';
import { AuditModule } from 'src/modules/objects/audit/audit.module';
import { UserTokenModule } from 'src/modules/objects/user-token/user-token.module';
import { UserModule } from 'src/modules/objects/user/user.module';
import { SessionModule } from '../session/session.module';
import { TwoFactorController } from './two-factor.controller';
@Module({
imports: [UserModule, UserTokenModule, AuditModule],
imports: [UserModule, UserTokenModule, AuditModule, SessionModule],
controllers: [TwoFactorController],
})
export class TwoFactorModule implements NestModule {
constructor(@Inject('SESSION') private _session: RequestHandler) {}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(CSRFMiddleware, UserMiddleware)
.apply(
this._session,
CSRFMiddleware,
UserMiddleware,
AuthMiddleware,
FlashMiddleware,
ValidateCSRFMiddleware,
)
.forRoutes(TwoFactorController);
consumer.apply(AuthMiddleware).forRoutes('account/two-factor/activate');
consumer.apply(FlashMiddleware).forRoutes('account/two-factor/activate');
}
}