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

170 lines
4.4 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 { TokenService } from 'src/modules/utility/services/token.service';
import { authenticator as totp } from 'otplib';
import * as bcrypt from 'bcrypt';
totp.options = {
window: 2,
};
@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 totp.verify({ token, secret });
}
public getTOTPURL(secret: string, username: string): string {
return totp.keyuri(username, 'Icy Network', secret);
}
public createTOTPSecret(): string {
return totp.generateSecret();
}
public async activateTOTP(
user: User,
secret: string,
): Promise<UserTOTPToken> {
const totp = new UserTOTPToken();
totp.activated = true;
totp.user = user;
totp.token = secret;
totp.recovery_token = Array.from({ length: 8 }, () =>
this.token.generateString(8),
).join(' ');
await this.userTOTPTokenRepository.save(totp);
return totp;
}
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;
}
}