import { Inject, Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { UserToken, UserTokenType } from './user-token.entity'; import { UserTOTPToken } from './user-totp-token.entity'; import { User } from './user.entity'; import speakeasy from '@levminer/speakeasy'; import * as bcrypt from 'bcrypt'; import { TokenService } from 'src/modules/utility/services/token.service'; @Injectable() export class UserService { constructor( @Inject('USER_REPOSITORY') private userRepository: Repository, @Inject('USER_TOKEN_REPOSITORY') private userTokenRepository: Repository, @Inject('USER_TOTP_TOKEN_REPOSITORY') private userTOTPTokenRepository: Repository, private token: TokenService, ) {} public async getById(id: number): Promise { return this.userRepository.findOne({ id }); } public async getByUUID(uuid: string): Promise { return this.userRepository.findOne({ uuid }); } public async getByEmail(email: string): Promise { return this.userRepository.findOne({ email }); } public async getByUsername(username: string): Promise { return this.userRepository.findOne({ username }); } public async get(input: string | number): Promise { if (typeof input === 'number') { return this.getById(input); } if (input.includes('@')) { return this.getByEmail(input); } if (input.length === 36 && input.includes('-')) { return this.getByUUID(input); } return this.getByUsername(input); } public async comparePasswords( hash: string, password: string, ): Promise { return bcrypt.compare(password, hash); } public async hashPassword(password: string): Promise { const salt = await bcrypt.genSalt(10); return bcrypt.hash(password, salt); } /** * Check if the user has TOTP enabled * @param user User object * @returns true if the user has TOTP enabled */ public async userHasTOTP(user: User): Promise { return !!(await this.getUserTOTP(user)); } /** * Get the TOTP token of a user * @param user User object * @returns TOTP token */ public async getUserTOTP(user: User): Promise { return this.userTOTPTokenRepository.findOne({ user, activated: true, }); } public validateTOTP(secret: string, token: string): boolean { return speakeasy.totp.verify({ secret, encoding: 'base32', token, window: 6, }); } public async createUserToken( user: User, type: UserTokenType, expiresAt?: Date, ): Promise { const token = new UserToken(); token.token = this.token.generateString(64); token.user = user; token.type = type; token.expires_at = expiresAt; await this.userTokenRepository.save(token); return token; } public async sendActivationEmail(user: User): Promise { const activationToken = await this.createUserToken( user, UserTokenType.ACTIVATION, new Date(Date.now() + 3600 * 1000), ); } public async userRegistration(newUserInfo: { username: string; display_name: string; email: string; password: string; }): Promise { if (!!(await this.getByEmail(newUserInfo.email))) { throw new Error('Email is already in use!'); } if (!!(await this.getByUsername(newUserInfo.username))) { throw new Error('Username is already in use!'); } const hashword = await this.hashPassword(newUserInfo.password); const user = new User(); user.email = newUserInfo.email; user.uuid = this.token.createUUID(); user.username = newUserInfo.username; user.display_name = newUserInfo.display_name; user.password = hashword; await this.userRepository.insert(user); // TODO: activation email return user; } }