import { Body, Controller, Get, HttpCode, HttpStatus, Post, Req, Res, } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { Throttle } from '@nestjs/throttler'; import type { Request, Response } from 'express'; import { Public } from './decorators/public.decorator'; import { AnyAuthenticatedUser } from './decorators/any-authenticated-user.decorator'; import { CurrentUser } from './decorators/current-user.decorator'; import type { AuthenticatedUser } from './types/authenticated-user'; import { LoginDto } from './dto/login.dto'; import { AccessTokenDto } from './dto/access-token.dto'; import { MfaVerifyDto } from './dto/mfa-verify.dto'; import { AuthService, CurrentUserProfile } from './auth.service'; import { SENSITIVE_ACTION_THROTTLE } from './util/throttle'; import { clearRefreshTokenCookie, extractRefreshToken, setRefreshTokenCookie, } from './util/refresh-token-cookie'; export interface MfaRequiredResponse { mfaRequired: true; mfaToken: string; } @ApiTags('auth') @Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {} @Public() @Throttle(SENSITIVE_ACTION_THROTTLE) @Post('login') @HttpCode(HttpStatus.OK) async login( @Body() dto: LoginDto, @Res({ passthrough: true }) res: Response, ): Promise { const result = await this.authService.login(dto.email, dto.password); if (result.mfaRequired) { return { mfaRequired: true, mfaToken: result.mfaToken }; } setRefreshTokenCookie(res, result.refreshToken); return { accessToken: result.accessToken }; } @Public() @Throttle(SENSITIVE_ACTION_THROTTLE) @Post('mfa/verify') @HttpCode(HttpStatus.OK) async verifyMfa( @Body() dto: MfaVerifyDto, @Res({ passthrough: true }) res: Response, ): Promise { const { accessToken, refreshToken } = await this.authService.verifyMfaAndLogin(dto.mfaToken, dto.code); setRefreshTokenCookie(res, refreshToken); return { accessToken }; } @Public() @Throttle(SENSITIVE_ACTION_THROTTLE) @Post('refresh') @HttpCode(HttpStatus.OK) async refresh( @Req() req: Request, @Res({ passthrough: true }) res: Response, ): Promise { const { accessToken, refreshToken } = await this.authService.refresh( extractRefreshToken(req), ); setRefreshTokenCookie(res, refreshToken); return { accessToken }; } @Public() @Post('logout') @HttpCode(HttpStatus.NO_CONTENT) async logout( @Req() req: Request, @Res({ passthrough: true }) res: Response, ): Promise { await this.authService.logout(extractRefreshToken(req)); clearRefreshTokenCookie(res); } @ApiBearerAuth() @AnyAuthenticatedUser() @Get('me') me(@CurrentUser() user: AuthenticatedUser): Promise { return this.authService.getCurrentUserProfile(user.id); } }