diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 49e481d..c2c79f3 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,44 +1,43 @@ import { Controller, Body, - Req, - Res, Post, UseGuards, Query, Get, BadRequestException, } from '@nestjs/common'; -import { Request, Response } from 'express'; -import { AuthGuard } from '@nestjs/passport'; import { AuthService } from './auth.service'; import { UserService } from 'src/user/user.service'; import { JwtPayload, SignupPayload } from 'src/interfaces/auth'; import { SignupGuard } from './guard/signup.guard'; import { Page } from 'src/common/enums/page.enum'; import { CreateUserDto, LoginDto } from 'src/dtos/user.dto'; +import { ApiTags } from '@nestjs/swagger'; import { - ApiCreatedResponse, - ApiHeader, - ApiOperation, - ApiQuery, - ApiTags, - ApiBody, - ApiBadRequestResponse, - ApiConflictResponse, - ApiNotAcceptableResponse, - ApiUnauthorizedResponse, - ApiRequestTimeoutResponse, - ApiNotFoundResponse, -} from '@nestjs/swagger'; -import { - SignupTokenResponseDto, TokenResponseDto, -} from './dtos/tokenResponseDto'; -import { SignupPhoneRequestDto } from './dtos/signupPhoneRequest.dto'; -import { SignupEmailRequestDto } from './dtos/signupEmailRequest.dto'; -import { SignupImageRequestDto } from './dtos/signupImageRequest.dto'; - + SignupPhoneRequestDto, + SignupEmailRequestDto, + SignupImageRequestDto, + SignupTokenResponseDto, +} from './dtos'; +import { RefreshGuard } from './guard/refresh.guard'; +import { SignupUser } from 'src/decorators/signupUser.decorator'; +import { User } from 'src/decorators/accessUser.decorator'; +import { + AlreadyCheckDocs, + AlreadyRegisteredDocs, + CheckCodeDocs, + CheckMailDocs, + LoginDocs, + RefreshDocs, + SignupBackDocs, + SignupDocs, + SignupEmailDocs, + SignupImageDocs, + SignupPhoneNumberDocs, + SignupStartDocs, +} from 'src/decorators/swagger/auth.decorator'; @Controller('auth') @ApiTags('auth') export class AuthController { @@ -48,319 +47,183 @@ export class AuthController { ) {} @UseGuards(SignupGuard) - @ApiQuery({ - name: 'code', - description: '인증번호', - type: String, - example: '010123', - }) @Post('/check/phone') - @ApiConflictResponse({ description: '사용 중 전화번호' }) - @ApiBadRequestResponse({ description: 'invalid signup token' }) - @ApiUnauthorizedResponse({ description: '인증번호 오류' }) - @ApiRequestTimeoutResponse({ description: '인증번호 시간 초과' }) - @ApiCreatedResponse({ description: '성공', type: SignupTokenResponseDto }) + @CheckCodeDocs() async checkCode( - @Req() req: Request, + @SignupUser() signupPayload: SignupPayload, @Query('code') code: string, @Body() body: SignupPhoneRequestDto, - ) { - try { - const { id, page } = req.user as SignupPayload; - if (Page[page] != 'checkPhoneNumber') { - throw new BadRequestException('invalid signup token'); - } + ): Promise { + const { id, page } = signupPayload; + if (Page[page] != 'checkPhoneNumber') { + throw new BadRequestException('invalid signup token'); + } - await this.authService.checkCode(id, code, body.phoneNumber); - const signupToken = await this.authService.getSignupToken( - req.user as SignupPayload, - ); + await this.authService.checkCode(id, code, body.phoneNumber); + const signupToken = await this.authService.getSignupToken(signupPayload); - return { signupToken: signupToken }; - } catch (err) { - console.log(err); - return err; - } + return { signupToken }; } @Get('/check/email') - @ApiOperation({ - summary: '이메일 인증', - description: '이메일 인증', - }) - @ApiQuery({ - name: 'code', - description: '인증 코드', - type: String, - example: '123456', - }) - @ApiQuery({ - name: 'email', - description: '이메일', - type: String, - example: '123456@korea.ac.kr', - }) - async checkMail(@Query('code') code: string, @Query('email') email: string) { + @CheckMailDocs() + async checkMail( + @Query('code') code: string, + @Query('email') email: string, + ): Promise { await this.authService.checkMail(code, email); return '

가입 완료!

블러팅 앱으로 돌아가주세요.'; } @UseGuards(SignupGuard) @Post('/signup') - @ApiCreatedResponse({ - description: 'new signup token', - type: SignupTokenResponseDto, - }) - @ApiHeader({ - name: 'authorization', - required: true, - example: 'Bearer asdas.asdasd.asd', - }) - @ApiBody({ - description: '유저 정보 차례대로 하나씩', - type: CreateUserDto, - }) - @ApiOperation({ - summary: '회원가입', - description: - 'signup token과 body의 정보로 회원가입 진행 및 signup token 재발행', - }) + @SignupDocs() async signup( - @Req() req: Request, + @SignupUser() signupPayload: SignupPayload, @Body() info: CreateUserDto, - @Res() res: Response, ) { - try { - const { id, infoId, page } = req.user as SignupPayload; - if (page == 16) { - const result = await this.authService.checkComplete(id); - if (!result) throw new BadRequestException('invalid info'); - await this.userService.createSocketUser(id); - return res.json({ - refreshToken: await this.authService.getRefreshToken({ - id: id, - }), - accessToken: await this.authService.getAccessToken({ - id: id, - }), - userId: id, - }); - } + const { id, infoId, page } = signupPayload; + if (page == 16) { + const result = await this.authService.checkComplete(id); + if (!result) throw new BadRequestException('invalid info'); + await this.userService.createSocketUser(id); + return { + refreshToken: await this.authService.getRefreshToken(id), + accessToken: await this.authService.getAccessToken(id), + userId: id, + }; + } - const pageName = Object.keys(Page).find((key) => Page[key] == page); - if (info[pageName] == undefined || info[pageName] == null) - throw new BadRequestException('invalid info'); - switch (pageName) { - case 'userName': - await this.userService.updateUser(id, 'userName', info['userName']); - break; - default: - await this.userService.updateUserInfo( - infoId, - pageName, - info[pageName], - ); - } + const pageName = Object.keys(Page).find((key) => Page[key] == page); + if (info[pageName] == undefined || info[pageName] == null) + throw new BadRequestException('invalid info'); + switch (pageName) { + case 'userName': + await this.userService.updateUser(id, 'userName', info['userName']); + break; + default: + await this.userService.updateUserInfo(infoId, pageName, info[pageName]); + } - const signupToken = await this.authService.getSignupToken( - req.user as SignupPayload, - ); + const signupToken = await this.authService.getSignupToken(signupPayload); - return res.json({ signupToken: signupToken }); - } catch (err) { - res.status(err.status).json(err); - } + return { signupToken }; } @Get('/signup/start') - @ApiCreatedResponse({ - description: 'new signup token', - type: SignupTokenResponseDto, - }) - @ApiOperation({ - summary: '회원가입 시작', - description: '첫 signup token 발행', - }) - async signupStart(@Req() req: Request, @Res() res: Response) { - try { - const user = await this.userService.createUser(); - const userInfo = await this.userService.createUserInfo(user); - const signupToken = await this.authService.getSignupToken({ - id: user.id, - infoId: userInfo.id, - page: 0, - }); + @SignupStartDocs() + async signupStart(): Promise { + const user = await this.userService.createUser(); + const userInfo = await this.userService.createUserInfo(user); + const signupToken = await this.authService.getSignupToken({ + id: user.id, + infoId: userInfo.id, + page: 0, + }); - return res.json({ signupToken: signupToken }); - } catch (err) { - res.status(err.status).json(err); - } + return { signupToken }; } @Post('/signup/phonenumber') @UseGuards(SignupGuard) - @ApiOperation({ summary: '휴대폰 인증 요청' }) - @ApiBadRequestResponse({ - description: 'invalid signup token 또는 전화번호 오류', - }) - @ApiConflictResponse({ description: '사용 중 전화번호' }) - @ApiNotAcceptableResponse({ description: '10초 내 재요청' }) - @ApiCreatedResponse({ - description: 'new signup token', - type: SignupTokenResponseDto, - }) + @SignupPhoneNumberDocs() async signupPhoneNumber( - @Req() req: Request, - @Res() res: Response, + @SignupUser() signupPayload: SignupPayload, @Body() body: SignupPhoneRequestDto, - ) { - try { - const { id, page } = req.user as SignupPayload; - if (Page[page] != 'phoneNumber') { - throw new BadRequestException('invalid signup token'); - } - await this.authService.validatePhoneNumber(body.phoneNumber, id); - const signupToken = await this.authService.getSignupToken( - req.user as SignupPayload, - ); - - return res.json({ signupToken: signupToken }); - } catch (err) { - console.log(err); - res.status(err.status).json(err); + ): Promise { + const { id, page } = signupPayload; + if (Page[page] != 'phoneNumber') { + throw new BadRequestException('invalid signup token'); } + await this.authService.validatePhoneNumber(body.phoneNumber, id); + const signupToken = await this.authService.getSignupToken(signupPayload); + + return { signupToken }; } @Post('/signup/images') @UseGuards(SignupGuard) - @ApiBadRequestResponse({ description: 'invalid signup token' }) - @ApiCreatedResponse({ - description: 'new signup token', - type: SignupTokenResponseDto, - }) - async signupImage(@Body() body: SignupImageRequestDto, @Req() req: Request) { - const { id } = req.user as SignupPayload; + @SignupImageDocs() + async signupImage( + @SignupUser() signupPayload: SignupPayload, + @Body() body: SignupImageRequestDto, + ): Promise { + const { id } = signupPayload; await this.userService.updateUserImages(id, body.images); - const signupToken = await this.authService.getSignupToken( - req.user as SignupPayload, - ); + const signupToken = await this.authService.getSignupToken(signupPayload); return { signupToken }; } @Post('/signup/email') @UseGuards(SignupGuard) - @ApiBadRequestResponse({ - description: 'invalid signup token or invalid email', - }) - @ApiConflictResponse({ description: '이미 가입된 이메일' }) - @ApiNotAcceptableResponse({ description: '10초 내 재요청' }) - @ApiCreatedResponse({ - description: 'new signup token', - type: SignupTokenResponseDto, - }) - async signupEmail(@Req() req: Request, @Body() body: SignupEmailRequestDto) { - const { id, page } = req.user as SignupPayload; + @SignupEmailDocs() + async signupEmail( + @SignupUser() signupPayload: SignupPayload, + @Body() body: SignupEmailRequestDto, + ): Promise { + const { id, page } = signupPayload; if (Page[page] != 'email') { throw new BadRequestException('invalid signup token'); } await this.authService.sendVerificationCode(id, body.email); - const signupToken = await this.authService.getSignupToken( - req.user as SignupPayload, - ); + const signupToken = await this.authService.getSignupToken(signupPayload); - return { signupToken: signupToken }; + return { signupToken }; } @Get('/signup/back') @UseGuards(SignupGuard) - @ApiBadRequestResponse({ - description: 'invalid signup token', - }) - @ApiCreatedResponse({ - description: 'new signup token', - type: SignupTokenResponseDto, - }) - @ApiOperation({ - summary: '회원가입 뒤로가기', - description: '이전 signup token 발행', - }) - async signupBack(@Req() req: Request, @Res() res: Response) { - try { - const { id, infoId, page } = req.user as SignupPayload; - const signupToken = await this.authService.getSignupToken({ - id: id, - infoId: infoId, - page: page - 2, - }); + @SignupBackDocs() + async signupBack( + @SignupUser() signupPayload: SignupPayload, + ): Promise { + const { id, infoId, page } = signupPayload; + const signupToken = await this.authService.getSignupToken({ + id, + infoId, + page: page - 2, + }); - return res.json({ signupToken: signupToken }); - } catch (err) { - res.status(err.status).json(err); - } + return { signupToken }; } @Post('/login') - @ApiOperation({ deprecated: true }) - async login(@Body() loginDto: LoginDto, @Res() res: Response) { + @LoginDocs() + async login(@Body() loginDto: LoginDto) { const { id } = loginDto; const user = await this.authService.validateUser(id); - const refreshToken = await this.authService.getRefreshToken({ - id: user.id, - }); - const accessToken = await this.authService.getAccessToken({ id: user.id }); - return res.json({ + const refreshToken = await this.authService.getRefreshToken(user.id); + const accessToken = await this.authService.getAccessToken(user.id); + return { id: user.id, - refreshToken: refreshToken, - accessToken: accessToken, - }); + refreshToken, + accessToken, + }; } - @UseGuards(AuthGuard('refresh')) + @UseGuards(RefreshGuard) @Post('/refresh') - @ApiCreatedResponse({ - description: 'new access token', - type: TokenResponseDto, - }) - @ApiHeader({ - name: 'authorization', - required: true, - example: 'Bearer asdas.asdasd.asd', - }) - @ApiOperation({ - summary: 'accesstoken 갱신', - description: 'refresh token으로 access token 갱신', - }) - async refresh(@Req() req: Request): Promise { - const { id } = req.user as JwtPayload; - const refreshToken = await this.authService.getRefreshToken({ - id: id, - }); - const accessToken = await this.authService.getAccessToken({ id: id }); + @RefreshDocs() + async refresh(@User() user: JwtPayload): Promise { + const { id } = user; + const refreshToken = await this.authService.getRefreshToken(id); + const accessToken = await this.authService.getAccessToken(id); return { - refreshToken: refreshToken, - accessToken: accessToken, + refreshToken, + accessToken, }; } @Post('/already/signed') - @ApiNotFoundResponse({ description: '없는 번호' }) - @ApiNotAcceptableResponse({ description: '10초 내 재요청' }) - async alreadyRegistered(@Body() body: SignupPhoneRequestDto) { + @AlreadyRegisteredDocs() + async alreadyRegistered(@Body() body: SignupPhoneRequestDto): Promise { await this.authService.alreadySigned(body.phoneNumber); } @Post('/alreay/signed/check') - @ApiBadRequestResponse({ description: 'invalid code' }) - @ApiRequestTimeoutResponse({ description: '3분지남' }) - @ApiCreatedResponse({ description: '성공', type: TokenResponseDto }) - @ApiQuery({ - name: 'code', - description: '인증번호', - type: String, - example: '010123', - }) + @AlreadyCheckDocs() async alreadyCheck(@Query('code') code: string) { const result = await this.authService.checkCodeAlready(code); return result; diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 6f1d00e..b25a414 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -6,13 +6,16 @@ import { AuthPhoneNumberEntity, UserImageEntity, } from 'src/entities'; +import { + JwtSignupStrategy, + JwtAccessStrategy, + JwtRefreshStrategy, +} from './passport'; + import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; -import { JwtSignupStrategy } from './passport/jwt-signup.strategy'; -import { JwtAccessStrategy } from './passport/jwt-access.strategy'; -import { JwtRefreshStrategy } from './passport/jwt-refresh.strategy'; import { MailerModule } from '@nestjs-modules/mailer'; import { UserModule } from 'src/user/user.module'; import { PointModule } from 'src/point/point.module'; diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index eba79cc..1b6a034 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Injectable, - Logger, BadRequestException, ConflictException, NotAcceptableException, @@ -10,11 +9,15 @@ import { NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { AuthPhoneNumberEntity, AuthMailEntity } from 'src/entities'; import { Repository } from 'typeorm'; -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; import { MailerService } from '@nestjs-modules/mailer'; import { JwtService } from '@nestjs/jwt'; +import { + AuthPhoneNumberEntity, + AuthMailEntity, + UserEntity, +} from 'src/entities'; import { UserService } from 'src/user/user.service'; import { JwtPayload, SignupPayload } from 'src/interfaces/auth'; import { UnivMailMap } from 'src/common/const'; @@ -36,33 +39,31 @@ export class AuthService { private readonly pointService: PointService, ) {} - private readonly logger = new Logger(AuthService.name); - - async getRefreshToken({ id }) { + async getRefreshToken(id: number): Promise { const payload: JwtPayload = { - id: id, + id, signedAt: new Date( new Date().getTime() + 9 * 60 * 60 * 1000, ).toISOString(), }; - const refreshJwt = await this.jwtService.sign(payload, { + const refreshJwt = this.jwtService.sign(payload, { secret: process.env.REFRESH_TOKEN_SECRET_KEY, }); - this.userService.updateUser(id, 'token', refreshJwt); + await this.userService.updateUser(id, 'token', refreshJwt); return refreshJwt; } - async getAccessToken({ id }) { + getAccessToken(id: number): string { const payload: JwtPayload = { - id: id, + id, signedAt: new Date( new Date().getTime() + 9 * 60 * 60 * 1000, ).toISOString(), }; - const accessJwt = await this.jwtService.sign(payload, { + const accessJwt = this.jwtService.sign(payload, { secret: process.env.ACCESS_TOKEN_SECRET_KEY, expiresIn: '1h', }); @@ -70,21 +71,21 @@ export class AuthService { return accessJwt; } - async getSignupToken(signupPayload: SignupPayload) { + getSignupToken(signupPayload: SignupPayload): string { const payload: SignupPayload = { id: signupPayload.id, infoId: signupPayload.infoId, page: signupPayload.page + 1, }; - const signupJwt = await this.jwtService.sign(payload, { + const signupJwt = this.jwtService.sign(payload, { secret: process.env.JWT_SECRET_KEY, }); return signupJwt; } - async validateUser(id: number) { + async validateUser(id: number): Promise { const user = await this.userService.findUserByVal('id', id); if (!user || id == undefined) { @@ -93,7 +94,10 @@ export class AuthService { return user; } - async validatePhoneNumber(phoneNumber: string, userId: number) { + async validatePhoneNumber( + phoneNumber: string, + userId: number, + ): Promise { const phone = await this.authPhoneNumberRepository.findOne({ where: { user: { id: userId }, isValid: false }, order: { createdAt: 'DESC' }, @@ -157,7 +161,7 @@ export class AuthService { hmac.update('\n'); hmac.update(`${accessKey}`); const hash = hmac.digest('base64'); - let response; + let response: AxiosResponse; try { response = await axios.post(API_URL, body, { headers: { @@ -177,7 +181,11 @@ export class AuthService { ); } - async checkCode(userId: number, code: string, phoneNumber: string) { + async checkCode( + userId: number, + code: string, + phoneNumber: string, + ): Promise { const phone = await this.authPhoneNumberRepository.findOne({ where: { user: { id: userId }, code }, relations: ['user'], @@ -200,7 +208,7 @@ export class AuthService { return true; } - async sendVerificationCode(userId: number, to: string) { + async sendVerificationCode(userId: number, to: string): Promise { const existingUser = await this.userService.findUserByVal('email', to); if (existingUser) throw new ConflictException('이미 가입된 이메일입니다.'); const mail = await this.authMailRepository.findOne({ @@ -241,12 +249,12 @@ export class AuthService { await this.authMailRepository.save(entity); } - async checkComplete(id: number) { + async checkComplete(id: number): Promise { const user = await this.userService.findUserByVal('id', id); return user.phoneNumber && user.email != null; } - async checkMail(code: string, email: string) { + async checkMail(code: string, email: string): Promise { const mail = await this.authMailRepository.findOne({ where: { code: code, @@ -269,7 +277,7 @@ export class AuthService { await this.authMailRepository.delete(mail); } - async alreadySigned(phoneNumber: string) { + async alreadySigned(phoneNumber: string): Promise { const user = await this.userService.findUserByPhone(phoneNumber); if (!user) throw new NotFoundException('가입되지 않은 전화번호입니다.'); @@ -373,8 +381,8 @@ export class AuthService { await this.authPhoneNumberRepository.delete(phone); const id = phone.user.id; - const refreshJwt = await this.getRefreshToken({ id }); - const accessJwt = await this.getAccessToken({ id }); + const refreshJwt = await this.getRefreshToken(id); + const accessJwt = await this.getAccessToken(id); await this.userService.createSocketUser(id); return { refreshToken: refreshJwt, accessToken: accessJwt, id }; diff --git a/src/auth/guard/acces.guard.ts b/src/auth/guard/acces.guard.ts new file mode 100644 index 0000000..57678e7 --- /dev/null +++ b/src/auth/guard/acces.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class AccessGuard extends AuthGuard('access') {} diff --git a/src/auth/guard/refresh.guard.ts b/src/auth/guard/refresh.guard.ts new file mode 100644 index 0000000..5eb4e74 --- /dev/null +++ b/src/auth/guard/refresh.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class RefreshGuard extends AuthGuard('refresh') {} diff --git a/src/auth/guard/signup.guard.ts b/src/auth/guard/signup.guard.ts index ae1fdd8..9a564b6 100644 --- a/src/auth/guard/signup.guard.ts +++ b/src/auth/guard/signup.guard.ts @@ -1,28 +1,5 @@ -import { Injectable, ExecutionContext } from '@nestjs/common'; -import { AuthService } from '../auth.service'; -import { UserService } from 'src/user/user.service'; +import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; -import { Reflector } from '@nestjs/core'; -import { lastValueFrom, Observable } from 'rxjs'; @Injectable() -export class SignupGuard extends AuthGuard('signup') { - constructor( - private readonly authService: AuthService, - private readonly userService: UserService, - private readonly reflector: Reflector, - ) { - super(reflector); - } - - async canActivate(context: ExecutionContext): Promise { - const result = super.canActivate(context); - if (typeof result === 'boolean' || result instanceof Promise) { - return result; - } else if (result instanceof Observable) { - return await lastValueFrom(result); - } else { - throw new Error('Unexpected canActivate return value'); - } - } -} +export class SignupGuard extends AuthGuard('signup') {} diff --git a/src/auth/passport/index.ts b/src/auth/passport/index.ts new file mode 100644 index 0000000..39ee0cc --- /dev/null +++ b/src/auth/passport/index.ts @@ -0,0 +1,3 @@ +export * from './jwt-access.strategy'; +export * from './jwt-refresh.strategy'; +export * from './jwt-signup.strategy'; diff --git a/src/auth/passport/jwt-access.strategy.ts b/src/auth/passport/jwt-access.strategy.ts index d739140..dbbad55 100644 --- a/src/auth/passport/jwt-access.strategy.ts +++ b/src/auth/passport/jwt-access.strategy.ts @@ -1,12 +1,11 @@ -import { Injectable, HttpException } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy, ExtractJwt } from 'passport-jwt'; import { JwtPayload } from 'src/interfaces/auth'; -import { UserService } from 'src/user/user.service'; @Injectable() export class JwtAccessStrategy extends PassportStrategy(Strategy, 'access') { - constructor(private readonly userService: UserService) { + constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.ACCESS_TOKEN_SECRET_KEY, @@ -14,12 +13,6 @@ export class JwtAccessStrategy extends PassportStrategy(Strategy, 'access') { } async validate(payload: JwtPayload) { - const user = await this.userService.findUserByVal('id', payload.id); - - if (!user || user.id != payload.id) { - throw new HttpException('Invalid access token', 401); - } - - return user; + return payload; } } diff --git a/src/auth/passport/jwt-signup.strategy.ts b/src/auth/passport/jwt-signup.strategy.ts index 1bd8425..e89c6c8 100644 --- a/src/auth/passport/jwt-signup.strategy.ts +++ b/src/auth/passport/jwt-signup.strategy.ts @@ -2,11 +2,10 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy, ExtractJwt } from 'passport-jwt'; import { SignupPayload } from 'src/interfaces/auth'; -import { UserService } from 'src/user/user.service'; @Injectable() export class JwtSignupStrategy extends PassportStrategy(Strategy, 'signup') { - constructor(private readonly userService: UserService) { + constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET_KEY, diff --git a/src/decorators/user.decorator.ts b/src/decorators/accessUser.decorator.ts similarity index 57% rename from src/decorators/user.decorator.ts rename to src/decorators/accessUser.decorator.ts index 583ee8c..0698821 100644 --- a/src/decorators/user.decorator.ts +++ b/src/decorators/accessUser.decorator.ts @@ -1,10 +1,10 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { JwtPayload } from 'src/interfaces/auth'; export const User = createParamDecorator( - (data: unknown, ctx: ExecutionContext) => { + (_: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); - console.log(data); - return request.user; + return request.user as JwtPayload; }, ); diff --git a/src/decorators/signupUser.decorator.ts b/src/decorators/signupUser.decorator.ts new file mode 100644 index 0000000..1e18d7a --- /dev/null +++ b/src/decorators/signupUser.decorator.ts @@ -0,0 +1,10 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { SignupPayload } from 'src/interfaces/auth'; + +export const SignupUser = createParamDecorator( + (_: never, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + + return request.user as SignupPayload; + }, +); diff --git a/src/decorators/swagger/auth.decorator.ts b/src/decorators/swagger/auth.decorator.ts new file mode 100644 index 0000000..9de77e1 --- /dev/null +++ b/src/decorators/swagger/auth.decorator.ts @@ -0,0 +1,187 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBody, + ApiConflictResponse, + ApiCreatedResponse, + ApiHeader, + ApiNotAcceptableResponse, + ApiNotFoundResponse, + ApiOperation, + ApiQuery, + ApiRequestTimeoutResponse, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; +import { SignupTokenResponseDto, TokenResponseDto } from 'src/auth/dtos'; +import { CreateUserDto } from 'src/dtos/user.dto'; + +export function CheckCodeDocs() { + return applyDecorators( + ApiQuery({ + name: 'code', + description: '인증번호', + type: String, + example: '010123', + }), + ApiConflictResponse({ description: '사용 중 전화번호' }), + ApiBadRequestResponse({ description: 'invalid signup token' }), + ApiUnauthorizedResponse({ description: '인증번호 오류' }), + ApiRequestTimeoutResponse({ description: '인증번호 시간 초과' }), + ApiCreatedResponse({ description: '성공', type: SignupTokenResponseDto }), + ); +} + +export function CheckMailDocs() { + return applyDecorators( + ApiOperation({ + summary: '이메일 인증', + description: '이메일 인증', + }), + ApiQuery({ + name: 'code', + description: '인증 코드', + type: String, + example: '123456', + }), + ApiQuery({ + name: 'email', + description: '이메일', + type: String, + example: '123456@korea.ac.kr', + }), + ); +} + +export function SignupDocs() { + return applyDecorators( + ApiCreatedResponse({ + description: 'new signup token', + type: SignupTokenResponseDto, + }), + ApiHeader({ + name: 'authorization', + required: true, + example: 'Bearer asdas.asdasd.asd', + }), + ApiBody({ + description: '유저 정보 차례대로 하나씩', + type: CreateUserDto, + }), + ApiOperation({ + summary: '회원가입', + description: + 'signup token과 body의 정보로 회원가입 진행 및 signup token 재발행', + }), + ); +} + +export function SignupStartDocs() { + return applyDecorators( + ApiCreatedResponse({ + description: 'new signup token', + type: SignupTokenResponseDto, + }), + ApiOperation({ + summary: '회원가입 시작', + description: '첫 signup token 발행', + }), + ); +} + +export function SignupPhoneNumberDocs() { + return applyDecorators( + ApiOperation({ summary: '휴대폰 인증 요청' }), + ApiBadRequestResponse({ + description: 'invalid signup token 또는 전화번호 오류', + }), + ApiConflictResponse({ description: '사용 중 전화번호' }), + ApiNotAcceptableResponse({ description: '10초 내 재요청' }), + ApiCreatedResponse({ + description: 'new signup token', + type: SignupTokenResponseDto, + }), + ); +} + +export function SignupImageDocs() { + return applyDecorators( + ApiBadRequestResponse({ description: 'invalid signup token' }), + ApiCreatedResponse({ + description: 'new signup token', + type: SignupTokenResponseDto, + }), + ); +} + +export function SignupEmailDocs() { + return applyDecorators( + ApiBadRequestResponse({ + description: 'invalid signup token or invalid email', + }), + ApiConflictResponse({ description: '이미 가입된 이메일' }), + ApiNotAcceptableResponse({ description: '10초 내 재요청' }), + ApiCreatedResponse({ + description: 'new signup token', + type: SignupTokenResponseDto, + }), + ); +} + +export function SignupBackDocs() { + return applyDecorators( + ApiBadRequestResponse({ + description: 'invalid signup token', + }), + ApiCreatedResponse({ + description: 'new signup token', + type: SignupTokenResponseDto, + }), + ApiOperation({ + summary: '회원가입 뒤로가기', + description: '이전 signup token 발행', + }), + ); +} + +export function LoginDocs() { + return applyDecorators(ApiOperation({ deprecated: true })); +} + +export function RefreshDocs() { + return applyDecorators( + ApiCreatedResponse({ + description: 'new access token', + type: TokenResponseDto, + }), + ApiHeader({ + name: 'authorization', + required: true, + example: 'Bearer asdas.asdasd.asd', + }), + ApiOperation({ + summary: 'accesstoken 갱신', + description: 'refresh token으로 access token 갱신', + }), + ); +} + +export function AlreadyRegisteredDocs() { + return applyDecorators( + ApiNotFoundResponse({ description: '없는 번호' }), + ApiNotAcceptableResponse({ description: '10초 내 재요청' }), + ); +} + +export function AlreadyCheckDocs() { + return applyDecorators( + ApiBadRequestResponse({ description: 'invalid code' }), + ApiRequestTimeoutResponse({ description: '3분지남' }), + ApiCreatedResponse({ description: '성공', type: TokenResponseDto }), + ApiQuery({ + name: 'code', + description: '인증번호', + type: String, + example: '010123', + }), + ); +}