icynet-auth-server/src/modules/objects/user/user.service.ts

147 lines
3.8 KiB
TypeScript

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<User>,
@Inject('USER_TOKEN_REPOSITORY')
private userTokenRepository: Repository<UserToken>,
@Inject('USER_TOTP_TOKEN_REPOSITORY')
private userTOTPTokenRepository: Repository<UserTOTPToken>,
private token: TokenService,
) {}
public async getById(id: number): Promise<User> {
return this.userRepository.findOne({ id });
}
public async getByUUID(uuid: string): Promise<User> {
return this.userRepository.findOne({ uuid });
}
public async getByEmail(email: string): Promise<User> {
return this.userRepository.findOne({ email });
}
public async getByUsername(username: string): Promise<User> {
return this.userRepository.findOne({ username });
}
public async get(input: string | number): Promise<User> {
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<boolean> {
return bcrypt.compare(password, hash);
}
public async hashPassword(password: string): Promise<string> {
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<boolean> {
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<UserTOTPToken> {
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<UserToken> {
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<void> {
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<User> {
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;
}
}