@pagopa/io-react-native-wallet 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/lib/commonjs/credential/presentation/03-get-request-object.js +3 -2
  2. package/lib/commonjs/credential/presentation/03-get-request-object.js.map +1 -1
  3. package/lib/commonjs/credential/presentation/05-verify-request-object.js +57 -22
  4. package/lib/commonjs/credential/presentation/05-verify-request-object.js.map +1 -1
  5. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js +9 -13
  6. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
  7. package/lib/commonjs/credential/presentation/08-send-authorization-response.js +85 -3
  8. package/lib/commonjs/credential/presentation/08-send-authorization-response.js.map +1 -1
  9. package/lib/commonjs/credential/presentation/README.md +14 -4
  10. package/lib/commonjs/credential/presentation/errors.js +18 -11
  11. package/lib/commonjs/credential/presentation/errors.js.map +1 -1
  12. package/lib/commonjs/credential/presentation/index.js +6 -0
  13. package/lib/commonjs/credential/presentation/index.js.map +1 -1
  14. package/lib/commonjs/credential/presentation/types.js +14 -7
  15. package/lib/commonjs/credential/presentation/types.js.map +1 -1
  16. package/lib/commonjs/utils/error-codes.js +9 -1
  17. package/lib/commonjs/utils/error-codes.js.map +1 -1
  18. package/lib/commonjs/utils/errors.js +31 -14
  19. package/lib/commonjs/utils/errors.js.map +1 -1
  20. package/lib/module/credential/presentation/03-get-request-object.js +3 -2
  21. package/lib/module/credential/presentation/03-get-request-object.js.map +1 -1
  22. package/lib/module/credential/presentation/05-verify-request-object.js +58 -23
  23. package/lib/module/credential/presentation/05-verify-request-object.js.map +1 -1
  24. package/lib/module/credential/presentation/07-evaluate-dcql-query.js +8 -12
  25. package/lib/module/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
  26. package/lib/module/credential/presentation/08-send-authorization-response.js +82 -1
  27. package/lib/module/credential/presentation/08-send-authorization-response.js.map +1 -1
  28. package/lib/module/credential/presentation/README.md +14 -4
  29. package/lib/module/credential/presentation/errors.js +9 -9
  30. package/lib/module/credential/presentation/errors.js.map +1 -1
  31. package/lib/module/credential/presentation/index.js +2 -2
  32. package/lib/module/credential/presentation/index.js.map +1 -1
  33. package/lib/module/credential/presentation/types.js +12 -6
  34. package/lib/module/credential/presentation/types.js.map +1 -1
  35. package/lib/module/utils/error-codes.js +7 -0
  36. package/lib/module/utils/error-codes.js.map +1 -1
  37. package/lib/module/utils/errors.js +23 -14
  38. package/lib/module/utils/errors.js.map +1 -1
  39. package/lib/typescript/credential/presentation/03-get-request-object.d.ts.map +1 -1
  40. package/lib/typescript/credential/presentation/05-verify-request-object.d.ts +2 -1
  41. package/lib/typescript/credential/presentation/05-verify-request-object.d.ts.map +1 -1
  42. package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts.map +1 -1
  43. package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts +30 -2
  44. package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts.map +1 -1
  45. package/lib/typescript/credential/presentation/errors.d.ts +6 -7
  46. package/lib/typescript/credential/presentation/errors.d.ts.map +1 -1
  47. package/lib/typescript/credential/presentation/index.d.ts +3 -3
  48. package/lib/typescript/credential/presentation/index.d.ts.map +1 -1
  49. package/lib/typescript/credential/presentation/types.d.ts +24 -17
  50. package/lib/typescript/credential/presentation/types.d.ts.map +1 -1
  51. package/lib/typescript/utils/error-codes.d.ts +8 -0
  52. package/lib/typescript/utils/error-codes.d.ts.map +1 -1
  53. package/lib/typescript/utils/errors.d.ts +32 -18
  54. package/lib/typescript/utils/errors.d.ts.map +1 -1
  55. package/package.json +1 -1
  56. package/src/credential/presentation/03-get-request-object.ts +3 -2
  57. package/src/credential/presentation/05-verify-request-object.ts +73 -15
  58. package/src/credential/presentation/07-evaluate-dcql-query.ts +7 -17
  59. package/src/credential/presentation/08-send-authorization-response.ts +110 -3
  60. package/src/credential/presentation/README.md +14 -4
  61. package/src/credential/presentation/errors.ts +9 -9
  62. package/src/credential/presentation/index.ts +4 -0
  63. package/src/credential/presentation/types.ts +22 -10
  64. package/src/utils/error-codes.ts +11 -0
  65. package/src/utils/errors.ts +59 -29
@@ -1,3 +1,4 @@
1
+ import { RelyingPartyResponseError } from "../../utils/errors";
1
2
  import { hasStatusOrThrow } from "../../utils/misc";
2
3
  import { RequestObjectWalletCapabilities } from "./types";
3
4
 
@@ -40,7 +41,7 @@ export const getRequestObject: GetRequestObject = async (
40
41
  },
41
42
  body: formUrlEncodedBody.toString(),
42
43
  })
43
- .then(hasStatusOrThrow(200))
44
+ .then(hasStatusOrThrow(200, RelyingPartyResponseError))
44
45
  .then((res) => res.text());
45
46
 
46
47
  return {
@@ -51,7 +52,7 @@ export const getRequestObject: GetRequestObject = async (
51
52
  const requestObjectEncodedJwt = await appFetch(requestUri, {
52
53
  method: "GET",
53
54
  })
54
- .then(hasStatusOrThrow(200))
55
+ .then(hasStatusOrThrow(200, RelyingPartyResponseError))
55
56
  .then((res) => res.text());
56
57
 
57
58
  return {
@@ -1,6 +1,6 @@
1
1
  import { decode as decodeJwt, verify } from "@pagopa/io-react-native-jwt";
2
2
  import type { RelyingPartyEntityConfiguration } from "../../trust";
3
- import { UnverifiedEntityError } from "./errors";
3
+ import { InvalidRequestObjectError } from "./errors";
4
4
  import { RequestObject } from "./types";
5
5
  import { getJwksFromConfig } from "./04-retrieve-rp-jwks";
6
6
 
@@ -15,39 +15,38 @@ export type VerifyRequestObject = (
15
15
  ) => Promise<{ requestObject: RequestObject }>;
16
16
 
17
17
  /**
18
- * Function to verify the Request Object's signature and the client ID.
18
+ * Function to verify the Request Object's validity, from the signature to the required properties.
19
19
  * @param requestObjectEncodedJwt The Request Object in JWT format
20
20
  * @param context.clientId The client ID to verify
21
21
  * @param context.rpConf The Entity Configuration of the Relying Party
22
22
  * @param context.state Optional state
23
23
  * @returns The verified Request Object
24
+ * @throws {InvalidRequestObjectError} if the Request Object cannot be validated
24
25
  */
25
26
  export const verifyRequestObject: VerifyRequestObject = async (
26
27
  requestObjectEncodedJwt,
27
28
  { clientId, rpConf, rpSubject, state }
28
29
  ) => {
29
30
  const requestObjectJwt = decodeJwt(requestObjectEncodedJwt);
30
- const { keys } = getJwksFromConfig(rpConf);
31
31
 
32
- // Verify token signature to ensure the request object is authentic
33
- const pubKey = keys?.find(
34
- ({ kid }) => kid === requestObjectJwt.protectedHeader.kid
35
- );
32
+ const pubKey = getSigPublicKey(rpConf, requestObjectJwt.protectedHeader.kid);
36
33
 
37
- if (!pubKey) {
38
- throw new UnverifiedEntityError("Request Object signature verification!");
34
+ try {
35
+ // Standard claims are verified within `verify`
36
+ await verify(requestObjectEncodedJwt, pubKey, { issuer: clientId });
37
+ } catch (_) {
38
+ throw new InvalidRequestObjectError(
39
+ "The Request Object signature verification failed"
40
+ );
39
41
  }
40
42
 
41
- // Standard claims are verified within `verify`
42
- await verify(requestObjectEncodedJwt, pubKey, { issuer: clientId });
43
-
44
- const requestObject = RequestObject.parse(requestObjectJwt.payload);
43
+ const requestObject = validateRequestObjectShape(requestObjectJwt.payload);
45
44
 
46
45
  const isClientIdMatch =
47
46
  clientId === requestObject.client_id && clientId === rpSubject;
48
47
 
49
48
  if (!isClientIdMatch) {
50
- throw new UnverifiedEntityError(
49
+ throw new InvalidRequestObjectError(
51
50
  "Client ID does not match Request Object or Entity Configuration"
52
51
  );
53
52
  }
@@ -56,8 +55,67 @@ export const verifyRequestObject: VerifyRequestObject = async (
56
55
  state && requestObject.state ? state === requestObject.state : true;
57
56
 
58
57
  if (!isStateMatch) {
59
- throw new UnverifiedEntityError("State does not match Request Object");
58
+ throw new InvalidRequestObjectError(
59
+ "The provided state does not match the Request Object's"
60
+ );
60
61
  }
61
62
 
62
63
  return { requestObject };
63
64
  };
65
+
66
+ /**
67
+ * Validate the shape of the Request Object to ensure all required properties are present and are of the expected type.
68
+ *
69
+ * @param payload The Request Object to validate
70
+ * @returns A valid Request Object
71
+ * @throws {InvalidRequestObjectError} when the Request Object cannot be parsed
72
+ */
73
+ const validateRequestObjectShape = (payload: unknown): RequestObject => {
74
+ const requestObjectParse = RequestObject.safeParse(payload);
75
+
76
+ if (requestObjectParse.success) {
77
+ return requestObjectParse.data;
78
+ }
79
+
80
+ throw new InvalidRequestObjectError(
81
+ "The Request Object cannot be parsed successfully",
82
+ formatFlattenedZodErrors(requestObjectParse.error.flatten())
83
+ );
84
+ };
85
+
86
+ /**
87
+ * Get the public key to verify the Request Object's signature from the Relying Party's EC.
88
+ *
89
+ * @param rpConf The Relying Party's EC
90
+ * @param kid The identifier of the key to find
91
+ * @returns The corresponding public key to verify the signature
92
+ * @throws {InvalidRequestObjectError} when the key cannot be found
93
+ */
94
+ const getSigPublicKey = (
95
+ rpConf: RelyingPartyEntityConfiguration["payload"]["metadata"],
96
+ kid: string | undefined
97
+ ) => {
98
+ try {
99
+ const { keys } = getJwksFromConfig(rpConf);
100
+
101
+ const pubKey = keys.find((k) => k.kid === kid);
102
+
103
+ if (!pubKey) throw new Error();
104
+
105
+ return pubKey;
106
+ } catch (_) {
107
+ throw new InvalidRequestObjectError(
108
+ `The public key for signature verification (${kid}) cannot be found in the Entity Configuration`
109
+ );
110
+ }
111
+ };
112
+
113
+ /**
114
+ * Utility to format flattened Zod errors into a simplified string `key1: key1_error, key2: key2_error`
115
+ */
116
+ const formatFlattenedZodErrors = (
117
+ errors: Zod.typeToFlattenedError<RequestObject>
118
+ ): string =>
119
+ Object.entries(errors.fieldErrors)
120
+ .map(([key, error]) => `${key}: ${error[0]}`)
121
+ .join(", ");
@@ -1,13 +1,7 @@
1
- import {
2
- DcqlQuery,
3
- DcqlError,
4
- DcqlCredentialSetError,
5
- DcqlQueryResult,
6
- } from "dcql";
1
+ import { DcqlQuery, DcqlError, DcqlQueryResult } from "dcql";
7
2
  import { isValiError } from "valibot";
8
3
  import { decode, prepareVpToken } from "../../sd-jwt";
9
4
  import type { Disclosure } from "../../sd-jwt/types";
10
- import { ValidationFailed } from "../../utils/errors";
11
5
  import { createCryptoContextFor } from "../../utils/crypto";
12
6
  import type { RemotePresentation } from "./types";
13
7
  import { CredentialsNotFoundError, type NotFoundDetail } from "./errors";
@@ -167,20 +161,16 @@ export const evaluateDcqlQuery: EvaluateDcqlQuery = (
167
161
  };
168
162
  });
169
163
  } catch (error) {
170
- // Invalid DCQL query structure
164
+ // Invalid DCQL query structure. Remap to `DcqlError` for consistency.
171
165
  if (isValiError(error)) {
172
- throw new ValidationFailed({
173
- message: "Invalid DCQL query",
174
- reason: error.issues.map((issue) => issue.message).join(", "),
166
+ throw new DcqlError({
167
+ message: "Failed to parse the provided DCQL query",
168
+ code: "PARSE_ERROR",
169
+ cause: error.issues,
175
170
  });
176
171
  }
177
172
 
178
- if (error instanceof DcqlError) {
179
- // TODO [SIW-2110]: handle invalid DQCL query or let the error propagate
180
- }
181
- if (error instanceof DcqlCredentialSetError) {
182
- // TODO [SIW-2110]: handle missing credentials or let the error propagate
183
- }
173
+ // Let other errors propagate so they can be caught with `err instanceof DcqlError`
184
174
  throw error;
185
175
  }
186
176
  };
@@ -7,12 +7,18 @@ import { hasStatusOrThrow, type Out } from "../../utils/misc";
7
7
  import {
8
8
  type RemotePresentation,
9
9
  DirectAuthorizationBodyPayload,
10
+ ErrorResponse,
10
11
  type LegacyRemotePresentation,
11
- LegacyDirectAuthorizationBodyPayload,
12
12
  } from "./types";
13
13
  import * as z from "zod";
14
14
  import type { JWK } from "../../utils/jwk";
15
15
  import type { RelyingPartyEntityConfiguration } from "../../trust";
16
+ import {
17
+ RelyingPartyResponseError,
18
+ ResponseErrorBuilder,
19
+ UnexpectedStatusCodeError,
20
+ RelyingPartyResponseErrorCodes,
21
+ } from "../../utils/errors";
16
22
 
17
23
  export type AuthorizationResponse = z.infer<typeof AuthorizationResponse>;
18
24
  export const AuthorizationResponse = z.object({
@@ -61,7 +67,7 @@ export const choosePublicKeyToEncrypt = (
61
67
  export const buildDirectPostJwtBody = async (
62
68
  requestObject: Out<VerifyRequestObject>["requestObject"],
63
69
  rpConf: RelyingPartyEntityConfiguration["payload"]["metadata"],
64
- payload: DirectAuthorizationBodyPayload | LegacyDirectAuthorizationBodyPayload
70
+ payload: DirectAuthorizationBodyPayload
65
71
  ): Promise<string> => {
66
72
  type Jwe = ConstructorParameters<typeof EncryptJwe>[1];
67
73
 
@@ -98,6 +104,34 @@ export const buildDirectPostJwtBody = async (
98
104
  return formBody.toString();
99
105
  };
100
106
 
107
+ /**
108
+ * Builds a URL-encoded form body for a direct POST response without encryption.
109
+ *
110
+ * @param requestObject - Contains state, nonce, and other relevant info.
111
+ * @param payload - Object that contains either the VP token to encrypt and the stringified mapping of the credential disclosures or the error code
112
+ * @returns A URL-encoded string suitable for an `application/x-www-form-urlencoded` POST body.
113
+ */
114
+ export const buildDirectPostBody = async (
115
+ requestObject: Out<VerifyRequestObject>["requestObject"],
116
+ payload: DirectAuthorizationBodyPayload
117
+ ): Promise<string> => {
118
+ const formUrlEncodedBody = new URLSearchParams({
119
+ ...(requestObject.state && { state: requestObject.state }),
120
+ ...Object.entries(payload).reduce(
121
+ (acc, [key, value]) => ({
122
+ ...acc,
123
+ [key]:
124
+ Array.isArray(value) || typeof value === "object"
125
+ ? JSON.stringify(value)
126
+ : value,
127
+ }),
128
+ {} as Record<string, string>
129
+ ),
130
+ });
131
+
132
+ return formUrlEncodedBody.toString();
133
+ };
134
+
101
135
  /**
102
136
  * Type definition for the function that sends the authorization response
103
137
  * to the Relying Party, completing the presentation flow.
@@ -218,5 +252,78 @@ export const sendAuthorizationResponse: SendAuthorizationResponse = async (
218
252
  })
219
253
  .then(hasStatusOrThrow(200))
220
254
  .then((res) => res.json())
221
- .then(AuthorizationResponse.parse);
255
+ .then(AuthorizationResponse.parse)
256
+ .catch(handleAuthorizationResponseError);
257
+ };
258
+
259
+ /**
260
+ * Type definition for the function that sends the authorization response
261
+ * to the Relying Party, completing the presentation flow.
262
+ */
263
+ export type SendAuthorizationErrorResponse = (
264
+ requestObject: Out<VerifyRequestObject>["requestObject"],
265
+ error: { error: ErrorResponse; errorDescription: string },
266
+ context?: {
267
+ appFetch?: GlobalFetch["fetch"];
268
+ }
269
+ ) => Promise<AuthorizationResponse>;
270
+
271
+ /**
272
+ * Sends the authorization error response to the Relying Party (RP) using the specified `response_mode`.
273
+ * This function completes the presentation flow in an OpenID 4 Verifiable Presentations scenario.
274
+ *
275
+ * @param requestObject - The request details, including presentation requirements.
276
+ * @param error - The response error value, with description
277
+ * @param context - Contains optional custom fetch implementation.
278
+ * @returns Parsed and validated authorization response from the Relying Party.
279
+ */
280
+ export const sendAuthorizationErrorResponse: SendAuthorizationErrorResponse =
281
+ async (
282
+ requestObject,
283
+ { error, errorDescription },
284
+ { appFetch = fetch } = {}
285
+ ): Promise<AuthorizationResponse> => {
286
+ const requestBody = await buildDirectPostBody(requestObject, {
287
+ error,
288
+ error_description: errorDescription,
289
+ });
290
+
291
+ return await appFetch(requestObject.response_uri, {
292
+ method: "POST",
293
+ headers: {
294
+ "Content-Type": "application/x-www-form-urlencoded",
295
+ },
296
+ body: requestBody,
297
+ })
298
+ .then(hasStatusOrThrow(200, RelyingPartyResponseError))
299
+ .then((res) => res.json())
300
+ .then(AuthorizationResponse.parse);
301
+ };
302
+
303
+ /**
304
+ * Handle the the presentation error by mapping it to a custom exception.
305
+ * If the error is not an instance of {@link UnexpectedStatusCodeError}, it is thrown as is.
306
+ * @param e - The error to be handled
307
+ * @throws {RelyingPartyResponseError} with a specific code for more context
308
+ */
309
+ const handleAuthorizationResponseError = (e: unknown) => {
310
+ if (!(e instanceof UnexpectedStatusCodeError)) {
311
+ throw e;
312
+ }
313
+
314
+ throw new ResponseErrorBuilder(RelyingPartyResponseError)
315
+ .handle(400, {
316
+ code: RelyingPartyResponseErrorCodes.InvalidAuthorizationResponse,
317
+ message:
318
+ "The Authorization Response contains invalid parameters or it is malformed",
319
+ })
320
+ .handle(403, {
321
+ code: RelyingPartyResponseErrorCodes.InvalidAuthorizationResponse,
322
+ message: "The Authorization Response was forbidden",
323
+ })
324
+ .handle("*", {
325
+ code: RelyingPartyResponseErrorCodes.RelyingPartyGenericError,
326
+ message: "Unable to successfully send the Authorization Response",
327
+ })
328
+ .buildFrom(e);
222
329
  };
@@ -24,10 +24,20 @@ sequenceDiagram
24
24
 
25
25
  ## Mapped results
26
26
 
27
- |Error|Description|
28
- |-----|-----------|
29
- |`ValidationFailed`|The presentation request is not valid, for instance the DCQL query is invalid.|
30
- |`CredentialsNotFoundError`|The presentation cannot be completed because the Wallet does not contain all requested credentials. The missing credentials can be found in `details`.|
27
+ | Error | Description|
28
+ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
29
+ | `InvalidRequestObject` | The Request Object is not valid, for instance it is malformed or its signature cannot be verified. |
30
+ | `DcqlError` | The DCQL query cannot be evaluated because it contains errors. |
31
+ | `CredentialsNotFoundError` | The presentation cannot be completed because the Wallet does not contain all requested credentials. The missing credentials can be found in `details`. |
32
+ | `RelyingPartyResponseError` | Error in the Relying Party's response. See the next table for more details. |
33
+
34
+ #### RelyingPartyResponseError
35
+ The following HTTP errors are mapped to a `RelyingPartyResponseError` with specific codes.
36
+
37
+ | HTTP Status | Error Code | Description |
38
+ | ------------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
39
+ | `400`, `403` | `ERR_RP_INVALID_AUTHORIZATION_RESPONSE` | The Relying Party rejected the Authorization Response sent by the Wallet because it was deemed invalid. |
40
+ | `*` | `ERR_RP_GENERIC_ERROR` | This is a generic error code to map unexpected errors that occurred when interacting with the Relying Party. |
31
41
 
32
42
 
33
43
  ## Examples
@@ -1,4 +1,5 @@
1
1
  import { IoWalletError, serializeAttrs } from "../../utils/errors";
2
+ export { DcqlError } from "dcql";
2
3
 
3
4
  /**
4
5
  * An error subclass thrown when auth request decode fail
@@ -57,18 +58,17 @@ export class InvalidQRCodeError extends IoWalletError {
57
58
  }
58
59
 
59
60
  /**
60
- * When the entity is unverified because the Relying Party is not trusted.
61
- *
61
+ * When the Request Object sent by the Relying Party is not valid
62
62
  */
63
- export class UnverifiedEntityError extends IoWalletError {
64
- code = "ERR_UNVERIFIED_RP_ENTITY";
63
+ export class InvalidRequestObjectError extends IoWalletError {
64
+ code = "ERR_INVALID_REQUEST_OBJECT";
65
65
 
66
- /**
67
- * @param reason A description of why the entity cannot be verified.
68
- */
69
- constructor(reason: string) {
70
- const message = `Unverified entity: ${reason}.`;
66
+ /** Detailed reason for the Request Object validation failure. */
67
+ reason: string;
68
+
69
+ constructor(message: string, reason = "unspecified") {
71
70
  super(message);
71
+ this.reason = reason;
72
72
  }
73
73
  }
74
74
 
@@ -33,6 +33,8 @@ import {
33
33
  type SendAuthorizationResponse,
34
34
  sendLegacyAuthorizationResponse,
35
35
  type SendLegacyAuthorizationResponse,
36
+ sendAuthorizationErrorResponse,
37
+ type SendAuthorizationErrorResponse,
36
38
  } from "./08-send-authorization-response";
37
39
  import * as Errors from "./errors";
38
40
 
@@ -49,6 +51,7 @@ export {
49
51
  prepareRemotePresentations,
50
52
  sendAuthorizationResponse,
51
53
  sendLegacyAuthorizationResponse,
54
+ sendAuthorizationErrorResponse,
52
55
  Errors,
53
56
  };
54
57
  export type {
@@ -64,4 +67,5 @@ export type {
64
67
  PrepareRemotePresentations,
65
68
  SendAuthorizationResponse,
66
69
  SendLegacyAuthorizationResponse,
70
+ SendAuthorizationErrorResponse,
67
71
  };
@@ -133,26 +133,38 @@ export const RequestObjectWalletCapabilities = z.object({
133
133
  });
134
134
 
135
135
  /**
136
- * Authorization Response payload when using `presentation_definition`.
137
- * @deprecated Use `DirectAuthorizationBodyPayload`
136
+ * This type models the possible error responses the OpenID4VP protocol allows for a presentation of a credential.
137
+ * When the Wallet encounters one of these errors, it will notify the Relying Party through the `response_uri` endpoint.
138
+ * See https://italia.github.io/eid-wallet-it-docs/versione-corrente/en/pid-eaa-presentation.html#authorization-response-errors for more information.
138
139
  */
139
- export type LegacyDirectAuthorizationBodyPayload = z.infer<
140
- typeof LegacyDirectAuthorizationBodyPayload
141
- >;
140
+ export type ErrorResponse = z.infer<typeof ErrorResponse>;
141
+ export const ErrorResponse = z.enum([
142
+ "invalid_request_object",
143
+ "invalid_request_uri",
144
+ "vp_formats_not_supported",
145
+ "invalid_request",
146
+ "access_denied",
147
+ "invalid_client",
148
+ ]);
149
+
142
150
  /**
143
151
  * @deprecated Use `DirectAuthorizationBodyPayload`
144
152
  */
145
- export const LegacyDirectAuthorizationBodyPayload = z.object({
153
+ const LegacyDirectAuthorizationBodyPayload = z.object({
146
154
  vp_token: z.union([z.string(), z.array(z.string())]).optional(),
147
155
  presentation_submission: z.record(z.string(), z.unknown()),
148
156
  });
149
157
 
150
158
  /**
151
- * Authorization Response payload when using DCQL queries.
159
+ * Authorization Response payload sent to the Relying Party.
152
160
  */
153
161
  export type DirectAuthorizationBodyPayload = z.infer<
154
162
  typeof DirectAuthorizationBodyPayload
155
163
  >;
156
- export const DirectAuthorizationBodyPayload = z.object({
157
- vp_token: z.record(z.string(), z.string()),
158
- });
164
+ export const DirectAuthorizationBodyPayload = z.union([
165
+ z.object({
166
+ vp_token: z.record(z.string(), z.string()),
167
+ }),
168
+ z.object({ error: ErrorResponse, error_description: z.string() }),
169
+ LegacyDirectAuthorizationBodyPayload,
170
+ ]);
@@ -43,8 +43,19 @@ export const WalletProviderResponseErrorCodes = {
43
43
  WalletInstanceNotFound: "ERR_IO_WALLET_INSTANCE_NOT_FOUND",
44
44
  } as const;
45
45
 
46
+ export const RelyingPartyResponseErrorCodes = {
47
+ RelyingPartyGenericError: "ERR_RP_GENERIC_ERROR",
48
+ /**
49
+ * An error code thrown then the Relying Party rejects the Wallet's Authorization Response.
50
+ */
51
+ InvalidAuthorizationResponse: "ERR_RP_INVALID_AUTHORIZATION_RESPONSE",
52
+ } as const;
53
+
46
54
  export type IssuerResponseErrorCode =
47
55
  (typeof IssuerResponseErrorCodes)[keyof typeof IssuerResponseErrorCodes];
48
56
 
49
57
  export type WalletProviderResponseErrorCode =
50
58
  (typeof WalletProviderResponseErrorCodes)[keyof typeof WalletProviderResponseErrorCodes];
59
+
60
+ export type RelyingPartyResponseErrorCode =
61
+ (typeof RelyingPartyResponseErrorCodes)[keyof typeof RelyingPartyResponseErrorCodes];
@@ -3,11 +3,17 @@ import type { CredentialIssuerEntityConfiguration } from "../trust";
3
3
  import {
4
4
  IssuerResponseErrorCodes,
5
5
  WalletProviderResponseErrorCodes,
6
+ RelyingPartyResponseErrorCodes,
6
7
  type IssuerResponseErrorCode,
7
8
  type WalletProviderResponseErrorCode,
9
+ type RelyingPartyResponseErrorCode,
8
10
  } from "./error-codes";
9
11
 
10
- export { IssuerResponseErrorCodes, WalletProviderResponseErrorCodes };
12
+ export {
13
+ IssuerResponseErrorCodes,
14
+ WalletProviderResponseErrorCodes,
15
+ RelyingPartyResponseErrorCodes,
16
+ };
11
17
 
12
18
  // An error reason that supports both a string and a generic JSON object
13
19
  type GenericErrorReason = string | Record<string, unknown>;
@@ -110,8 +116,6 @@ export class UnexpectedStatusCodeError extends IoWalletError {
110
116
  /**
111
117
  * An error subclass thrown when an Issuer HTTP request fails.
112
118
  * The specific error can be found in the `code` property.
113
- *
114
- * The class is generic over the error code to narrow down the reason.
115
119
  */
116
120
  export class IssuerResponseError extends UnexpectedStatusCodeError {
117
121
  code: IssuerResponseErrorCode;
@@ -149,6 +153,25 @@ export class WalletProviderResponseError extends UnexpectedStatusCodeError {
149
153
  }
150
154
  }
151
155
 
156
+ /**
157
+ * An error subclass thrown when a Relying Party HTTP request fails.
158
+ * The specific error can be found in the `code` property.
159
+ */
160
+ export class RelyingPartyResponseError extends UnexpectedStatusCodeError {
161
+ code: RelyingPartyResponseErrorCode;
162
+
163
+ constructor(params: {
164
+ code?: RelyingPartyResponseErrorCode;
165
+ message: string;
166
+ reason: GenericErrorReason;
167
+ statusCode: number;
168
+ }) {
169
+ super(params);
170
+ this.code =
171
+ params.code ?? RelyingPartyResponseErrorCodes.RelyingPartyGenericError;
172
+ }
173
+ }
174
+
152
175
  type LocalizedIssuanceError = {
153
176
  [locale: string]: {
154
177
  title: string;
@@ -200,36 +223,43 @@ export function extractErrorMessageFromIssuerConf(
200
223
  }
201
224
 
202
225
  /**
203
- * Type guard for issuer errors.
204
- * @param error The error to check
205
- * @param code Optional code to narrow down the issuer error
226
+ * Factory function to create a type guard for specific error classes.
227
+ *
228
+ * @param errorClass The error class to create the type guard for
229
+ * @returns A type guard that checks if the error is an instance of the given class and has the expected code
206
230
  */
207
- export const isIssuerResponseError = (
208
- error: unknown,
209
- code?: IssuerResponseErrorCode
210
- ): error is IssuerResponseError =>
211
- error instanceof IssuerResponseError && error.code === (code ?? error.code);
231
+ const makeErrorTypeGuard =
232
+ <T extends typeof UnexpectedStatusCodeError>(ErrorClass: T) =>
233
+ (error: unknown, code?: ExtractErrorCode<T>): error is InstanceType<T> =>
234
+ error instanceof ErrorClass && error.code === (code ?? error.code);
235
+
236
+ export const isIssuerResponseError = makeErrorTypeGuard(IssuerResponseError);
237
+ export const isWalletProviderResponseError = makeErrorTypeGuard(
238
+ WalletProviderResponseError
239
+ );
240
+ export const isRelyingPartyResponseError = makeErrorTypeGuard(
241
+ RelyingPartyResponseError
242
+ );
243
+
244
+ // Mapping type between error classes and their allowed codes
245
+ type ErrorCodeMap =
246
+ | {
247
+ type: typeof IssuerResponseError;
248
+ code: IssuerResponseErrorCode;
249
+ }
250
+ | {
251
+ type: typeof WalletProviderResponseError;
252
+ code: WalletProviderResponseErrorCode;
253
+ }
254
+ | {
255
+ type: typeof RelyingPartyResponseError;
256
+ code: RelyingPartyResponseErrorCode;
257
+ };
212
258
 
213
- /**
214
- * Type guard for wallet provider errors.
215
- * @param error The error to check
216
- * @param code Optional code to narrow down the wallet provider error
217
- */
218
- export const isWalletProviderResponseError = (
219
- error: unknown,
220
- code?: WalletProviderResponseErrorCode
221
- ): error is WalletProviderResponseError =>
222
- error instanceof WalletProviderResponseError &&
223
- error.code === (code ?? error.code);
224
-
225
- type ErrorCodeMap<T> = T extends typeof IssuerResponseError
226
- ? IssuerResponseErrorCode
227
- : T extends typeof WalletProviderResponseError
228
- ? WalletProviderResponseErrorCode
229
- : never;
259
+ type ExtractErrorCode<T> = Extract<ErrorCodeMap, { type: T }>["code"];
230
260
 
231
261
  type ErrorCase<T> = {
232
- code: ErrorCodeMap<T>;
262
+ code: ExtractErrorCode<T>;
233
263
  message: string;
234
264
  reason?: GenericErrorReason;
235
265
  };