import { Controller, Get, Query, Redirect, Req, Res } from '@nestjs/common'; import type { Response } from 'express'; import type { AuthenticatedRequest } from './authenticated-request'; import { Public } from './guards/public.decorator'; import { AppConfigService } from '../config/config.service'; import { SensitiveRateLimit } from '../common/rate-limit/sensitive-rate-limit.decorator'; import { AuthService } from './auth.service'; @Controller('auth') export class AuthController { constructor( private readonly auth: AuthService, private readonly config: AppConfigService, ) {} @Get('login') @Public() @SensitiveRateLimit() @Redirect() async login() { return { url: await this.auth.createLoginUrl() }; } @Get('callback') @Public() @SensitiveRateLimit() async callback( @Query('code') code: string, @Query('state') state: string, @Req() req: AuthenticatedRequest, @Res() res: Response, ) { const { session, csrfToken } = await this.auth.completeLogin( code, state, req.get('user-agent'), req.ip, ); res.cookie(this.config.session.cookieName, session.id, { httpOnly: true, signed: true, secure: this.config.isProduction, sameSite: 'lax', path: '/', expires: session.absoluteExpiresAt, }); res.cookie('csrf_token', csrfToken, { httpOnly: false, secure: this.config.isProduction, sameSite: 'lax', path: '/', expires: session.absoluteExpiresAt, }); res.redirect(this.config.frontendBaseUrl); } @Get('logout') @Public() @SensitiveRateLimit() async logout(@Req() req: AuthenticatedRequest, @Res() res: Response) { const sessionId = req.signedCookies?.[this.config.session.cookieName] as | string | undefined; await this.auth.logout(sessionId); res.clearCookie(this.config.session.cookieName, { path: '/' }); res.clearCookie('csrf_token', { path: '/' }); res.redirect(this.config.frontendBaseUrl); } }