Refacto set first password

This commit is contained in:
Maxime Lalo 2023-11-24 14:23:55 +01:00
parent d3f9527f85
commit 97fd3f0d86
2 changed files with 111 additions and 87 deletions

View File

@ -7,6 +7,7 @@ import CustomersService, {
InvalidPasswordError, InvalidPasswordError,
InvalidTotpCodeError, InvalidTotpCodeError,
NotRegisteredCustomerError, NotRegisteredCustomerError,
PasswordAlreadySetError,
SmsNotExpiredError, SmsNotExpiredError,
TotpCodeExpiredError, TotpCodeExpiredError,
} from "@Services/customer/CustomersService/CustomersService"; } from "@Services/customer/CustomersService/CustomersService";
@ -115,47 +116,29 @@ export default class AuthController extends ApiController {
return; 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>(customer);
const payload = await this.authService.getCustomerJwtPayload([customerHydrated]);
const accessToken = this.authService.generateAccessToken(payload);
const refreshToken = this.authService.generateRefreshToken(payload);
try { try {
const customer = await this.customerService.setFirstPassword(email, smsCode, password);
if (!customer) {
this.httpBadRequest(response, "Customer not found");
return;
}
const customerHydrated = Customer.hydrate<Customer>(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 }); this.httpSuccess(response, { accessToken, refreshToken });
} catch (error) { } 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); console.log(error);
this.httpInternalError(response); this.httpInternalError(response);
return; return;

View File

@ -34,6 +34,12 @@ export class InvalidPasswordError extends Error {
super("Invalid password"); super("Invalid password");
} }
} }
export class PasswordAlreadySetError extends Error {
constructor() {
super("Password already set");
}
}
@Service() @Service()
export default class CustomersService extends BaseService { export default class CustomersService extends BaseService {
constructor(private customerRepository: CustomersRepository, private authService: AuthService) { constructor(private customerRepository: CustomersRepository, private authService: AuthService) {
@ -56,10 +62,6 @@ 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 : Send SMS to verify the email of a customer (2FA) * @description : Send SMS to verify the email of a customer (2FA)
* 1: Check if the customer exists * 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) { public async setFirstPassword(email: string, smsCode: string, password: string): Promise<Customer | null> {
return await this.customerRepository.update( // 1: Check if the customer exists
customer.uid as string, const customer = await this.getByEmail(email);
Customer.hydrate<Customer>({ if (!customer) return null;
...customer,
}),
{
smsCode: totpPin.toString(),
smsCodeExpire: expireAt,
},
);
}
private generateTotp() { // 2: Check if the password is already set
return Math.floor(100000 + Math.random() * 900000); if (customer.password) throw new PasswordAlreadySetError();
}
private async sendSmsCodeToCustomer(totpPin: number, customer: Customer) { // 3: Check if the SMS code is existing and is not expired
console.log(totpPin); if (!customer.smsCode || !customer.smsCodeExpire || new Date().getTime() > customer.smsCodeExpire.getTime())
} throw new TotpCodeExpiredError();
/** // 4: Check if the SMS code is valid
* @description : Set password for a customer if (customer.smsCode !== smsCode) throw new InvalidTotpCodeError();
* @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>({
...customer,
}),
{
password,
},
);
}
private getByEmail(email: string) { // 5: Hash the password
return this.customerRepository.findOne({ const hashedPassword = await this.authService.hashPassword(password);
where: {
contact: { // 6: Set the password in database
email, await this.setPassword(customer, hashedPassword);
},
}, // 7: Returns the customer
include: { return customer;
contact: true,
},
});
} }
/** /**
@ -173,4 +161,57 @@ export default class CustomersService extends BaseService {
// 6: Return the customer // 6: Return the customer
return 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);
}
} }