import { writeFileSync, mkdirSync } from "fs"; mkdirSync("src/auth/twofa", { recursive: true }); // ─── two-factor.service.ts ─────────────────────────────────────────────────── writeFileSync("src/auth/twofa/two-factor.service.ts", `import { BadRequestException, Injectable } from "@nestjs/common"; import { authenticator } from "otplib"; import * as QRCode from "qrcode"; import { PrismaService } from "../../prisma/prisma.service"; import { EncryptionService } from "../../common/encryption.service"; @Injectable() export class TwoFactorService { constructor( private readonly prisma: PrismaService, private readonly encryption: EncryptionService, ) {} async generateSecret(userId: string) { const user = await this.prisma.user.findUnique({ where: { id: userId }, select: { email: true, twoFactorEnabled: true }, }); if (!user) throw new BadRequestException("User not found."); if (user.twoFactorEnabled) { throw new BadRequestException("2FA is already enabled."); } const secret = authenticator.generateSecret(); const otpAuthUrl = authenticator.keyuri(user.email, "LedgerOne", secret); const qrCodeDataUrl = await QRCode.toDataURL(otpAuthUrl); // Store encrypted secret temporarily (not yet enabled) await this.prisma.user.update({ where: { id: userId }, data: { twoFactorSecret: this.encryption.encrypt(secret) }, }); return { qrCode: qrCodeDataUrl, otpAuthUrl }; } async enableTwoFactor(userId: string, token: string) { const user = await this.prisma.user.findUnique({ where: { id: userId }, select: { twoFactorSecret: true, twoFactorEnabled: true }, }); if (!user?.twoFactorSecret) { throw new BadRequestException("Please generate a 2FA secret first."); } if (user.twoFactorEnabled) { throw new BadRequestException("2FA is already enabled."); } const secret = this.encryption.decrypt(user.twoFactorSecret); const isValid = authenticator.verify({ token, secret }); if (!isValid) { throw new BadRequestException("Invalid TOTP token."); } await this.prisma.user.update({ where: { id: userId }, data: { twoFactorEnabled: true }, }); await this.prisma.auditLog.create({ data: { userId, action: "auth.2fa.enabled", metadata: {} }, }); return { message: "2FA enabled successfully." }; } async disableTwoFactor(userId: string, token: string) { const user = await this.prisma.user.findUnique({ where: { id: userId }, select: { twoFactorSecret: true, twoFactorEnabled: true }, }); if (!user?.twoFactorEnabled || !user.twoFactorSecret) { throw new BadRequestException("2FA is not enabled."); } const secret = this.encryption.decrypt(user.twoFactorSecret); const isValid = authenticator.verify({ token, secret }); if (!isValid) { throw new BadRequestException("Invalid TOTP token."); } await this.prisma.user.update({ where: { id: userId }, data: { twoFactorEnabled: false, twoFactorSecret: null }, }); await this.prisma.auditLog.create({ data: { userId, action: "auth.2fa.disabled", metadata: {} }, }); return { message: "2FA disabled successfully." }; } verifyToken(secret: string, token: string): boolean { return authenticator.verify({ token, secret }); } decryptSecret(encryptedSecret: string): string { return this.encryption.decrypt(encryptedSecret); } } `); // ─── two-factor.controller.ts ──────────────────────────────────────────────── writeFileSync("src/auth/twofa/two-factor.controller.ts", `import { Body, Controller, Delete, Post } from "@nestjs/common"; import { ok } from "../../common/response"; import { TwoFactorService } from "./two-factor.service"; import { CurrentUser } from "../../common/decorators/current-user.decorator"; @Controller("auth/2fa") export class TwoFactorController { constructor(private readonly twoFactorService: TwoFactorService) {} @Post("generate") async generate(@CurrentUser() userId: string) { const data = await this.twoFactorService.generateSecret(userId); return ok(data); } @Post("enable") async enable(@CurrentUser() userId: string, @Body("token") token: string) { const data = await this.twoFactorService.enableTwoFactor(userId, token); return ok(data); } @Delete("disable") async disable(@CurrentUser() userId: string, @Body("token") token: string) { const data = await this.twoFactorService.disableTwoFactor(userId, token); return ok(data); } } `); // ─── two-factor.module.ts ──────────────────────────────────────────────────── writeFileSync("src/auth/twofa/two-factor.module.ts", `import { Module } from "@nestjs/common"; import { TwoFactorController } from "./two-factor.controller"; import { TwoFactorService } from "./two-factor.service"; @Module({ controllers: [TwoFactorController], providers: [TwoFactorService], exports: [TwoFactorService], }) export class TwoFactorModule {} `); console.log("2FA files written");