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

Commit

Permalink
♻️ Change twoFactorEnabled -> twoFactorMethod
Browse files Browse the repository at this point in the history
  • Loading branch information
AnandChowdhary committed Nov 1, 2020
1 parent a8178f4 commit 20d8ceb
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 31 deletions.
4 changes: 0 additions & 4 deletions src/modules/auth/auth.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ export class RegisterDto {
@IsOptional()
timezone?: string;

@IsBoolean()
@IsOptional()
twoFactorEnabled?: boolean;

@IsObject()
@IsOptional()
attributes?: Record<string, any>;
Expand Down
11 changes: 5 additions & 6 deletions src/modules/auth/auth.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Request as ExpressRequest } from 'express';
import { Request as NestRequest } from '@nestjs/common';
import { MfaMethod } from '@prisma/client';
import { Request as ExpressRequest } from 'express';

export interface AccessTokenClaims {
sub: string;
Expand All @@ -13,7 +14,7 @@ export interface TokenResponse {

export interface TotpTokenResponse {
totpToken: string;
type: MfaTypes;
type: MfaMethod;
multiFactorRequired: true;
}

Expand All @@ -22,11 +23,9 @@ export interface AccessTokenParsed {
scopes: string[];
}

export type MfaTypes = 'TOTP' | 'EMAIL';

export interface MfaTokenPayload {
id: number;
type: MfaTypes;
type: MfaMethod;
}

type CombinedRequest = ExpressRequest & typeof NestRequest;
Expand All @@ -37,7 +36,7 @@ export interface UserRequest extends CombinedRequest {
export interface ValidatedUser {
id: number;
name: string;
twoFactorEnabled: boolean;
twoFactorMethod: MfaMethod;
twoFactorSecret: string | null;
checkLocationOnLogin: boolean;
prefersEmailAddress: string;
Expand Down
29 changes: 15 additions & 14 deletions src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { randomStringGenerator } from '@nestjs/common/utils/random-string-genera
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { Authenticator } from '@otplib/core';
import { emails, users } from '@prisma/client';
import { emails, MfaMethod, users } from '@prisma/client';
import { compare, hash } from 'bcrypt';
import anonymize from 'ip-anonymize';
import { authenticator } from 'otplib';
Expand All @@ -35,7 +35,6 @@ import { RegisterDto } from './auth.dto';
import {
AccessTokenClaims,
MfaTokenPayload,
MfaTypes,
TokenResponse,
TotpTokenResponse,
ValidatedUser,
Expand Down Expand Up @@ -74,7 +73,7 @@ export class AuthService {
id: true,
password: true,
emails: true,
twoFactorEnabled: true,
twoFactorMethod: true,
twoFactorSecret: true,
checkLocationOnLogin: true,
prefersEmail: true,
Expand All @@ -95,7 +94,7 @@ export class AuthService {
return {
name: user.name,
id: user.id,
twoFactorEnabled: user.twoFactorEnabled,
twoFactorMethod: user.twoFactorMethod,
twoFactorSecret: user.twoFactorSecret,
checkLocationOnLogin: user.checkLocationOnLogin,
prefersEmailAddress: user.prefersEmail.emailSafe,
Expand All @@ -114,7 +113,7 @@ export class AuthService {
if (!user) throw new UnauthorizedException();
if (code)
return this.loginUserWithTotpCode(ipAddress, userAgent, user.id, code);
if (user.twoFactorEnabled) return this.mfaResponse(user);
if (user.twoFactorMethod !== 'NONE') return this.mfaResponse(user);
await this.checkLoginSubnet(
ipAddress,
userAgent,
Expand Down Expand Up @@ -281,10 +280,10 @@ export class AuthService {
async enableTotp(userId: number, code: string): Promise<Expose<users>> {
const user = await this.prisma.users.findOne({
where: { id: userId },
select: { twoFactorSecret: true, twoFactorEnabled: true },
select: { twoFactorSecret: true, twoFactorMethod: true },
});
if (!user) throw new NotFoundException();
if (!user.twoFactorEnabled)
if (user.twoFactorMethod !== 'NONE')
throw new BadRequestException(
'Two-factor authentication is already enabled',
);
Expand All @@ -296,7 +295,7 @@ export class AuthService {
);
const result = await this.prisma.users.update({
where: { id: userId },
data: { twoFactorEnabled: true, twoFactorSecret: user.twoFactorSecret },
data: { twoFactorMethod: 'TOTP', twoFactorSecret: user.twoFactorSecret },
});
return this.prisma.expose<users>(result);
}
Expand Down Expand Up @@ -400,12 +399,12 @@ export class AuthService {
name: true,
prefersEmail: true,
twoFactorSecret: true,
twoFactorEnabled: true,
twoFactorMethod: true,
checkLocationOnLogin: true,
},
});
if (!user) throw new NotFoundException();
if (!user.twoFactorEnabled || !user.twoFactorSecret)
if (user.twoFactorMethod === 'NONE' || !user.twoFactorSecret)
throw new BadRequestException('Two-factor authentication is not enabled');
if (this.authenticator.check(code, user.twoFactorSecret))
return this.loginResponse(ipAddress, userAgent, id);
Expand Down Expand Up @@ -483,14 +482,16 @@ export class AuthService {
}

private async mfaResponse(user: ValidatedUser): Promise<TotpTokenResponse> {
const type: MfaTypes = user.twoFactorSecret ? 'TOTP' : 'EMAIL';
const mfaTokenPayload: MfaTokenPayload = { type, id: user.id };
const mfaTokenPayload: MfaTokenPayload = {
type: user.twoFactorMethod,
id: user.id,
};
const totpToken = this.tokensService.signJwt(
MULTI_FACTOR_TOKEN,
mfaTokenPayload,
this.configService.get<string>('security.mfaTokenExpiry'),
);
if (type === 'EMAIL') {
if (user.twoFactorMethod === 'EMAIL') {
this.email.send({
to: `"${user.name}" <${user.prefersEmailAddress}>`,
template: 'auth/mfa-code',
Expand All @@ -509,7 +510,7 @@ export class AuthService {
},
});
}
return { totpToken, type, multiFactorRequired: true };
return { totpToken, type: user.twoFactorMethod, multiFactorRequired: true };
}

private async checkLoginSubnet(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export class MultiFactorAuthenticationService {
async requestTotpMfa(userId: number): Promise<string> {
const enabled = await this.prisma.users.findOne({
where: { id: userId },
select: { twoFactorEnabled: true },
select: { twoFactorMethod: true },
});
if (!enabled) throw new NotFoundException('User not found');
if (enabled.twoFactorEnabled)
if (enabled.twoFactorMethod !== 'NONE')
throw new BadRequestException(
'Two-factor authentication is already enabled',
);
Expand All @@ -40,14 +40,14 @@ export class MultiFactorAuthenticationService {
async disableTotpMfa(userId: number): Promise<Expose<users>> {
const enabled = await this.prisma.users.findOne({
where: { id: userId },
select: { twoFactorEnabled: true },
select: { twoFactorMethod: true },
});
if (!enabled) throw new NotFoundException('User not found');
if (!enabled.twoFactorEnabled)
if (enabled.twoFactorMethod === 'NONE')
throw new BadRequestException('Two-factor authentication is not enabled');
const user = await this.prisma.users.update({
where: { id: userId },
data: { twoFactorEnabled: false, twoFactorSecret: null },
data: { twoFactorMethod: 'NONE', twoFactorSecret: null },
});
return this.prisma.expose<users>(user);
}
Expand Down
5 changes: 3 additions & 2 deletions src/modules/users/users.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
IsBoolean,
IsEnum,
IsIn,
IsLocale,
IsObject,
Expand Down Expand Up @@ -68,9 +69,9 @@ export class UpdateUserDto {
@IsOptional()
timezone?: string;

@IsBoolean()
@IsEnum(['NONE', 'TOTP', 'EMAIL'])
@IsOptional()
twoFactorEnabled?: boolean;
twoFactorMethod?: string;

@IsObject()
@IsOptional()
Expand Down

0 comments on commit 20d8ceb

Please sign in to comment.