Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Commit

Permalink
✨ Add email MFA
Browse files Browse the repository at this point in the history
  • Loading branch information
AnandChowdhary committed Nov 1, 2020
1 parent 979636c commit b147b68
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class MultiFactorAuthenticationController {
) {}

@Post('regenerate')
@Scopes('user-{userId}:write-mfa')
@Scopes('user-{userId}:write-mfa-regenerate')
async regenerateBackupCodes(
@Param('userId', ParseIntPipe) userId: number,
): Promise<string[]> {
Expand All @@ -39,7 +39,7 @@ export class MultiFactorAuthenticationController {
}

@Post('totp')
@Scopes('user-{userId}:write-mfa')
@Scopes('user-{userId}:write-mfa-totp')
async enableTotp(
@Param('userId', ParseIntPipe) userId: number,
@Body() body: EnableTotpMfaDto,
Expand All @@ -54,7 +54,7 @@ export class MultiFactorAuthenticationController {
}

@Post('sms')
@Scopes('user-{userId}:write-mfa')
@Scopes('user-{userId}:write-mfa-sms')
async enableSms(
@Param('userId', ParseIntPipe) userId: number,
@Body() body: EnableSmsMfaDto,
Expand All @@ -72,4 +72,19 @@ export class MultiFactorAuthenticationController {
);
throw new BadRequestError('Phone number or token is required');
}

@Post('email')
@Scopes('user-{userId}:write-mfa-email')
async enableEmail(
@Param('userId', ParseIntPipe) userId: number,
@Body() body: EnableTotpMfaDto,
): Promise<string[] | void> {
if (body.token)
return this.multiFactorAuthenticationService.enableMfa(
'EMAIL',
userId,
body.token,
);
return this.multiFactorAuthenticationService.requestEmailMfa(userId);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from '../auth/auth.module';
import { EmailModule } from '../email/email.module';
import { PrismaModule } from '../prisma/prisma.module';
import { TwilioModule } from '../twilio/twilio.module';
import { MultiFactorAuthenticationController } from './multi-factor-authentication.controller';
import { MultiFactorAuthenticationService } from './multi-factor-authentication.service';

@Module({
imports: [PrismaModule, AuthModule, TwilioModule, ConfigModule],
imports: [PrismaModule, AuthModule, TwilioModule, EmailModule, ConfigModule],
controllers: [MultiFactorAuthenticationController],
providers: [MultiFactorAuthenticationService],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ConfigService } from '@nestjs/config';
import { MfaMethod, users } from '@prisma/client';
import { hash } from 'bcrypt';
import { AuthService } from '../auth/auth.service';
import { EmailService } from '../email/email.service';
import { Expose } from '../prisma/prisma.interface';
import { PrismaService } from '../prisma/prisma.service';
import { TwilioService } from '../twilio/twilio.service';
Expand All @@ -19,6 +20,7 @@ export class MultiFactorAuthenticationService {
private auth: AuthService,
private configService: ConfigService,
private twilioService: TwilioService,
private emailService: EmailService,
) {}

async requestTotpMfa(userId: number): Promise<string> {
Expand Down Expand Up @@ -49,14 +51,46 @@ export class MultiFactorAuthenticationService {
where: { id: userId },
data: { twoFactorSecret: secret, twoFactorPhone: phone },
});
this.twilioService.send({
return this.twilioService.send({
to: phone,
body: `${this.auth.getOneTimePassword(secret)} is your ${
this.configService.get<string>('sms.smsServiceName') ?? ''
} verification code.`,
});
}

async requestEmailMfa(userId: number): Promise<void> {
const user = await this.prisma.users.findOne({
where: { id: userId },
select: {
twoFactorMethod: true,
prefersEmail: true,
name: true,
id: true,
},
});
if (!user) throw new NotFoundException('User not found');
if (user.twoFactorMethod !== 'NONE')
throw new BadRequestException(
'Two-factor authentication is already enabled',
);
const secret = randomStringGenerator() as string;
await this.prisma.users.update({
where: { id: userId },
data: { twoFactorSecret: secret },
});
if (!user.prefersEmail)
throw new BadRequestException('User has no email attached to it');
return this.emailService.send({
to: `"${user.name}" <${user.prefersEmail.emailSafe}>`,
template: 'auth/enable-email-mfa',
data: {
name: user.name,
code: this.auth.getOneTimePassword(secret),
},
});
}

async enableMfa(
method: MfaMethod,
userId: number,
Expand Down
7 changes: 7 additions & 0 deletions src/templates/auth/enable-email-mfa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Enable multi-factor authentication with {{ code }}

Hi {{name}},

Enter the following code to enable email-based multi-factor authentication: **{{ code }}**.

If you didn't request this email, you can just ignore it; we won't give anyone else access to your account.

0 comments on commit b147b68

Please sign in to comment.