218 lines
6.0 KiB
TypeScript
218 lines
6.0 KiB
TypeScript
import { Customers, Prisma } from "@prisma/client";
|
|
import CustomersRepository from "@Repositories/CustomersRepository";
|
|
import BaseService from "@Services/BaseService";
|
|
import AuthService from "@Services/common/AuthService/AuthService";
|
|
import { Customer } from "le-coffre-resources/dist/Notary";
|
|
import { Service } from "typedi";
|
|
|
|
export class SmsNotExpiredError extends Error {
|
|
constructor() {
|
|
super("SMS code not expired");
|
|
}
|
|
}
|
|
|
|
export class TotpCodeExpiredError extends Error {
|
|
constructor() {
|
|
super("Totp code not found or expired");
|
|
}
|
|
}
|
|
|
|
export class InvalidTotpCodeError extends Error {
|
|
constructor() {
|
|
super("Invalid Totp code");
|
|
}
|
|
}
|
|
|
|
export class NotRegisteredCustomerError extends Error {
|
|
constructor() {
|
|
super("Customer not registered");
|
|
}
|
|
}
|
|
|
|
export class InvalidPasswordError extends Error {
|
|
constructor() {
|
|
super("Invalid password");
|
|
}
|
|
}
|
|
|
|
export class PasswordAlreadySetError extends Error {
|
|
constructor() {
|
|
super("Password already set");
|
|
}
|
|
}
|
|
@Service()
|
|
export default class CustomersService extends BaseService {
|
|
constructor(private customerRepository: CustomersRepository, private authService: AuthService) {
|
|
super();
|
|
}
|
|
|
|
/**
|
|
* @description : Get all Customers
|
|
* @throws {Error} If Customers cannot be get
|
|
*/
|
|
public async get(query: Prisma.CustomersFindManyArgs): Promise<Customers[]> {
|
|
return this.customerRepository.findMany(query);
|
|
}
|
|
|
|
/**
|
|
* @description : Get all Customers
|
|
* @throws {Error} If Customers cannot be get
|
|
*/
|
|
public async getOne(query: Prisma.CustomersFindFirstArgs): Promise<Customers | null> {
|
|
return this.customerRepository.findOne(query);
|
|
}
|
|
|
|
/**
|
|
* @description : Send SMS to verify the email of a customer (2FA)
|
|
* 1: Check if the customer exists
|
|
* 2: Check if the SMS code is still valid
|
|
* 3: Generate a new SMS code
|
|
* 4: Save the SMS code in database
|
|
* 5: Send the SMS code to the customer
|
|
*/
|
|
public async verifyEmail2FASms(email: string): Promise<Customer | null> {
|
|
const customer = await this.getByEmail(email);
|
|
if (!customer) return null;
|
|
const now = new Date().getTime();
|
|
// Check if the SMS code is still valid
|
|
if (customer.smsCodeExpire && now < customer.smsCodeExpire.getTime()) throw new SmsNotExpiredError();
|
|
|
|
const totpPin = this.generateTotp();
|
|
|
|
await this.saveTotpPin(customer, totpPin, new Date(now + 5 * 60000));
|
|
|
|
// Send the SMS code to the customer
|
|
await this.sendSmsCodeToCustomer(totpPin, customer);
|
|
return customer;
|
|
}
|
|
|
|
/**
|
|
* @description : Set the password of a customer when it's the first time they connect
|
|
* 1: Check if the customer exists
|
|
* 2: Check if the password is already set
|
|
* 3: Check if the SMS code is existing and is not expired
|
|
* 4: Check if the SMS code is valid
|
|
* 5: Hash the password
|
|
* 6: Set the password in database
|
|
* 7: Returns the customer
|
|
* @param email
|
|
* @param smsCode
|
|
* @param password
|
|
* @returns
|
|
*/
|
|
public async setFirstPassword(email: string, smsCode: string, password: string): Promise<Customer | null> {
|
|
// 1: Check if the customer exists
|
|
const customer = await this.getByEmail(email);
|
|
if (!customer) return null;
|
|
|
|
// 2: Check if the password is already set
|
|
if (customer.password) throw new PasswordAlreadySetError();
|
|
|
|
// 3: Check if the SMS code is existing and is not expired
|
|
if (!customer.smsCode || !customer.smsCodeExpire || new Date().getTime() > customer.smsCodeExpire.getTime())
|
|
throw new TotpCodeExpiredError();
|
|
|
|
// 4: Check if the SMS code is valid
|
|
if (customer.smsCode !== smsCode) throw new InvalidTotpCodeError();
|
|
|
|
// 5: Hash the password
|
|
const hashedPassword = await this.authService.hashPassword(password);
|
|
|
|
// 6: Set the password in database
|
|
await this.setPassword(customer, hashedPassword);
|
|
|
|
// 7: Returns the customer
|
|
return customer;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @description : Login a customer
|
|
* 1: Check if the customer exists
|
|
* 2: Check if the SMS code is existing and is not expired
|
|
* 3: Check if the SMS code is valid
|
|
* 4: Check if the user has a password or it's their first login
|
|
* 5: Check if the password is valid
|
|
* 6: Return the customer
|
|
* @param email
|
|
* @param smsCode
|
|
* @param password
|
|
* @returns Customer | null
|
|
*/
|
|
public async login(email: string, smsCode: string, password: string): Promise<Customer | null> {
|
|
// 1: Check if the customer exists
|
|
const customer = await this.getByEmail(email);
|
|
if (!customer) return null;
|
|
|
|
// 2: Check if the SMS code is existing and is not expired
|
|
if (!customer.smsCode || !customer.smsCodeExpire || new Date().getTime() > customer.smsCodeExpire.getTime())
|
|
throw new TotpCodeExpiredError();
|
|
|
|
// 3: Check if the SMS code is valid
|
|
if (customer.smsCode !== smsCode) throw new InvalidTotpCodeError();
|
|
|
|
// 4: Check if the user has a password or it's their first login
|
|
if (!customer.password) throw new NotRegisteredCustomerError();
|
|
|
|
// 5: Check if the password is valid
|
|
const isPasswordValid = await this.authService.comparePassword(password, customer.password);
|
|
if (!isPasswordValid) throw new InvalidPasswordError();
|
|
|
|
// 6: Return the customer
|
|
return customer;
|
|
}
|
|
|
|
/**
|
|
* @description : Set password for a customer
|
|
* @throws {Error} If customer cannot be updated
|
|
*/
|
|
private async setPassword(customer: Customer, password: string) {
|
|
return await this.customerRepository.update(
|
|
customer.uid as string,
|
|
Customer.hydrate<Customer>({
|
|
...customer,
|
|
}),
|
|
{
|
|
password,
|
|
},
|
|
);
|
|
}
|
|
|
|
private getByEmail(email: string) {
|
|
return this.customerRepository.findOne({
|
|
where: {
|
|
contact: {
|
|
email,
|
|
},
|
|
},
|
|
include: {
|
|
contact: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @description : Saves a TotpPin in database
|
|
*/
|
|
private async saveTotpPin(customer: Customer, totpPin: number, expireAt: Date) {
|
|
return await this.customerRepository.update(
|
|
customer.uid as string,
|
|
Customer.hydrate<Customer>({
|
|
...customer,
|
|
}),
|
|
{
|
|
smsCode: totpPin.toString(),
|
|
smsCodeExpire: expireAt,
|
|
},
|
|
);
|
|
}
|
|
|
|
private generateTotp() {
|
|
return Math.floor(100000 + Math.random() * 900000);
|
|
}
|
|
|
|
private async sendSmsCodeToCustomer(totpPin: number, customer: Customer) {
|
|
console.log(totpPin);
|
|
}
|
|
}
|