Merge branch 'staging' into preprod
This commit is contained in:
commit
6605c3101b
@ -5,7 +5,7 @@ scwSecretKey: AgChoEnPitXp4Ny/rVMEcevaWKNVpyj2cJYAcq+yFqKwVwnLB+ffDvwqz9XBHu+6d4
|
|||||||
lecoffreBack:
|
lecoffreBack:
|
||||||
serviceAccountName: lecoffre-back-sa
|
serviceAccountName: lecoffre-back-sa
|
||||||
envSecrets: stg-env
|
envSecrets: stg-env
|
||||||
command: "'sh', '-c', 'export $(xargs </etc/env/.env) && npm run start'"
|
command: "'sh', '-c', 'export $(xargs </etc/env/.env) && npm run api:start'"
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: docker-pull-secret
|
- name: docker-pull-secret
|
||||||
image:
|
image:
|
||||||
|
@ -46,6 +46,8 @@
|
|||||||
"@pinata/sdk": "^2.1.0",
|
"@pinata/sdk": "^2.1.0",
|
||||||
"@prisma/client": "^4.11.0",
|
"@prisma/client": "^4.11.0",
|
||||||
"adm-zip": "^0.5.10",
|
"adm-zip": "^0.5.10",
|
||||||
|
"axios": "^1.6.2",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
@ -55,13 +57,14 @@
|
|||||||
"file-type-checker": "^1.0.8",
|
"file-type-checker": "^1.0.8",
|
||||||
"fp-ts": "^2.16.1",
|
"fp-ts": "^2.16.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.94",
|
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.104",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"monocle-ts": "^2.3.13",
|
"monocle-ts": "^2.3.13",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"next": "^13.1.5",
|
"next": "^13.1.5",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
"node-schedule": "^2.1.1",
|
"node-schedule": "^2.1.1",
|
||||||
|
"ovh": "^2.0.3",
|
||||||
"prisma-query": "^2.0.0",
|
"prisma-query": "^2.0.0",
|
||||||
"puppeteer": "^21.3.4",
|
"puppeteer": "^21.3.4",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
@ -74,6 +77,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/adm-zip": "^0.5.3",
|
"@types/adm-zip": "^0.5.3",
|
||||||
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/cors": "^2.8.13",
|
"@types/cors": "^2.8.13",
|
||||||
"@types/cron": "^2.0.1",
|
"@types/cron": "^2.0.1",
|
||||||
"@types/express": "^4.17.16",
|
"@types/express": "^4.17.16",
|
||||||
|
244
src/app/api/customer/AuthController.ts
Normal file
244
src/app/api/customer/AuthController.ts
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
import { Response, Request } from "express";
|
||||||
|
import { Controller, Post } from "@ControllerPattern/index";
|
||||||
|
import ApiController from "@Common/system/controller-pattern/ApiController";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
import CustomersService, {
|
||||||
|
InvalidPasswordError,
|
||||||
|
InvalidTotpCodeError,
|
||||||
|
NotRegisteredCustomerError,
|
||||||
|
SmsNotExpiredError,
|
||||||
|
TooSoonForNewCode,
|
||||||
|
TotpCodeExpiredError,
|
||||||
|
} from "@Services/customer/CustomersService/CustomersService";
|
||||||
|
import AuthService from "@Services/common/AuthService/AuthService";
|
||||||
|
import { Customer } from "le-coffre-resources/dist/SuperAdmin";
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
@Service()
|
||||||
|
export default class AuthController extends ApiController {
|
||||||
|
constructor(private customerService: CustomersService, private authService: AuthService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/api/v1/customer/auth/mail/verify-sms")
|
||||||
|
protected async mailVerifySms(req: Request, response: Response) {
|
||||||
|
const email = req.body["email"];
|
||||||
|
if (!email) {
|
||||||
|
this.httpBadRequest(response, "Email is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await this.customerService.verifyEmail2FASms(email);
|
||||||
|
if (!res) {
|
||||||
|
this.httpNotFoundRequest(response, "Customer not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.httpSuccess(response, {
|
||||||
|
partialPhoneNumber: res.customer.contact?.cell_phone_number.replace(/\s/g, "").slice(-4),
|
||||||
|
totpCodeUid: res.totpCode.uid,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
this.httpInternalError(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/api/v1/customer/auth/ask-new-password")
|
||||||
|
protected async askNewPassword(req: Request, response: Response) {
|
||||||
|
const email = req.body["email"];
|
||||||
|
if (!email) {
|
||||||
|
this.httpBadRequest(response, "Email is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const customer = await this.customerService.generateCodeForNewPassword(email);
|
||||||
|
if (!customer) {
|
||||||
|
this.httpNotFoundRequest(response, "Customer not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.httpSuccess(response, { partialPhoneNumber: customer.contact?.cell_phone_number.replace(/\s/g, "").slice(-4) });
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SmsNotExpiredError) {
|
||||||
|
this.httpTooEarlyRequest(response, error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(error);
|
||||||
|
this.httpInternalError(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/api/v1/customer/auth/login")
|
||||||
|
protected async login(req: Request, response: Response) {
|
||||||
|
const email = req.body["email"];
|
||||||
|
const totpCode = req.body["totpCode"];
|
||||||
|
const password = req.body["password"];
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
this.httpBadRequest(response, "email is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!totpCode) {
|
||||||
|
this.httpBadRequest(response, "totpCode is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
this.httpBadRequest(response, "password is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const customer = await this.customerService.login(email, totpCode, 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 });
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof TotpCodeExpiredError || error instanceof NotRegisteredCustomerError) {
|
||||||
|
this.httpBadRequest(response, error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof InvalidTotpCodeError || error instanceof InvalidPasswordError) {
|
||||||
|
this.httpUnauthorized(response, error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(error);
|
||||||
|
this.httpInternalError(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/api/v1/customer/auth/set-password")
|
||||||
|
protected async setPassword(req: Request, response: Response) {
|
||||||
|
const email = req.body["email"];
|
||||||
|
const totpCode = req.body["totpCode"];
|
||||||
|
const password = req.body["password"];
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
this.httpBadRequest(response, "email is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!totpCode) {
|
||||||
|
this.httpBadRequest(response, "totpCode is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
this.httpBadRequest(response, "password is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordRegex = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,}$/);
|
||||||
|
if (!passwordRegex.test(password)) {
|
||||||
|
this.httpBadRequest(response, "Password must contain at least 8 characters, 1 uppercase, 1 lowercase and 1 number");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const customer = await this.customerService.setPassword(email, totpCode, 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 });
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof TotpCodeExpiredError) {
|
||||||
|
this.httpBadRequest(response, error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof InvalidTotpCodeError) {
|
||||||
|
this.httpUnauthorized(response, error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(error);
|
||||||
|
this.httpInternalError(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/api/v1/customer/auth/verify-totp-code")
|
||||||
|
protected async verifyTotpCode(req: Request, response: Response) {
|
||||||
|
const totpCode = req.body["totpCode"];
|
||||||
|
const email = req.body["email"];
|
||||||
|
if (!totpCode) {
|
||||||
|
this.httpBadRequest(response, "totpCode is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
this.httpBadRequest(response, "email is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const code = await this.customerService.verifyTotpCode(totpCode, email);
|
||||||
|
if (!code) {
|
||||||
|
this.httpNotFoundRequest(response, "Customer not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.httpSuccess(response, {
|
||||||
|
validCode: true,
|
||||||
|
reason: code.reason,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof InvalidTotpCodeError || error instanceof TotpCodeExpiredError) {
|
||||||
|
this.httpUnauthorized(response, error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(error);
|
||||||
|
this.httpInternalError(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/api/v1/customer/auth/send-another-code")
|
||||||
|
protected async sendAnotherCode(req: Request, response: Response) {
|
||||||
|
const email = req.body["email"];
|
||||||
|
const totpCodeUid = req.body["totpCodeUid"];
|
||||||
|
if (!email) {
|
||||||
|
this.httpBadRequest(response, "email is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!totpCodeUid) {
|
||||||
|
this.httpBadRequest(response, "totpCodeUid is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await this.customerService.askAnotherCode(email, totpCodeUid);
|
||||||
|
if (!res) {
|
||||||
|
this.httpNotFoundRequest(response, "Customer not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.httpSuccess(response, {
|
||||||
|
partialPhoneNumber: res.customer.contact?.cell_phone_number.replace(/\s/g, "").slice(-4),
|
||||||
|
totpCodeUid: res.totpCode.uid,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof TooSoonForNewCode || error instanceof TotpCodeExpiredError) {
|
||||||
|
this.httpUnauthorized(response, error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(error);
|
||||||
|
this.httpInternalError(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,7 +45,7 @@ import LiveVoteController from "./api/super-admin/LiveVoteController";
|
|||||||
import DocumentControllerId360 from "./api/id360/DocumentController";
|
import DocumentControllerId360 from "./api/id360/DocumentController";
|
||||||
import CustomerControllerId360 from "./api/id360/CustomerController";
|
import CustomerControllerId360 from "./api/id360/CustomerController";
|
||||||
import UserNotificationController from "./api/notary/UserNotificationController";
|
import UserNotificationController from "./api/notary/UserNotificationController";
|
||||||
|
import AuthController from "./api/customer/AuthController";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description This allow to declare all controllers used in the application
|
* @description This allow to declare all controllers used in the application
|
||||||
@ -99,5 +99,6 @@ export default {
|
|||||||
Container.get(UserNotificationController);
|
Container.get(UserNotificationController);
|
||||||
Container.get(DocumentControllerId360);
|
Container.get(DocumentControllerId360);
|
||||||
Container.get(CustomerControllerId360);
|
Container.get(CustomerControllerId360);
|
||||||
|
Container.get(AuthController);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -109,6 +109,24 @@ export class BackendVariables {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
public readonly DOCAPOST_APP_PASSWORD!: string;
|
public readonly DOCAPOST_APP_PASSWORD!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly SMS_PROVIDER!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly OVH_APP_KEY!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly OVH_APP_SECRET!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly OVH_CONSUMER_KEY!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly OVH_SMS_SERVICE_NAME!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly SMS_FACTOR_TOKEN!: string;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
this.DATABASE_PORT = process.env["DATABASE_PORT"]!;
|
this.DATABASE_PORT = process.env["DATABASE_PORT"]!;
|
||||||
@ -146,6 +164,13 @@ export class BackendVariables {
|
|||||||
this.BACK_API_HOST = process.env["BACK_API_HOST"]!;
|
this.BACK_API_HOST = process.env["BACK_API_HOST"]!;
|
||||||
this.DOCAPOST_APP_ID = process.env["DOCAPOST_APP_ID"]!;
|
this.DOCAPOST_APP_ID = process.env["DOCAPOST_APP_ID"]!;
|
||||||
this.DOCAPOST_APP_PASSWORD = process.env["DOCAPOST_APP_PASSWORD"]!;
|
this.DOCAPOST_APP_PASSWORD = process.env["DOCAPOST_APP_PASSWORD"]!;
|
||||||
|
this.SMS_PROVIDER = process.env["SMS_PROVIDER"]!;
|
||||||
|
this.OVH_APP_KEY = process.env["OVH_APP_KEY"]!;
|
||||||
|
this.OVH_APP_SECRET = process.env["OVH_APP_SECRET"]!;
|
||||||
|
this.OVH_CONSUMER_KEY = process.env["OVH_CONSUMER_KEY"]!;
|
||||||
|
this.OVH_SMS_SERVICE_NAME = process.env["OVH_SMS_SERVICE_NAME"]!;
|
||||||
|
this.SMS_FACTOR_TOKEN = process.env["SMS_FACTOR_TOKEN"]!;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
public async validate(groups?: string[]) {
|
public async validate(groups?: string[]) {
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "customers" ADD COLUMN "password" VARCHAR(255),
|
||||||
|
ADD COLUMN "totpCode" VARCHAR(255),
|
||||||
|
ADD COLUMN "totpCodeExpire" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP;
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
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,
|
||||||
|
"created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3),
|
||||||
|
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;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "totp_codes" ADD COLUMN "resent" BOOLEAN NOT NULL DEFAULT false;
|
@ -101,7 +101,8 @@ model Customers {
|
|||||||
updated_at DateTime? @updatedAt
|
updated_at DateTime? @updatedAt
|
||||||
office_folders OfficeFolders[] @relation("OfficeFolderHasCustomers")
|
office_folders OfficeFolders[] @relation("OfficeFolderHasCustomers")
|
||||||
documents Documents[]
|
documents Documents[]
|
||||||
|
password String? @db.VarChar(255)
|
||||||
|
totpCodes TotpCodes[]
|
||||||
@@map("customers")
|
@@map("customers")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,6 +342,25 @@ 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)
|
||||||
|
resent Boolean @default(false)
|
||||||
|
expire_at DateTime? @default(now())
|
||||||
|
created_at DateTime? @default(now())
|
||||||
|
updated_at DateTime? @updatedAt
|
||||||
|
@@map("totp_codes")
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TotpCodesReasons {
|
||||||
|
LOGIN
|
||||||
|
RESET_PASSWORD
|
||||||
|
FIRST_LOGIN
|
||||||
|
}
|
||||||
|
|
||||||
enum ECivility {
|
enum ECivility {
|
||||||
MALE
|
MALE
|
||||||
FEMALE
|
FEMALE
|
||||||
|
@ -173,8 +173,8 @@ export default async function main() {
|
|||||||
first_name: "Angela",
|
first_name: "Angela",
|
||||||
last_name: "Dubois",
|
last_name: "Dubois",
|
||||||
email: "angela.dubois@gmail.com",
|
email: "angela.dubois@gmail.com",
|
||||||
phone_number: "06 12 34 56 78",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 12 34 56 78",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -185,8 +185,8 @@ export default async function main() {
|
|||||||
first_name: "Maxime",
|
first_name: "Maxime",
|
||||||
last_name: "Lalo",
|
last_name: "Lalo",
|
||||||
email: "maxime.lalo@smart-chain.fr",
|
email: "maxime.lalo@smart-chain.fr",
|
||||||
phone_number: "06 23 45 67 89",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 23 45 67 89",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -197,8 +197,8 @@ export default async function main() {
|
|||||||
first_name: "Vincent",
|
first_name: "Vincent",
|
||||||
last_name: "Alamelle",
|
last_name: "Alamelle",
|
||||||
email: "vincent.alamelle@smart-chain.fr",
|
email: "vincent.alamelle@smart-chain.fr",
|
||||||
phone_number: "06 34 56 78 90",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 34 56 78 90",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -209,8 +209,8 @@ export default async function main() {
|
|||||||
first_name: "Melissa",
|
first_name: "Melissa",
|
||||||
last_name: "Desde",
|
last_name: "Desde",
|
||||||
email: "melissa.desde@smart-chain.fr",
|
email: "melissa.desde@smart-chain.fr",
|
||||||
phone_number: "06 45 67 89 01",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 45 67 89 01",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -221,8 +221,8 @@ export default async function main() {
|
|||||||
first_name: "Maxime",
|
first_name: "Maxime",
|
||||||
last_name: "Leroy",
|
last_name: "Leroy",
|
||||||
email: "maxime.leroy@hotmail.fr",
|
email: "maxime.leroy@hotmail.fr",
|
||||||
phone_number: "06 56 78 90 12",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 56 78 90 12",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -233,8 +233,8 @@ export default async function main() {
|
|||||||
first_name: "Paul",
|
first_name: "Paul",
|
||||||
last_name: "Dupont",
|
last_name: "Dupont",
|
||||||
email: "paul.dupont@outlook.com",
|
email: "paul.dupont@outlook.com",
|
||||||
phone_number: "06 67 89 01 23",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 67 89 01 23",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -245,8 +245,8 @@ export default async function main() {
|
|||||||
first_name: "Jean",
|
first_name: "Jean",
|
||||||
last_name: "Dubignot",
|
last_name: "Dubignot",
|
||||||
email: "jean.dubignot@gmail.com",
|
email: "jean.dubignot@gmail.com",
|
||||||
phone_number: "06 78 90 12 34",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 78 90 12 34",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -257,8 +257,8 @@ export default async function main() {
|
|||||||
first_name: "Vincent",
|
first_name: "Vincent",
|
||||||
last_name: "Martin",
|
last_name: "Martin",
|
||||||
email: "vincent.martin@gmail.com",
|
email: "vincent.martin@gmail.com",
|
||||||
phone_number: "06 89 01 23 45",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 89 01 23 45",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -269,8 +269,8 @@ export default async function main() {
|
|||||||
first_name: "Lucie",
|
first_name: "Lucie",
|
||||||
last_name: "Chevalier",
|
last_name: "Chevalier",
|
||||||
email: "lucie.chevalier@outlook.com",
|
email: "lucie.chevalier@outlook.com",
|
||||||
phone_number: "07 12 34 56 78",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 12 34 56 78",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -281,8 +281,8 @@ export default async function main() {
|
|||||||
first_name: "Sébastien",
|
first_name: "Sébastien",
|
||||||
last_name: "Dubois",
|
last_name: "Dubois",
|
||||||
email: "sebastien.dubois@gmail.com",
|
email: "sebastien.dubois@gmail.com",
|
||||||
phone_number: "07 23 45 67 89",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 23 45 67 89",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -293,8 +293,8 @@ export default async function main() {
|
|||||||
first_name: "Mathilde",
|
first_name: "Mathilde",
|
||||||
last_name: "Durand",
|
last_name: "Durand",
|
||||||
email: "mathilde.durand@gmail.com",
|
email: "mathilde.durand@gmail.com",
|
||||||
phone_number: "07 34 56 78 90",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 34 56 78 90",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -305,8 +305,8 @@ export default async function main() {
|
|||||||
first_name: "Antoine",
|
first_name: "Antoine",
|
||||||
last_name: "Bernard",
|
last_name: "Bernard",
|
||||||
email: "antoine.bernard@outlook.com",
|
email: "antoine.bernard@outlook.com",
|
||||||
phone_number: "07 45 67 89 01",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 45 67 89 01",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -317,8 +317,8 @@ export default async function main() {
|
|||||||
first_name: "Camille",
|
first_name: "Camille",
|
||||||
last_name: "Laurent",
|
last_name: "Laurent",
|
||||||
email: "camille.laurent@gmail.com",
|
email: "camille.laurent@gmail.com",
|
||||||
phone_number: "07 56 78 90 12",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 56 78 90 12",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -329,8 +329,8 @@ export default async function main() {
|
|||||||
first_name: "Julien",
|
first_name: "Julien",
|
||||||
last_name: "Mercier",
|
last_name: "Mercier",
|
||||||
email: "julien.mercier@hotmail.fr",
|
email: "julien.mercier@hotmail.fr",
|
||||||
phone_number: "07 67 89 01 23",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 67 89 01 23",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -341,8 +341,8 @@ export default async function main() {
|
|||||||
first_name: "Charlotte",
|
first_name: "Charlotte",
|
||||||
last_name: "Lefebvre",
|
last_name: "Lefebvre",
|
||||||
email: "charlotte.lefebvre@gmail.com",
|
email: "charlotte.lefebvre@gmail.com",
|
||||||
phone_number: "07 78 90 12 34",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 78 90 12 34",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -353,8 +353,8 @@ export default async function main() {
|
|||||||
first_name: "Caroline",
|
first_name: "Caroline",
|
||||||
last_name: "Pallut",
|
last_name: "Pallut",
|
||||||
email: "caroline.pallut@gmail.com",
|
email: "caroline.pallut@gmail.com",
|
||||||
phone_number: "07 89 01 23 45",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 89 01 23 45",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -365,8 +365,8 @@ export default async function main() {
|
|||||||
first_name: "Nadège",
|
first_name: "Nadège",
|
||||||
last_name: "Gauchet",
|
last_name: "Gauchet",
|
||||||
email: "nedege.gauchet@outlook.com",
|
email: "nedege.gauchet@outlook.com",
|
||||||
phone_number: "06 11 22 33 44",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 11 22 33 44",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -377,8 +377,8 @@ export default async function main() {
|
|||||||
first_name: "Matthieu",
|
first_name: "Matthieu",
|
||||||
last_name: "Bougeard",
|
last_name: "Bougeard",
|
||||||
email: "matthieu.bougeard@gmail.com",
|
email: "matthieu.bougeard@gmail.com",
|
||||||
phone_number: "07 22 33 44 55",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 22 33 44 55",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -389,8 +389,8 @@ export default async function main() {
|
|||||||
first_name: "Cécile",
|
first_name: "Cécile",
|
||||||
last_name: "Celton",
|
last_name: "Celton",
|
||||||
email: "cecile.celton@outlook.com",
|
email: "cecile.celton@outlook.com",
|
||||||
phone_number: "06 55 66 77 88",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "06 55 66 77 88",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -401,8 +401,8 @@ export default async function main() {
|
|||||||
first_name: "Gwendal",
|
first_name: "Gwendal",
|
||||||
last_name: "Texier",
|
last_name: "Texier",
|
||||||
email: "gwendal.texier@gmail.com",
|
email: "gwendal.texier@gmail.com",
|
||||||
phone_number: "07 88 99 00 11",
|
phone_number: "+33785186013",
|
||||||
cell_phone_number: "07 88 99 00 11",
|
cell_phone_number: "+33785186013",
|
||||||
birthdate: null,
|
birthdate: null,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
@ -1548,7 +1548,7 @@ export default async function main() {
|
|||||||
office: offices[0],
|
office: offices[0],
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date(),
|
updated_at: new Date(),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const deedTypes: DeedType[] = [
|
const deedTypes: DeedType[] = [
|
||||||
|
@ -4,6 +4,10 @@ import { Service } from "typedi";
|
|||||||
import { Customers, ECivility, ECustomerStatus, Prisma } from "@prisma/client";
|
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 = {
|
||||||
|
totpCodeExpire?: Date | null;
|
||||||
|
password?: string;
|
||||||
|
};
|
||||||
@Service()
|
@Service()
|
||||||
export default class CustomersRepository extends BaseRepository {
|
export default class CustomersRepository extends BaseRepository {
|
||||||
constructor(private database: Database) {
|
constructor(private database: Database) {
|
||||||
@ -25,6 +29,15 @@ export default class CustomersRepository extends BaseRepository {
|
|||||||
return this.model.findMany({ ...query, include: { contact: { include: { address: true } } } });
|
return this.model.findMany({ ...query, include: { contact: { include: { address: true } } } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Find one customers
|
||||||
|
*/
|
||||||
|
public async findOne(query: Prisma.CustomersFindFirstArgs) {
|
||||||
|
query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
|
||||||
|
if (!query.include) return this.model.findFirst({ ...query, include: { contact: true } });
|
||||||
|
return this.model.findFirst({ ...query, include: { contact: { include: { address: true } }, ...query.include } });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description : Create a customer
|
* @description : Create a customer
|
||||||
*/
|
*/
|
||||||
@ -61,7 +74,7 @@ export default class CustomersRepository extends BaseRepository {
|
|||||||
/**
|
/**
|
||||||
* @description : Update data from a customer
|
* @description : Update data from a customer
|
||||||
*/
|
*/
|
||||||
public async update(uid: string, customer: Customer): Promise<Customers> {
|
public async update(uid: string, customer: Customer, excludedVars?: IExcludedCustomerVars): Promise<Customers> {
|
||||||
const updateArgs: Prisma.CustomersUpdateArgs = {
|
const updateArgs: Prisma.CustomersUpdateArgs = {
|
||||||
where: {
|
where: {
|
||||||
uid: uid,
|
uid: uid,
|
||||||
@ -79,8 +92,10 @@ export default class CustomersRepository extends BaseRepository {
|
|||||||
address: {},
|
address: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
password: excludedVars && excludedVars.password,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (customer.contact!.address) {
|
if (customer.contact!.address) {
|
||||||
updateArgs.data.contact!.update!.address!.update = {
|
updateArgs.data.contact!.update!.address!.update = {
|
||||||
address: customer.contact!.address!.address,
|
address: customer.contact!.address!.address,
|
||||||
@ -88,6 +103,7 @@ export default class CustomersRepository extends BaseRepository {
|
|||||||
city: customer.contact!.address!.city,
|
city: customer.contact!.address!.city,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.model.update({ ...updateArgs, include: { contact: true } });
|
return this.model.update({ ...updateArgs, include: { contact: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
79
src/common/repositories/TotpCodesRepository.ts
Normal file
79
src/common/repositories/TotpCodesRepository.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
@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 totp code
|
||||||
|
*/
|
||||||
|
public async create(totpCode: TotpCode): Promise<TotpCodes> {
|
||||||
|
const createArgs: Prisma.TotpCodesCreateArgs = {
|
||||||
|
data: {
|
||||||
|
code: totpCode.code!,
|
||||||
|
reason: totpCode.reason!,
|
||||||
|
customer: {
|
||||||
|
connect: {
|
||||||
|
uid: totpCode.customer_uid!,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expire_at: totpCode.expire_at!,
|
||||||
|
resent: totpCode.resent!,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.model.create({ ...createArgs });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable a totp code
|
||||||
|
*/
|
||||||
|
public async disable(totpCode: TotpCode): Promise<TotpCodes> {
|
||||||
|
return this.model.update({
|
||||||
|
where: {
|
||||||
|
uid: totpCode.uid!,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
expire_at: new Date(),
|
||||||
|
resent: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete many totp codes
|
||||||
|
*/
|
||||||
|
public async deleteMany(query: Prisma.TotpCodesDeleteManyArgs) {
|
||||||
|
return this.model.deleteMany({ ...query });
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -6,6 +6,7 @@ import UsersService from "@Services/super-admin/UsersService/UsersService";
|
|||||||
import CustomersService from "@Services/super-admin/CustomersService/CustomersService";
|
import CustomersService from "@Services/super-admin/CustomersService/CustomersService";
|
||||||
import { ECustomerStatus } from "@prisma/client";
|
import { ECustomerStatus } from "@prisma/client";
|
||||||
import { Customer } from "le-coffre-resources/dist/Notary";
|
import { Customer } from "le-coffre-resources/dist/Notary";
|
||||||
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
enum PROVIDER_OPENID {
|
enum PROVIDER_OPENID {
|
||||||
idNot = "idNot",
|
idNot = "idNot",
|
||||||
@ -19,9 +20,9 @@ export interface ICustomerJwtPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IdNotJwtPayload {
|
export interface IdNotJwtPayload {
|
||||||
sub: string,
|
sub: string;
|
||||||
profile_idn: string,
|
profile_idn: string;
|
||||||
entity_idn: string,
|
entity_idn: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserJwtPayload {
|
export interface IUserJwtPayload {
|
||||||
@ -98,4 +99,12 @@ export default class AuthService extends BaseService {
|
|||||||
public verifyRefreshToken(token: string, callback?: VerifyCallback) {
|
public verifyRefreshToken(token: string, callback?: VerifyCallback) {
|
||||||
return jwt.verify(token, this.variables.REFRESH_TOKEN_SECRET, callback);
|
return jwt.verify(token, this.variables.REFRESH_TOKEN_SECRET, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public comparePassword(password: string, hash: string): Promise<boolean> {
|
||||||
|
return bcrypt.compare(password, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hashPassword(password: string): Promise<string> {
|
||||||
|
return bcrypt.hash(password, 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import IdNotService from "../IdNotService/IdNotService";
|
|||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
import NotificationBuilder from "@Common/notifications/NotificationBuilder";
|
import NotificationBuilder from "@Common/notifications/NotificationBuilder";
|
||||||
import EmailBuilder from "@Common/emails/EmailBuilder";
|
import EmailBuilder from "@Common/emails/EmailBuilder";
|
||||||
|
import TotpService from "@Services/customer/TotpService/TotpService";
|
||||||
// import { PrismaClient } from "@prisma/client";
|
// import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@ -16,6 +17,7 @@ export default class CronService {
|
|||||||
private idNotService: IdNotService,
|
private idNotService: IdNotService,
|
||||||
private notificationBuilder: NotificationBuilder,
|
private notificationBuilder: NotificationBuilder,
|
||||||
private emailBuilder: EmailBuilder,
|
private emailBuilder: EmailBuilder,
|
||||||
|
private totpService: TotpService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async sendMails() {
|
public async sendMails() {
|
||||||
@ -86,8 +88,8 @@ export default class CronService {
|
|||||||
// Once a day at midnight
|
// Once a day at midnight
|
||||||
try {
|
try {
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const expiringDocuments15Days: [{uid: string, expiration_date: Date}] = await prisma.$queryRaw
|
const expiringDocuments15Days: [{ uid: string; expiration_date: Date }] =
|
||||||
`SELECT distinct o.uid, f.created_at as expiration_date
|
await prisma.$queryRaw`SELECT distinct o.uid, f.created_at as expiration_date
|
||||||
FROM documents d
|
FROM documents d
|
||||||
JOIN files f ON d.uid=f.document_uid
|
JOIN files f ON d.uid=f.document_uid
|
||||||
JOIN office_folders o ON o.uid=d.folder_uid
|
JOIN office_folders o ON o.uid=d.folder_uid
|
||||||
@ -95,11 +97,24 @@ export default class CronService {
|
|||||||
AND d.document_status = 'DEPOSITED'
|
AND d.document_status = 'DEPOSITED'
|
||||||
AND current_date = (DATE(f.created_at) + interval '3 months' - interval '2 days');`;
|
AND current_date = (DATE(f.created_at) + interval '3 months' - interval '2 days');`;
|
||||||
|
|
||||||
expiringDocuments15Days.forEach(expiringFolder => {
|
expiringDocuments15Days.forEach((expiringFolder) => {
|
||||||
this.notificationBuilder.sendDocumentExpiringSoonNotification(expiringFolder.uid, expiringFolder.expiration_date);
|
this.notificationBuilder.sendDocumentExpiringSoonNotification(expiringFolder.uid, expiringFolder.expiration_date);
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Start job
|
||||||
|
if (!cronJob.running) {
|
||||||
|
cronJob.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async cleanExpiredTotpCodes() {
|
||||||
|
const cronJob = new CronJob("0 0 * * *", async () => {
|
||||||
|
// Once a day at midnight
|
||||||
|
try {
|
||||||
|
await this.totpService.cleanExpiredTotpCodes();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
@ -117,11 +117,16 @@ export default class IdNotService extends BaseService {
|
|||||||
const query = new URLSearchParams({
|
const query = new URLSearchParams({
|
||||||
client_id: this.variables.IDNOT_CLIENT_ID,
|
client_id: this.variables.IDNOT_CLIENT_ID,
|
||||||
client_secret: this.variables.IDNOT_CLIENT_SECRET,
|
client_secret: this.variables.IDNOT_CLIENT_SECRET,
|
||||||
redirect_uri: `${this.variables.APP_HOST}/authorized-client`,
|
redirect_uri: this.variables.IDNOT_REDIRECT_URL,
|
||||||
code: code,
|
code: code,
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query);
|
||||||
|
|
||||||
const token = await fetch(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query, { method: "POST" });
|
const token = await fetch(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query, { method: "POST" });
|
||||||
|
if(token.status !== 200) console.error(await token.text());
|
||||||
|
|
||||||
const decodedToken = (await token.json()) as IIdNotToken;
|
const decodedToken = (await token.json()) as IIdNotToken;
|
||||||
const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload;
|
const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload;
|
||||||
|
|
||||||
|
36
src/services/common/OvhService/OvhService.ts
Normal file
36
src/services/common/OvhService/OvhService.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class OvhService extends BaseService {
|
||||||
|
constructor(private variables: BackendVariables) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendSms(phoneNumber: string, message: string): Promise<boolean> {
|
||||||
|
const ovh = require("ovh")({
|
||||||
|
appKey: this.variables.OVH_APP_KEY,
|
||||||
|
appSecret: this.variables.OVH_APP_SECRET,
|
||||||
|
consumerKey: this.variables.OVH_CONSUMER_KEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
const serviceName = this.variables.OVH_SMS_SERVICE_NAME;
|
||||||
|
|
||||||
|
await ovh.request('POST', '/sms/' + serviceName + '/jobs/', {
|
||||||
|
message: message,
|
||||||
|
sender: "LeCoffre",
|
||||||
|
senderForResponse: true,
|
||||||
|
receivers: [phoneNumber],
|
||||||
|
}, (error: any, response: any) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Error sending Ovh Sms');
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
console.log('SMS sent successfully via Ovh');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
32
src/services/common/SmsFactorService/SmsFactorService.ts
Normal file
32
src/services/common/SmsFactorService/SmsFactorService.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class SmsFactorService extends BaseService {
|
||||||
|
constructor(private variables: BackendVariables) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendSms(phoneNumber: string, message: string){
|
||||||
|
axios
|
||||||
|
.get(
|
||||||
|
"https://api.smsfactor.com/send/simulate?to=" +
|
||||||
|
phoneNumber +
|
||||||
|
"&sender=LeCoffre&text=" +
|
||||||
|
message +
|
||||||
|
"&token=" +
|
||||||
|
this.variables.SMS_FACTOR_TOKEN,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
console.log("SMS sent successfully via Sms Factor");
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error sending Sms Factor SMS");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,66 @@
|
|||||||
import { Customers, Prisma } from "@prisma/client";
|
// import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
import { Customers, Prisma, TotpCodes } 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 TotpCodesResource, { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes";
|
||||||
|
import { Customer } from "le-coffre-resources/dist/Notary";
|
||||||
import { Service } from "typedi";
|
import { Service } from "typedi";
|
||||||
|
import OvhService from "@Services/common/OvhService/OvhService";
|
||||||
|
import SmsFactorService from "@Services/common/SmsFactorService/SmsFactorService";
|
||||||
|
|
||||||
|
export class SmsNotExpiredError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("SMS code not expired");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TotpCodeExpiredError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("Totp code not found or expired");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidTotpCodeError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("Invalid Totp code");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotRegisteredCustomerError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("Customer not registered");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidPasswordError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("Invalid password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PasswordAlreadySetError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("Password already set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TooSoonForNewCode extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("You need to wait at least 30 seconds before asking for a new code");
|
||||||
|
}
|
||||||
|
}
|
||||||
@Service()
|
@Service()
|
||||||
export default class CustomersService extends BaseService {
|
export default class CustomersService extends BaseService {
|
||||||
constructor(private customerRepository: CustomersRepository) {
|
constructor(
|
||||||
|
private customerRepository: CustomersRepository,
|
||||||
|
private authService: AuthService,
|
||||||
|
private totpCodesRepository: TotpCodesRepository,
|
||||||
|
// private variables: BackendVariables,
|
||||||
|
private ovhService: OvhService,
|
||||||
|
private smsFactorService: SmsFactorService,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,4 +71,337 @@ export default class CustomersService extends BaseService {
|
|||||||
public async get(query: Prisma.CustomersFindManyArgs): Promise<Customers[]> {
|
public async get(query: Prisma.CustomersFindManyArgs): Promise<Customers[]> {
|
||||||
return this.customerRepository.findMany(query);
|
return this.customerRepository.findMany(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Get all Customers
|
||||||
|
* @throws {Error} If Customers cannot be get
|
||||||
|
*/
|
||||||
|
public async getOne(query: Prisma.CustomersFindFirstArgs): Promise<Customers | null> {
|
||||||
|
return this.customerRepository.findOne(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Send SMS to verify the email of a customer (2FA)
|
||||||
|
* 1: Check if the customer exists
|
||||||
|
* 2: Check in the array of totpCodes if one 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 verifyEmail2FASms(email: string): Promise<{ customer: Customer; totpCode: TotpCodesResource } | null> {
|
||||||
|
// 1: Check if the customer exists
|
||||||
|
const customer = await this.getByEmail(email);
|
||||||
|
if (!customer) return null;
|
||||||
|
const now = new Date().getTime();
|
||||||
|
|
||||||
|
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) return { customer, totpCode: validTotpCode };
|
||||||
|
|
||||||
|
// 3: Generate a new SMS code
|
||||||
|
const totpPin = this.generateTotp();
|
||||||
|
|
||||||
|
const reason = customer.password ? TotpCodesReasons.LOGIN : TotpCodesReasons.FIRST_LOGIN;
|
||||||
|
// 4: Save the SMS code in database
|
||||||
|
const totpCode = await this.saveTotpPin(customer, totpPin, new Date(now + 5 * 60 * 1000), reason);
|
||||||
|
if (!totpCode) return null;
|
||||||
|
// 5: Send the SMS code to the customer
|
||||||
|
// if(this.variables.ENV !== 'dev')
|
||||||
|
await this.sendSmsCodeToCustomer(totpPin, customer);
|
||||||
|
return {
|
||||||
|
customer,
|
||||||
|
totpCode: TotpCodesResource.hydrate<TotpCodesResource>({
|
||||||
|
...totpCode,
|
||||||
|
reason: totpCode.reason as TotpCodesReasons,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Send SMS to verify the email of a customer (2FA)
|
||||||
|
* 1: Check if the customer exists
|
||||||
|
* 2: Check in the array of totpCodes if one 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 generateCodeForNewPassword(email: string): Promise<Customer | null> {
|
||||||
|
// 1: Check if the customer exists
|
||||||
|
const customer = await this.getByEmail(email);
|
||||||
|
if (!customer) return null;
|
||||||
|
const now = new Date().getTime();
|
||||||
|
|
||||||
|
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 && totpCode.reason === TotpCodesReasons.RESET_PASSWORD;
|
||||||
|
});
|
||||||
|
if (validTotpCode) throw new SmsNotExpiredError();
|
||||||
|
|
||||||
|
// 3: Archive all active totp codes for this customer
|
||||||
|
const activeTotpCodes = customerHydrated.totpCodes?.filter((totpCode) => {
|
||||||
|
return totpCode.expire_at && totpCode.expire_at.getTime() > now;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (activeTotpCodes) {
|
||||||
|
await Promise.all(
|
||||||
|
activeTotpCodes.map(async (totpCode) => {
|
||||||
|
await this.totpCodesRepository.disable(totpCode);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3: Generate a new SMS code
|
||||||
|
const totpPin = this.generateTotp();
|
||||||
|
|
||||||
|
// 4: Save the SMS code in database
|
||||||
|
await this.saveTotpPin(customer, totpPin, new Date(now + 5 * 60000), TotpCodesReasons.RESET_PASSWORD);
|
||||||
|
|
||||||
|
// 5: Send the SMS code to the customer
|
||||||
|
// if(this.variables.ENV !== 'dev')
|
||||||
|
await this.sendSmsCodeToCustomer(totpPin, customer);
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Set the password of a customer when it's the first time they connect
|
||||||
|
* 1: Check if the customer exists
|
||||||
|
* 2: Check if a totp code is existing and is not expired in the array
|
||||||
|
* 3: Check if the SMS code is valid
|
||||||
|
* 4: Check the totpcode reason is valid
|
||||||
|
* 5: Disable the totp code used
|
||||||
|
* 6: Hash the password
|
||||||
|
* 7: Set the password in database and return the result of the update
|
||||||
|
* @param email
|
||||||
|
* @param totpCode
|
||||||
|
* @param password
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async setPassword(email: string, totpCode: string, password: string): Promise<Customer | null> {
|
||||||
|
// 1: Check if the customer exists
|
||||||
|
const customer = await this.getByEmail(email);
|
||||||
|
if (!customer) return null;
|
||||||
|
|
||||||
|
const customerHydrated = Customer.hydrate<Customer>(customer);
|
||||||
|
// 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
|
||||||
|
if (validTotpCode.code !== totpCode) throw new InvalidTotpCodeError();
|
||||||
|
|
||||||
|
// 4: Check the totpcode reason is valid
|
||||||
|
// If the customer already has a password, the reason must be RESET_PASSWORD
|
||||||
|
// If the customer doesn't have a password, the reason must be FIRST_LOGIN
|
||||||
|
if (
|
||||||
|
(customer.password && validTotpCode.reason !== TotpCodesReasons.RESET_PASSWORD) ||
|
||||||
|
(!customer.password && validTotpCode.reason !== TotpCodesReasons.FIRST_LOGIN)
|
||||||
|
)
|
||||||
|
throw new InvalidTotpCodeError();
|
||||||
|
|
||||||
|
// 5: Disable the totp code used
|
||||||
|
await this.totpCodesRepository.disable(validTotpCode);
|
||||||
|
|
||||||
|
// 6: Hash the password
|
||||||
|
const hashedPassword = await this.authService.hashPassword(password);
|
||||||
|
|
||||||
|
// 7: Set the password in database and return the result of the update
|
||||||
|
return await this.setPasswordInDatabase(customer, hashedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description : Login a customer
|
||||||
|
* 1: Check if the customer exists
|
||||||
|
* 2: Check if a totp code is existing and is not expired in the array
|
||||||
|
* 3: Check if the SMS code is valid
|
||||||
|
* 4: Check if the user has a password or it's their first login
|
||||||
|
* 5: Check if the password is valid
|
||||||
|
* 6: Disable the totp code used
|
||||||
|
* 7: Return the customer
|
||||||
|
* @param email
|
||||||
|
* @param totpCode
|
||||||
|
* @param password
|
||||||
|
* @returns Customer | null
|
||||||
|
*/
|
||||||
|
public async login(email: string, totpCode: string, password: string): Promise<Customer | null> {
|
||||||
|
// 1: Check if the customer exists
|
||||||
|
const customer = await this.getByEmail(email);
|
||||||
|
if (!customer) return null;
|
||||||
|
const customerHydrated = Customer.hydrate<Customer>(customer);
|
||||||
|
|
||||||
|
// 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() && totpCode.reason === TotpCodesReasons.LOGIN;
|
||||||
|
});
|
||||||
|
if (!validTotpCode) throw new TotpCodeExpiredError();
|
||||||
|
|
||||||
|
// 3: Check if the SMS code is valid
|
||||||
|
if (validTotpCode.code !== totpCode) throw new InvalidTotpCodeError();
|
||||||
|
|
||||||
|
// 4: Check if the user has a password or it's their first login
|
||||||
|
if (!customer.password) throw new NotRegisteredCustomerError();
|
||||||
|
|
||||||
|
// 5: Check if the password is valid
|
||||||
|
const isPasswordValid = await this.authService.comparePassword(password, customer.password);
|
||||||
|
if (!isPasswordValid) throw new InvalidPasswordError();
|
||||||
|
|
||||||
|
// 6: Disable the totp code used
|
||||||
|
await this.totpCodesRepository.disable(validTotpCode);
|
||||||
|
|
||||||
|
// 7: Return the customer
|
||||||
|
return await this.customerRepository.update(
|
||||||
|
customer.uid as string,
|
||||||
|
Customer.hydrate<Customer>({
|
||||||
|
...customer,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async askAnotherCode(email: string, totpCodeUid: string): Promise<{ customer: Customer; totpCode: TotpCodes } | null> {
|
||||||
|
// 1: Check if the customer exists
|
||||||
|
const customer = await this.getByEmail(email);
|
||||||
|
if (!customer) return null;
|
||||||
|
const now = new Date().getTime();
|
||||||
|
|
||||||
|
const customerHydrated = Customer.hydrate<Customer>(customer);
|
||||||
|
|
||||||
|
// 2: Get last code sent and check if it's still valid
|
||||||
|
const totpCodeToResend = customerHydrated.totpCodes?.find((totpCode) => {
|
||||||
|
return totpCode.uid === totpCodeUid && totpCode.expire_at && totpCode.expire_at.getTime() > now;
|
||||||
|
});
|
||||||
|
if (!totpCodeToResend) throw new TotpCodeExpiredError();
|
||||||
|
|
||||||
|
// 3: Check if it was created more than 30 seconds ago and hasn't been resent yet
|
||||||
|
if (totpCodeToResend.created_at && totpCodeToResend.created_at.getTime() > now - 30000 && totpCodeToResend.resent)
|
||||||
|
throw new TooSoonForNewCode();
|
||||||
|
|
||||||
|
// 4: Generate a new SMS code
|
||||||
|
const totpPin = this.generateTotp();
|
||||||
|
|
||||||
|
// 5: Disable the old code
|
||||||
|
await this.totpCodesRepository.disable(totpCodeToResend);
|
||||||
|
|
||||||
|
// 6: Save the SMS code in database with the same reason as the old one
|
||||||
|
const totpCode = await this.saveTotpPin(customer, totpPin, new Date(now + 5 * 60 * 1000), totpCodeToResend.reason!, true);
|
||||||
|
|
||||||
|
// 7: Send the SMS code to the customer
|
||||||
|
// if(this.variables.ENV !== 'dev')
|
||||||
|
await this.sendSmsCodeToCustomer(totpPin, customer);
|
||||||
|
return { customer, totpCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Set password for a customer
|
||||||
|
* @throws {Error} If customer cannot be updated
|
||||||
|
*/
|
||||||
|
private async setPasswordInDatabase(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,
|
||||||
|
totpCodes: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Saves a TotpPin in database
|
||||||
|
*/
|
||||||
|
private async saveTotpPin(
|
||||||
|
customer: Customer,
|
||||||
|
totpPin: number,
|
||||||
|
expireAt: Date,
|
||||||
|
reason: TotpCodesReasons,
|
||||||
|
resent?: boolean,
|
||||||
|
): Promise<TotpCodes> {
|
||||||
|
// Create the totpCode in table using repository
|
||||||
|
return await this.totpCodesRepository.create(
|
||||||
|
TotpCodesResource.hydrate<TotpCodesResource>({
|
||||||
|
reason,
|
||||||
|
customer_uid: customer.uid,
|
||||||
|
customer: Customer.hydrate<Customer>(customer),
|
||||||
|
created_at: new Date(),
|
||||||
|
code: totpPin.toString(),
|
||||||
|
expire_at: expireAt,
|
||||||
|
resent: resent || false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateTotp() {
|
||||||
|
return Math.floor(100000 + Math.random() * 900000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendSmsCodeToCustomer(totpPin: number, customer: Customer) {
|
||||||
|
const message = "Votre code de vérification LEcoffre.io est : " + totpPin.toString();
|
||||||
|
|
||||||
|
// Sélectionnez le fournisseur de SMS en fonction de la variable d'environnement
|
||||||
|
//const selectedProvider = this.variables.SMS_PROVIDER === "OVH" ? this.ovhService : this.smsFactorService;
|
||||||
|
|
||||||
|
// Envoi du SMS
|
||||||
|
if (!customer.contact?.cell_phone_number) return;
|
||||||
|
let success = await this.ovhService.sendSms(customer.contact?.cell_phone_number, message);
|
||||||
|
|
||||||
|
|
||||||
|
// Si l'envoi échoue, basculez automatiquement sur le second fournisseur
|
||||||
|
if (!success) {
|
||||||
|
//const alternateProvider = this.variables.SMS_PROVIDER === "OVH" ? this.smsFactorService : this.ovhService;
|
||||||
|
await this.smsFactorService.sendSms(customer.contact?.cell_phone_number, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 1: Check if the customer exists
|
||||||
|
* 2: Check if a totp code is existing and is not expired in the array
|
||||||
|
* 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<TotpCodesResource | null> {
|
||||||
|
// 1: Check if the customer exists
|
||||||
|
const customer = await this.getByEmail(email);
|
||||||
|
if (!customer) return null;
|
||||||
|
|
||||||
|
const customerHydrated = Customer.hydrate<Customer>(customer);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if (validTotpCode.code !== totpCode) throw new InvalidTotpCodeError();
|
||||||
|
|
||||||
|
// 4: Return the customer
|
||||||
|
return validTotpCode;
|
||||||
|
}
|
||||||
}
|
}
|
51
src/services/customer/TotpService/TotpService.ts
Normal file
51
src/services/customer/TotpService/TotpService.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Prisma, TotpCodes } from "@prisma/client";
|
||||||
|
import TotpCodesRepository from "@Repositories/TotpCodesRepository";
|
||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class TotpService extends BaseService {
|
||||||
|
constructor(private totpCodesRepository: TotpCodesRepository) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Get all totp codes
|
||||||
|
* @throws {Error} If totp codes cannot be get
|
||||||
|
*/
|
||||||
|
public async get(query: Prisma.TotpCodesFindManyArgs): Promise<TotpCodes[]> {
|
||||||
|
return this.totpCodesRepository.findMany(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Get one totp code
|
||||||
|
* @throws {Error} If totp code cannot be get
|
||||||
|
*/
|
||||||
|
public async getOne(query: Prisma.TotpCodesFindFirstArgs): Promise<TotpCodes | null> {
|
||||||
|
return this.totpCodesRepository.findOne(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Delete many totp codes
|
||||||
|
* @throws {Error} If totp code cannot be deleted
|
||||||
|
*/
|
||||||
|
public async deleteMany(query: Prisma.TotpCodesDeleteManyArgs): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.totpCodesRepository.deleteMany(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Delete every totp code expired for more than 30 days
|
||||||
|
* @throws {Error} If totp codes cannot be deleted
|
||||||
|
*/
|
||||||
|
public async cleanExpiredTotpCodes() {
|
||||||
|
const query: Prisma.TotpCodesDeleteManyArgs = {
|
||||||
|
where: {
|
||||||
|
expire_at: {
|
||||||
|
lte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.totpCodesRepository.deleteMany(query);
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,7 @@
|
|||||||
// "module": "es2022",
|
// "module": "es2022",
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
@ -18,7 +14,7 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
/* Strict Type-Checking Options */
|
/* Strict Type-Checking Options */
|
||||||
"allowUnreachableCode": false,
|
"allowUnreachableCode": false,
|
||||||
"allowUnusedLabels": false,
|
"allowUnusedLabels": true,
|
||||||
"exactOptionalPropertyTypes": false,
|
"exactOptionalPropertyTypes": false,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@ -32,6 +28,7 @@
|
|||||||
"noPropertyAccessFromIndexSignature": true,
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
/* Additional Checks */
|
/* Additional Checks */
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": false,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"useUnknownInCatchVariables": true,
|
"useUnknownInCatchVariables": true,
|
||||||
@ -39,39 +36,17 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@App/*": [
|
"@App/*": ["src/app/*"],
|
||||||
"src/app/*"
|
"@Api/*": ["src/app/api/*"],
|
||||||
],
|
"@Services/*": ["src/services/*"],
|
||||||
"@Api/*": [
|
"@Repositories/*": ["src/common/repositories/*"],
|
||||||
"src/app/api/*"
|
"@Entries/*": ["src/entries/*"],
|
||||||
],
|
"@Common/*": ["src/common/*"],
|
||||||
"@Services/*": [
|
"@Config/*": ["src/common/config/*"],
|
||||||
"src/services/*"
|
"@Entities/*": ["src/common/ressources/*"],
|
||||||
],
|
"@System/*": ["src/common/system/*"],
|
||||||
"@Repositories/*": [
|
"@ControllerPattern/*": ["src/common/system/controller-pattern/*"],
|
||||||
"src/common/repositories/*"
|
"@Test/*": ["src/test/*"]
|
||||||
],
|
|
||||||
"@Entries/*": [
|
|
||||||
"src/entries/*"
|
|
||||||
],
|
|
||||||
"@Common/*": [
|
|
||||||
"src/common/*"
|
|
||||||
],
|
|
||||||
"@Config/*": [
|
|
||||||
"src/common/config/*"
|
|
||||||
],
|
|
||||||
"@Entities/*": [
|
|
||||||
"src/common/ressources/*"
|
|
||||||
],
|
|
||||||
"@System/*": [
|
|
||||||
"src/common/system/*"
|
|
||||||
],
|
|
||||||
"@ControllerPattern/*": [
|
|
||||||
"src/common/system/controller-pattern/*"
|
|
||||||
],
|
|
||||||
"@Test/*": [
|
|
||||||
"src/test/*"
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
// "rootDirs": [],
|
// "rootDirs": [],
|
||||||
// "typeRoots": [],
|
// "typeRoots": [],
|
||||||
@ -90,13 +65,8 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["**/*.ts", "**/*.tsx", "src/services/common/TestService"],
|
||||||
"**/*.ts",
|
"exclude": ["node_modules"]
|
||||||
"**/*.tsx", "src/services/common/TestService",
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user