diff --git a/src/app/api/customer/AuthController.ts b/src/app/api/customer/AuthController.ts index e57f695c..bd88164a 100644 --- a/src/app/api/customer/AuthController.ts +++ b/src/app/api/customer/AuthController.ts @@ -3,7 +3,7 @@ import { Controller, Post } from "@ControllerPattern/index"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; import { EnrollmentResponse } from "@Services/common/Id360Service/Id360Service"; -import CustomersService from "@Services/customer/CustomersService/CustomersService"; +import CustomersService, { SmsNotExpiredError } from "@Services/customer/CustomersService/CustomersService"; import AuthService, { ICustomerJwtPayload } from "@Services/common/AuthService/AuthService"; import { Customer } from "le-coffre-resources/dist/SuperAdmin"; @@ -14,61 +14,28 @@ export default class AuthController extends ApiController { super(); } - @Post("/api/v1/customer/pre-login") - protected async preLogin(req: Request, response: Response) { + @Post("/api/v1/customer/login/mail/verify-sms") + protected async mailVerifySms(req: Request, response: Response) { const email = req.body["email"]; if (!email) { this.httpBadRequest(response, "Email is required"); return; } - const customer = await this.customerService.getOne({ - where: { - contact: { - email, - }, - }, - include: { - contact: true, - }, - }); - - if (!customer) { - this.httpNotFoundRequest(response, "Customer not found"); - return; - } - - // if no sms code has been generated, generate it - // if code has expired, regenerate it - const now = new Date().getTime(); - if (customer.smsCodeExpire && now < customer.smsCodeExpire.getTime()) { - this.httpBadRequest(response, "Last sms code is still valid"); - return; - } - try { - await this.customerService.generateSmsCode(customer); - } catch (error) { - console.log(error); - this.httpInternalError(response); - } - - if (!customer.password) { - try { - this.httpSuccess(response, { info: "Sending a sms for first connection" }); - } catch (error) { - console.log(error); - this.httpInternalError(response); + const customer = await this.customerService.verifyEmail2FASms(email); + if (!customer) { + this.httpNotFoundRequest(response, "Customer not found"); + return; } - return; - } - - try { - this.httpSuccess(response, { info: "Sending a sms for a connection" }); + this.httpSuccess(response, { partialPhoneNumber: customer.contact?.cell_phone_number.slice(-4) }); } catch (error) { + if (error instanceof SmsNotExpiredError) { + this.httpTooEarlyRequest(response, error.message); + return; + } console.log(error); this.httpInternalError(response); - return; } } diff --git a/src/common/system/controller-pattern/BaseController.ts b/src/common/system/controller-pattern/BaseController.ts index 7baa9c02..0e5003cc 100644 --- a/src/common/system/controller-pattern/BaseController.ts +++ b/src/common/system/controller-pattern/BaseController.ts @@ -20,6 +20,10 @@ export default abstract class BaseController { return this.httpResponse(response, HttpCodes.BAD_REQUEST, responseData); } + protected httpTooEarlyRequest(response: Response, responseData: IResponseData = "Http Too Early Request") { + return this.httpResponse(response, HttpCodes.TOO_EARLY, responseData); + } + protected httpValidationError(response: Response, responseData: IResponseData = "Http Validation Error") { return this.httpResponse(response, HttpCodes.VALIDATION_ERROR, responseData); } diff --git a/src/common/system/controller-pattern/HttpCodes.ts b/src/common/system/controller-pattern/HttpCodes.ts index 95c4a67d..bd89e047 100644 --- a/src/common/system/controller-pattern/HttpCodes.ts +++ b/src/common/system/controller-pattern/HttpCodes.ts @@ -9,5 +9,6 @@ enum HttpCodes { NOT_FOUND = 404, UNAUTHORIZED = 401, FORBIDDEN = 403, + TOO_EARLY = 425, } export default HttpCodes; diff --git a/src/services/customer/CustomersService/CustomersService.ts b/src/services/customer/CustomersService/CustomersService.ts index 82eadc72..9b8dae07 100644 --- a/src/services/customer/CustomersService/CustomersService.ts +++ b/src/services/customer/CustomersService/CustomersService.ts @@ -4,6 +4,11 @@ import BaseService from "@Services/BaseService"; import { Customer } from "le-coffre-resources/dist/Notary"; import { Service } from "typedi"; +export class SmsNotExpiredError extends Error { + constructor() { + super("SMS code not expired"); + } +} @Service() export default class CustomersService extends BaseService { constructor(private customerRepository: CustomersRepository) { @@ -26,25 +31,58 @@ export default class CustomersService extends BaseService { return this.customerRepository.findOne(query); } + public async userExistsByEmail(email: string): Promise { + return !!(await this.getByEmail(email)); + } + /** - * @description : Generate a SMS code for a customer - * @throws {Error} + * @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 generateSmsCode(customer: Customer) { - const smsCode = Math.floor(100000 + Math.random() * 900000); - const now = new Date(); + 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 : 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: smsCode.toString(), - smsCodeExpire: new Date(now.getTime() + 5 * 60000), + smsCode: totpPin.toString(), + smsCodeExpire: expireAt, }, ); } + private generateTotp() { + return Math.floor(100000 + Math.random() * 900000); + } + + private async sendSmsCodeToCustomer(totpPin: number, customer: Customer) { + console.log(totpPin); + } + /** * @description : Set password for a customer * @throws {Error} If customer cannot be updated @@ -60,4 +98,17 @@ export default class CustomersService extends BaseService { }, ); } + + private getByEmail(email: string) { + return this.customerRepository.findOne({ + where: { + contact: { + email, + }, + }, + include: { + contact: true, + }, + }); + } }