Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Feature/base exceptions #316

Merged
merged 10 commits into from
Dec 12, 2024
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthAppleException } from './auth-apple-exception';

export class AuthAppleDecodeException extends RuntimeException {
export class AuthAppleDecodeException extends AuthAppleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Apple token was not able to be decoded.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthAppleException } from './auth-apple-exception';

export class AuthAppleEmailNotVerifiedException extends RuntimeException {
export class AuthAppleEmailNotVerifiedException extends AuthAppleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Apple email not is verified.',
Expand Down
13 changes: 13 additions & 0 deletions packages/nestjs-auth-apple/src/exceptions/auth-apple-exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
/**
* Generic auth local exception.
*/
export class AuthAppleException extends RuntimeException {
constructor(options?: RuntimeExceptionOptions) {
super(options);
this.errorCode = 'AUTH_APPLE_ERROR';
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthAppleException } from './auth-apple-exception';

export class AuthAppleInvalidAudienceException extends RuntimeException {
export class AuthAppleInvalidAudienceException extends AuthAppleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Apple audience is not valid.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthAppleException } from './auth-apple-exception';

export class AuthAppleInvalidIssuerException extends RuntimeException {
export class AuthAppleInvalidIssuerException extends AuthAppleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Apple token issuer is not valid.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthAppleException } from './auth-apple-exception';

export class AuthAppleMissingEmailException extends RuntimeException {
export class AuthAppleMissingEmailException extends AuthAppleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Apple did not return an email address for the user.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthAppleException } from './auth-apple-exception';

export class AuthAppleMissingIdException extends RuntimeException {
export class AuthAppleMissingIdException extends AuthAppleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Apple did not return an id for the user.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthAppleException } from './auth-apple-exception';

export class AuthApplePublicKeyException extends RuntimeException {
export class AuthApplePublicKeyException extends AuthAppleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Apple public key was not able to be retrieved.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthAppleException } from './auth-apple-exception';

export class AuthAppleTokenExpiredException extends RuntimeException {
export class AuthAppleTokenExpiredException extends AuthAppleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Apple oauth token has expired.',
Expand Down
11 changes: 11 additions & 0 deletions packages/nestjs-auth-apple/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ export { AuthAppleProfileInterface } from './interfaces/auth-apple-profile.inter
export { AuthAppleCredentialsInterface } from './interfaces/auth-apple-credentials.interface';
export { AuthAppleServiceInterface } from './interfaces/auth-apple-service.interface';

// exceptions
export { AuthAppleException } from './exceptions/auth-apple-exception';
export { AuthApplePublicKeyException } from './exceptions/auth-apple-public-key.exception';
export { AuthAppleMissingEmailException } from './exceptions/auth-apple-missing-email.exception';
export { AuthAppleDecodeException } from './exceptions/auth-apple-decode.exception';
export { AuthAppleInvalidAudienceException } from './exceptions/auth-apple-invalid-audience.exception';
export { AuthAppleEmailNotVerifiedException } from './exceptions/auth-apple-email-not-verified.exception';
export { AuthAppleTokenExpiredException } from './exceptions/auth-apple-token-expired.exception';
export { AuthAppleMissingIdException } from './exceptions/auth-apple-missing-id.exception';
export { AuthAppleInvalidIssuerException } from './exceptions/auth-apple-invalid-issuer.exception';

export {
AuthAppleGuard,
AuthAppleGuard as AppleAuthGuard,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthGithubException } from './auth-github.exception';

export class AuthGithubMissingEmailException extends RuntimeException {
export class AuthGithubMissingEmailException extends AuthGithubException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'GitHub did not return an email address for the user.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthGithubException } from './auth-github.exception';

export class AuthGithubMissingIdException extends RuntimeException {
export class AuthGithubMissingIdException extends AuthGithubException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'GitHub did not return an id for the user.',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
/**
* Generic auth github exception.
*/
export class AuthGithubException extends RuntimeException {
constructor(options?: RuntimeExceptionOptions) {
super(options);
this.errorCode = 'AUTH_GITHUB_ERROR';
}
}
5 changes: 5 additions & 0 deletions packages/nestjs-auth-github/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ export {
AuthGithubGuard,
AuthGithubGuard as GithubAuthGuard,
} from './auth-github.guard';

// exceptions
export { AuthGithubException } from './exceptions/auth-github.exception';
export { AuthGithubMissingEmailException } from './exceptions/auth-github-missing-email.exception';
export { AuthGithubMissingIdException } from './exceptions/auth-github-missing-id.exception';
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthGoogleException } from './auth-google.exception';

export class AuthGoogleMissingEmailException extends RuntimeException {
export class AuthGoogleMissingEmailException extends AuthGoogleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Google did not return an email address for the user.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthGoogleException } from './auth-google.exception';

export class AuthGoogleMissingIdException extends RuntimeException {
export class AuthGoogleMissingIdException extends AuthGoogleException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Google did not return an id for the user.',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
/**
* Generic auth google exception.
*/
export class AuthGoogleException extends RuntimeException {
constructor(options?: RuntimeExceptionOptions) {
super(options);
this.errorCode = 'AUTH_GOOGLE_ERROR';
}
}
4 changes: 4 additions & 0 deletions packages/nestjs-auth-google/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ export {
AuthGoogleGuard,
AuthGoogleGuard as GoogleAuthGuard,
} from './auth-google.guard';

export { AuthGoogleException } from './exceptions/auth-google.exception';
export { AuthGoogleMissingEmailException } from './exceptions/auth-google-missing-email.exception';
export { AuthGoogleMissingIdException } from './exceptions/auth-google-missing-id.exception';
1 change: 1 addition & 0 deletions packages/nestjs-auth-jwt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@concepta/nestjs-authentication": "^5.1.0",
"@concepta/nestjs-common": "^5.1.0",
"@concepta/nestjs-exception": "^5.1.0",
"@concepta/nestjs-jwt": "^5.1.0",
"@concepta/ts-common": "^5.1.0",
"@concepta/ts-core": "^5.1.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/nestjs-auth-jwt/src/auth-jwt.strategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { randomUUID } from 'crypto';
import { AuthorizationPayloadInterface } from '@concepta/ts-common';
import { AuthJwtSettingsInterface } from './interfaces/auth-jwt-settings.interface';
import { VerifyTokenServiceInterface } from '@concepta/nestjs-authentication';
import { UnauthorizedException } from '@nestjs/common';
import { AuthJwtUnauthorizedException } from './exceptions/auth-jwt-unauthorized.exception';

describe(AuthJwtStrategy, () => {
let user: UserFixture;
Expand Down Expand Up @@ -50,7 +50,7 @@ describe(AuthJwtStrategy, () => {
const t = async () => {
await authJwtStrategy.validate(authorizationPayload);
};
await expect(t).rejects.toThrow(UnauthorizedException);
await expect(t).rejects.toThrow(AuthJwtUnauthorizedException);
});
});
});
5 changes: 3 additions & 2 deletions packages/nestjs-auth-jwt/src/auth-jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { ReferenceIdInterface } from '@concepta/ts-core';
import { AuthorizationPayloadInterface } from '@concepta/ts-common';
import {
Expand All @@ -21,6 +21,7 @@ import {

import { AuthJwtSettingsInterface } from './interfaces/auth-jwt-settings.interface';
import { AuthJwtUserLookupServiceInterface } from './interfaces/auth-jwt-user-lookup-service.interface';
import { AuthJwtUnauthorizedException } from './exceptions/auth-jwt-unauthorized.exception';

@Injectable()
export class AuthJwtStrategy extends PassportStrategyFactory<JwtStrategy>(
Expand Down Expand Up @@ -60,7 +61,7 @@ export class AuthJwtStrategy extends PassportStrategyFactory<JwtStrategy>(
if (user) {
return user;
} else {
throw new UnauthorizedException();
throw new AuthJwtUnauthorizedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { RuntimeExceptionOptions } from '@concepta/nestjs-exception';
import { AuthJwtException } from './auth-jwt.exception';

export class AuthJwtUnauthorizedException extends AuthJwtException {
constructor(options?: RuntimeExceptionOptions) {
super({
safeMessage: 'Unable to authenticate user with provided JWT token.',
...options,
});

this.errorCode = 'AUTH_JWT_MISSING_PROFILE_ID_ERROR';
}
}
13 changes: 13 additions & 0 deletions packages/nestjs-auth-jwt/src/exceptions/auth-jwt.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
RuntimeException,
RuntimeExceptionOptions,
} from '@concepta/nestjs-exception';
/**
* Generic auth jwt exception.
*/
export class AuthJwtException extends RuntimeException {
constructor(options?: RuntimeExceptionOptions) {
super(options);
this.errorCode = 'AUTH_JWT_ERROR';
}
}
29 changes: 29 additions & 0 deletions packages/nestjs-auth-local/src/auth-local.controller.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { PasswordValidationService } from '@concepta/nestjs-password';
import { LOGIN_SUCCESS } from './__fixtures__/user/constants';
import { HttpAdapterHost } from '@nestjs/core';
import { ExceptionsFilter } from '@concepta/nestjs-exception';
import { AUTH_LOCAL_MODULE_VALIDATE_USER_SERVICE_TOKEN } from './auth-local.constants';
import { AuthLocalInvalidCredentialsException } from './exceptions/auth-local-invalid-credentials.exception';

describe('AuthLocalController (e2e)', () => {
let app: INestApplication;
Expand Down Expand Up @@ -56,6 +58,33 @@ describe('AuthLocalController (e2e)', () => {
});
});

it('POST auth/login username not found with custom message', async () => {
const validateUserService = app.get(
AUTH_LOCAL_MODULE_VALIDATE_USER_SERVICE_TOKEN,
);

jest
.spyOn(validateUserService, 'validateUser')
.mockImplementationOnce(() => {
throw new AuthLocalInvalidCredentialsException({
safeMessage: 'Custom invalid credentials message',
});
});

await supertest(app.getHttpServer())
.post('/auth/login')
.send({
...LOGIN_SUCCESS,
username: 'no_user',
})
.then((response) => {
expect(response.body.message).toBe(
'Custom invalid credentials message',
);
expect(response.status).toBe(401);
});
});

it('POST auth/login password fail ', async () => {
await supertest(app.getHttpServer())
.post('/auth/login')
Expand Down
1 change: 1 addition & 0 deletions packages/nestjs-auth-recovery/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
],
"dependencies": {
"@concepta/nestjs-common": "^5.1.0",
"@concepta/nestjs-exception": "^5.1.0",
"@concepta/ts-common": "^5.1.0",
"@concepta/ts-core": "^5.1.0",
"@concepta/typeorm-common": "^5.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AuthRecoveryService } from './services/auth-recovery.service';
import { AuthRecoveryRecoverLoginDto } from './dto/auth-recovery-recover-login.dto';
import { AuthRecoveryUpdatePasswordDto } from './dto/auth-recovery-update-password.dto';
import { mock } from 'jest-mock-extended';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { AuthRecoveryOtpInvalidException } from './exceptions/auth-recovery-otp-invalid.exception';

describe(AuthRecoveryController.name, () => {
let controller: AuthRecoveryController;
Expand Down Expand Up @@ -50,7 +50,7 @@ describe(AuthRecoveryController.name, () => {
.mockResolvedValue(null);

const t = () => controller.validatePasscode(passwordDto.passcode);
await expect(t).rejects.toThrow(NotFoundException);
await expect(t).rejects.toThrow(AuthRecoveryOtpInvalidException);

expect(validatePasscodeSpy).toHaveBeenCalledWith(passwordDto.passcode);
});
Expand All @@ -77,7 +77,7 @@ describe(AuthRecoveryController.name, () => {
.mockResolvedValue(null);

const t = () => controller.updatePassword(passwordDto);
await expect(t).rejects.toThrow(BadRequestException);
await expect(t).rejects.toThrow(AuthRecoveryOtpInvalidException);

expect(updatePasswordSpy).toHaveBeenCalledWith(
passwordDto.passcode,
Expand Down
Loading
Loading