✨ Beginning all routes for login/password
This commit is contained in:
parent
a93b2616ec
commit
220a77e063
@ -46,6 +46,7 @@
|
||||
"@pinata/sdk": "^2.1.0",
|
||||
"@prisma/client": "^4.11.0",
|
||||
"adm-zip": "^0.5.10",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"classnames": "^2.3.2",
|
||||
@ -55,7 +56,7 @@
|
||||
"file-type-checker": "^1.0.8",
|
||||
"fp-ts": "^2.16.1",
|
||||
"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.95",
|
||||
"module-alias": "^2.2.2",
|
||||
"monocle-ts": "^2.3.13",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
@ -74,6 +75,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "^0.5.3",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/cron": "^2.0.1",
|
||||
"@types/express": "^4.17.16",
|
||||
|
232
src/app/api/customer/AuthController.ts
Normal file
232
src/app/api/customer/AuthController.ts
Normal file
@ -0,0 +1,232 @@
|
||||
import { Response, Request } from "express";
|
||||
import { Controller, Post } from "@ControllerPattern/index";
|
||||
import ApiController from "@Common/system/controller-pattern/ApiController";
|
||||
import { Service } from "typedi";
|
||||
import { EnrollmentResponse } from "@Services/common/Id360Service/Id360Service";
|
||||
import CustomersService from "@Services/customer/CustomersService/CustomersService";
|
||||
import AuthService, { ICustomerJwtPayload } 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/pre-login")
|
||||
protected async preLogin(req: Request, response: Response) {
|
||||
const email = req.body["email"];
|
||||
if (!email) {
|
||||
this.httpBadRequest(response, "Email is required");
|
||||
return;
|
||||
}
|
||||
|
||||
let customer = await this.customerService.getOne({
|
||||
where: {
|
||||
contact: {
|
||||
email,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
contact: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
this.httpNotFoundRequest(response, "Customer not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// if code has more than 5mn, regenerate it
|
||||
if (
|
||||
!customer.smsCodeExpire ||
|
||||
(customer.smsCodeExpire && new Date().getTime() - customer.smsCodeExpire.getTime() > 5 * 60 * 1000)
|
||||
) {
|
||||
customer = await this.customerService.generateSmsCode(customer);
|
||||
}
|
||||
|
||||
if (!customer.password) {
|
||||
try {
|
||||
this.httpSuccess(response, { info: "Sending a sms for first connection" });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.httpInternalError(response);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.httpSuccess(response, { email, customer });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.httpInternalError(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Post("/api/v1/customer/login")
|
||||
protected async login(req: Request, response: Response) {
|
||||
const email = req.body["email"];
|
||||
const password = req.body["password"];
|
||||
if (!email) {
|
||||
this.httpBadRequest(response, "Email is required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
this.httpBadRequest(response, "Password is required");
|
||||
return;
|
||||
}
|
||||
|
||||
let customer = await this.customerService.getOne({
|
||||
where: {
|
||||
contact: {
|
||||
email,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
contact: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
this.httpNotFoundRequest(response, "Customer not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!customer.password) {
|
||||
this.httpBadRequest(response, "Customer not registered");
|
||||
return;
|
||||
}
|
||||
|
||||
// compare password to the hash
|
||||
const isPasswordValid = await this.authService.comparePassword(password, customer.password);
|
||||
if (!isPasswordValid) {
|
||||
this.httpBadRequest(response, "Invalid password");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.httpSuccess(response, { customer });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.httpInternalError(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Post("/api/v1/customer/set-password")
|
||||
protected async setPassword(req: Request, response: Response) {
|
||||
const email = req.body["email"];
|
||||
const smsCode = req.body["smsCode"];
|
||||
const password = req.body["password"];
|
||||
|
||||
if (!email) {
|
||||
this.httpBadRequest(response, "Email is required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!smsCode) {
|
||||
this.httpBadRequest(response, "Sms code is required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
this.httpBadRequest(response, "Password is required");
|
||||
return;
|
||||
}
|
||||
|
||||
const customer = await this.customerService.getOne({
|
||||
where: {
|
||||
contact: {
|
||||
email,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
contact: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
this.httpNotFoundRequest(response, "Customer not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!customer.smsCode) {
|
||||
this.httpBadRequest(response, "No sms code found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (customer.smsCode !== smsCode) {
|
||||
this.httpBadRequest(response, "Invalid sms code");
|
||||
return;
|
||||
}
|
||||
|
||||
if (customer.password) {
|
||||
this.httpBadRequest(response, "Password already set");
|
||||
return;
|
||||
}
|
||||
|
||||
const hashedPassword = await this.authService.hashPassword(password);
|
||||
await this.customerService.setPassword(customer, hashedPassword);
|
||||
|
||||
try {
|
||||
this.httpSuccess(response, { email });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.httpInternalError(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Post("/api/v1/customer/check-sms-code")
|
||||
protected async checkSmsCode(req: Request, response: Response) {
|
||||
const email = req.body["email"];
|
||||
const smsCode = req.body["smsCode"];
|
||||
|
||||
if (!email) {
|
||||
this.httpBadRequest(response, "Email is required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!smsCode) {
|
||||
this.httpBadRequest(response, "Sms code is required");
|
||||
return;
|
||||
}
|
||||
|
||||
const customer = await this.customerService.getOne({
|
||||
where: {
|
||||
contact: {
|
||||
email,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
contact: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
this.httpNotFoundRequest(response, "Customer not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!customer.smsCode) {
|
||||
this.httpBadRequest(response, "No sms code found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (customer.smsCode !== smsCode) {
|
||||
this.httpBadRequest(response, "Invalid sms code");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.httpSuccess(response, { success: "success" });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.httpInternalError(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ import LiveVoteController from "./api/super-admin/LiveVoteController";
|
||||
import DocumentControllerId360 from "./api/id360/DocumentController";
|
||||
import CustomerControllerId360 from "./api/id360/CustomerController";
|
||||
import UserNotificationController from "./api/notary/UserNotificationController";
|
||||
|
||||
import AuthController from "./api/customer/AuthController";
|
||||
|
||||
/**
|
||||
* @description This allow to declare all controllers used in the application
|
||||
@ -99,5 +99,6 @@ export default {
|
||||
Container.get(UserNotificationController);
|
||||
Container.get(DocumentControllerId360);
|
||||
Container.get(CustomerControllerId360);
|
||||
Container.get(AuthController);
|
||||
},
|
||||
};
|
||||
|
@ -0,0 +1,6 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "customers" ADD COLUMN "password" VARCHAR(255),
|
||||
ADD COLUMN "passwordCode" VARCHAR(255),
|
||||
ADD COLUMN "passwordcodeExpire" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "smsCode" VARCHAR(255),
|
||||
ADD COLUMN "smsCodeExpire" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP;
|
@ -101,6 +101,12 @@ model Customers {
|
||||
updated_at DateTime? @updatedAt
|
||||
office_folders OfficeFolders[] @relation("OfficeFolderHasCustomers")
|
||||
documents Documents[]
|
||||
password String? @db.VarChar(255)
|
||||
smsCode String? @db.VarChar(255)
|
||||
smsCodeExpire DateTime? @default(now())
|
||||
passwordCode String? @db.VarChar(255)
|
||||
passwordcodeExpire DateTime? @default(now())
|
||||
|
||||
|
||||
@@map("customers")
|
||||
}
|
||||
|
@ -25,6 +25,15 @@ export default class CustomersRepository extends BaseRepository {
|
||||
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 } } } });
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Create a customer
|
||||
*/
|
||||
@ -79,6 +88,11 @@ export default class CustomersRepository extends BaseRepository {
|
||||
address: {},
|
||||
},
|
||||
},
|
||||
smsCode: customer.smsCode,
|
||||
smsCodeExpire: customer.smsCodeExpire,
|
||||
passwordCode: customer.passwordCode,
|
||||
passwordcodeExpire: customer.passwordCodeExpire,
|
||||
password: customer.password,
|
||||
},
|
||||
};
|
||||
if (customer.contact!.address) {
|
||||
@ -88,6 +102,7 @@ export default class CustomersRepository extends BaseRepository {
|
||||
city: customer.contact!.address!.city,
|
||||
};
|
||||
}
|
||||
|
||||
return this.model.update({ ...updateArgs, include: { contact: true } });
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import UsersService from "@Services/super-admin/UsersService/UsersService";
|
||||
import CustomersService from "@Services/super-admin/CustomersService/CustomersService";
|
||||
import { ECustomerStatus } from "@prisma/client";
|
||||
import { Customer } from "le-coffre-resources/dist/Notary";
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
enum PROVIDER_OPENID {
|
||||
idNot = "idNot",
|
||||
@ -19,9 +20,9 @@ export interface ICustomerJwtPayload {
|
||||
}
|
||||
|
||||
export interface IdNotJwtPayload {
|
||||
sub: string,
|
||||
profile_idn: string,
|
||||
entity_idn: string,
|
||||
sub: string;
|
||||
profile_idn: string;
|
||||
entity_idn: string;
|
||||
}
|
||||
|
||||
export interface IUserJwtPayload {
|
||||
@ -44,7 +45,7 @@ export default class AuthService extends BaseService {
|
||||
}
|
||||
|
||||
public async getCustomerJwtPayload(customers: Customer[]): Promise<ICustomerJwtPayload | null> {
|
||||
for (const customer of customers){
|
||||
for (const customer of customers) {
|
||||
if (customer.status === ECustomerStatus["PENDING"]) {
|
||||
customer.status = ECustomerStatus["VALIDATED"];
|
||||
await this.customerService.update(customer.uid!, customer);
|
||||
@ -69,7 +70,7 @@ export default class AuthService extends BaseService {
|
||||
|
||||
if (user.office_role) {
|
||||
user.office_role.rules.forEach((rule) => {
|
||||
if(!rules.includes(rule.name)) {
|
||||
if (!rules.includes(rule.name)) {
|
||||
rules.push(rule.name);
|
||||
}
|
||||
});
|
||||
@ -84,11 +85,11 @@ export default class AuthService extends BaseService {
|
||||
};
|
||||
}
|
||||
public generateAccessToken(user: any): string {
|
||||
return jwt.sign({ ...user}, this.variables.ACCESS_TOKEN_SECRET, { expiresIn: "15m" });
|
||||
return jwt.sign({ ...user }, this.variables.ACCESS_TOKEN_SECRET, { expiresIn: "15m" });
|
||||
}
|
||||
|
||||
public generateRefreshToken(user: any): string {
|
||||
return jwt.sign({ ...user}, this.variables.REFRESH_TOKEN_SECRET, { expiresIn: "1h" });
|
||||
return jwt.sign({ ...user }, this.variables.REFRESH_TOKEN_SECRET, { expiresIn: "1h" });
|
||||
}
|
||||
|
||||
public verifyAccessToken(token: string, callback?: VerifyCallback) {
|
||||
@ -98,4 +99,12 @@ export default class AuthService extends BaseService {
|
||||
public verifyRefreshToken(token: string, callback?: VerifyCallback) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Customers, Prisma } from "@prisma/client";
|
||||
import CustomersRepository from "@Repositories/CustomersRepository";
|
||||
import BaseService from "@Services/BaseService";
|
||||
import { Customer } from "le-coffre-resources/dist/Notary";
|
||||
import { Service } from "typedi";
|
||||
|
||||
@Service()
|
||||
@ -16,4 +17,44 @@ export default class CustomersService extends BaseService {
|
||||
public async get(query: Prisma.CustomersFindManyArgs): Promise<Customers[]> {
|
||||
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 : Generate a SMS code for a customer
|
||||
* @throws {Error}
|
||||
*/
|
||||
public async generateSmsCode(customer: Customer) {
|
||||
const smsCode = Math.floor(100000 + Math.random() * 900000);
|
||||
return await this.customerRepository.update(
|
||||
customer.uid as string,
|
||||
Customer.hydrate<Customer>({
|
||||
...customer,
|
||||
smsCode: smsCode.toString(),
|
||||
smsCodeExpire: new Date(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description : Set password for a customer
|
||||
* @throws {Error} If customer cannot be updated
|
||||
*/
|
||||
public async setPassword(customer: Customer, password: string) {
|
||||
return await this.customerRepository.update(
|
||||
customer.uid as string,
|
||||
Customer.hydrate<Customer>({
|
||||
...customer,
|
||||
password,
|
||||
smsCode: null,
|
||||
smsCodeExpire: null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,11 +4,7 @@
|
||||
// "module": "es2022",
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
@ -18,7 +14,7 @@
|
||||
"resolveJsonModule": true,
|
||||
/* Strict Type-Checking Options */
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"allowUnusedLabels": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"noImplicitOverride": true,
|
||||
"strict": true,
|
||||
@ -31,7 +27,8 @@
|
||||
"alwaysStrict": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noImplicitReturns": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
@ -39,39 +36,17 @@
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@App/*": [
|
||||
"src/app/*"
|
||||
],
|
||||
"@Api/*": [
|
||||
"src/app/api/*"
|
||||
],
|
||||
"@Services/*": [
|
||||
"src/services/*"
|
||||
],
|
||||
"@Repositories/*": [
|
||||
"src/common/repositories/*"
|
||||
],
|
||||
"@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/*"
|
||||
],
|
||||
"@App/*": ["src/app/*"],
|
||||
"@Api/*": ["src/app/api/*"],
|
||||
"@Services/*": ["src/services/*"],
|
||||
"@Repositories/*": ["src/common/repositories/*"],
|
||||
"@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": [],
|
||||
// "typeRoots": [],
|
||||
@ -90,13 +65,8 @@
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowJs": true,
|
||||
"isolatedModules": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx", "src/services/common/TestService",
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["**/*.ts", "**/*.tsx", "src/services/common/TestService"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user