From 97fd3f0d86a671d28f8ce56d25c95039dcccee10 Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Fri, 24 Nov 2023 14:23:55 +0100 Subject: [PATCH] :sparkles: Refacto set first password --- src/app/api/customer/AuthController.ts | 59 +++----- .../CustomersService/CustomersService.ts | 139 ++++++++++++------ 2 files changed, 111 insertions(+), 87 deletions(-) diff --git a/src/app/api/customer/AuthController.ts b/src/app/api/customer/AuthController.ts index 4c9f000b..b2c1e204 100644 --- a/src/app/api/customer/AuthController.ts +++ b/src/app/api/customer/AuthController.ts @@ -7,6 +7,7 @@ import CustomersService, { InvalidPasswordError, InvalidTotpCodeError, NotRegisteredCustomerError, + PasswordAlreadySetError, SmsNotExpiredError, TotpCodeExpiredError, } from "@Services/customer/CustomersService/CustomersService"; @@ -115,47 +116,29 @@ export default class AuthController extends ApiController { return; } - const customer = await this.customerService.getOne({ - where: { - contact: { - email, - }, - }, - include: { - contact: true, - }, - }); - - if (!customer) { - this.httpNotFoundRequest(response, "Customer not found"); - return; - } - - if (customer.password) { - this.httpBadRequest(response, "Password already set, please login"); - return; - } - - if (!customer.smsCode) { - this.httpBadRequest(response, "No sms code found"); - return; - } - - if (customer.smsCode !== smsCode) { - this.httpBadRequest(response, "Invalid sms code"); - return; - } - - const hashedPassword = await this.authService.hashPassword(password); - await this.customerService.setPassword(customer, hashedPassword); - - const customerHydrated = Customer.hydrate(customer); - const payload = await this.authService.getCustomerJwtPayload([customerHydrated]); - const accessToken = this.authService.generateAccessToken(payload); - const refreshToken = this.authService.generateRefreshToken(payload); try { + const customer = await this.customerService.setFirstPassword(email, smsCode, password); + if (!customer) { + this.httpBadRequest(response, "Customer not found"); + return; + } + + const customerHydrated = Customer.hydrate(customer); + const payload = await this.authService.getCustomerJwtPayload([customerHydrated]); + const accessToken = this.authService.generateAccessToken(payload); + const refreshToken = this.authService.generateRefreshToken(payload); this.httpSuccess(response, { accessToken, refreshToken }); } catch (error) { + if (error instanceof TotpCodeExpiredError || error instanceof PasswordAlreadySetError) { + this.httpBadRequest(response, error.message); + return; + } + + if (error instanceof InvalidTotpCodeError) { + this.httpUnauthorized(response, error.message); + return; + } + console.log(error); this.httpInternalError(response); return; diff --git a/src/services/customer/CustomersService/CustomersService.ts b/src/services/customer/CustomersService/CustomersService.ts index 85aca519..63885261 100644 --- a/src/services/customer/CustomersService/CustomersService.ts +++ b/src/services/customer/CustomersService/CustomersService.ts @@ -34,6 +34,12 @@ export class InvalidPasswordError extends Error { 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) { @@ -56,10 +62,6 @@ export default class CustomersService extends BaseService { return this.customerRepository.findOne(query); } - public async userExistsByEmail(email: string): Promise { - return !!(await this.getByEmail(email)); - } - /** * @description : Send SMS to verify the email of a customer (2FA) * 1: Check if the customer exists @@ -85,56 +87,42 @@ export default class CustomersService extends BaseService { } /** - * @description : Saves a TotpPin in database + * @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 */ - 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, - }, - ); - } + 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; - private generateTotp() { - return Math.floor(100000 + Math.random() * 900000); - } + // 2: Check if the password is already set + if (customer.password) throw new PasswordAlreadySetError(); - private async sendSmsCodeToCustomer(totpPin: number, customer: Customer) { - console.log(totpPin); - } + // 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(); - /** - * @description : Set password for a customer - * @throws {Error} If customer cannot be updated - */ - public async setPassword(customer: Customer, password: string) { - return await this.customerRepository.update( - customer.uid as string, - Customer.hydrate({ - ...customer, - }), - { - password, - }, - ); - } + // 4: Check if the SMS code is valid + if (customer.smsCode !== smsCode) throw new InvalidTotpCodeError(); - private getByEmail(email: string) { - return this.customerRepository.findOne({ - where: { - contact: { - email, - }, - }, - include: { - contact: true, - }, - }); + // 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; } /** @@ -173,4 +161,57 @@ export default class CustomersService extends BaseService { // 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); + } }