diff --git a/packages/javascript/src/errors/__tests__/ThunderIDAPIError.test.ts b/packages/javascript/src/errors/__tests__/ThunderIDAPIError.test.ts index aa2d644..d8dc950 100644 --- a/packages/javascript/src/errors/__tests__/ThunderIDAPIError.test.ts +++ b/packages/javascript/src/errors/__tests__/ThunderIDAPIError.test.ts @@ -177,4 +177,20 @@ describe('ThunderIDAPIError — structured response body parsing', (): void => { const error: ThunderIDAPIError = new ThunderIDAPIError(errorText, 'CODE', 'javascript', 401); expect(error.message).toBe('Invalid credentials provided'); }); + + it('should never surface a raw expired-flow response body to consumers', (): void => { + const errorText: string = JSON.stringify({ + code: 'FES-1004', + description: { + defaultValue: 'Invalid flow execution ID provided in the request', + key: 'error.flowexecservice.invalid_execution_id_description', + }, + message: {defaultValue: 'Invalid request', key: 'error.flowexecservice.invalid_execution_id'}, + }); + const error: ThunderIDAPIError = new ThunderIDAPIError(errorText, 'FES-1004', 'react', 400, 'Bad Request'); + + expect(error.message).not.toContain('FES-1004'); + expect(error.message).not.toContain('error.flowexecservice'); + expect(error.code).toBe('FES-1004'); + }); }); diff --git a/packages/react/src/components/presentation/auth/AcceptInvite/AcceptInvite.tsx b/packages/react/src/components/presentation/auth/AcceptInvite/AcceptInvite.tsx index c2b23dd..e16dcb9 100644 --- a/packages/react/src/components/presentation/auth/AcceptInvite/AcceptInvite.tsx +++ b/packages/react/src/components/presentation/auth/AcceptInvite/AcceptInvite.tsx @@ -16,6 +16,7 @@ * under the License. */ +import {ThunderIDAPIError} from '@thunderid/browser'; import {FC, ReactElement, ReactNode, useMemo} from 'react'; import BaseAcceptInvite, {BaseAcceptInviteRenderProps, AcceptInviteFlowResponse} from './BaseAcceptInvite'; @@ -189,7 +190,7 @@ const AcceptInvite: FC = ({ * Makes an unauthenticated request to /flow/execute endpoint. */ const handleSubmit = async (payload: Record): Promise => { - const response: any = await fetch(`${apiBaseUrl}/flow/execute`, { + const response: Response = await fetch(`${apiBaseUrl}/flow/execute`, { body: JSON.stringify({ ...payload, verbose: true, @@ -202,8 +203,15 @@ const AcceptInvite: FC = ({ }); if (!response.ok) { - const errorText: any = await response.text(); - throw new Error(`Request failed: ${errorText}`); + const errorText: string = await response.text(); + // ThunderIDAPIError resolves the raw API body into a readable message instead of leaking it. + throw new ThunderIDAPIError( + errorText, + 'AcceptInvite-ResponseError-001', + 'react', + response.status, + response.statusText, + ); } return response.json(); diff --git a/packages/vue/src/components/presentation/accept-invite/AcceptInvite.ts b/packages/vue/src/components/presentation/accept-invite/AcceptInvite.ts index 4413ea2..41c58e6 100644 --- a/packages/vue/src/components/presentation/accept-invite/AcceptInvite.ts +++ b/packages/vue/src/components/presentation/accept-invite/AcceptInvite.ts @@ -16,6 +16,7 @@ * under the License. */ +import {ThunderIDAPIError} from '@thunderid/browser'; import {type Component, type PropType, type SetupContext, type VNode, defineComponent, h} from 'vue'; import BaseAcceptInvite from './BaseAcceptInvite'; import type {AcceptInviteFlowResponse, BaseAcceptInviteRenderProps} from './BaseAcceptInvite'; @@ -78,7 +79,14 @@ const AcceptInvite: Component = defineComponent({ if (!response.ok) { const errorText: string = await response.text(); - throw new Error(`Request failed: ${errorText}`); + // ThunderIDAPIError resolves the raw API body into a readable message instead of leaking it. + throw new ThunderIDAPIError( + errorText, + 'AcceptInvite-ResponseError-001', + 'vue', + response.status, + response.statusText, + ); } return response.json();