import { BadRequestException, Body, Controller, Get, HttpCode, ParseBoolPipe, Post, Request, UnauthorizedException, UseGuards } from '@nestjs/common'; import { AuthService } from '../services/auth'; import { AuthGuard } from '@nestjs/passport'; import { Public } from '../authguards'; import { Responses } from 'dto'; import { tfaTypes } from '../entities'; import { toDataURL } from 'qrcode'; import * as base32 from 'thirty-two'; @Controller('api/auth') export default class AuthController { constructor(private authService: AuthService) {} @Public() @UseGuards(AuthGuard('local')) @Post('login') @HttpCode(200) async login( @Request() req, @Body('otp') otp?: string ): Promise< Responses.Auth.LoginResponse | Responses.Auth.TfaRequiredResponse > { if (this.authService.requiresTfa(req.user)) { if (!otp) { if (req.user.tfaType == tfaTypes.EMAIL) await this.authService.sendTfaMail(req.user); return { statusCode: 200 }; } if (!(await this.authService.verifyTfa(req.user, otp))) throw new UnauthorizedException('Incorrect 2fa'); } return { statusCode: 200, jwt: await this.authService.login(req.user) }; } async tfa( req, code: string, type: tfaTypes ): Promise { if (!(await this.authService.verifyTfa(req.user, code, type))) { throw new UnauthorizedException('Incorrect 2fa'); } await this.authService.setTfaType(req.user, type); await this.authService.revokeAll(req.user); return { statusCode: 200 }; } @Post('2fa/complete/mail') async tfaMail( @Request() req, @Body('code') code: string ): Promise { return await this.tfa(req, code, tfaTypes.EMAIL); } @Post('2fa/complete/totp') async tfaTotp( @Request() req, @Body('code') code: string ): Promise { return await this.tfa(req, code, tfaTypes.TOTP); } @Get('2fa/setup') async setupTotp( @Request() req, @Body('mail', ParseBoolPipe) mail: boolean ): Promise< | Responses.Auth.RequestTotpTfaResponse | Responses.Auth.RequestEmailTfaResponse > { const secret = await this.authService.setupTfa(req.user); if (mail) return { statusCode: 200 }; return { statusCode: 200, qrCode: await toDataURL( `otpauth://totp/MFileserver:${req.user.name}?secret=${base32 .encode(secret) .toString()}&issuer=MFileserver` ), secret }; } @Public() @Post('signup') async signup( @Body('username') username, @Body('password') password ): Promise { if ((await this.authService.findUser(username)) != null) throw new BadRequestException('Username already taken'); await this.authService.signup(username, password); return { statusCode: 200 }; } @Post('refresh') async refresh(@Request() req): Promise { const token = await this.authService.login(req.user); await this.authService.revoke(req.token); return { statusCode: 200, jwt: token }; } }