Newer
Older
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import * as bcrypt from 'bcrypt';
import * as ejs from 'ejs';
import * as crypto from 'crypto';
import { Model } from 'mongoose';
import { LoginDto } from '../auth/login-dto';
import { CreateUserDto } from './create-user.dto';
import { User } from './user.schema';
import { MailerService } from '../mailer/mailer.service';
import { IUser } from './user.interface';
@Injectable()
export class UsersService {
constructor(@InjectModel(User.name) private userModel: Model<IUser>, private mailerService: MailerService) {}
/**
* Create a user account
* @param createUserDto CreateUserDto
*/
public async create(createUserDto: CreateUserDto): Promise<User> {
const userInDb = await this.findOne(createUserDto.email);
if (userInDb) {
throw new HttpException('User already exists', HttpStatus.BAD_REQUEST);
}

Hugo SUBTIL
committed
if (!this.isStrongPassword(createUserDto.password)) {
throw new HttpException(
'Weak password, it must contain ne lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer',
HttpStatus.UNPROCESSABLE_ENTITY
);
}
let createUser = new this.userModel(createUserDto);
createUser.password = await this.hashPassword(createUser.password);
// Send verification email
createUser = await this.verifyUserMail(createUser);

Hugo SUBTIL
committed
return await this.findOne(createUserDto.email);
}
/**
* Verify password strenth with the following rule:
* - The string must contain at least 1 lowercase alphabetical character
* - The string must contain at least 1 uppercase alphabetical character
* - The string must contain at least 1 numeric character
* - The string must contain at least one special character, reserved RegEx characters are escaped to avoid conflict
* - The string must be eight characters or longer
* @param password string
*/
private isStrongPassword(password: string): boolean {
const strongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})');
return strongRegex.test(password);
}
private async comparePassword(attempt: string, password: string): Promise<boolean> {
return await bcrypt.compare(attempt, password);
}
private async hashPassword(password: string): Promise<string> {

Hugo SUBTIL
committed
return await bcrypt.hash(password, process.env.SALT);
public async findOne(mail: string, passwordQuery?: boolean): Promise<IUser | undefined> {
if (passwordQuery) {
return this.userModel.findOne({ email: mail }).exec();
}
return this.userModel.findOne({ email: mail }).select('-password').exec();
}
public async findById(id: string, passwordQuery?: boolean): Promise<IUser | undefined> {
if (passwordQuery) {
return this.userModel.findById(id).exec();
}
return this.userModel.findById(id).select('-password').exec();
}
/**
* Return a user after credential checking.
* Use for login action
* @param param LoginDto
*/
public async findByLogin({ email, password }: LoginDto): Promise<User> {
const user = await this.findOne(email, true);
if (!user) {
throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED);
}
// compare passwords
const areEqual = await this.comparePassword(password, user.password);
if (!areEqual) {
throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED);
}
return user;
/**
* Generate activation token and send it to user by email, in order to validate
* a new account.
* @param user User
*/
private async verifyUserMail(user: IUser): Promise<any> {
const config = this.mailerService.config;
const ejsPath = this.mailerService.getTemplateLocation(config.templates.verify.ejs);
const jsonConfig = this.mailerService.loadJsonConfig(config.templates.verify.json);
const token = crypto.randomBytes(64).toString('hex');
const html = await ejs.renderFile(ejsPath, {
config,
token: token,
});
this.mailerService.send(user.email, jsonConfig.subject, html);
// Save token
user.validationToken = token;
return user;
}
/**
* Check that the given token is associated to userId. If it's true, validate user account.
* @param userId string
* @param token string
*/
public async validateUser(userId: string, token: string): Promise<any> {
const user = await this.findById(userId);
if (user && user.validationToken === token) {
user.validationToken = null;
user.emailVerified = true;
user.save();
} else {
throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED);
}
}
public async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<any> {
const user = await this.findById(userId, true);
const arePasswordEqual = await this.comparePassword(oldPassword, user.password);
if (!arePasswordEqual) {
throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED);
}
if (!this.isStrongPassword(newPassword)) {
throw new HttpException(
'Weak password, it must contain ne lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer',
HttpStatus.UNPROCESSABLE_ENTITY
);
}
user.password = await this.hashPassword(newPassword);
user.save();
}