From 54bc11110b0c7beabefdd7cbae72ef47bce87789 Mon Sep 17 00:00:00 2001 From: Dopeamin Date: Thu, 12 Sep 2024 10:21:35 +0200 Subject: [PATCH 1/4] changes JWTError into ValidationError --- src/errors/index.ts | 3 +- .../{jwtError.ts => validationError.ts} | 8 ++-- src/services/identifierService.ts | 2 +- src/services/sessionService.ts | 14 +++--- tests/unit/session.test.ts | 47 ++++++++++++------- 5 files changed, 45 insertions(+), 29 deletions(-) rename src/errors/{jwtError.ts => validationError.ts} (67%) diff --git a/src/errors/index.ts b/src/errors/index.ts index 6429b76..9ead4f4 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -1,5 +1,6 @@ import BaseError from './baseError.js'; import ServerError, { ErrorDetails, RequestData, ServerErrorType } from './serverError.js'; +import JWTError from './validationError.js'; import httpStatusCodes from './httpStatusCodes.js'; -export { BaseError, ServerError, httpStatusCodes, ErrorDetails, RequestData, ServerErrorType }; +export { BaseError, ServerError, httpStatusCodes, ErrorDetails, RequestData, ServerErrorType, JWTError }; diff --git a/src/errors/jwtError.ts b/src/errors/validationError.ts similarity index 67% rename from src/errors/jwtError.ts rename to src/errors/validationError.ts index 0a073bb..753c19a 100644 --- a/src/errors/jwtError.ts +++ b/src/errors/validationError.ts @@ -1,7 +1,7 @@ import BaseError from './baseError'; import httpStatusCodes from './httpStatusCodes'; -export enum JWTErrorNames { +export enum ValidationErrorNames { JWTClaimValidationFailed = 'CLAIM_VALIDATION_FAILED', InvalidIssuer = 'INVALID_ISSUER', InvalidShortSession = 'INVALID_SHORT_SESSION', @@ -9,10 +9,10 @@ export enum JWTErrorNames { JWTInvalid = 'JWT_INVALID', } -class JWTError extends BaseError { - constructor(name: JWTErrorNames, isOperational: boolean = false) { +class ValidationError extends BaseError { + constructor(name: ValidationErrorNames, isOperational: boolean = false) { super(name, httpStatusCodes[name].code, httpStatusCodes[name].description, isOperational); } } -export default JWTError; +export default ValidationError; diff --git a/src/services/identifierService.ts b/src/services/identifierService.ts index d22617c..24ac77c 100644 --- a/src/services/identifierService.ts +++ b/src/services/identifierService.ts @@ -9,7 +9,7 @@ import { IdentifiersApi, IdentifierList, } from '../generated/index.js'; -import { Assert, Helper, isErrorRsp } from '../helpers'; +import { Assert, Helper, isErrorRsp } from '../helpers/index.js'; import httpStatusCodes from '../errors/httpStatusCodes.js'; import BaseError from '../errors/baseError.js'; diff --git a/src/services/sessionService.ts b/src/services/sessionService.ts index 563151e..a97202b 100644 --- a/src/services/sessionService.ts +++ b/src/services/sessionService.ts @@ -2,7 +2,7 @@ /* eslint-disable class-methods-use-this */ import { JWTPayload, jwtVerify, createRemoteJWKSet, errors } from 'jose'; import { Assert } from '../helpers/index.js'; -import JWTError, { JWTErrorNames } from '../errors/jwtError.js'; +import ValidationError, { ValidationErrorNames } from '../errors/validationError.js'; import { User, UserStatus } from '../generated/api.js'; export interface SessionInterface { @@ -43,7 +43,7 @@ class Session implements SessionInterface { Assert.notEmptyString(shortSession, 'shortSession not given'); if (shortSession.length < MIN_SHORT_SESSION_LENGTH) { - throw new JWTError(JWTErrorNames.InvalidShortSession); + throw new ValidationError(ValidationErrorNames.InvalidShortSession); } try { @@ -51,22 +51,22 @@ class Session implements SessionInterface { const { userID, fullName, status, explicitWebauthnID } = payload as MyJWTPayload; - if (payload.iss && payload.iss !== this.issuer) { - throw new JWTError(JWTErrorNames.InvalidIssuer); + if (!payload.iss || payload.iss !== this.issuer) { + throw new ValidationError(ValidationErrorNames.InvalidIssuer); } return { userID, fullName, status, explicitWebauthnID }; } catch (error) { if (error instanceof errors.JWTClaimValidationFailed) { - throw new JWTError(JWTErrorNames.JWTClaimValidationFailed); + throw new ValidationError(ValidationErrorNames.JWTClaimValidationFailed); } if (error instanceof errors.JWTExpired) { - throw new JWTError(JWTErrorNames.JWTExpired); + throw new ValidationError(ValidationErrorNames.JWTExpired); } if (error instanceof errors.JWTInvalid) { - throw new JWTError(JWTErrorNames.JWTInvalid); + throw new ValidationError(ValidationErrorNames.JWTInvalid); } throw error; diff --git a/tests/unit/session.test.ts b/tests/unit/session.test.ts index 508d35b..084c69c 100644 --- a/tests/unit/session.test.ts +++ b/tests/unit/session.test.ts @@ -1,6 +1,6 @@ import { createRemoteJWKSet, jwtVerify, errors } from 'jose'; import { Session } from '../../src/services'; -import JWTError, { JWTErrorNames } from '../../src/errors/jwtError'; +import ValidationError, { ValidationErrorNames } from '../../src/errors/validationError'; import { UserStatus } from '../../src/generated'; import { httpStatusCodes } from '../../src/errors'; @@ -43,15 +43,15 @@ describe('Session Service Unit Tests', () => { ); }); - test('should throw JWTError if short session is too short', async () => { + test('should throw ValidationError if short session is too short', async () => { const shortSession = 'short'; - await expect(sessionService.getAndValidateCurrentUser(shortSession)).rejects.toThrow(JWTError); + await expect(sessionService.getAndValidateCurrentUser(shortSession)).rejects.toThrow(ValidationError); await expect(sessionService.getAndValidateCurrentUser(shortSession)).rejects.toThrow( - httpStatusCodes[JWTErrorNames.InvalidShortSession].description, + httpStatusCodes[ValidationErrorNames.InvalidShortSession].description, ); }); - test('should throw JWTError if issuer is mismatched', async () => { + test('should throw ValidationError if issuer is mismatched', async () => { (jwtVerify as jest.Mock).mockResolvedValue({ payload: { iss: 'https://invalid-issuer.com', @@ -61,36 +61,51 @@ describe('Session Service Unit Tests', () => { }, }); - await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow(JWTError); + await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow(ValidationError); await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow( - httpStatusCodes[JWTErrorNames.InvalidIssuer].description, + httpStatusCodes[ValidationErrorNames.InvalidIssuer].description, ); }); - test('should throw JWTError on JWTClaimValidationFailed', async () => { + test('should throw ValidationError if issuer is undefined', async () => { + (jwtVerify as jest.Mock).mockResolvedValue({ + payload: { + userID: TEST_USER_ID, + fullName: TEST_FULL_NAME, + status: TEST_STATUS, + }, + }); + + await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow(ValidationError); + await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow( + httpStatusCodes[ValidationErrorNames.InvalidIssuer].description, + ); + }); + + test('should throw ValidationError on JWTClaimValidationFailed', async () => { (jwtVerify as jest.Mock).mockRejectedValue(new errors.JWTClaimValidationFailed('message')); - await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow(JWTError); + await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow(ValidationError); await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow( - httpStatusCodes[JWTErrorNames.JWTClaimValidationFailed].description, + httpStatusCodes[ValidationErrorNames.JWTClaimValidationFailed].description, ); }); - test('should throw JWTError on JWTExpired', async () => { + test('should throw ValidationError on JWTExpired', async () => { (jwtVerify as jest.Mock).mockRejectedValue(new errors.JWTExpired('message')); - await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow(JWTError); + await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow(ValidationError); await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow( - httpStatusCodes[JWTErrorNames.JWTExpired].description, + httpStatusCodes[ValidationErrorNames.JWTExpired].description, ); }); - test('should throw JWTError on JWTInvalid', async () => { + test('should throw ValidationError on JWTInvalid', async () => { (jwtVerify as jest.Mock).mockRejectedValue(new errors.JWTInvalid()); - await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow(JWTError); + await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow(ValidationError); await expect(sessionService.getAndValidateCurrentUser(SHORT_SESSION)).rejects.toThrow( - httpStatusCodes[JWTErrorNames.JWTInvalid].description, + httpStatusCodes[ValidationErrorNames.JWTInvalid].description, ); }); From 2e8f504f035dc58279af20f744044f42e190f3fa Mon Sep 17 00:00:00 2001 From: Dopeamin Date: Thu, 12 Sep 2024 10:22:18 +0200 Subject: [PATCH 2/4] bump up package version to alpha.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81ba23e..f692834 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@corbado/node-sdk", - "version": "2.1.3", + "version": "3.0.0-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@corbado/node-sdk", - "version": "2.1.3", + "version": "3.0.0-alpha.1", "license": "MIT", "dependencies": { "@openapitools/openapi-generator-cli": "^2.13.5", diff --git a/package.json b/package.json index 3f9a246..e4c6ff2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@corbado/node-sdk", - "version": "3.0.0-alpha.0", + "version": "3.0.0-alpha.1", "description": "This Node.js SDK eases the integration of Corbado's passkey-first authentication solution.", "keywords": [ "passkeys", From b2642eee9bd2305386ca5f7063e8f4d6bda54e22 Mon Sep 17 00:00:00 2001 From: Dopeamin Date: Thu, 12 Sep 2024 13:25:11 +0200 Subject: [PATCH 3/4] fixes url assertion --- src/helpers/assert.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/assert.ts b/src/helpers/assert.ts index cb8cb65..28c7e82 100644 --- a/src/helpers/assert.ts +++ b/src/helpers/assert.ts @@ -73,7 +73,7 @@ class Assert { ); validate( - parsedUrl.pathname !== '/', + parsedUrl.pathname !== '/' && parsedUrl.pathname !== '/v2', `${errorName} URL path assertion failed`, INVALID_URL.code, 'path needs to be empty', From fb4ccd6c87b0801e9e7baf678e186d3e3b97da18 Mon Sep 17 00:00:00 2001 From: Dopeamin Date: Thu, 12 Sep 2024 13:26:13 +0200 Subject: [PATCH 4/4] fixes export --- src/errors/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/errors/index.ts b/src/errors/index.ts index 9ead4f4..db8705a 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -1,6 +1,6 @@ import BaseError from './baseError.js'; import ServerError, { ErrorDetails, RequestData, ServerErrorType } from './serverError.js'; -import JWTError from './validationError.js'; +import ValidationError from './validationError.js'; import httpStatusCodes from './httpStatusCodes.js'; -export { BaseError, ServerError, httpStatusCodes, ErrorDetails, RequestData, ServerErrorType, JWTError }; +export { BaseError, ServerError, httpStatusCodes, ErrorDetails, RequestData, ServerErrorType, ValidationError };