✨ TotpCodes in tables instead of in customer
This commit is contained in:
parent
cdbb3e3257
commit
b6e1b2ff62
@ -159,12 +159,15 @@ export default class AuthController extends ApiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const customer = await this.customerService.verifyTotpCode(totpCode, email);
|
const code = await this.customerService.verifyTotpCode(totpCode, email);
|
||||||
if (!customer) {
|
if (!code) {
|
||||||
this.httpNotFoundRequest(response, "Customer not found");
|
this.httpNotFoundRequest(response, "Customer not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.httpSuccess(response, { validCode: true, firstConnection: customer.password === null });
|
this.httpSuccess(response, {
|
||||||
|
validCode: true,
|
||||||
|
reason: code.reason,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof InvalidTotpCodeError || error instanceof TotpCodeExpiredError) {
|
if (error instanceof InvalidTotpCodeError || error instanceof TotpCodeExpiredError) {
|
||||||
this.httpUnauthorized(response, error.message);
|
this.httpUnauthorized(response, error.message);
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `totpCode` on the `customers` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `totpCodeExpire` on the `customers` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "TotpCodesReasons" AS ENUM ('LOGIN', 'RESET_PASSWORD', 'FIRST_LOGIN');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "customers" DROP COLUMN "totpCode",
|
||||||
|
DROP COLUMN "totpCodeExpire";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "totp_codes" (
|
||||||
|
"uid" TEXT NOT NULL,
|
||||||
|
"customer_uid" VARCHAR(255) NOT NULL,
|
||||||
|
"code" VARCHAR(255) NOT NULL,
|
||||||
|
"reason" "TotpCodesReasons" NOT NULL DEFAULT 'LOGIN',
|
||||||
|
"expire_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "totp_codes_pkey" PRIMARY KEY ("uid")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "totp_codes_uid_key" ON "totp_codes"("uid");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "totp_codes" ADD CONSTRAINT "totp_codes_customer_uid_fkey" FOREIGN KEY ("customer_uid") REFERENCES "customers"("uid") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -102,10 +102,7 @@ model Customers {
|
|||||||
office_folders OfficeFolders[] @relation("OfficeFolderHasCustomers")
|
office_folders OfficeFolders[] @relation("OfficeFolderHasCustomers")
|
||||||
documents Documents[]
|
documents Documents[]
|
||||||
password String? @db.VarChar(255)
|
password String? @db.VarChar(255)
|
||||||
totpCode String? @db.VarChar(255)
|
totpCodes TotpCodes[]
|
||||||
totpCodeExpire DateTime? @default(now())
|
|
||||||
|
|
||||||
|
|
||||||
@@map("customers")
|
@@map("customers")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,6 +342,23 @@ model Votes {
|
|||||||
@@map("votes")
|
@@map("votes")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model TotpCodes {
|
||||||
|
uid String @id @unique @default(uuid())
|
||||||
|
customer Customers @relation(fields: [customer_uid], references: [uid], onDelete: Cascade)
|
||||||
|
customer_uid String @db.VarChar(255)
|
||||||
|
code String @db.VarChar(255)
|
||||||
|
reason TotpCodesReasons @default(LOGIN)
|
||||||
|
expire_at DateTime? @default(now())
|
||||||
|
|
||||||
|
@@map("totp_codes")
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TotpCodesReasons {
|
||||||
|
LOGIN
|
||||||
|
RESET_PASSWORD
|
||||||
|
FIRST_LOGIN
|
||||||
|
}
|
||||||
|
|
||||||
enum ECivility {
|
enum ECivility {
|
||||||
MALE
|
MALE
|
||||||
FEMALE
|
FEMALE
|
||||||
|
@ -5,7 +5,6 @@ import { Customers, ECivility, ECustomerStatus, Prisma } from "@prisma/client";
|
|||||||
import { Customer } from "le-coffre-resources/dist/SuperAdmin";
|
import { Customer } from "le-coffre-resources/dist/SuperAdmin";
|
||||||
|
|
||||||
type IExcludedCustomerVars = {
|
type IExcludedCustomerVars = {
|
||||||
totpCode?: string | null;
|
|
||||||
totpCodeExpire?: Date | null;
|
totpCodeExpire?: Date | null;
|
||||||
password?: string;
|
password?: string;
|
||||||
};
|
};
|
||||||
@ -36,7 +35,7 @@ export default class CustomersRepository extends BaseRepository {
|
|||||||
public async findOne(query: Prisma.CustomersFindFirstArgs) {
|
public async findOne(query: Prisma.CustomersFindFirstArgs) {
|
||||||
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
|
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
|
||||||
if (!query.include) return this.model.findFirst({ ...query, include: { contact: true } });
|
if (!query.include) return this.model.findFirst({ ...query, include: { contact: true } });
|
||||||
return this.model.findFirst({ ...query, include: { contact: { include: { address: true } } } });
|
return this.model.findFirst({ ...query, include: { contact: { include: { address: true } }, ...query.include } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,8 +92,6 @@ export default class CustomersRepository extends BaseRepository {
|
|||||||
address: {},
|
address: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
totpCode: excludedVars && excludedVars.totpCode,
|
|
||||||
totpCodeExpire: excludedVars && excludedVars.totpCodeExpire,
|
|
||||||
password: excludedVars && excludedVars.password,
|
password: excludedVars && excludedVars.password,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
61
src/common/repositories/TotpCodesRepository.ts
Normal file
61
src/common/repositories/TotpCodesRepository.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import Database from "@Common/databases/database";
|
||||||
|
import BaseRepository from "@Repositories/BaseRepository";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
import { Prisma, TotpCodes } from "@prisma/client";
|
||||||
|
import { TotpCodes as TotpCode } from "le-coffre-resources/dist/Customer";
|
||||||
|
|
||||||
|
type IExcludedTotpCodesVars = {
|
||||||
|
code?: string;
|
||||||
|
expire_at?: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class TotpCodesRepository extends BaseRepository {
|
||||||
|
constructor(private database: Database) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
protected get model() {
|
||||||
|
return this.database.getClient().totpCodes;
|
||||||
|
}
|
||||||
|
protected get instanceDb() {
|
||||||
|
return this.database.getClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Find many totp codes
|
||||||
|
*/
|
||||||
|
public async findMany(query: Prisma.TotpCodesFindManyArgs) {
|
||||||
|
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
|
||||||
|
if (!query.include) return this.model.findMany({ ...query });
|
||||||
|
return this.model.findMany({ ...query });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Find one totp code
|
||||||
|
*/
|
||||||
|
public async findOne(query: Prisma.TotpCodesFindFirstArgs) {
|
||||||
|
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
|
||||||
|
if (!query.include) return this.model.findFirst({ ...query });
|
||||||
|
return this.model.findFirst({ ...query });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Create a customer
|
||||||
|
*/
|
||||||
|
public async create(totpCode: TotpCode, excludedVars: IExcludedTotpCodesVars): Promise<TotpCodes> {
|
||||||
|
const createArgs: Prisma.TotpCodesCreateArgs = {
|
||||||
|
data: {
|
||||||
|
code: excludedVars.code!,
|
||||||
|
reason: totpCode.reason!,
|
||||||
|
customer: {
|
||||||
|
connect: {
|
||||||
|
uid: totpCode.customer_uid!,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expire_at: excludedVars.expire_at!,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.model.create({ ...createArgs });
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
import { Customers, Prisma } from "@prisma/client";
|
import { Customers, Prisma } from "@prisma/client";
|
||||||
import CustomersRepository from "@Repositories/CustomersRepository";
|
import CustomersRepository from "@Repositories/CustomersRepository";
|
||||||
|
import TotpCodesRepository from "@Repositories/TotpCodesRepository";
|
||||||
import BaseService from "@Services/BaseService";
|
import BaseService from "@Services/BaseService";
|
||||||
import AuthService from "@Services/common/AuthService/AuthService";
|
import AuthService from "@Services/common/AuthService/AuthService";
|
||||||
|
import TotpCodes, { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes";
|
||||||
import { Customer } from "le-coffre-resources/dist/Notary";
|
import { Customer } from "le-coffre-resources/dist/Notary";
|
||||||
import { Service } from "typedi";
|
import { Service } from "typedi";
|
||||||
|
|
||||||
@ -42,7 +44,11 @@ export class PasswordAlreadySetError extends Error {
|
|||||||
}
|
}
|
||||||
@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,
|
||||||
|
private totpCodesRepository: TotpCodesRepository,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,23 +71,33 @@ export default class CustomersService extends BaseService {
|
|||||||
/**
|
/**
|
||||||
* @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
|
||||||
* 2: Check if the SMS code is still valid
|
* 2: Check in the array of totpCodes if one is still valid
|
||||||
* 3: Generate a new SMS code
|
* 3: Generate a new SMS code
|
||||||
* 4: Save the SMS code in database
|
* 4: Save the SMS code in database
|
||||||
* 5: Send the SMS code to the customer
|
* 5: Send the SMS code to the customer
|
||||||
*/
|
*/
|
||||||
public async verifyEmail2FASms(email: string): Promise<Customer | null> {
|
public async verifyEmail2FASms(email: string): Promise<Customer | null> {
|
||||||
|
// 1: Check if the customer exists
|
||||||
const customer = await this.getByEmail(email);
|
const customer = await this.getByEmail(email);
|
||||||
if (!customer) return null;
|
if (!customer) return null;
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
// Check if the SMS code is still valid
|
|
||||||
if (customer.totpCodeExpire && now < customer.totpCodeExpire.getTime()) throw new SmsNotExpiredError();
|
|
||||||
|
|
||||||
|
const customerHydrated = Customer.hydrate<Customer>(customer);
|
||||||
|
|
||||||
|
// 2: Check in the array of totpCodes if one is still valid
|
||||||
|
const validTotpCode = customerHydrated.totpCodes?.find((totpCode) => {
|
||||||
|
return totpCode.expire_at && totpCode.expire_at.getTime() > now;
|
||||||
|
});
|
||||||
|
if (validTotpCode) throw new SmsNotExpiredError();
|
||||||
|
|
||||||
|
// 3: Generate a new SMS code
|
||||||
const totpPin = this.generateTotp();
|
const totpPin = this.generateTotp();
|
||||||
|
|
||||||
await this.saveTotpPin(customer, totpPin, new Date(now + 5 * 60000));
|
const reason = customer.password ? TotpCodesReasons.LOGIN : TotpCodesReasons.FIRST_LOGIN;
|
||||||
|
// 4: Save the SMS code in database
|
||||||
|
await this.saveTotpPin(customer, totpPin, new Date(now + 5 * 60000), reason);
|
||||||
|
|
||||||
// Send the SMS code to the customer
|
// 5: Send the SMS code to the customer
|
||||||
await this.sendSmsCodeToCustomer(totpPin, customer);
|
await this.sendSmsCodeToCustomer(totpPin, customer);
|
||||||
return customer;
|
return customer;
|
||||||
}
|
}
|
||||||
@ -90,11 +106,10 @@ export default class CustomersService extends BaseService {
|
|||||||
* @description : Set the password of a customer when it's the first time they connect
|
* @description : Set the password of a customer when it's the first time they connect
|
||||||
* 1: Check if the customer exists
|
* 1: Check if the customer exists
|
||||||
* 2: Check if the password is already set
|
* 2: Check if the password is already set
|
||||||
* 3: Check if the SMS code is existing and is not expired
|
* 3: Check if a totp code is existing and is not expired in the array
|
||||||
* 4: Check if the SMS code is valid
|
* 4: Check if the SMS code is valid
|
||||||
* 5: Hash the password
|
* 5: Hash the password
|
||||||
* 6: Set the password in database
|
* 6: Set the password in database and return the result of the update
|
||||||
* 7: Returns the customer
|
|
||||||
* @param email
|
* @param email
|
||||||
* @param totpCode
|
* @param totpCode
|
||||||
* @param password
|
* @param password
|
||||||
@ -108,28 +123,28 @@ export default class CustomersService extends BaseService {
|
|||||||
// 2: Check if the password is already set
|
// 2: Check if the password is already set
|
||||||
if (customer.password) throw new PasswordAlreadySetError();
|
if (customer.password) throw new PasswordAlreadySetError();
|
||||||
|
|
||||||
// 3: Check if the SMS code is existing and is not expired
|
const customerHydrated = Customer.hydrate<Customer>(customer);
|
||||||
if (!customer.totpCode || !customer.totpCodeExpire || new Date().getTime() > customer.totpCodeExpire.getTime())
|
// 3: Check if a totp code is existing and is not expired in the array
|
||||||
throw new TotpCodeExpiredError();
|
const validTotpCode = customerHydrated.totpCodes?.find((totpCode) => {
|
||||||
|
return totpCode.expire_at && new Date().getTime() < totpCode.expire_at.getTime();
|
||||||
|
});
|
||||||
|
if (!validTotpCode) throw new TotpCodeExpiredError();
|
||||||
|
|
||||||
// 4: Check if the SMS code is valid
|
// 4: Check if the SMS code is valid
|
||||||
if (customer.totpCode !== totpCode) throw new InvalidTotpCodeError();
|
if (validTotpCode.code !== totpCode) throw new InvalidTotpCodeError();
|
||||||
|
|
||||||
// 5: Hash the password
|
// 5: Hash the password
|
||||||
const hashedPassword = await this.authService.hashPassword(password);
|
const hashedPassword = await this.authService.hashPassword(password);
|
||||||
|
|
||||||
// 6: Set the password in database
|
// 6: Set the password in database and return the result of the update
|
||||||
await this.setPassword(customer, hashedPassword);
|
return await this.setPassword(customer, hashedPassword);
|
||||||
|
|
||||||
// 7: Returns the customer
|
|
||||||
return customer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @description : Login a customer
|
* @description : Login a customer
|
||||||
* 1: Check if the customer exists
|
* 1: Check if the customer exists
|
||||||
* 2: Check if the SMS code is existing and is not expired
|
* 2: Check if a totp code is existing and is not expired in the array
|
||||||
* 3: Check if the SMS code is valid
|
* 3: Check if the SMS code is valid
|
||||||
* 4: Check if the user has a password or it's their first login
|
* 4: Check if the user has a password or it's their first login
|
||||||
* 5: Check if the password is valid
|
* 5: Check if the password is valid
|
||||||
@ -143,13 +158,16 @@ export default class CustomersService extends BaseService {
|
|||||||
// 1: Check if the customer exists
|
// 1: Check if the customer exists
|
||||||
const customer = await this.getByEmail(email);
|
const customer = await this.getByEmail(email);
|
||||||
if (!customer) return null;
|
if (!customer) return null;
|
||||||
|
const customerHydrated = Customer.hydrate<Customer>(customer);
|
||||||
|
|
||||||
// 2: Check if the SMS code is existing and is not expired
|
// 2: Check if a totp code is existing and is not expired in the array
|
||||||
if (!customer.totpCode || !customer.totpCodeExpire || new Date().getTime() > customer.totpCodeExpire.getTime())
|
const validTotpCode = customerHydrated.totpCodes?.find((totpCode) => {
|
||||||
throw new TotpCodeExpiredError();
|
return totpCode.expire_at && new Date().getTime() < totpCode.expire_at.getTime();
|
||||||
|
});
|
||||||
|
if (!validTotpCode) throw new TotpCodeExpiredError();
|
||||||
|
|
||||||
// 3: Check if the SMS code is valid
|
// 3: Check if the SMS code is valid
|
||||||
if (customer.totpCode !== totpCode) throw new InvalidTotpCodeError();
|
if (validTotpCode.code !== totpCode) throw new InvalidTotpCodeError();
|
||||||
|
|
||||||
// 4: Check if the user has a password or it's their first login
|
// 4: Check if the user has a password or it's their first login
|
||||||
if (!customer.password) throw new NotRegisteredCustomerError();
|
if (!customer.password) throw new NotRegisteredCustomerError();
|
||||||
@ -158,18 +176,12 @@ export default class CustomersService extends BaseService {
|
|||||||
const isPasswordValid = await this.authService.comparePassword(password, customer.password);
|
const isPasswordValid = await this.authService.comparePassword(password, customer.password);
|
||||||
if (!isPasswordValid) throw new InvalidPasswordError();
|
if (!isPasswordValid) throw new InvalidPasswordError();
|
||||||
|
|
||||||
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,
|
||||||
}),
|
}),
|
||||||
{
|
|
||||||
totpCode: null,
|
|
||||||
totpCodeExpire: null,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
// 6: Return the customer
|
|
||||||
return customer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -183,8 +195,6 @@ export default class CustomersService extends BaseService {
|
|||||||
...customer,
|
...customer,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
totpCode: null,
|
|
||||||
totpCodeExpire: null,
|
|
||||||
password,
|
password,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -199,6 +209,7 @@ export default class CustomersService extends BaseService {
|
|||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
contact: true,
|
contact: true,
|
||||||
|
totpCodes: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -206,16 +217,24 @@ export default class CustomersService extends BaseService {
|
|||||||
/**
|
/**
|
||||||
* @description : Saves a TotpPin in database
|
* @description : Saves a TotpPin in database
|
||||||
*/
|
*/
|
||||||
private async saveTotpPin(customer: Customer, totpPin: number, expireAt: Date) {
|
private async saveTotpPin(customer: Customer, totpPin: number, expireAt: Date, reason: TotpCodesReasons) {
|
||||||
|
// Create the totpCode in table using repository
|
||||||
|
await this.totpCodesRepository.create(
|
||||||
|
TotpCodes.hydrate<TotpCodes>({
|
||||||
|
reason,
|
||||||
|
customer_uid: customer.uid,
|
||||||
|
customer: Customer.hydrate<Customer>(customer),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
code: totpPin.toString(),
|
||||||
|
expire_at: expireAt,
|
||||||
|
},
|
||||||
|
);
|
||||||
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,
|
||||||
}),
|
}),
|
||||||
{
|
|
||||||
totpCode: totpPin.toString(),
|
|
||||||
totpCodeExpire: expireAt,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,24 +246,33 @@ export default class CustomersService extends BaseService {
|
|||||||
console.log(totpPin);
|
console.log(totpPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async verifyTotpCode(totpCode: string, email: string): Promise<Customer | null> {
|
/**
|
||||||
// 1: Check if the customer exists
|
*
|
||||||
// 2: Check if the SMS code is existing and is not expired
|
* 1: Check if the customer exists
|
||||||
// 3: Check if the SMS code is valid
|
* 2: Check if a totp code is existing and is not expired in the array
|
||||||
// 4: Return the customer
|
* 3: Check if the totp code is valid
|
||||||
|
* 4: Return the customer
|
||||||
|
* @param totpCode
|
||||||
|
* @param email
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async verifyTotpCode(totpCode: string, email: string): Promise<TotpCodes | null> {
|
||||||
// 1: Check if the customer exists
|
// 1: Check if the customer exists
|
||||||
const customer = await this.getByEmail(email);
|
const customer = await this.getByEmail(email);
|
||||||
if (!customer) return null;
|
if (!customer) return null;
|
||||||
|
|
||||||
// 2: Check if the SMS code is existing and is not expired
|
const customerHydrated = Customer.hydrate<Customer>(customer);
|
||||||
if (!customer.totpCode || !customer.totpCodeExpire || new Date().getTime() > customer.totpCodeExpire.getTime())
|
|
||||||
throw new TotpCodeExpiredError();
|
// 2: Check if a totp code is existing and is not expired in the array
|
||||||
|
const validTotpCode = customerHydrated.totpCodes?.find((totpCode) => {
|
||||||
|
return totpCode.expire_at && new Date().getTime() < totpCode.expire_at.getTime();
|
||||||
|
});
|
||||||
|
if (!validTotpCode) throw new TotpCodeExpiredError();
|
||||||
|
|
||||||
// 3: Check if the SMS code is valid
|
// 3: Check if the SMS code is valid
|
||||||
if (customer.totpCode !== totpCode) throw new InvalidTotpCodeError();
|
if (validTotpCode.code !== totpCode) throw new InvalidTotpCodeError();
|
||||||
|
|
||||||
// 4: Return the customer
|
// 4: Return the customer
|
||||||
return customer;
|
return validTotpCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user