import { Controller, Request, Post, UseGuards, Body, Res, Delete, Patch } from '@nestjs/common'; import { LoginAuthGuard } from './guards/login-auth.guard'; import { AuthService } from './auth.service'; import { UsersService } from 'src/users/users.service'; import { RegisterUserDto } from './dto/register-user.dto'; import { Response } from 'express'; import { JwtRefreshGuard } from './guards/jwt-refresh.guard'; import { OfflineGuard } from './guards/offline.guard'; import { UserEntity } from 'src/users/entities/users.entity'; import { QueryFailedError } from 'typeorm'; import { PinoLogger } from 'nestjs-pino'; import { JwtAccessGuard } from './guards/jwt-access.guard'; @Controller('auth') export class AuthController { constructor( private auth: AuthService, private users: UsersService, private logger: PinoLogger, ) { } @UseGuards(LoginAuthGuard) @Post('login') async login( @Request() req, @Res({ passthrough: true }) response: Response, ) { let data: AuthenticationDto | null; try { data = await this.auth.login(req.user); if (!data.access_token || !data.refresh_token || !data.refresh_exp) { response.statusCode = 500; return { success: false, error_message: 'Something went wrong with tokens while logging in.', }; } } catch (err) { this.logger.error({ class: AuthController.name, method: this.login.name, user: req.user, msg: 'Failed to login.', error: err, }); response.statusCode = 500; return { success: false, error_message: 'Something went wrong while logging in.', }; } response.cookie('Authentication', data.access_token, { httpOnly: true, secure: true, expires: new Date(data.exp), sameSite: 'strict', }); response.cookie('Refresh', data.refresh_token, { httpOnly: true, secure: true, expires: new Date(data.refresh_exp), sameSite: 'strict', }); this.logger.info({ class: AuthController.name, method: this.login.name, user: req.user, access_token: data.access_token, refresh_token: data.refresh_token, msg: 'User logged in.', }); return { success: true, }; } @UseGuards(JwtAccessGuard) @Delete('login') async logout( @Request() req, @Res({ passthrough: true }) response: Response, ) { const accessToken = req.cookies?.Authentication; const refreshToken = req.cookies?.Refresh; response.clearCookie('Authentication'); response.clearCookie('Refresh'); if (!refreshToken || !await this.auth.revoke(req.user.userId, refreshToken)) { // User has already logged off. this.logger.info({ class: AuthController.name, method: this.login.name, user: req.user, msg: 'User has already logged off via ' + (!refreshToken ? 'cookies' : 'database'), }); response.statusCode = 400; return { success: false, error_message: 'User has already logged off.' }; } this.logger.info({ class: AuthController.name, method: this.logout.name, user: req.user, msg: 'User logged off', }); return { success: true, }; } @UseGuards(JwtRefreshGuard) @Patch('login') async refresh( @Request() req, @Res({ passthrough: true }) response: Response, ) { this.logger.info({ class: AuthController.name, method: this.login.name, user: req.user, refresh_token: req.cookies.Refresh, msg: 'User logged in.', }); const refreshToken = req.cookies.Refresh; const data = await this.auth.renew(req.user, refreshToken); response.cookie('Authentication', data.access_token, { httpOnly: true, secure: true, expires: new Date(data.exp), sameSite: 'strict', }); this.logger.debug({ class: AuthController.name, method: this.refresh.name, user: req.user, access_token: data.access_token, msg: 'Updated Authentication cookie for access token.', }); if (data.refresh_token != refreshToken) { response.cookie('Refresh', data.refresh_token, { httpOnly: true, secure: true, expires: new Date(data.refresh_exp), sameSite: 'strict', }); this.logger.debug({ class: AuthController.name, method: this.refresh.name, user: req.user, refresh_token: data.refresh_token, msg: 'Updated Refresh cookie for refresh token.', }); } return { success: true }; } @UseGuards(OfflineGuard) @Post('register') async register( @Request() req, @Res({ passthrough: true }) response: Response, @Body() body: RegisterUserDto, ) { let user: UserEntity | null; let data: AuthenticationDto | null; try { const { user_login, user_name, password } = body; user = await this.users.register(user_login.toLowerCase(), user_name, password, true); this.logger.info({ class: AuthController.name, method: this.register.name, user: req.user, msg: 'User registered', }); } catch (err) { if (err instanceof QueryFailedError) { if (err.message.includes('duplicate key value violates unique constraint "users_user_login_key"')) { this.logger.warn({ class: AuthController.name, method: this.register.name, user: req.user, msg: 'Failed to register due to duplicate userLogin.', }); response.statusCode = 409; return { success: false, error_message: 'Username already exist.', }; } } this.logger.error({ class: AuthController.name, method: this.register.name, user: req.user, msg: 'Failed to register.', error: err, }); response.statusCode = 500; return { success: false, error_message: 'Something went wrong when creating user.', }; } try { data = await this.auth.login(user); if (!data.access_token || !data.refresh_token || !data.refresh_exp) { this.logger.error({ class: AuthController.name, method: this.register.name, user: req.user, access_token: data.access_token, refresh_token: data.refresh_token, msg: 'Failed to generate tokens after registering.', }); response.statusCode = 500; return { success: false, error_message: 'Something went wrong with tokens while logging in.', }; } } catch (err) { this.logger.error({ class: AuthController.name, method: this.register.name, user: req.user, msg: 'Failed to login after registering.', error: err, }); response.statusCode = 500; return { success: false, error_message: 'Something went wrong while logging in.', }; } response.cookie('Authentication', data.access_token, { httpOnly: true, secure: true, expires: new Date(data.exp), sameSite: 'strict', }); response.cookie('Refresh', data.refresh_token, { httpOnly: true, secure: true, expires: new Date(data.refresh_exp), sameSite: 'strict', }); return { success: true, }; } }