diff --git a/src/modules/multi-factor-authentication/multi-factor-authentication.controller.ts b/src/modules/multi-factor-authentication/multi-factor-authentication.controller.ts index 4ec21341f..225f7d0d6 100644 --- a/src/modules/multi-factor-authentication/multi-factor-authentication.controller.ts +++ b/src/modules/multi-factor-authentication/multi-factor-authentication.controller.ts @@ -23,14 +23,7 @@ export class MultiFactorAuthenticationController { private multiFactorAuthenticationService: MultiFactorAuthenticationService, ) {} - @Post('regenerate') - @Scopes('user-{userId}:write-mfa-regenerate') - async regenerateBackupCodes( - @Param('userId', ParseIntPipe) userId: number, - ): Promise { - return this.multiFactorAuthenticationService.regenerateBackupCodes(userId); - } - + /** Disable MFA for a user */ @Delete() @Scopes('user-{userId}:delete-mfa-*') async disable2FA( @@ -39,53 +32,70 @@ export class MultiFactorAuthenticationController { return this.multiFactorAuthenticationService.disableMfa(userId); } + /** Regenerate backup codes for a user */ + @Post('regenerate') + @Scopes('user-{userId}:write-mfa-regenerate') + async regenerateBackupCodes( + @Param('userId', ParseIntPipe) userId: number, + ): Promise { + return this.multiFactorAuthenticationService.regenerateBackupCodes(userId); + } + + /** Enable TOTP-based MFA for a user */ @Post('totp') @Scopes('user-{userId}:write-mfa-totp') async enableTotp( @Param('userId', ParseIntPipe) userId: number, @Body() body: EnableTotpMfaDto, - ): Promise { + ): Promise { if (body.token) return this.multiFactorAuthenticationService.enableMfa( 'TOTP', userId, body.token, ); - return this.multiFactorAuthenticationService.requestTotpMfa(userId); + return { + img: await this.multiFactorAuthenticationService.requestTotpMfa(userId), + }; } + /** Enable SMS-based MFA for a user */ @Post('sms') @Scopes('user-{userId}:write-mfa-sms') async enableSms( @Param('userId', ParseIntPipe) userId: number, @Body() body: EnableSmsMfaDto, - ): Promise { + ): Promise { if (body.token) return this.multiFactorAuthenticationService.enableMfa( 'SMS', userId, body.token, ); - if (body.phone) - return this.multiFactorAuthenticationService.requestSmsMfa( + if (body.phone) { + await this.multiFactorAuthenticationService.requestSmsMfa( userId, body.phone, ); + return { success: true }; + } throw new BadRequestException(MFA_PHONE_OR_TOKEN_REQUIRED); } + /** Enable email-based MFA for a user */ @Post('email') @Scopes('user-{userId}:write-mfa-email') async enableEmail( @Param('userId', ParseIntPipe) userId: number, @Body() body: EnableTotpMfaDto, - ): Promise { + ): Promise { if (body.token) return this.multiFactorAuthenticationService.enableMfa( 'EMAIL', userId, body.token, ); - return this.multiFactorAuthenticationService.requestEmailMfa(userId); + await this.multiFactorAuthenticationService.requestEmailMfa(userId); + return { success: true }; } } diff --git a/src/modules/multi-factor-authentication/multi-factor-authentication.service.ts b/src/modules/multi-factor-authentication/multi-factor-authentication.service.ts index cf283746a..8910ff1bc 100644 --- a/src/modules/multi-factor-authentication/multi-factor-authentication.service.ts +++ b/src/modules/multi-factor-authentication/multi-factor-authentication.service.ts @@ -8,7 +8,6 @@ import { ConfigService } from '@nestjs/config'; import type { MfaMethod } from '@prisma/client'; import { User } from '@prisma/client'; import { hash } from 'bcrypt'; -import { Configuration } from '../../config/configuration.interface'; import { MFA_ENABLED_CONFLICT, MFA_NOT_ENABLED, @@ -52,18 +51,16 @@ export class MultiFactorAuthenticationService { if (!enabled) throw new NotFoundException(USER_NOT_FOUND); if (enabled.twoFactorMethod !== 'NONE') throw new ConflictException(MFA_ENABLED_CONFLICT); - const secret = this.tokensService.generateUuid(); + const secret = this.auth.authenticator.generateSecret(); await this.prisma.user.update({ where: { id: userId }, data: { twoFactorSecret: secret, twoFactorPhone: phone }, }); return this.twilioService.send({ to: phone, - body: `${this.auth.getOneTimePassword( - secret, - )} is your ${this.configService.get( - 'meta.appName', - )} verification code.`, + body: `${this.auth.getOneTimePassword(secret)} is your ${ + this.configService.get('meta.appName') ?? '' + } verification code.`, }); } @@ -80,7 +77,7 @@ export class MultiFactorAuthenticationService { if (!user) throw new NotFoundException(USER_NOT_FOUND); if (user.twoFactorMethod !== 'NONE') throw new ConflictException(MFA_ENABLED_CONFLICT); - const secret = this.tokensService.generateUuid(); + const secret = this.auth.authenticator.generateSecret(); await this.prisma.user.update({ where: { id: userId }, data: { twoFactorSecret: secret }, @@ -124,13 +121,11 @@ export class MultiFactorAuthenticationService { await this.prisma.backupCode.deleteMany({ where: { user: { id } } }); const codes: string[] = []; for await (const _ of [...Array(10)]) { - const unsafeCode = this.tokensService.generateUuid(); + const unsafeCode = await this.tokensService.generateRandomString(10); codes.push(unsafeCode); const code = await hash( unsafeCode, - this.configService.get( - 'security.saltRounds', - ) ?? 10, + this.configService.get('security.saltRounds') ?? 10, ); await this.prisma.backupCode.create({ data: { user: { connect: { id } }, code },