Refacto auth controller

This commit is contained in:
Maxime Lalo 2023-11-24 12:01:22 +01:00
parent a3deb8dc23
commit fc8b86b7ca
4 changed files with 75 additions and 52 deletions

View File

@ -3,7 +3,7 @@ import { Controller, Post } from "@ControllerPattern/index";
import ApiController from "@Common/system/controller-pattern/ApiController"; import ApiController from "@Common/system/controller-pattern/ApiController";
import { Service } from "typedi"; import { Service } from "typedi";
import { EnrollmentResponse } from "@Services/common/Id360Service/Id360Service"; 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 AuthService, { ICustomerJwtPayload } from "@Services/common/AuthService/AuthService";
import { Customer } from "le-coffre-resources/dist/SuperAdmin"; import { Customer } from "le-coffre-resources/dist/SuperAdmin";
@ -14,61 +14,28 @@ export default class AuthController extends ApiController {
super(); super();
} }
@Post("/api/v1/customer/pre-login") @Post("/api/v1/customer/login/mail/verify-sms")
protected async preLogin(req: Request, response: Response) { protected async mailVerifySms(req: Request, response: Response) {
const email = req.body["email"]; const email = req.body["email"];
if (!email) { if (!email) {
this.httpBadRequest(response, "Email is required"); this.httpBadRequest(response, "Email is required");
return; 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 { try {
await this.customerService.generateSmsCode(customer); const customer = await this.customerService.verifyEmail2FASms(email);
} catch (error) { if (!customer) {
console.log(error); this.httpNotFoundRequest(response, "Customer not found");
this.httpInternalError(response); return;
}
if (!customer.password) {
try {
this.httpSuccess(response, { info: "Sending a sms for first connection" });
} catch (error) {
console.log(error);
this.httpInternalError(response);
} }
return; this.httpSuccess(response, { partialPhoneNumber: customer.contact?.cell_phone_number.slice(-4) });
}
try {
this.httpSuccess(response, { info: "Sending a sms for a connection" });
} catch (error) { } catch (error) {
if (error instanceof SmsNotExpiredError) {
this.httpTooEarlyRequest(response, error.message);
return;
}
console.log(error); console.log(error);
this.httpInternalError(response); this.httpInternalError(response);
return;
} }
} }

View File

@ -20,6 +20,10 @@ export default abstract class BaseController {
return this.httpResponse(response, HttpCodes.BAD_REQUEST, responseData); 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") { protected httpValidationError(response: Response, responseData: IResponseData = "Http Validation Error") {
return this.httpResponse(response, HttpCodes.VALIDATION_ERROR, responseData); return this.httpResponse(response, HttpCodes.VALIDATION_ERROR, responseData);
} }

View File

@ -9,5 +9,6 @@ enum HttpCodes {
NOT_FOUND = 404, NOT_FOUND = 404,
UNAUTHORIZED = 401, UNAUTHORIZED = 401,
FORBIDDEN = 403, FORBIDDEN = 403,
TOO_EARLY = 425,
} }
export default HttpCodes; export default HttpCodes;

View File

@ -4,6 +4,11 @@ import BaseService from "@Services/BaseService";
import { Customer } from "le-coffre-resources/dist/Notary"; import { Customer } from "le-coffre-resources/dist/Notary";
import { Service } from "typedi"; import { Service } from "typedi";
export class SmsNotExpiredError extends Error {
constructor() {
super("SMS code not expired");
}
}
@Service() @Service()
export default class CustomersService extends BaseService { export default class CustomersService extends BaseService {
constructor(private customerRepository: CustomersRepository) { constructor(private customerRepository: CustomersRepository) {
@ -26,25 +31,58 @@ export default class CustomersService extends BaseService {
return this.customerRepository.findOne(query); return this.customerRepository.findOne(query);
} }
public async userExistsByEmail(email: string): Promise<boolean> {
return !!(await this.getByEmail(email));
}
/** /**
* @description : Generate a SMS code for a customer * @description : Send SMS to verify the email of a customer (2FA)
* @throws {Error} * 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) { public async verifyEmail2FASms(email: string): Promise<Customer | null> {
const smsCode = Math.floor(100000 + Math.random() * 900000); const customer = await this.getByEmail(email);
const now = new Date(); 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( return await this.customerRepository.update(
customer.uid as string, customer.uid as string,
Customer.hydrate<Customer>({ Customer.hydrate<Customer>({
...customer, ...customer,
}), }),
{ {
smsCode: smsCode.toString(), smsCode: totpPin.toString(),
smsCodeExpire: new Date(now.getTime() + 5 * 60000), 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 * @description : Set password for a customer
* @throws {Error} If customer cannot be updated * @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,
},
});
}
} }