import { randomBytes } from 'node:crypto'; import { BadRequestException, Injectable } from '@nestjs/common'; import { encode } from 'hi-base32'; import { TOTP } from 'otpauth'; import * as QRCode from 'qrcode'; import { PrismaService } from '@/src/core/prisma/prisma.service'; import { EnableTotpInput } from '@/src/module/auth/totp/inputs/enable-totp.input'; import { User } from '@prisma/generated'; @Injectable() export class TotpService { constructor( private readonly prismaService: PrismaService, ) { } async generate(user: User) { const secret = encode(randomBytes(15)) .replace(/=/g, '') .substring(0, 24); const totp = new TOTP({ issuer: 'TeaStream', label: user.email, algorithm: 'SHA-1', digits: 6, secret, }); const qrcodeUrl = await QRCode.toDataURL(totp.toString()); return { qrcodeUrl, secret, }; } async enable(user: User, input: EnableTotpInput) { const { pin, secret } = input; const totp = new TOTP({ issuer: 'TeaStream', label: user.email, algorithm: 'SHA-1', digits: 6, secret, }); const delta = totp.validate({ token: pin }); if (delta === null) { throw new BadRequestException('Невереый код'); } await this.prismaService.user.update({ where: { id: user.id }, data: { isTotpEnabled: true, totpSecret: secret, }, }); return true; } async disable(user: User) { await this.prismaService.user.update({ where: { id: user.id }, data: { isTotpEnabled: false, totpSecret: null, }, }); return true; } }