2023-11-24 14:23:55 +01:00

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);
}
}