import { Inject, Injectable, MiddlewareConsumer, Module, NestModule, SetMetadata, UnauthorizedException } from '@nestjs/common'; import { DynamicModule, NestMiddleware, RouteInfo } from '@nestjs/common/interfaces'; import { APP_GUARD, Reflector } from '@nestjs/core'; import { CHeaderFields, EEnvConfig, ENextaPermission } from './constant.config'; import { AsyncLocalStorage } from 'async_hooks'; import { AuthContext, AuthType } from './interface'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { AsyncLocalStorageModule } from './AsyncLocalStorageModule'; import { NextFunction, Request, Response } from 'express'; import { RolesGuard } from './guards'; export interface MiddlewareModuleOptions { excludePaths?: (string | RouteInfo)[]; } export const MIDDLEWARE_MODULE_OPTIONS_PROVIDER = 'MIDDLEWARE_MODULE_OPTIONS_PROVIDER'; @Injectable() export class AuthContextMiddleware implements NestMiddleware { constructor( private readonly als: AsyncLocalStorage, private readonly configService: ConfigService, ) { } use(req: Request, res: Response, next: NextFunction) { const [type, token] = (req.headers as any).authorization?.split(' ') || ['', '']; let authData: AuthType | null = null; const tokenString = Buffer.from(token, 'base64').toString(); if (type === 'Basic') { const basicInternalToken = `${this.configService.get(EEnvConfig.INTERNAL_USERNAME)}:${this.configService.get(EEnvConfig.INTERNAL_PASSWORD)}`; const basicClientToken = `${this.configService.get(EEnvConfig.CLIENT_USERNAME)}:${this.configService.get(EEnvConfig.CLIENT_PASSWORD)}`; if (tokenString === basicInternalToken) { authData = AuthType.INTERNAL; } if (tokenString === basicClientToken) { authData = AuthType.CLIENT; } } if (!authData) next(new UnauthorizedException('Token invalid!')); const authContext = new AuthContext( req.header(CHeaderFields.USER_ID) ?? '', req.header(CHeaderFields.WORKSPACE_ID) ?? '', req.header(CHeaderFields.WORKSPACE_TYPE) ?? '', req.headers[CHeaderFields.AUTHORITIES] as string, authData ?? undefined, ); this.als.run(authContext, () => { next(); }); } } @Module({ imports: [AsyncLocalStorageModule, ConfigModule], controllers: [], providers: [ { provide: APP_GUARD, useClass: RolesGuard, }, { provide: MIDDLEWARE_MODULE_OPTIONS_PROVIDER, useValue: {}, }, ], exports: [], }) export class MiddlewareModule implements NestModule { private options: MiddlewareModuleOptions; constructor(@Inject(MIDDLEWARE_MODULE_OPTIONS_PROVIDER) private middlewareModuleOptions: MiddlewareModuleOptions) { this.options = middlewareModuleOptions; this.options.excludePaths = this.options.excludePaths || []; } static forRoot(options: MiddlewareModuleOptions): DynamicModule { return { module: MiddlewareModule, providers: [ { provide: APP_GUARD, useClass: RolesGuard, }, { provide: MIDDLEWARE_MODULE_OPTIONS_PROVIDER, useValue: options, }, Reflector, ], exports: [MIDDLEWARE_MODULE_OPTIONS_PROVIDER], imports: [AsyncLocalStorageModule], }; } configure(consumer: MiddlewareConsumer) { consumer .apply(AuthContextMiddleware) .exclude(...this.options.excludePaths!) .forRoutes('*'); } }