diff --git a/src/common/exceptions/authentication.exception.ts b/src/common/exceptions/authentication.exception.ts new file mode 100644 index 000000000..3962f0245 --- /dev/null +++ b/src/common/exceptions/authentication.exception.ts @@ -0,0 +1,50 @@ +import { + GenericServerException, + WorkOSErrorData, +} from './generic-server.exception'; + +export type AuthenticationErrorCode = + | 'email_verification_required' + | 'organization_selection_required' + | 'mfa_enrollment' + | 'mfa_challenge' + | 'mfa_verification' + | 'sso_required'; + +export interface AuthenticationErrorData extends WorkOSErrorData { + code: AuthenticationErrorCode; + pending_authentication_token?: string; + user?: Record; + organizations?: Array<{ id: string; name: string }>; +} + +const AUTHENTICATION_ERROR_CODES: ReadonlySet = new Set([ + 'email_verification_required', + 'organization_selection_required', + 'mfa_enrollment', + 'mfa_challenge', + 'mfa_verification', + 'sso_required', +]); + +export function isAuthenticationErrorData( + data: WorkOSErrorData, +): data is AuthenticationErrorData { + return ( + typeof data.code === 'string' && AUTHENTICATION_ERROR_CODES.has(data.code) + ); +} + +export class AuthenticationException extends GenericServerException { + readonly name = 'AuthenticationException'; + readonly pendingAuthenticationToken: string | undefined; + + constructor( + status: number, + readonly rawData: AuthenticationErrorData, + requestID: string, + ) { + super(status, rawData.message, rawData, requestID); + this.pendingAuthenticationToken = rawData.pending_authentication_token; + } +} diff --git a/src/common/exceptions/generic-server.exception.ts b/src/common/exceptions/generic-server.exception.ts index a9241eed3..40849b14f 100644 --- a/src/common/exceptions/generic-server.exception.ts +++ b/src/common/exceptions/generic-server.exception.ts @@ -1,18 +1,26 @@ import { RequestException } from '../interfaces/request-exception.interface'; +export interface WorkOSErrorData { + code?: string; + message?: string; + [key: string]: unknown; +} + export class GenericServerException extends Error implements RequestException { readonly name: string = 'GenericServerException'; readonly message: string = 'The request could not be completed.'; + readonly code: string | undefined; constructor( readonly status: number, message: string | undefined, - readonly rawData: unknown, + readonly rawData: WorkOSErrorData, readonly requestID: string, ) { super(); if (message) { this.message = message; } + this.code = rawData.code; } } diff --git a/src/common/exceptions/index.ts b/src/common/exceptions/index.ts index a3bdc7575..4838bed50 100644 --- a/src/common/exceptions/index.ts +++ b/src/common/exceptions/index.ts @@ -1,4 +1,5 @@ export * from './api-key-required.exception'; +export * from './authentication.exception'; export * from './generic-server.exception'; export * from './bad-request.exception'; export * from './no-api-key-provided.exception'; diff --git a/src/common/interfaces/event.interface.ts b/src/common/interfaces/event.interface.ts index d1108b0c9..9def867be 100644 --- a/src/common/interfaces/event.interface.ts +++ b/src/common/interfaces/event.interface.ts @@ -856,6 +856,11 @@ export interface VaultByokKeyVerificationCompletedEventResponse extends EventRes data: VaultByokKeyVerificationCompletedEventResponseData; } +export interface UnknownEvent extends EventBase { + event: string; + data: Record; +} + export type Event = | AuthenticationEmailVerificationSucceededEvent | AuthenticationMfaSucceededEvent diff --git a/src/common/interfaces/workos-response-error.interface.ts b/src/common/interfaces/workos-response-error.interface.ts index c8d5b7a33..14b74ac81 100644 --- a/src/common/interfaces/workos-response-error.interface.ts +++ b/src/common/interfaces/workos-response-error.interface.ts @@ -6,4 +6,5 @@ export interface WorkOSResponseError { error?: string; errors?: UnprocessableEntityError[]; message: string; + [key: string]: unknown; } diff --git a/src/common/serializers/event.serializer.ts b/src/common/serializers/event.serializer.ts index 6a74d8e60..662dfb8d2 100644 --- a/src/common/serializers/event.serializer.ts +++ b/src/common/serializers/event.serializer.ts @@ -302,5 +302,11 @@ export const deserializeEvent = (event: EventResponse): Event => { event: event.event, data: deserializeVaultByokKeyVerificationCompletedEvent(event.data), }; + default: + return { + ...eventBase, + event: (event as { event: string }).event, + data: (event as { data: Record }).data, + } as Event; } }; diff --git a/src/workos.ts b/src/workos.ts index 1014c4a5c..17f066009 100644 --- a/src/workos.ts +++ b/src/workos.ts @@ -1,6 +1,8 @@ import { ApiKeyRequiredException, + AuthenticationException, GenericServerException, + isAuthenticationErrorData, NotFoundException, UnauthorizedException, UnprocessableEntityException, @@ -494,6 +496,8 @@ export class WorkOS { message, requestID, }); + } else if (isAuthenticationErrorData(data)) { + throw new AuthenticationException(status, data, requestID); } else { throw new GenericServerException( status,