import { InjectRepository } from '@nestjs/typeorm'; import { UserTokenEntity } from '../database/entities/user-token.entity'; import { authenticator as totp } from 'otplib'; import { Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; import { UserEntity } from '../database/entities/user.entity'; import { UserTokenType, generateString } from '@freeblox/shared'; totp.options = { window: 2, }; @Injectable() export class OTPService { constructor( @InjectRepository(UserTokenEntity) private readonly userTokenRepository: Repository, ) {} /** * Check if the user has TOTP enabled * @param user User object * @returns true if the user has TOTP enabled */ public async userHasTOTP(user: UserEntity): Promise { return !!(await this.getUserTOTP(user)); } /** * Get the TOTP token of a user * @param user User object * @returns TOTP token */ public async getUserTOTP(user: UserEntity): Promise { return this.userTokenRepository.findOne({ where: { user: { id: user.id }, type: UserTokenType.TOTP }, relations: ['user'], }); } public validateTOTP(secret: string, token: string): boolean { return totp.verify({ token, secret }); } public getTOTPURL(secret: string, username: string): string { return totp.keyuri(username, 'Freeblox', secret); } public createTOTPSecret(): string { return totp.generateSecret(); } public async activateTOTP( user: UserEntity, secret: string, ): Promise { const totp = new UserTokenEntity(); const recovery = new UserTokenEntity(); totp.user = user; totp.token = secret; totp.type = UserTokenType.TOTP; recovery.user = user; recovery.token = Array.from({ length: 8 }, () => generateString(8)).join( ' ', ); recovery.type = UserTokenType.RECOVERY; await this.userTokenRepository.save(totp); await this.userTokenRepository.save(recovery); return [totp, recovery]; } public async deactivateTOTP(token: UserTokenEntity): Promise { if (!token) { return; } await this.userTokenRepository.delete({ type: UserTokenType.RECOVERY, user: { id: token.user.id }, }); await this.userTokenRepository.remove(token); } }