feat(EE): MFA implementation (#1381)
* feat(EE): MFA implementation for enterprise edition - Add TOTP-based two-factor authentication - Add backup codes support - Add MFA enforcement at workspace level - Add MFA setup and challenge UI pages - Support MFA for login and password reset flows - Add MFA validation for secure pages * fix types * remove unused object * sync * remove unused type * sync * refactor: rename MFA enabled field to is_enabled * sync
This commit is contained in:
@@ -47,7 +47,7 @@ export class AuthService {
|
||||
includePassword: true,
|
||||
});
|
||||
|
||||
const errorMessage = 'email or password does not match';
|
||||
const errorMessage = 'Email or password does not match';
|
||||
if (!user || user?.deletedAt) {
|
||||
throw new UnauthorizedException(errorMessage);
|
||||
}
|
||||
@@ -156,10 +156,13 @@ export class AuthService {
|
||||
});
|
||||
}
|
||||
|
||||
async passwordReset(passwordResetDto: PasswordResetDto, workspaceId: string) {
|
||||
async passwordReset(
|
||||
passwordResetDto: PasswordResetDto,
|
||||
workspace: Workspace,
|
||||
) {
|
||||
const userToken = await this.userTokenRepo.findById(
|
||||
passwordResetDto.token,
|
||||
workspaceId,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -170,7 +173,9 @@ export class AuthService {
|
||||
throw new BadRequestException('Invalid or expired token');
|
||||
}
|
||||
|
||||
const user = await this.userRepo.findById(userToken.userId, workspaceId);
|
||||
const user = await this.userRepo.findById(userToken.userId, workspace.id, {
|
||||
includeUserMfa: true,
|
||||
});
|
||||
if (!user || user.deletedAt) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
@@ -183,7 +188,7 @@ export class AuthService {
|
||||
password: newPasswordHash,
|
||||
},
|
||||
user.id,
|
||||
workspaceId,
|
||||
workspace.id,
|
||||
trx,
|
||||
);
|
||||
|
||||
@@ -201,7 +206,18 @@ export class AuthService {
|
||||
template: emailTemplate,
|
||||
});
|
||||
|
||||
return this.tokenService.generateAccessToken(user);
|
||||
// Check if user has MFA enabled or workspace enforces MFA
|
||||
const userHasMfa = user?.['mfa']?.isEnabled || false;
|
||||
const workspaceEnforcesMfa = workspace.enforceMfa || false;
|
||||
|
||||
if (userHasMfa || workspaceEnforcesMfa) {
|
||||
return {
|
||||
requiresLogin: true,
|
||||
};
|
||||
}
|
||||
|
||||
const authToken = await this.tokenService.generateAccessToken(user);
|
||||
return { authToken };
|
||||
}
|
||||
|
||||
async verifyUserToken(
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
JwtAttachmentPayload,
|
||||
JwtCollabPayload,
|
||||
JwtExchangePayload,
|
||||
JwtMfaTokenPayload,
|
||||
JwtPayload,
|
||||
JwtType,
|
||||
} from '../dto/jwt-payload';
|
||||
@@ -76,6 +77,22 @@ export class TokenService {
|
||||
return this.jwtService.sign(payload, { expiresIn: '1h' });
|
||||
}
|
||||
|
||||
async generateMfaToken(
|
||||
user: User,
|
||||
workspaceId: string,
|
||||
): Promise<string> {
|
||||
if (user.deactivatedAt || user.deletedAt) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
const payload: JwtMfaTokenPayload = {
|
||||
sub: user.id,
|
||||
workspaceId,
|
||||
type: JwtType.MFA_TOKEN,
|
||||
};
|
||||
return this.jwtService.sign(payload, { expiresIn: '5m' });
|
||||
}
|
||||
|
||||
async verifyJwt(token: string, tokenType: string) {
|
||||
const payload = await this.jwtService.verifyAsync(token, {
|
||||
secret: this.environmentService.getAppSecret(),
|
||||
|
||||
Reference in New Issue
Block a user