diff --git a/src/common/exceptions/authentication.exception.ts b/src/common/exceptions/authentication.exception.ts index 8d89f3412..93651bed4 100644 --- a/src/common/exceptions/authentication.exception.ts +++ b/src/common/exceptions/authentication.exception.ts @@ -12,14 +12,22 @@ export type AuthenticationErrorCode = | 'mfa_verification' | 'sso_required'; -export interface AuthenticationErrorData extends WorkOSErrorData { - code: AuthenticationErrorCode; +interface BaseAuthenticationErrorData extends WorkOSErrorData { pending_authentication_token?: string; user?: UserResponse; organizations?: Array<{ id: string; name: string }>; connection_ids?: string[]; } +export type AuthenticationErrorData = + | (BaseAuthenticationErrorData & { + code: Exclude; + }) + | (BaseAuthenticationErrorData & { + error: 'sso_required'; + error_description: string; + }); + const AUTHENTICATION_ERROR_CODES: ReadonlySet = new Set([ 'email_verification_required', 'organization_selection_required', @@ -29,16 +37,44 @@ const AUTHENTICATION_ERROR_CODES: ReadonlySet = new Set([ 'sso_required', ]); -export function isAuthenticationErrorData( +function parseAuthenticationErrorCode( + value: unknown, +): AuthenticationErrorCode | undefined { + if (typeof value !== 'string') { + return; + } + + if (!AUTHENTICATION_ERROR_CODES.has(value)) { + return; + } + + return value as AuthenticationErrorCode; +} + +function getAuthenticationErrorCode( + data: AuthenticationErrorData, +): AuthenticationErrorCode; +function getAuthenticationErrorCode( data: WorkOSErrorData, -): data is AuthenticationErrorData { +): AuthenticationErrorCode | undefined; +function getAuthenticationErrorCode( + data: WorkOSErrorData, +): AuthenticationErrorCode | undefined { return ( - typeof data.code === 'string' && AUTHENTICATION_ERROR_CODES.has(data.code) + parseAuthenticationErrorCode(data.code) ?? + parseAuthenticationErrorCode(data.error) ); } +export function isAuthenticationErrorData( + data: WorkOSErrorData, +): data is AuthenticationErrorData { + return getAuthenticationErrorCode(data) !== undefined; +} + export class AuthenticationException extends GenericServerException { readonly name = 'AuthenticationException'; + override readonly code: AuthenticationErrorCode; readonly pendingAuthenticationToken: string | undefined; constructor( @@ -46,7 +82,15 @@ export class AuthenticationException extends GenericServerException { readonly rawData: AuthenticationErrorData, requestID: string, ) { - super(status, rawData.message, rawData, requestID); + const code = getAuthenticationErrorCode(rawData); + + super( + status, + rawData.message ?? (rawData.error_description as string | undefined), + rawData, + requestID, + ); + this.code = code; this.pendingAuthenticationToken = rawData.pending_authentication_token; } } diff --git a/src/workos.spec.ts b/src/workos.spec.ts index 08b6a3516..83d07a97c 100644 --- a/src/workos.spec.ts +++ b/src/workos.spec.ts @@ -2,6 +2,7 @@ import fetch from 'jest-fetch-mock'; import { fetchOnce, fetchHeaders, fetchBody } from './common/utils/test-utils'; import { ApiKeyRequiredException, + AuthenticationException, GenericServerException, NotFoundException, OauthException, @@ -355,6 +356,34 @@ describe('WorkOS', () => { ), ); }); + + it('throws an AuthenticationException for known authentication errors', async () => { + const rawData = { + error: 'sso_required', + error_description: + 'User must authenticate using one of the matching connections.', + email: 'user@example.com', + connection_ids: ['conn_123'], + }; + + fetchOnce(rawData, { + status: 400, + headers: { 'X-Request-ID': 'a-request-id' }, + }); + + const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + const request = workos.post('/path', {}); + + await expect(request).rejects.toBeInstanceOf(AuthenticationException); + await expect(request).rejects.toMatchObject({ + code: 'sso_required', + message: + 'User must authenticate using one of the matching connections.', + name: 'AuthenticationException', + rawData, + status: 400, + }); + }); }); describe('when the api responses with a 429', () => { diff --git a/src/workos.ts b/src/workos.ts index 9c4a143d6..d2ab92f17 100644 --- a/src/workos.ts +++ b/src/workos.ts @@ -483,7 +483,9 @@ export class WorkOS { ); } default: { - if (error || errorDescription) { + if (isAuthenticationErrorData(data)) { + throw new AuthenticationException(status, data, requestID); + } else if (error || errorDescription) { throw new OauthException( status, requestID, @@ -500,8 +502,6 @@ export class WorkOS { message, requestID, }); - } else if (isAuthenticationErrorData(data)) { - throw new AuthenticationException(status, data, requestID); } else { throw new GenericServerException( status,