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 { return this.customerRepository.findMany(query); } /** * @description : Get all Customers * @throws {Error} If Customers cannot be get */ public async getOne(query: Prisma.CustomersFindFirstArgs): Promise { 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 { 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 { // 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 { // 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, }), { 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, }), { smsCode: totpPin.toString(), smsCodeExpire: expireAt, }, ); } private generateTotp() { return Math.floor(100000 + Math.random() * 900000); } private async sendSmsCodeToCustomer(totpPin: number, customer: Customer) { console.log(totpPin); } }