From 173ec9e8d8d177f133e2a41a1d4f62003bd0ed4f Mon Sep 17 00:00:00 2001 From: David Arranz Date: Thu, 16 May 2024 13:56:46 +0200 Subject: [PATCH] . --- server/package.json | 1 - .../application/FindUserByEmail.useCase.ts | 88 +++++++++ .../{LoginUseCase.ts => Login.useCase.ts} | 29 +-- .../contexts/auth/application/authServices.ts | 18 ++ server/src/contexts/auth/application/index.ts | 3 +- .../src/contexts/auth/domain/entities/User.ts | 7 + .../repository/AuthRepository.interface.ts | 5 +- .../auth/infrastructure/Auth.repository.ts | 2 +- .../controllers/AuthenticateController.ts | 3 +- .../express/controllers/LoginController.ts | 108 ---------- .../express/controllers/index.ts | 21 +- .../controllers/login/Login.controller.ts | 71 +++++++ .../express/controllers/login/index.ts | 13 ++ .../login/presenter/Login.presenter.ts | 29 +++ .../controllers/login/presenter/index.ts | 1 + .../{authenticate.ts => authMiddleware.ts} | 12 +- .../express/passport/configurePassportAuth.ts | 5 +- .../express/passport/emailStrategy.ts | 9 +- .../infrastructure/express/passport/index.ts | 2 +- .../express/passport/jwtStrategy.ts | 69 +++++-- .../express/passport/passport.bak | 184 ------------------ .../auth/infrastructure/express/routes.ts | 45 +---- .../infrastructure/mappers/user.mapper.ts | 4 +- .../infrastructure/sequelize/user.model.ts | 18 +- .../catalog/infrastructure/express/routes.ts | 2 +- .../domain/repositories/Adapter.interface.ts | 2 +- .../BusinessTransaction.interface.ts | 5 - .../repositories/BusinessTransaction.ts | 4 + .../common/domain/repositories/index.ts | 2 +- .../infrastructure/InfrastructureError.ts | 20 +- .../firebird/FirebirdAdapter.ts | 2 +- .../firebird/FirebirdBusinessTransaction.ts | 12 +- .../sequelize/SequelizeAdapter.ts | 36 +--- .../sequelize/SequelizeBusinessTransaction.ts | 5 +- .../sequelize/SequelizeModel.interface.ts | 5 - .../application/dto/ILogin_Response.dto.ts | 5 + .../QuickSearch/QuickSearchCriteria.ts | 4 +- 37 files changed, 366 insertions(+), 485 deletions(-) create mode 100644 server/src/contexts/auth/application/FindUserByEmail.useCase.ts rename server/src/contexts/auth/application/{LoginUseCase.ts => Login.useCase.ts} (75%) delete mode 100644 server/src/contexts/auth/infrastructure/express/controllers/LoginController.ts create mode 100644 server/src/contexts/auth/infrastructure/express/controllers/login/Login.controller.ts create mode 100644 server/src/contexts/auth/infrastructure/express/controllers/login/index.ts create mode 100644 server/src/contexts/auth/infrastructure/express/controllers/login/presenter/Login.presenter.ts create mode 100644 server/src/contexts/auth/infrastructure/express/controllers/login/presenter/index.ts rename server/src/contexts/auth/infrastructure/express/passport/{authenticate.ts => authMiddleware.ts} (85%) delete mode 100644 server/src/contexts/auth/infrastructure/express/passport/passport.bak delete mode 100644 server/src/contexts/common/domain/repositories/BusinessTransaction.interface.ts create mode 100644 server/src/contexts/common/domain/repositories/BusinessTransaction.ts diff --git a/server/package.json b/server/package.json index d688cca..4971332 100644 --- a/server/package.json +++ b/server/package.json @@ -79,7 +79,6 @@ "remove": "^0.1.5", "response-time": "^2.3.2", "sequelize": "^6.37.3", - "sequelize-revision": "^6.0.0", "sequelize-typescript": "^2.1.5", "shallow-equal-object": "^1.1.1", "ts-node": "^10.9.1", diff --git a/server/src/contexts/auth/application/FindUserByEmail.useCase.ts b/server/src/contexts/auth/application/FindUserByEmail.useCase.ts new file mode 100644 index 0000000..9fab3cd --- /dev/null +++ b/server/src/contexts/auth/application/FindUserByEmail.useCase.ts @@ -0,0 +1,88 @@ +import { + IUseCase, + IUseCaseError, + IUseCaseRequest, + UseCaseError, + handleUseCaseError, +} from "@/contexts/common/application"; +import { IRepositoryManager } from "@/contexts/common/domain"; +import { IInfrastructureError } from "@/contexts/common/infrastructure"; +import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize"; +import { Result, ensureUserEmailIsValid } from "@shared/contexts"; +import { User } from "../domain"; +import { findUserByEmail } from "./authServices"; + +export interface FindUserByEmailRequest extends IUseCaseRequest { + email: string; +} + +export type FindUserByEmailResponseOrError = + | Result + | Result; + +export class FindUserByEmailUseCase + implements + IUseCase> +{ + private _adapter: ISequelizeAdapter; + private _repositoryManager: IRepositoryManager; + + constructor(props: { + adapter: ISequelizeAdapter; + repositoryManager: IRepositoryManager; + }) { + this._adapter = props.adapter; + this._repositoryManager = props.repositoryManager; + } + + private getRepositoryByName(name: string) { + return this._repositoryManager.getRepository(name); + } + + async execute( + request: FindUserByEmailRequest, + ): Promise { + const { email } = request; + + // Validaciones de datos + + const emailOrError = ensureUserEmailIsValid(email); + if (emailOrError.isFailure) { + return Result.fail( + handleUseCaseError( + UseCaseError.INVALID_INPUT_DATA, + "Email or password is not valid", + emailOrError.error, + ), + ); + } + + // Crear auth + try { + const user = await findUserByEmail( + emailOrError.object, + this._adapter, + this.getRepositoryByName("Auth"), + ); + + if (user === null) { + return Result.fail( + handleUseCaseError( + UseCaseError.NOT_FOUND_ERROR, + `User with email ${email} not found`, + ), + ); + } + return Result.ok(user); + } catch (error: unknown) { + const _error = error as IInfrastructureError; + return Result.fail( + handleUseCaseError( + UseCaseError.REPOSITORY_ERROR, + "Error al buscar el usuario", + _error, + ), + ); + } + } +} diff --git a/server/src/contexts/auth/application/LoginUseCase.ts b/server/src/contexts/auth/application/Login.useCase.ts similarity index 75% rename from server/src/contexts/auth/application/LoginUseCase.ts rename to server/src/contexts/auth/application/Login.useCase.ts index a3317ad..46c89ba 100644 --- a/server/src/contexts/auth/application/LoginUseCase.ts +++ b/server/src/contexts/auth/application/Login.useCase.ts @@ -7,14 +7,9 @@ import { import { IRepositoryManager } from "@/contexts/common/domain"; import { IInfrastructureError } from "@/contexts/common/infrastructure"; import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize"; -import { - Email, - ILogin_DTO, - Result, - ensureUserEmailIsValid, -} from "@shared/contexts"; +import { ILogin_DTO, Result, ensureUserEmailIsValid } from "@shared/contexts"; import { User } from "../domain"; -import { IAuthRepository } from "../domain/repository"; +import { findUserByEmail } from "./authServices"; export type LoginResponseOrError = | Result @@ -56,7 +51,11 @@ export class LoginUseCase // Crear auth try { - const user = await this.findUserEmail(emailOrError.object); + const user = await findUserByEmail( + emailOrError.object, + this._adapter, + this.getRepositoryByName("Auth"), + ); if (user === null || !user.verifyPassword(password)) { return Result.fail( handleUseCaseError( @@ -77,18 +76,4 @@ export class LoginUseCase ); } } - - private async findUserEmail(email: Email): Promise { - const transaction = this._adapter.startTransaction(); - const authRepoBuilder = this.getRepositoryByName("Auth"); - - let user: User | null = null; - - await transaction.complete(async (t) => { - const authRepo = authRepoBuilder({ transaction: t }); - user = await authRepo.findByEmail(email); - }); - - return user; - } } diff --git a/server/src/contexts/auth/application/authServices.ts b/server/src/contexts/auth/application/authServices.ts index e69de29..eee0e89 100644 --- a/server/src/contexts/auth/application/authServices.ts +++ b/server/src/contexts/auth/application/authServices.ts @@ -0,0 +1,18 @@ +import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain"; +import { Email } from "@shared/contexts"; +import { User } from "../domain"; +import { IAuthRepository } from "../domain/repository"; + +export const findUserByEmail = async ( + email: Email, + adapter: IAdapter, + repository: RepositoryBuilder, +): Promise => { + const user = await adapter + .startTransaction() + .complete(async (t) => + repository({ transaction: t }).findUserByEmail(email), + ); + + return user; +}; diff --git a/server/src/contexts/auth/application/index.ts b/server/src/contexts/auth/application/index.ts index 1c1e388..260da68 100644 --- a/server/src/contexts/auth/application/index.ts +++ b/server/src/contexts/auth/application/index.ts @@ -1 +1,2 @@ -export * from "./authServices"; +export * from "./FindUserByEmail.useCase"; +export * from "./Login.useCase"; diff --git a/server/src/contexts/auth/domain/entities/User.ts b/server/src/contexts/auth/domain/entities/User.ts index 93f7620..a8725bd 100644 --- a/server/src/contexts/auth/domain/entities/User.ts +++ b/server/src/contexts/auth/domain/entities/User.ts @@ -4,11 +4,13 @@ import { AggregateRoot, Email, IDomainError, + Name, Result, UniqueID, } from "@shared/contexts"; export interface IUserProps { + name: Name; email: Email; password?: string; hashed_password?: string; @@ -16,6 +18,7 @@ export interface IUserProps { export interface IUser { id: UniqueID; + name: Name; email: Email; hashed_password: string; @@ -51,6 +54,10 @@ export class User extends AggregateRoot implements IUser { this._protectPassword(props); } + get name(): Name { + return this.props.name; + } + get email(): Email { return this.props.email; } diff --git a/server/src/contexts/auth/domain/repository/AuthRepository.interface.ts b/server/src/contexts/auth/domain/repository/AuthRepository.interface.ts index 7944042..85a87f0 100644 --- a/server/src/contexts/auth/domain/repository/AuthRepository.interface.ts +++ b/server/src/contexts/auth/domain/repository/AuthRepository.interface.ts @@ -1,8 +1,7 @@ import { IRepository } from "@/contexts/common/domain"; -import { Email, UniqueID } from "@shared/contexts"; +import { Email } from "@shared/contexts"; import { User } from "../entities"; export interface IAuthRepository extends IRepository { - getById(id: UniqueID): Promise; - findByEmail(email: Email): Promise; + findUserByEmail(email: Email): Promise; } diff --git a/server/src/contexts/auth/infrastructure/Auth.repository.ts b/server/src/contexts/auth/infrastructure/Auth.repository.ts index 45afba3..9bf5373 100644 --- a/server/src/contexts/auth/infrastructure/Auth.repository.ts +++ b/server/src/contexts/auth/infrastructure/Auth.repository.ts @@ -41,7 +41,7 @@ export class AuthRepository return this.mapper.mapToDomain(rawUser); } - public async findByEmail(email: Email): Promise { + public async findUserByEmail(email: Email): Promise { const rawUser: any = await this._getBy( "User_Model", "email", diff --git a/server/src/contexts/auth/infrastructure/express/controllers/AuthenticateController.ts b/server/src/contexts/auth/infrastructure/express/controllers/AuthenticateController.ts index af51bc2..3ba3051 100644 --- a/server/src/contexts/auth/infrastructure/express/controllers/AuthenticateController.ts +++ b/server/src/contexts/auth/infrastructure/express/controllers/AuthenticateController.ts @@ -1,4 +1,5 @@ // Import the necessary packages and modules +import { User } from "@/contexts/auth/domain"; import { IServerError } from "@/contexts/common/domain/errors"; import { ExpressController } from "@/contexts/common/infrastructure/express"; import passport from "passport"; @@ -41,7 +42,7 @@ export class AuthenticateController extends ExpressController { { session: false }, ( err: any, - user?: Express.User | false | null, + user?: User | false | null, info?: object | string | Array, status?: number | Array, ) => { diff --git a/server/src/contexts/auth/infrastructure/express/controllers/LoginController.ts b/server/src/contexts/auth/infrastructure/express/controllers/LoginController.ts deleted file mode 100644 index f91e4ac..0000000 --- a/server/src/contexts/auth/infrastructure/express/controllers/LoginController.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { IUseCaseError, UseCaseError } from "@/contexts/common/application"; -import { IServerError } from "@/contexts/common/domain/errors"; -import { - IInfrastructureError, - InfrastructureError, - handleInfrastructureError, -} from "@/contexts/common/infrastructure"; -import { ExpressController } from "@/contexts/common/infrastructure/express"; -import { ILogin_DTO, ensureLogin_DTOIsValid } from "@shared/contexts"; - -export class LoginController extends ExpressController { - private useCase: LoginUseCase; - private presenter: ILoginPresenter; - private context: IAuthContext; - - constructor( - props: { - useCase: LoginUseCase; - presenter: ILoginPresenter; - }, - context: IAuthContext, - ) { - super(); - - const { useCase, presenter } = props; - this.useCase = useCase; - this.presenter = presenter; - this.context = context; - } - - async executeImpl() { - try { - const loginDTO: ILogin_DTO = this.req.body; - - // Validaciones de DTO - const loginDTOOrError = ensureLogin_DTOIsValid(loginDTO); - - if (loginDTOOrError.isFailure) { - const errorMessage = "Login data not valid"; - const infraError = handleInfrastructureError( - InfrastructureError.INVALID_INPUT_DATA, - errorMessage, - loginDTOOrError.error, - ); - return this.invalidInputError(errorMessage, infraError); - } - - const result = await this.useCase.execute(); - - if (result.isFailure) { - return this._handleExecuteError(result.error); - } - - console.log("login OK => generate token JWT"); - - const customer = result.object; - - return this.created( - this.presenter.map(customer, this.context), - ); - } catch (e: unknown) { - return this.fail(e as IServerError); - } - } - - private _handleExecuteError(error: IUseCaseError) { - let errorMessage: string; - let infraError: IInfrastructureError; - - switch (error.code) { - case UseCaseError.INVALID_INPUT_DATA: - errorMessage = "Login data not valid"; - infraError = handleInfrastructureError( - InfrastructureError.INVALID_INPUT_DATA, - errorMessage, - error, - ); - return this.invalidInputError(errorMessage, infraError); - break; - - case UseCaseError.NOT_FOUND_ERROR: - errorMessage = "User not found"; - - infraError = handleInfrastructureError( - InfrastructureError.INVALID_INPUT_DATA, - errorMessage, - error, - ); - return this.conflictError(error.message, error); - break; - - case UseCaseError.UNEXCEPTED_ERROR: - errorMessage = error.message; - - infraError = handleInfrastructureError( - InfrastructureError.UNEXCEPTED_ERROR, - errorMessage, - error, - ); - return this.internalServerError(errorMessage, infraError); - break; - - default: - errorMessage = error.message; - return this.clientError(errorMessage); - } - } -} diff --git a/server/src/contexts/auth/infrastructure/express/controllers/index.ts b/server/src/contexts/auth/infrastructure/express/controllers/index.ts index 73b710c..f54d11b 100644 --- a/server/src/contexts/auth/infrastructure/express/controllers/index.ts +++ b/server/src/contexts/auth/infrastructure/express/controllers/index.ts @@ -1,20 +1 @@ -import { AuthenticateController } from "./AuthenticateController"; - -const authenticate = (context: any) => { - //const adapter = context.adapter; - //const repoManager = context.repositoryManager; - - /*repoManager.registerRepository("Auth", (params = { transaction: null }) => { - const { transaction } = params; - - return new AuthRepository({ - transaction, - adapter, - mapper: createAuthMapper(context), - }); - });*/ - - //const listArticlesUseCase = new ListArticlesUseCase(context); - - return new AuthenticateController(); -}; +export * from "./login"; diff --git a/server/src/contexts/auth/infrastructure/express/controllers/login/Login.controller.ts b/server/src/contexts/auth/infrastructure/express/controllers/login/Login.controller.ts new file mode 100644 index 0000000..eb47379 --- /dev/null +++ b/server/src/contexts/auth/infrastructure/express/controllers/login/Login.controller.ts @@ -0,0 +1,71 @@ +import { config } from "@/config"; +import { User } from "@/contexts/auth/domain"; +import { IServerError } from "@/contexts/common/domain/errors"; +import { + InfrastructureError, + handleInfrastructureError, +} from "@/contexts/common/infrastructure"; +import { ExpressController } from "@/contexts/common/infrastructure/express"; +import { ILogin_Response_DTO } from "@shared/contexts"; +import JWT from "jsonwebtoken"; +import { IAuthContext } from "../../../Auth.context"; +import { ILoginPresenter, ILoginUser } from "./presenter"; + +export class LoginController extends ExpressController { + private presenter: ILoginPresenter; + private context: IAuthContext; + + constructor( + props: { + presenter: ILoginPresenter; + }, + context: IAuthContext, + ) { + super(); + + const { presenter } = props; + this.presenter = presenter; + this.context = context; + } + + async executeImpl() { + try { + const user = this.req.user; + + if (!user) { + const errorMessage = "Unexpected missing user data"; + const infraError = handleInfrastructureError( + InfrastructureError.UNEXCEPTED_ERROR, + errorMessage, + ); + return this.internalServerError(errorMessage, infraError); + } + + const loginUser: ILoginUser = { + user, + token: this._generateUserToken(user), + refreshToken: this._generateUserRefreshToken(user), + }; + + return this.ok( + this.presenter.map(loginUser, this.context), + ); + } catch (e: unknown) { + return this.fail(e as IServerError); + } + } + + private _generateUserToken(user: User) { + return JWT.sign({ email: user.email.toString() }, config.jwt.secret_key, { + expiresIn: config.jwt.token_expiration, + }); + } + + private _generateUserRefreshToken(user: User) { + return JWT.sign( + { email: user.email.toString() }, + config.jwt.refresh_secret_key, + { expiresIn: config.jwt.refresh_token_expiration }, + ); + } +} diff --git a/server/src/contexts/auth/infrastructure/express/controllers/login/index.ts b/server/src/contexts/auth/infrastructure/express/controllers/login/index.ts new file mode 100644 index 0000000..b0097d1 --- /dev/null +++ b/server/src/contexts/auth/infrastructure/express/controllers/login/index.ts @@ -0,0 +1,13 @@ +import { IAuthContext } from "../../../Auth.context"; +import { LoginController } from "./Login.controller"; +import { loginPresenter } from "./presenter"; + +export const createLoginController = (context: IAuthContext) => { + return new LoginController( + { + //useCase: listArticlesUseCase, + presenter: loginPresenter, + }, + context, + ); +}; diff --git a/server/src/contexts/auth/infrastructure/express/controllers/login/presenter/Login.presenter.ts b/server/src/contexts/auth/infrastructure/express/controllers/login/presenter/Login.presenter.ts new file mode 100644 index 0000000..fcc418a --- /dev/null +++ b/server/src/contexts/auth/infrastructure/express/controllers/login/presenter/Login.presenter.ts @@ -0,0 +1,29 @@ +import { IUser } from "@/contexts/auth/domain"; +import { IAuthContext } from "@/contexts/auth/infrastructure/Auth.context"; +import { ILogin_Response_DTO } from "@shared/contexts"; + +export interface ILoginUser { + user: IUser; + token: string; + refreshToken: string; +} + +export interface ILoginPresenter { + map: (user: ILoginUser, context: IAuthContext) => ILogin_Response_DTO; +} + +export const loginPresenter: ILoginPresenter = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + map: (loginUser: ILoginUser, context: IAuthContext): ILogin_Response_DTO => { + const { user, token, refreshToken } = loginUser; + + return { + id: user.id.toString(), + name: user.name.toString(), + email: user.email.toString(), + roles: ["ROLE_USER"], + token, + refresh_token: refreshToken, + }; + }, +}; diff --git a/server/src/contexts/auth/infrastructure/express/controllers/login/presenter/index.ts b/server/src/contexts/auth/infrastructure/express/controllers/login/presenter/index.ts new file mode 100644 index 0000000..a0f7409 --- /dev/null +++ b/server/src/contexts/auth/infrastructure/express/controllers/login/presenter/index.ts @@ -0,0 +1 @@ +export * from "./Login.presenter"; diff --git a/server/src/contexts/auth/infrastructure/express/passport/authenticate.ts b/server/src/contexts/auth/infrastructure/express/passport/authMiddleware.ts similarity index 85% rename from server/src/contexts/auth/infrastructure/express/passport/authenticate.ts rename to server/src/contexts/auth/infrastructure/express/passport/authMiddleware.ts index 564e04f..f6810fd 100644 --- a/server/src/contexts/auth/infrastructure/express/passport/authenticate.ts +++ b/server/src/contexts/auth/infrastructure/express/passport/authMiddleware.ts @@ -1,9 +1,12 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import Express from "express"; import passport from "passport"; -export const authenticate = ( +export const isLoggedUser = passport.authenticate("local-jwt", { + session: false, +}); + +/*export const authenticate = ( req: Express.Request, res: Express.Response, next: Express.NextFunction, @@ -14,7 +17,7 @@ export const authenticate = ( { session: false }, ( err: any, - user?: Express.User | false | null, + user?: User | false | null, info?: object | string | Array, status?: number | Array, ) => { @@ -32,3 +35,6 @@ export const authenticate = ( }, )(req, res, next); }; + + +*/ diff --git a/server/src/contexts/auth/infrastructure/express/passport/configurePassportAuth.ts b/server/src/contexts/auth/infrastructure/express/passport/configurePassportAuth.ts index 6d4044a..a602bee 100644 --- a/server/src/contexts/auth/infrastructure/express/passport/configurePassportAuth.ts +++ b/server/src/contexts/auth/infrastructure/express/passport/configurePassportAuth.ts @@ -1,11 +1,10 @@ import { PassportStatic } from "passport"; import { AuthContext } from "../../Auth.context"; import { initEmailStrategy } from "./emailStrategy"; -import { jwtStrategy } from "./jwtStrategy"; +import { initJWTStrategy } from "./jwtStrategy"; // Export a function that will be used to configure Passport authentication export const configurePassportAuth = (passport: PassportStatic) => { - console.log("passport: configuring strategies !!!!!!!!!!!!!!!!!!"); passport.use("local-email", initEmailStrategy(AuthContext.getInstance())); - passport.use("local-jwt", jwtStrategy); + passport.use("local-jwt", initJWTStrategy(AuthContext.getInstance())); }; diff --git a/server/src/contexts/auth/infrastructure/express/passport/emailStrategy.ts b/server/src/contexts/auth/infrastructure/express/passport/emailStrategy.ts index dc65a81..df3b61e 100644 --- a/server/src/contexts/auth/infrastructure/express/passport/emailStrategy.ts +++ b/server/src/contexts/auth/infrastructure/express/passport/emailStrategy.ts @@ -1,9 +1,10 @@ -import { LoginUseCase } from "@/contexts/auth/application/LoginUseCase"; import { IServerError } from "@/contexts/common/domain/errors"; import { PassportStrategyController } from "@/contexts/common/infrastructure/express"; import { ensureLogin_DTOIsValid } from "@shared/contexts"; import { Strategy as EmailStrategy, IVerifyOptions } from "passport-local"; +import { LoginUseCase } from "@/contexts/auth/application"; +import { User } from "@/contexts/auth/domain"; import { IAuthContext } from "../../Auth.context"; import { registerAuthRepository } from "../../Auth.repository"; @@ -32,11 +33,7 @@ class EmailStrategyController extends PassportStrategyController { public async verifyStrategy( email: string, password: string, - done: ( - error: any, - user?: Express.User | false, - options?: IVerifyOptions, - ) => void, + done: (error: any, user?: User | false, options?: IVerifyOptions) => void, ) { const loginDTOOrError = ensureLogin_DTOIsValid({ email, password }); diff --git a/server/src/contexts/auth/infrastructure/express/passport/index.ts b/server/src/contexts/auth/infrastructure/express/passport/index.ts index fdacb83..6c00109 100644 --- a/server/src/contexts/auth/infrastructure/express/passport/index.ts +++ b/server/src/contexts/auth/infrastructure/express/passport/index.ts @@ -1,2 +1,2 @@ -export * from "./authenticate"; +export * from "./authMiddleware"; export * from "./configurePassportAuth"; diff --git a/server/src/contexts/auth/infrastructure/express/passport/jwtStrategy.ts b/server/src/contexts/auth/infrastructure/express/passport/jwtStrategy.ts index 5359493..c8ec3c1 100644 --- a/server/src/contexts/auth/infrastructure/express/passport/jwtStrategy.ts +++ b/server/src/contexts/auth/infrastructure/express/passport/jwtStrategy.ts @@ -1,27 +1,60 @@ import { config } from "@/config"; -import { ExtractJwt, Strategy as JWTStrategy } from "passport-jwt"; +import { FindUserByEmailUseCase } from "@/contexts/auth/application/FindUserByEmail.useCase"; +import { IServerError } from "@/contexts/common/domain/errors"; +import { PassportStrategyController } from "@/contexts/common/infrastructure/express"; +import { + ExtractJwt, + Strategy as JWTStrategy, + VerifiedCallback, +} from "passport-jwt"; +import { IAuthContext } from "../../Auth.context"; +import { registerAuthRepository } from "../../Auth.repository"; const strategyOpts = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // Extract the JWT from the Authorization header secretOrKey: config.jwt.secret_key, }; -export const jwtStrategy = new JWTStrategy( - strategyOpts, - async (jwt_payload, done) => { - console.log( - "PASSPORT USE LOCAL-JWT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", - ); - console.log(jwt_payload); +class JWTStrategyController extends PassportStrategyController { + private useCase: FindUserByEmailUseCase; + private context: IAuthContext; - /* - const user = await authenticateUserByEmail("assa@asds.com", "password"); - if (user) { - return done(null, user); - } else { - return done(null, false); + constructor( + props: { + useCase: FindUserByEmailUseCase; + }, + context: any, + ) { + super(); + + const { useCase } = props; + this.useCase = useCase; + this.context = context; + } + + public async verifyStrategy(payload: any, done: VerifiedCallback) { + const { email } = payload; + + try { + const result = await this.useCase.execute({ email }); + if (result.isFailure) { + return done(null, false); + } + + return done(null, result.object); + } catch (e: unknown) { + return done(e as IServerError); } - */ - return done(null, { id: "xzxxxxx", email: "assa@asds.com" }); - }, -); + } +} + +export const initJWTStrategy = (context: IAuthContext) => + new JWTStrategy(strategyOpts, async (...params) => { + registerAuthRepository(context); + return new JWTStrategyController( + { + useCase: new FindUserByEmailUseCase(context), + }, + context, + ).verifyStrategy(...params); + }); diff --git a/server/src/contexts/auth/infrastructure/express/passport/passport.bak b/server/src/contexts/auth/infrastructure/express/passport/passport.bak deleted file mode 100644 index c33f3bb..0000000 --- a/server/src/contexts/auth/infrastructure/express/passport/passport.bak +++ /dev/null @@ -1,184 +0,0 @@ -import { - InfrastructureError, - handleInfrastructureError, -} from "@/contexts/common/infrastructure"; -import { ensureLogin_DTOIsValid } from "@shared/contexts"; -import passport from "passport"; -import { ExtractJwt, Strategy as JwtStrategy } from "passport-jwt"; -import { Strategy as LocalStrategy } from "passport-local"; - -/*declare global { - namespace Express { - interface User { - id?: string; - email: string; - password: string; - } - } -}*/ - -//import { authenticateUserByEmail, deserializeUserById } from './../../../services'; -const authenticateUserByEmail = async ( - email: string, - password: string, -): Promise => { - // Simulación de búsqueda del usuario en la base de datos - const user = { - id: "1", - email: "usuario@example.com", - password: "$2a$10$EdfkgQ7vMTGvXq1qI7qJQ.g3WiYGqCSCdzhZ/LG20YKwA1YjgLJnO", - }; // Contraseña hasheada: "password" - - return user; - - /*const res = await pool.query('SELECT * FROM users WHERE email = $1', [email]); - - if (res.rows.length) { - const user = res.rows[0]; - const match = await bcrypt.compare(password, user.password); - - if (match) { - return user; - } else { - throw new Error('Incorrect email and/or password'); - } - } else { - throw new Error('User not found'); - }*/ -}; - -const deserializeUserById = async ( - id: number, -): Promise => { - /*const res = await pool.query('SELECT * FROM users WHERE id = $1', [id]); - - if (res.rows.length) { - return res.rows[0]; - } else { - throw new Error('User was not found'); - }*/ - - const user = { - id: "1", - email: "usuario@example.com", - password: "$2a$10$EdfkgQ7vMTGvXq1qI7qJQ.g3WiYGqCSCdzhZ/LG20YKwA1YjgLJnO", - }; // Contraseña hasheada: "password" - - return user; -}; - -// Configurar la estrategia de autenticación local de Passport.js -passport.use( - "local-email", - new LocalStrategy( - { - usernameField: "email", // Campo utilizado para el email en el formulario - passwordField: "password", - }, - async (email, password, done) => { - console.log("local-email"); - - const loginDTO = { email, password }; - const loginDTOOrError = ensureLogin_DTOIsValid(loginDTO); - - if (loginDTOOrError.isFailure) { - const errorMessage = "Login data not valid"; - const infraError = handleInfrastructureError( - InfrastructureError.INVALID_INPUT_DATA, - errorMessage, - loginDTOOrError.error, - ); - done(infraError); - } - - const result = await this.useCase.execute(); - - try { - // Buscar el usuario en la base de datos por email - const user = await authenticateUserByEmail(email, password); - return done(null, user as Express.User); - } catch (error) { - return done(error); - } - }, - ), -); - -passport.use( - "local-jwt", - new JwtStrategy( - { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: "secret", - }, - async (jwt_payload, done) => { - console.log(jwt_payload); - - const user = await authenticateUserByEmail("assa@asds.com", "password"); - return done(null, user); - }, - ), -); - -/*passport.use( - "jwt2", - new CustomStrategy(async (req, done) => { - const token = - req && req.headers && req.headers["x-access-token"] - ? req.headers["x-access-token"] - : null; - const appVersion = - req && req.headers && req.headers["accept-version"] - ? req.headers["accept-version"] - : null; - console.log("appVersion: ", appVersion); - - if (!token) { - console.error("Unauthorized. Token missing."); - return done(null, false, { message: "Unauthorized. Token missing." }); - } - - const result = securityHelper.verify(token); - //console.log('token result => ', result); - - if (result && result.id) { - //recuperamos el usuario de la petición - let user = await authService.extraMethods.findUser({ id: result.id }); - if (user) { - user = user.toJSON(); - userService._updateLastLoginAndVersionUser(user.id, appVersion); - user.app_version = appVersion; - user.token = token; - delete user.password; - - console.log("Logged in Successfully"); - console.log(user); - return done(null, user, { message: "Logged in Successfully" }); - } else { - console.error("Unauthorized. User not found."); - return done(null, false, { message: "Unauthorized. User not found." }); - } - } else { - //console.log('Token no válido'); - console.error("Unauthorized. Invalid token."); - return done(null, false, { message: "Unauthorized. Invalid token." }); - } - }), -);*/ - -// Serializar y deserializar el usuario para almacenar y recuperar la sesión -passport.serializeUser((user: Express.User, done) => { - done(null, user.id); -}); - -passport.deserializeUser(async (id: number, done) => { - try { - const user = await deserializeUserById(id); - - done(null, user as Express.User); - } catch (error) { - done(error, null); - } -}); - -export { passport }; diff --git a/server/src/contexts/auth/infrastructure/express/routes.ts b/server/src/contexts/auth/infrastructure/express/routes.ts index 492c84b..cf02cd8 100644 --- a/server/src/contexts/auth/infrastructure/express/routes.ts +++ b/server/src/contexts/auth/infrastructure/express/routes.ts @@ -1,8 +1,8 @@ -import { config } from "@/config"; +import { registerMiddleware } from "@/contexts/common/infrastructure/express"; import Express from "express"; -import JWT from "jsonwebtoken"; import passport from "passport"; -import { User } from "../../domain"; +import { createLoginController } from "./controllers"; +import { isLoggedUser } from "./passport"; /*authRoutes.post( "/login", @@ -40,48 +40,13 @@ authRoutes.get( export const AuthRouter = (appRouter: Express.Router) => { const authRoutes: Express.Router = Express.Router({ mergeParams: true }); - //appRouter.use(registerMiddleware("authenticate", authenticate)); + appRouter.use(registerMiddleware("isLoggedUser", isLoggedUser)); authRoutes.post( "/login", passport.authenticate("local-email", { session: false }), - (req, res, next) => { - if (req.isAuthenticated()) { - const user: User = req.user; - - const accessToken = JWT.sign( - { id: user.id, email: user.email }, - config.jwt.secret_key, - { expiresIn: config.jwt.token_expiration }, - ); - const refreshToken = JWT.sign( - { id: user.id, email: user.email }, - config.jwt.refresh_secret_key, - { expiresIn: config.jwt.refresh_token_expiration }, - ); - - //refreshTokens.push(refreshToken); - - return res.json({ accessToken, refreshToken }); - } - return res.status(401).json({}); - }, - ); - - authRoutes.post( - "/login2", (req: Express.Request, res: Express.Response, next: Express.NextFunction) => - passport.authenticate( - "local-email", - { session: false }, - (err, user, info) => { - console.log(err, user, info); - next(err); - }, - )(req, res, next), - (req, res, next) => { - res.status(200).json({}); - }, + createLoginController(res.locals["context"]).execute(req, res, next), ); appRouter.use("/auth", authRoutes); diff --git a/server/src/contexts/auth/infrastructure/mappers/user.mapper.ts b/server/src/contexts/auth/infrastructure/mappers/user.mapper.ts index cf1106a..d736a50 100644 --- a/server/src/contexts/auth/infrastructure/mappers/user.mapper.ts +++ b/server/src/contexts/auth/infrastructure/mappers/user.mapper.ts @@ -2,7 +2,7 @@ import { ISequelizeMapper, SequelizeMapper, } from "@/contexts/common/infrastructure"; -import { Email, UniqueID } from "@shared/contexts"; +import { Email, Name, UniqueID } from "@shared/contexts"; import { IUserProps, User } from "../../domain/entities"; import { IAuthContext } from "../Auth.context"; import { TCreationUser_Attributes, User_Model } from "../sequelize/user.model"; @@ -20,6 +20,7 @@ class UserMapper protected toDomainMappingImpl(source: User_Model, params: any): User { const props: IUserProps = { + name: this.mapsValue(source, "name", Name.create), email: this.mapsValue(source, "email", Email.create), hashed_password: source.password, }; @@ -40,6 +41,7 @@ class UserMapper ) { return { id: source.id.toPrimitive(), + name: source.name.toPrimitive(), email: source.email.toPrimitive(), password: source.hashed_password, }; diff --git a/server/src/contexts/auth/infrastructure/sequelize/user.model.ts b/server/src/contexts/auth/infrastructure/sequelize/user.model.ts index 3ef6f82..f88b243 100644 --- a/server/src/contexts/auth/infrastructure/sequelize/user.model.ts +++ b/server/src/contexts/auth/infrastructure/sequelize/user.model.ts @@ -20,6 +20,7 @@ export class User_Model extends Model< static associate(connection: Sequelize) {} declare id: string; + declare name: string; declare email: string; declare password: string; } @@ -32,10 +33,15 @@ export default (sequelize: Sequelize) => { primaryKey: true, }, + name: { + type: DataTypes.STRING, + }, + email: { type: DataTypes.STRING, allowNull: false, }, + password: { type: DataTypes.STRING, allowNull: false, @@ -45,15 +51,15 @@ export default (sequelize: Sequelize) => { sequelize, tableName: "users", - //paranoid: true, // softs deletes - //timestamps: true, + paranoid: true, // softs deletes + timestamps: true, //version: true, - //createdAt: "created_at", - //updatedAt: "updated_at", - //deletedAt: "deleted_at", + createdAt: "created_at", + updatedAt: "updated_at", + deletedAt: "deleted_at", - indexes: [{ name: "email_idx", unique: true, fields: ["email"] }], + indexes: [{ name: "email_idx", fields: ["email"] }], }, ); diff --git a/server/src/contexts/catalog/infrastructure/express/routes.ts b/server/src/contexts/catalog/infrastructure/express/routes.ts index 3c14354..58623aa 100644 --- a/server/src/contexts/catalog/infrastructure/express/routes.ts +++ b/server/src/contexts/catalog/infrastructure/express/routes.ts @@ -13,7 +13,7 @@ export const CatalogRouter = (appRouter: Express.Router) => { catalogRoutes.get( "/", - applyMiddleware("authenticate"), + applyMiddleware("isLoggedUser"), (req: Express.Request, res: Express.Response, next: Express.NextFunction) => createListArticlesController(res.locals["context"]).execute( req, diff --git a/server/src/contexts/common/domain/repositories/Adapter.interface.ts b/server/src/contexts/common/domain/repositories/Adapter.interface.ts index 17d10be..d754e39 100644 --- a/server/src/contexts/common/domain/repositories/Adapter.interface.ts +++ b/server/src/contexts/common/domain/repositories/Adapter.interface.ts @@ -1,4 +1,4 @@ -import { TBusinessTransaction } from "./BusinessTransaction.interface"; +import { TBusinessTransaction } from "./BusinessTransaction"; export interface IAdapter { startTransaction: () => TBusinessTransaction; diff --git a/server/src/contexts/common/domain/repositories/BusinessTransaction.interface.ts b/server/src/contexts/common/domain/repositories/BusinessTransaction.interface.ts deleted file mode 100644 index d7dcb75..0000000 --- a/server/src/contexts/common/domain/repositories/BusinessTransaction.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -type TUnitOfWork = { - start(): unknown; -}; - -export type TBusinessTransaction = TUnitOfWork; diff --git a/server/src/contexts/common/domain/repositories/BusinessTransaction.ts b/server/src/contexts/common/domain/repositories/BusinessTransaction.ts new file mode 100644 index 0000000..0fe1846 --- /dev/null +++ b/server/src/contexts/common/domain/repositories/BusinessTransaction.ts @@ -0,0 +1,4 @@ +export type TBusinessTransaction = { + start(): void; + complete(work: (...params: any[]) => Promise): Promise; +}; diff --git a/server/src/contexts/common/domain/repositories/index.ts b/server/src/contexts/common/domain/repositories/index.ts index f1e3f1e..19e3336 100644 --- a/server/src/contexts/common/domain/repositories/index.ts +++ b/server/src/contexts/common/domain/repositories/index.ts @@ -1,5 +1,5 @@ export * from "./Adapter.interface"; -export * from "./BusinessTransaction.interface"; +export * from "./BusinessTransaction"; export * from "./Repository.interface"; export * from "./RepositoryBuilder"; export * from "./RepositoryManager"; diff --git a/server/src/contexts/common/infrastructure/InfrastructureError.ts b/server/src/contexts/common/infrastructure/InfrastructureError.ts index 8ca50a1..0bf4a7a 100755 --- a/server/src/contexts/common/infrastructure/InfrastructureError.ts +++ b/server/src/contexts/common/infrastructure/InfrastructureError.ts @@ -18,7 +18,7 @@ export class InfrastructureError public static create( code: string, message: string, - payload?: Record + payload?: Record, ): InfrastructureError { return new InfrastructureError(code, message, payload); } @@ -31,16 +31,17 @@ function _isJoiError(error: Error) { export function handleInfrastructureError( code: string, message: string, - error: Error // UseCaseError | ValidationError + error?: Error, // UseCaseError | ValidationError ): IInfrastructureError { let payload = {}; - if (_isJoiError(error)) { - //Joi => error.details - payload = (error).details; - } else { - // UseCaseError - /*const useCaseError = error; + if (error) { + if (_isJoiError(error)) { + //Joi => error.details + payload = (error).details; + } else { + // UseCaseError + /*const useCaseError = error; if (useCaseError.payload.path) { const errorItem = {}; errorItem[`${useCaseError.payload.path}`] = useCaseError.message; @@ -48,7 +49,8 @@ export function handleInfrastructureError( errors: [errorItem], }; }*/ - payload = (error).payload; + payload = (error).payload; + } } console.log(payload); diff --git a/server/src/contexts/common/infrastructure/firebird/FirebirdAdapter.ts b/server/src/contexts/common/infrastructure/firebird/FirebirdAdapter.ts index 20b1e4f..801ae76 100644 --- a/server/src/contexts/common/infrastructure/firebird/FirebirdAdapter.ts +++ b/server/src/contexts/common/infrastructure/firebird/FirebirdAdapter.ts @@ -40,7 +40,7 @@ export class FirebirdAdapter implements IFirebirdAdapter { protected constructor( connection: Firebird.ConnectionPool, - queryBuilder: IRepositoryQueryBuilder + queryBuilder: IRepositoryQueryBuilder, ) { this._connection = connection; this._queryBuilder = queryBuilder; diff --git a/server/src/contexts/common/infrastructure/firebird/FirebirdBusinessTransaction.ts b/server/src/contexts/common/infrastructure/firebird/FirebirdBusinessTransaction.ts index 6a6498b..328eab6 100644 --- a/server/src/contexts/common/infrastructure/firebird/FirebirdBusinessTransaction.ts +++ b/server/src/contexts/common/infrastructure/firebird/FirebirdBusinessTransaction.ts @@ -1,11 +1,11 @@ import Firebird from "node-firebird"; -import { TBusinessTransaction } from "../../domain/repositories"; +import { IBusinessTransaction } from "../../domain/repositories"; import { InfrastructureError } from "../InfrastructureError"; -export type FirebirdBusinessTransactionType = TBusinessTransaction & { +export type FirebirdBusinessTransactionType = IBusinessTransaction & { start: (a: unknown) => unknown; complete( - work: (t: Firebird.Transaction, db: Firebird.Database) => unknown + work: (t: Firebird.Transaction, db: Firebird.Database) => unknown, ): void; }; @@ -23,7 +23,7 @@ export class FirebirdBusinessTransaction } public complete( - work: (t: Firebird.Transaction, db?: Firebird.Database) => unknown + work: (t: Firebird.Transaction, db?: Firebird.Database) => unknown, ): void { this._connection.get((err, db: Firebird.Database) => { if (err) { @@ -36,7 +36,7 @@ export class FirebirdBusinessTransaction if (err) { InfrastructureError.create( InfrastructureError.UNEXCEPTED_ERROR, - err + err, ); } @@ -48,7 +48,7 @@ export class FirebirdBusinessTransaction } finally { db.detach(); } - } + }, ); }); } diff --git a/server/src/contexts/common/infrastructure/sequelize/SequelizeAdapter.ts b/server/src/contexts/common/infrastructure/sequelize/SequelizeAdapter.ts index 5b6b205..a7b272c 100644 --- a/server/src/contexts/common/infrastructure/sequelize/SequelizeAdapter.ts +++ b/server/src/contexts/common/infrastructure/sequelize/SequelizeAdapter.ts @@ -1,7 +1,6 @@ import { config } from "@/config"; import rTracer from "cls-rtracer"; import { DataTypes, Sequelize } from "sequelize"; -import { SequelizeRevision } from "sequelize-revision"; import { initLogger } from "@/infrastructure/logger"; import * as glob from "glob"; @@ -43,38 +42,23 @@ export class SequelizeAdapter implements ISequelizeAdapter { const { queryBuilder } = params; const connection = initConnection(); - const sequelizeRevision = new SequelizeRevision(connection, { - UUID: true, - tableName: "revisions", - //changeTableName: "", - underscored: true, - underscoredAttributes: true, - }); - const models = registerModels(connection, sequelizeRevision); + const models = registerModels(connection); - return new SequelizeAdapter( - connection, - models, - queryBuilder, - sequelizeRevision - ); + return new SequelizeAdapter(connection, models, queryBuilder); } private _connection: Sequelize; private _models: ISequelizeModels; private _queryBuilder: ISequelizeQueryBuilder; - private _revisions: SequelizeRevision; protected constructor( connection: Sequelize, models: ISequelizeModels, queryBuilder: ISequelizeQueryBuilder, - revisions: SequelizeRevision ) { this._connection = connection; this._models = models; this._queryBuilder = queryBuilder; - this._revisions = revisions; } get queryBuilder(): ISequelizeQueryBuilder { @@ -95,7 +79,7 @@ export class SequelizeAdapter implements ISequelizeAdapter { } throw InfrastructureError.create( InfrastructureError.RESOURCE_NOT_FOUND_ERROR, - `[SequelizeAdapter] ${modelName} sequelize model not exists!` + `[SequelizeAdapter] ${modelName} sequelize model not exists!`, ); } @@ -139,10 +123,7 @@ function initConnection(): Sequelize { }); } -function registerModels( - connection: Sequelize, - sequelizeRevision: SequelizeRevision -): ISequelizeModels { +function registerModels(connection: Sequelize): ISequelizeModels { const cwd = path.resolve(`${__dirname}/../../../`); const models: ISequelizeModels = {}; @@ -164,18 +145,9 @@ function registerModels( if (model) models[model.name] = model; }); - // Register revisions models - const [Revision, RevisionChanges] = sequelizeRevision.defineModels(); - //models[Revision.name] = Revision; - //models[RevisionChanges.name] = RevisionChanges; - for (const modelName in models) { const model = models[modelName]; - if (model.trackRevision) { - model.trackRevision(connection, sequelizeRevision); - } - if (model.associate) { model.associate(connection, models); } diff --git a/server/src/contexts/common/infrastructure/sequelize/SequelizeBusinessTransaction.ts b/server/src/contexts/common/infrastructure/sequelize/SequelizeBusinessTransaction.ts index d9f0a79..c7ae198 100644 --- a/server/src/contexts/common/infrastructure/sequelize/SequelizeBusinessTransaction.ts +++ b/server/src/contexts/common/infrastructure/sequelize/SequelizeBusinessTransaction.ts @@ -1,9 +1,8 @@ import { Sequelize, Transaction } from "sequelize"; -import { TBusinessTransaction } from "../../domain/repositories"; +import { TBusinessTransaction } from "../../domain"; import { InfrastructureError } from "../InfrastructureError"; export type SequelizeBusinessTransactionType = TBusinessTransaction & { - start(): void; complete(work: (t: Transaction) => Promise): Promise; }; @@ -57,7 +56,7 @@ export class SequelizeBusinessTransaction throw InfrastructureError.create( InfrastructureError.UNEXCEPTED_ERROR, - (error as Error).message + (error as Error).message, ); } } diff --git a/server/src/contexts/common/infrastructure/sequelize/SequelizeModel.interface.ts b/server/src/contexts/common/infrastructure/sequelize/SequelizeModel.interface.ts index 3c5bad1..38a5e0e 100644 --- a/server/src/contexts/common/infrastructure/sequelize/SequelizeModel.interface.ts +++ b/server/src/contexts/common/infrastructure/sequelize/SequelizeModel.interface.ts @@ -1,5 +1,4 @@ import { Model, Sequelize } from "sequelize"; -import { SequelizeRevision } from "sequelize-revision"; interface ISequelizeModel extends Model {} @@ -9,10 +8,6 @@ interface ISequelizeModels { interface ISequelizeModel extends Model { associate?: (connection: Sequelize, models?: ISequelizeModels) => void; hooks?: (connection: Sequelize) => void; - trackRevision?: ( - connection: Sequelize, - sequelizeRevision: SequelizeRevision - ) => void; } export { ISequelizeModel, ISequelizeModels }; diff --git a/shared/lib/contexts/auth/application/dto/ILogin_Response.dto.ts b/shared/lib/contexts/auth/application/dto/ILogin_Response.dto.ts index a30712c..d095a51 100644 --- a/shared/lib/contexts/auth/application/dto/ILogin_Response.dto.ts +++ b/shared/lib/contexts/auth/application/dto/ILogin_Response.dto.ts @@ -1,3 +1,8 @@ export interface ILogin_Response_DTO { + id: string; + name: string; + email: string; + roles: string[]; token: string; + refresh_token: string; } diff --git a/shared/lib/contexts/common/domain/entities/QueryCriteria/QuickSearch/QuickSearchCriteria.ts b/shared/lib/contexts/common/domain/entities/QueryCriteria/QuickSearch/QuickSearchCriteria.ts index 106a048..e4e3e48 100644 --- a/shared/lib/contexts/common/domain/entities/QueryCriteria/QuickSearch/QuickSearchCriteria.ts +++ b/shared/lib/contexts/common/domain/entities/QueryCriteria/QuickSearch/QuickSearchCriteria.ts @@ -33,7 +33,7 @@ export class QuickSearchCriteria } } - return Result.ok(String(searchString)); + return Result.ok(searchString); } public static create(value: UndefinedOr) { @@ -49,7 +49,7 @@ export class QuickSearchCriteria } private static sanitize(searchTerm: UndefinedOr): string { - return String(Joi.string().trim().validate(searchTerm).value); + return String(Joi.string().default("").trim().validate(searchTerm).value); } get searchTerm(): string {