From 0b77e704502477761e0d758716519d9e585c6580 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 7 Dec 2024 12:49:36 -0500 Subject: [PATCH] feat: improve auth local exceptions handling --- .../src/auth-local.controller.e2e-spec.ts | 2 +- .../src/auth-local.strategy.spec.ts | 66 ++++++++++++++++--- .../src/auth-local.strategy.ts | 44 ++++++++----- ...uth-local-invalid-credentials.exception.ts | 14 ++++ ...uth-local-invalid-login-data.exception.ts} | 5 +- .../auth-local-invalid-password.exception.ts | 14 ++++ .../auth-local-missing-login-dto.exception.ts | 15 +++++ ...-local-missing-password-field.exception.ts | 16 +++++ ...-local-missing-username-field.exception.ts | 16 +++++ .../auth-local-unauthorized.exception.ts | 16 +++++ .../auth-local-user-inactive.exception.ts | 14 ++++ ...auth-local-username-not-found.exception.ts | 14 ++++ .../src/exceptions/auth-local.exception.ts | 14 ++++ .../invalid-credentials.exception.ts | 17 ----- packages/nestjs-auth-local/src/index.ts | 7 ++ .../auth-local-validate-user.service.ts | 9 ++- .../src/exceptions/runtime.exception.ts | 26 ++++---- 17 files changed, 246 insertions(+), 63 deletions(-) create mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-invalid-credentials.exception.ts rename packages/nestjs-auth-local/src/exceptions/{invalid-login-data.exception.ts => auth-local-invalid-login-data.exception.ts} (71%) create mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-invalid-password.exception.ts create mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-missing-login-dto.exception.ts create mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-missing-password-field.exception.ts create mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-missing-username-field.exception.ts create mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-unauthorized.exception.ts create mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-user-inactive.exception.ts create mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-username-not-found.exception.ts create mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local.exception.ts delete mode 100644 packages/nestjs-auth-local/src/exceptions/invalid-credentials.exception.ts diff --git a/packages/nestjs-auth-local/src/auth-local.controller.e2e-spec.ts b/packages/nestjs-auth-local/src/auth-local.controller.e2e-spec.ts index 247b9521a..e154ae222 100644 --- a/packages/nestjs-auth-local/src/auth-local.controller.e2e-spec.ts +++ b/packages/nestjs-auth-local/src/auth-local.controller.e2e-spec.ts @@ -50,7 +50,7 @@ describe('AuthLocalController (e2e)', () => { }) .then((response) => { expect(response.body.message).toBe( - 'The provided credentials are incorrect. Please try again.', + 'The provided username or password is incorrect. Please try again.', ); expect(response.status).toBe(401); }); diff --git a/packages/nestjs-auth-local/src/auth-local.strategy.spec.ts b/packages/nestjs-auth-local/src/auth-local.strategy.spec.ts index a3664698e..b71dec235 100644 --- a/packages/nestjs-auth-local/src/auth-local.strategy.spec.ts +++ b/packages/nestjs-auth-local/src/auth-local.strategy.spec.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; import { mock } from 'jest-mock-extended'; import { PasswordValidationService } from '@concepta/nestjs-password'; -import { BadRequestException } from '@nestjs/common'; +import { BadRequestException, HttpStatus } from '@nestjs/common'; import { AuthLocalStrategy } from './auth-local.strategy'; import { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.interface'; import { AuthLocalUserLookupServiceInterface } from './interfaces/auth-local-user-lookup-service.interface'; @@ -11,8 +11,9 @@ import { AuthLocalValidateUserService } from './services/auth-local-validate-use import { UserFixture } from './__fixtures__/user/user.entity.fixture'; import { ReferenceIdInterface } from '@concepta/ts-core'; import { AuthLocalValidateUserInterface } from './interfaces/auth-local-validate-user.interface'; -import { InvalidCredentialsException } from './exceptions/invalid-credentials.exception'; -import { InvalidLoginDataException } from './exceptions/invalid-login-data.exception'; +import { AuthLocalException } from './exceptions/auth-local.exception'; +import { AuthLocalInvalidCredentialsException } from './exceptions/auth-local-invalid-credentials.exception'; +import { AuthLocalInvalidLoginDataException } from './exceptions/auth-local-invalid-login-data.exception'; describe(AuthLocalStrategy.name, () => { const USERNAME = 'username'; @@ -43,6 +44,7 @@ describe(AuthLocalStrategy.name, () => { user = new UserFixture(); user.id = randomUUID(); user.active = true; + jest.resetAllMocks(); jest.spyOn(userLookUpService, 'byUsername').mockResolvedValue(user); }); @@ -64,7 +66,7 @@ describe(AuthLocalStrategy.name, () => { expect(result.id).toBe(user.id); }); - it('should return user', async () => { + it('should fail to validate user', async () => { jest .spyOn(validateUserService, 'validateUser') .mockImplementationOnce((_dto: AuthLocalValidateUserInterface) => { @@ -72,7 +74,55 @@ describe(AuthLocalStrategy.name, () => { }); const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - await expect(t).rejects.toThrow(InvalidCredentialsException); + await expect(t).rejects.toThrow(AuthLocalInvalidCredentialsException); + }); + + it('should fail to validate user with custom message', async () => { + jest + .spyOn(validateUserService, 'validateUser') + .mockImplementation((_dto: AuthLocalValidateUserInterface) => { + throw new AuthLocalInvalidCredentialsException({ + message: 'Custom message', + safeMessage: 'Custom safe message', + }); + }); + + const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); + + try { + await t(); + } catch (error: unknown) { + if (error instanceof AuthLocalInvalidCredentialsException) { + expect(error.httpStatus).toBe(HttpStatus.UNAUTHORIZED); + expect(error.message).toBe('Custom message'); + expect(error.safeMessage).toBe('Custom safe message'); + } else { + throw new Error('Wrong error type'); + } + } + }); + + it('should fail with internal server error', async () => { + jest + .spyOn(validateUserService, 'validateUser') + .mockImplementation((_dto: AuthLocalValidateUserInterface) => { + throw new Error('This is really bad'); + }); + + const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); + + try { + await t(); + } catch (error: unknown) { + if (error instanceof AuthLocalException) { + expect(error?.httpStatus).toBe(HttpStatus.INTERNAL_SERVER_ERROR); + expect(error?.context?.originalError?.message).toBe( + 'This is really bad', + ); + } else { + throw new Error('Wrong error type'); + } + } }); it('should throw error on validateOrReject', async () => { @@ -87,14 +137,14 @@ describe(AuthLocalStrategy.name, () => { .mockRejectedValueOnce(BadRequestException); const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - await expect(t).rejects.toThrow(InvalidLoginDataException); + await expect(t).rejects.toThrow(AuthLocalInvalidLoginDataException); }); it('should return no user on userLookupService.byUsername', async () => { jest.spyOn(userLookUpService, 'byUsername').mockResolvedValue(null); const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - await expect(t).rejects.toThrow(InvalidCredentialsException); + await expect(t).rejects.toThrow(AuthLocalInvalidCredentialsException); }); it('should be invalid on passwordService.validateObject', async () => { @@ -103,7 +153,7 @@ describe(AuthLocalStrategy.name, () => { .mockResolvedValue(false); const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - await expect(t).rejects.toThrow(InvalidCredentialsException); + await expect(t).rejects.toThrow(AuthLocalInvalidCredentialsException); }); }); diff --git a/packages/nestjs-auth-local/src/auth-local.strategy.ts b/packages/nestjs-auth-local/src/auth-local.strategy.ts index ff1a8c28a..67275ed5f 100644 --- a/packages/nestjs-auth-local/src/auth-local.strategy.ts +++ b/packages/nestjs-auth-local/src/auth-local.strategy.ts @@ -12,8 +12,12 @@ import { import { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.interface'; import { AuthLocalValidateUserServiceInterface } from './interfaces/auth-local-validate-user-service.interface'; -import { InvalidCredentialsException } from './exceptions/invalid-credentials.exception'; -import { InvalidLoginDataException } from './exceptions/invalid-login-data.exception'; +import { AuthLocalException } from './exceptions/auth-local.exception'; +import { AuthLocalInvalidCredentialsException } from './exceptions/auth-local-invalid-credentials.exception'; +import { AuthLocalInvalidLoginDataException } from './exceptions/auth-local-invalid-login-data.exception'; +import { AuthLocalMissingLoginDtoException } from './exceptions/auth-local-missing-login-dto.exception'; +import { AuthLocalMissingUsernameFieldException } from './exceptions/auth-local-missing-username-field.exception'; +import { AuthLocalMissingPasswordFieldException } from './exceptions/auth-local-missing-password-field.exception'; /** * Define the Local strategy using passport. @@ -61,7 +65,7 @@ export class AuthLocalStrategy extends PassportStrategyFactory( try { await validateOrReject(dto); } catch (e) { - throw new InvalidLoginDataException({ + throw new AuthLocalInvalidLoginDataException({ originalError: e, }); } @@ -74,12 +78,23 @@ export class AuthLocalStrategy extends PassportStrategyFactory( username, password, }); - // did we get a valid user? - if (!validatedUser) { - throw new Error(`No valid user found: ${username}`); - } } catch (e) { - throw new InvalidCredentialsException({ originalError: e }); + // did they throw an invalid credentials exception? + if (e instanceof AuthLocalInvalidCredentialsException) { + // yes, use theirs + throw e; + } else { + // something else went wrong + throw new AuthLocalException({ originalError: e }); + } + } + + // did we get a valid user? + if (!validatedUser) { + throw new AuthLocalInvalidCredentialsException({ + message: `Unable to validate user with username: %s`, + messageParams: [username], + }); } return validatedUser; @@ -93,24 +108,17 @@ export class AuthLocalStrategy extends PassportStrategyFactory( // is the login dto missing? if (!loginDto) { - // TODO: Change Error to a Exception - throw new Error('Login DTO is required, did someone remove the default?'); + throw new AuthLocalMissingLoginDtoException(); } // is the username field missing? if (!usernameField) { - // TODO: Change Error to a Exception - throw new Error( - 'Login username field is required, did someone remove the default?', - ); + throw new AuthLocalMissingUsernameFieldException(); } // is the password field missing? if (!passwordField) { - // TODO: Change Error to a Exception - throw new Error( - 'Login password field is required, did someone remove the default?', - ); + throw new AuthLocalMissingPasswordFieldException(); } return { loginDto, usernameField, passwordField }; diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-credentials.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-credentials.exception.ts new file mode 100644 index 000000000..18113aea1 --- /dev/null +++ b/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-credentials.exception.ts @@ -0,0 +1,14 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-exception'; +import { AuthLocalUnauthorizedException } from './auth-local-unauthorized.exception'; + +export class AuthLocalInvalidCredentialsException extends AuthLocalUnauthorizedException { + constructor(options?: Omit) { + super({ + safeMessage: + 'The provided username or password is incorrect. Please try again.', + ...options, + }); + + this.errorCode = 'AUTH_LOCAL_INVALID_CREDENTIALS_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/invalid-login-data.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-login-data.exception.ts similarity index 71% rename from packages/nestjs-auth-local/src/exceptions/invalid-login-data.exception.ts rename to packages/nestjs-auth-local/src/exceptions/auth-local-invalid-login-data.exception.ts index f0fcb7008..801bcd648 100644 --- a/packages/nestjs-auth-local/src/exceptions/invalid-login-data.exception.ts +++ b/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-login-data.exception.ts @@ -4,10 +4,11 @@ import { RuntimeExceptionOptions, } from '@concepta/nestjs-exception'; -export class InvalidLoginDataException extends RuntimeException { +export class AuthLocalInvalidLoginDataException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { super({ - message: + message: 'Data validation error occurred before user validation.', + safeMessage: 'The provided username or password is incorrect. Please try again.', httpStatus: HttpStatus.BAD_REQUEST, ...options, diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-password.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-password.exception.ts new file mode 100644 index 000000000..aeb728c51 --- /dev/null +++ b/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-password.exception.ts @@ -0,0 +1,14 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-exception'; +import { AuthLocalInvalidCredentialsException } from './auth-local-invalid-credentials.exception'; + +export class AuthLocalInvalidPasswordException extends AuthLocalInvalidCredentialsException { + constructor(userName: string, options?: RuntimeExceptionOptions) { + super({ + message: `Invalid password for username: %s`, + messageParams: [userName], + ...options, + }); + + this.errorCode = 'AUTH_LOCAL_INVALID_PASSWORD_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-missing-login-dto.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-missing-login-dto.exception.ts new file mode 100644 index 000000000..62ccfd34d --- /dev/null +++ b/packages/nestjs-auth-local/src/exceptions/auth-local-missing-login-dto.exception.ts @@ -0,0 +1,15 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-exception'; +import { HttpStatus } from '@nestjs/common'; +import { AuthLocalException } from './auth-local.exception'; + +export class AuthLocalMissingLoginDtoException extends AuthLocalException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: 'Login DTO is required, did someone remove the default?', + httpStatus: HttpStatus.BAD_REQUEST, + ...options, + }); + + this.errorCode = 'AUTH_LOCAL_MISSING_LOGIN_DTO_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-missing-password-field.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-missing-password-field.exception.ts new file mode 100644 index 000000000..b8a2bd999 --- /dev/null +++ b/packages/nestjs-auth-local/src/exceptions/auth-local-missing-password-field.exception.ts @@ -0,0 +1,16 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-exception'; +import { HttpStatus } from '@nestjs/common'; +import { AuthLocalException } from './auth-local.exception'; + +export class AuthLocalMissingPasswordFieldException extends AuthLocalException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: + 'Login password field is required, did someone remove the default?', + httpStatus: HttpStatus.BAD_REQUEST, + ...options, + }); + + this.errorCode = 'AUTH_LOCAL_MISSING_PASSWORD_FIELD_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-missing-username-field.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-missing-username-field.exception.ts new file mode 100644 index 000000000..b4cb91db9 --- /dev/null +++ b/packages/nestjs-auth-local/src/exceptions/auth-local-missing-username-field.exception.ts @@ -0,0 +1,16 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-exception'; +import { HttpStatus } from '@nestjs/common'; +import { AuthLocalException } from './auth-local.exception'; + +export class AuthLocalMissingUsernameFieldException extends AuthLocalException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: + 'Login username field is required, did someone remove the default?', + httpStatus: HttpStatus.BAD_REQUEST, + ...options, + }); + + this.errorCode = 'AUTH_LOCAL_MISSING_USERNAME_FIELD_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-unauthorized.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-unauthorized.exception.ts new file mode 100644 index 000000000..59cad0c0c --- /dev/null +++ b/packages/nestjs-auth-local/src/exceptions/auth-local-unauthorized.exception.ts @@ -0,0 +1,16 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-exception'; +import { HttpStatus } from '@nestjs/common'; +import { AuthLocalException } from './auth-local.exception'; + +export class AuthLocalUnauthorizedException extends AuthLocalException { + constructor(options?: Omit) { + super({ + message: 'Unauthorized', + safeMessage: 'Unauthorized', + ...options, + httpStatus: HttpStatus.UNAUTHORIZED, + }); + + this.errorCode = 'AUTH_LOCAL_UNAUTHORIZED_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-user-inactive.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-user-inactive.exception.ts new file mode 100644 index 000000000..5a4918650 --- /dev/null +++ b/packages/nestjs-auth-local/src/exceptions/auth-local-user-inactive.exception.ts @@ -0,0 +1,14 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-exception'; +import { AuthLocalInvalidCredentialsException } from './auth-local-invalid-credentials.exception'; + +export class AuthLocalUserInactiveException extends AuthLocalInvalidCredentialsException { + constructor(userName: string, options?: RuntimeExceptionOptions) { + super({ + message: `User with username '%s' is inactive`, + messageParams: [userName], + ...options, + }); + + this.errorCode = 'AUTH_LOCAL_USER_INACTIVE_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-username-not-found.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-username-not-found.exception.ts new file mode 100644 index 000000000..65691ba10 --- /dev/null +++ b/packages/nestjs-auth-local/src/exceptions/auth-local-username-not-found.exception.ts @@ -0,0 +1,14 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-exception'; +import { AuthLocalInvalidCredentialsException } from './auth-local-invalid-credentials.exception'; + +export class AuthLocalUsernameNotFoundException extends AuthLocalInvalidCredentialsException { + constructor(userName: string, options?: RuntimeExceptionOptions) { + super({ + message: `No user found for username: %s`, + messageParams: [userName], + ...options, + }); + + this.errorCode = 'AUTH_LOCAL_USERNAME_NOT_FOUND_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local.exception.ts new file mode 100644 index 000000000..d3c043daa --- /dev/null +++ b/packages/nestjs-auth-local/src/exceptions/auth-local.exception.ts @@ -0,0 +1,14 @@ +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-exception'; + +/** + * Generic auth local exception. + */ +export class AuthLocalException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super(options); + this.errorCode = 'AUTH_LOCAL_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/invalid-credentials.exception.ts b/packages/nestjs-auth-local/src/exceptions/invalid-credentials.exception.ts deleted file mode 100644 index 31db71c09..000000000 --- a/packages/nestjs-auth-local/src/exceptions/invalid-credentials.exception.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HttpStatus } from '@nestjs/common'; -import { - RuntimeException, - RuntimeExceptionOptions, -} from '@concepta/nestjs-exception'; - -export class InvalidCredentialsException extends RuntimeException { - constructor(options?: RuntimeExceptionOptions) { - super({ - message: 'The provided credentials are incorrect. Please try again.', - httpStatus: HttpStatus.UNAUTHORIZED, - ...options, - }); - - this.errorCode = 'AUTH_LOCAL_INVALID_CREDENTIALS_ERROR'; - } -} diff --git a/packages/nestjs-auth-local/src/index.ts b/packages/nestjs-auth-local/src/index.ts index b2cd55474..c938215d8 100644 --- a/packages/nestjs-auth-local/src/index.ts +++ b/packages/nestjs-auth-local/src/index.ts @@ -16,3 +16,10 @@ export { AuthLocalGuard, AuthLocalGuard as LocalAuthGuard, } from './auth-local.guard'; + +export { AuthLocalException } from './exceptions/auth-local.exception'; +export { AuthLocalInvalidLoginDataException } from './exceptions/auth-local-invalid-login-data.exception'; +export { AuthLocalInvalidCredentialsException } from './exceptions/auth-local-invalid-credentials.exception'; +export { AuthLocalInvalidPasswordException } from './exceptions/auth-local-invalid-password.exception'; +export { AuthLocalUserInactiveException } from './exceptions/auth-local-user-inactive.exception'; +export { AuthLocalUsernameNotFoundException } from './exceptions/auth-local-username-not-found.exception'; diff --git a/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.ts b/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.ts index f102fab32..ec79a8b51 100644 --- a/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.ts +++ b/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.ts @@ -9,6 +9,9 @@ import { import { AuthLocalValidateUserInterface } from '../interfaces/auth-local-validate-user.interface'; import { AuthLocalValidateUserServiceInterface } from '../interfaces/auth-local-validate-user-service.interface'; import { AuthLocalUserLookupServiceInterface } from '../interfaces/auth-local-user-lookup-service.interface'; +import { AuthLocalUsernameNotFoundException } from '../exceptions/auth-local-username-not-found.exception'; +import { AuthLocalUserInactiveException } from '../exceptions/auth-local-user-inactive.exception'; +import { AuthLocalInvalidPasswordException } from '../exceptions/auth-local-invalid-password.exception'; @Injectable() export class AuthLocalValidateUserService @@ -35,14 +38,14 @@ export class AuthLocalValidateUserService // did we get a user? if (!user) { - throw new Error(`No user found for username: ${dto.username}`); + throw new AuthLocalUsernameNotFoundException(dto.username); } const isUserActive = await this.isActive(user); // is the user active? if (!isUserActive) { - throw new Error(`User with username '${dto.username}' is inactive`); + throw new AuthLocalUserInactiveException(dto.username); } // validate password @@ -53,7 +56,7 @@ export class AuthLocalValidateUserService // password is valid? if (!isValid) { - throw new Error(`Invalid password for username: ${user.username}`); + throw new AuthLocalInvalidPasswordException(user.username); } // return the user diff --git a/packages/nestjs-exception/src/exceptions/runtime.exception.ts b/packages/nestjs-exception/src/exceptions/runtime.exception.ts index 8292deed8..ef9335c38 100644 --- a/packages/nestjs-exception/src/exceptions/runtime.exception.ts +++ b/packages/nestjs-exception/src/exceptions/runtime.exception.ts @@ -10,8 +10,8 @@ export class RuntimeException implements RuntimeExceptionInterface { private _errorCode = 'RUNTIME_EXCEPTION'; - private _httpStatus: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR; - private _safeMessage?: string; + readonly httpStatus: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + readonly safeMessage?: string; public context: RuntimeExceptionContext = {}; constructor( @@ -52,14 +52,20 @@ export class RuntimeException httpStatus, } = finalOptions; - super(format(message, ...messageParams)); + const formattedMessage = format(message ?? '', ...messageParams); + const formattedSafeMessage = format( + safeMessage ?? '', + ...safeMessageParams, + ); + + super(formattedMessage.length ? formattedMessage : formattedSafeMessage); if (httpStatus) { - this._httpStatus = httpStatus; + this.httpStatus = httpStatus; } - if (safeMessage) { - this._safeMessage = format(safeMessage, ...safeMessageParams); + if (formattedSafeMessage.length) { + this.safeMessage = formattedSafeMessage; } this.context.originalError = mapNonErrorToException(originalError); @@ -72,12 +78,4 @@ export class RuntimeException protected set errorCode(v: string) { this._errorCode = v; } - - public get httpStatus() { - return this._httpStatus; - } - - public get safeMessage() { - return this._safeMessage; - } }