feat: Implement two-factor authentication (2FA) with dedicated endpoints for setup, enabling, disabling, verification, and backup code management.

This commit is contained in:
pradeepkumar
2026-02-06 09:30:12 +05:30
parent ea05e18c15
commit 750cb55764
10 changed files with 989 additions and 19 deletions

View File

@@ -4,6 +4,8 @@ import {
ConflictException,
BadRequestException,
NotFoundException,
Inject,
forwardRef,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
@@ -22,6 +24,7 @@ import {
VerifyEmailDto,
} from './dto';
import { UserRole, AuthProvider, UserStatus } from '@prisma/client';
import { TwoFactorService } from './two-factor/two-factor.service';
@Injectable()
export class AuthService {
@@ -30,6 +33,8 @@ export class AuthService {
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
private readonly eventEmitter: EventEmitter2,
@Inject(forwardRef(() => TwoFactorService))
private readonly twoFactorService: TwoFactorService,
) {}
// ==========================================
@@ -162,6 +167,21 @@ export class AuthService {
throw new UnauthorizedException('Invalid email or password');
}
// Check if 2FA is enabled
if (user.twoFactorEnabled) {
// Generate temporary token for 2FA verification
const tempToken = await this.twoFactorService.generateTempToken(user.id);
return {
requiresTwoFactor: true,
tempToken,
user: {
id: user.id,
email: user.email,
},
};
}
// Generate tokens
const tokens = await this.generateTokens(user.id, user.email, user.role);
@@ -615,6 +635,141 @@ export class AuthService {
return { message: 'Logged out successfully' };
}
// ==========================================
// TWO-FACTOR AUTHENTICATION - VERIFY LOGIN
// ==========================================
async verifyTwoFactorLogin(
tempToken: string,
token: string,
userAgent?: string,
ipAddress?: string,
) {
// Verify temp token and get user ID
const userId = await this.twoFactorService.verifyTempToken(tempToken);
// Verify the TOTP token
const isValid = await this.twoFactorService.verifyToken(userId, token);
if (!isValid) {
throw new UnauthorizedException('Invalid verification code');
}
// Get user with profile
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: {
userProfile: true,
agentProfile: true,
},
});
if (!user || user.status !== UserStatus.ACTIVE) {
throw new UnauthorizedException('User not found or inactive');
}
// Generate tokens
const tokens = await this.generateTokens(user.id, user.email, user.role);
// Create session
await this.createSession(
user.id,
tokens.accessToken,
tokens.refreshToken,
userAgent,
ipAddress,
);
// Update last login
await this.prisma.user.update({
where: { id: user.id },
data: { lastLoginAt: new Date() },
});
// Get profile based on role
const profile =
user.role === UserRole.AGENT ? user.agentProfile : user.userProfile;
return {
user: {
id: user.id,
email: user.email,
role: user.role,
firstName: profile?.firstName,
lastName: profile?.lastName,
avatar: profile?.avatar,
},
...tokens,
};
}
// ==========================================
// TWO-FACTOR AUTHENTICATION - VERIFY BACKUP CODE
// ==========================================
async verifyBackupCodeLogin(
tempToken: string,
backupCode: string,
userAgent?: string,
ipAddress?: string,
) {
// Verify temp token and get user ID
const userId = await this.twoFactorService.verifyTempToken(tempToken);
// Verify and consume the backup code
const isValid = await this.twoFactorService.verifyBackupCode(
userId,
backupCode,
);
if (!isValid) {
throw new UnauthorizedException('Invalid backup code');
}
// Get user with profile
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: {
userProfile: true,
agentProfile: true,
},
});
if (!user || user.status !== UserStatus.ACTIVE) {
throw new UnauthorizedException('User not found or inactive');
}
// Generate tokens
const tokens = await this.generateTokens(user.id, user.email, user.role);
// Create session
await this.createSession(
user.id,
tokens.accessToken,
tokens.refreshToken,
userAgent,
ipAddress,
);
// Update last login
await this.prisma.user.update({
where: { id: user.id },
data: { lastLoginAt: new Date() },
});
// Get profile based on role
const profile =
user.role === UserRole.AGENT ? user.agentProfile : user.userProfile;
return {
user: {
id: user.id,
email: user.email,
role: user.role,
firstName: profile?.firstName,
lastName: profile?.lastName,
avatar: profile?.avatar,
},
...tokens,
};
}
// ==========================================
// GET CURRENT USER
// ==========================================