diff --git a/src/constant/message.ts b/src/constant/message.ts index 337d905..26a6ff0 100644 --- a/src/constant/message.ts +++ b/src/constant/message.ts @@ -1,4 +1,7 @@ export const Message = { + // 401 Unauthorized + TOKEN_REQUIRED: 'Token is required', + // 403 Forbidden CANNOT_CREATE_GROUP: 'Cannot create group', CANNOT_MODIFY_GROUP: 'Cannot modify group', diff --git a/src/core/auth/AuthGuard.ts b/src/core/auth/AuthGuard.ts new file mode 100644 index 0000000..e56716b --- /dev/null +++ b/src/core/auth/AuthGuard.ts @@ -0,0 +1,43 @@ +import { Request } from 'express'; +import { + CanActivate, + ExecutionContext, + Inject, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; + +import { ITokenVerifier, TokenVerifier } from '@sight/core/auth/ITokenVerifier'; + +import { Message } from '@sight/constant/message'; +import { ClsService } from 'nestjs-cls'; + +@Injectable() +export class AuthGuard implements CanActivate { + constructor( + @Inject(TokenVerifier) + private readonly tokenVerifier: ITokenVerifier, + private readonly clsService: ClsService, + ) {} + + canActivate(context: ExecutionContext): boolean { + const req: Request = context.switchToHttp().getRequest(); + const authorizationHeader = req.headers['authorization']; + + if (!authorizationHeader) { + throw new UnauthorizedException(Message.TOKEN_REQUIRED); + } + + const token = authorizationHeader.split(' ')[1]; + if (!token) { + throw new UnauthorizedException(Message.TOKEN_REQUIRED); + } + + const requester = this.tokenVerifier.verify(token); + req['requester'] = requester; + + this.clsService.set('requester', requester); + + return true; + } +} diff --git a/src/core/auth/AuthModule.ts b/src/core/auth/AuthModule.ts new file mode 100644 index 0000000..c6cfe08 --- /dev/null +++ b/src/core/auth/AuthModule.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { APP_GUARD } from '@nestjs/core'; + +import { AuthGuard } from '@sight/core/auth/AuthGuard'; + +@Module({ + providers: [ + { provide: APP_GUARD, useClass: AuthGuard }, + // TODO: TokenVerifier 구현 후 추가 + ], +}) +export class AuthModule {} diff --git a/src/core/auth/IRequester.ts b/src/core/auth/IRequester.ts new file mode 100644 index 0000000..45dec46 --- /dev/null +++ b/src/core/auth/IRequester.ts @@ -0,0 +1,6 @@ +import { UserRole } from '@sight/core/auth/UserRole'; + +export interface IRequester { + userId: string; + role: UserRole; +} diff --git a/src/core/auth/ITokenVerifier.ts b/src/core/auth/ITokenVerifier.ts new file mode 100644 index 0000000..766e0de --- /dev/null +++ b/src/core/auth/ITokenVerifier.ts @@ -0,0 +1,7 @@ +import { IRequester } from '@sight/core/auth/IRequester'; + +export const TokenVerifier = Symbol('TokenVerifier'); + +export interface ITokenVerifier { + verify: (token: string) => IRequester; +} diff --git a/src/core/auth/Requester.ts b/src/core/auth/Requester.ts new file mode 100644 index 0000000..42374ed --- /dev/null +++ b/src/core/auth/Requester.ts @@ -0,0 +1,5 @@ +import { createParamDecorator } from '@nestjs/common'; + +export const Requester = createParamDecorator((data, req) => { + return req['requester']; +}); diff --git a/src/core/auth/UserRole.ts b/src/core/auth/UserRole.ts new file mode 100644 index 0000000..f1043a1 --- /dev/null +++ b/src/core/auth/UserRole.ts @@ -0,0 +1,5 @@ +export const UserRole = { + USER: 'USER', + ADMIN: 'ADMIN', +} as const; +export type UserRole = (typeof UserRole)[keyof typeof UserRole]; diff --git a/src/core/persistence/transaction/TransactionMiddleware.ts b/src/core/persistence/transaction/TransactionMiddleware.ts deleted file mode 100644 index 8dd19c4..0000000 --- a/src/core/persistence/transaction/TransactionMiddleware.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { EntityManager } from '@mikro-orm/core'; -import { Injectable, NestMiddleware } from '@nestjs/common'; -import { NextFunction } from 'express'; -import { ClsService } from 'nestjs-cls'; - -import { TRANSACTIONAL_ENTITY_MANAGER } from '@sight/core/persistence/transaction/constant'; - -@Injectable() -export class TransactionMiddleware implements NestMiddleware { - constructor( - private readonly cls: ClsService, - private readonly em: EntityManager, - ) {} - - use(request: Request, response: Response, next: NextFunction) { - this.cls.set(TRANSACTIONAL_ENTITY_MANAGER, this.em); - next(); - } -} diff --git a/src/core/persistence/transaction/TransactionModule.ts b/src/core/persistence/transaction/TransactionModule.ts index 7e3e886..0e308c9 100644 --- a/src/core/persistence/transaction/TransactionModule.ts +++ b/src/core/persistence/transaction/TransactionModule.ts @@ -1,25 +1,15 @@ -import { - MiddlewareConsumer, - Module, - NestModule, - OnModuleInit, -} from '@nestjs/common'; +import { Module, OnModuleInit } from '@nestjs/common'; import { DiscoveryModule } from '@nestjs/core'; import { TransactionalApplier } from '@sight/core/persistence/transaction/TransactionalApplier'; -import { TransactionMiddleware } from '@sight/core/persistence/transaction/TransactionMiddleware'; @Module({ imports: [DiscoveryModule], providers: [TransactionalApplier], }) -export class TransactionModule implements NestModule, OnModuleInit { +export class TransactionModule implements OnModuleInit { constructor(private readonly transactionalApplier: TransactionalApplier) {} - configure(consumer: MiddlewareConsumer) { - consumer.apply(TransactionMiddleware).forRoutes('*'); - } - onModuleInit() { this.transactionalApplier.bindTransactional(); } diff --git a/src/core/persistence/transaction/TransactionalApplier.ts b/src/core/persistence/transaction/TransactionalApplier.ts index 04b3a18..15f8cbe 100644 --- a/src/core/persistence/transaction/TransactionalApplier.ts +++ b/src/core/persistence/transaction/TransactionalApplier.ts @@ -1,5 +1,5 @@ import { EntityManager } from '@mikro-orm/core'; -import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { ICommandHandler, IEventHandler } from '@nestjs/cqrs'; import { ClsService } from 'nestjs-cls'; @@ -36,25 +36,14 @@ export class TransactionalApplier { private createWrappedFunction(originalFn: AsyncFn, transaction: boolean) { const wrapper = async (...args: any[]) => { - const entityManager: EntityManager | undefined = this.cls.get( - TRANSACTIONAL_ENTITY_MANAGER, - ); - if (!entityManager) { - throw new InternalServerErrorException('Entity manager is not exists'); - } - if (transaction) { - return await entityManager.transactional(async (manager) => { - return await this.cls.runWith( - { [TRANSACTIONAL_ENTITY_MANAGER]: manager }, - () => originalFn(args), - ); + return await this.em.transactional(async (manager) => { + this.cls.set(TRANSACTIONAL_ENTITY_MANAGER, manager); + return await originalFn(args); }); } else { - return await this.cls.runWith( - { [TRANSACTIONAL_ENTITY_MANAGER]: this.em }, - () => originalFn(args), - ); + this.cls.set(TRANSACTIONAL_ENTITY_MANAGER, this.em); + return await originalFn(args); } }; Object.setPrototypeOf(wrapper, originalFn);