@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.
- package/lib/commonjs/credential/presentation/03-get-request-object.js +3 -2
- package/lib/commonjs/credential/presentation/03-get-request-object.js.map +1 -1
- package/lib/commonjs/credential/presentation/05-verify-request-object.js +57 -22
- package/lib/commonjs/credential/presentation/05-verify-request-object.js.map +1 -1
- package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js +9 -13
- package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
- package/lib/commonjs/credential/presentation/08-send-authorization-response.js +85 -3
- package/lib/commonjs/credential/presentation/08-send-authorization-response.js.map +1 -1
- package/lib/commonjs/credential/presentation/README.md +14 -4
- package/lib/commonjs/credential/presentation/errors.js +18 -11
- package/lib/commonjs/credential/presentation/errors.js.map +1 -1
- package/lib/commonjs/credential/presentation/index.js +6 -0
- package/lib/commonjs/credential/presentation/index.js.map +1 -1
- package/lib/commonjs/credential/presentation/types.js +14 -7
- package/lib/commonjs/credential/presentation/types.js.map +1 -1
- package/lib/commonjs/utils/error-codes.js +9 -1
- package/lib/commonjs/utils/error-codes.js.map +1 -1
- package/lib/commonjs/utils/errors.js +31 -14
- package/lib/commonjs/utils/errors.js.map +1 -1
- package/lib/module/credential/presentation/03-get-request-object.js +3 -2
- package/lib/module/credential/presentation/03-get-request-object.js.map +1 -1
- package/lib/module/credential/presentation/05-verify-request-object.js +58 -23
- package/lib/module/credential/presentation/05-verify-request-object.js.map +1 -1
- package/lib/module/credential/presentation/07-evaluate-dcql-query.js +8 -12
- package/lib/module/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
- package/lib/module/credential/presentation/08-send-authorization-response.js +82 -1
- package/lib/module/credential/presentation/08-send-authorization-response.js.map +1 -1
- package/lib/module/credential/presentation/README.md +14 -4
- package/lib/module/credential/presentation/errors.js +9 -9
- package/lib/module/credential/presentation/errors.js.map +1 -1
- package/lib/module/credential/presentation/index.js +2 -2
- package/lib/module/credential/presentation/index.js.map +1 -1
- package/lib/module/credential/presentation/types.js +12 -6
- package/lib/module/credential/presentation/types.js.map +1 -1
- package/lib/module/utils/error-codes.js +7 -0
- package/lib/module/utils/error-codes.js.map +1 -1
- package/lib/module/utils/errors.js +23 -14
- package/lib/module/utils/errors.js.map +1 -1
- package/lib/typescript/credential/presentation/03-get-request-object.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/05-verify-request-object.d.ts +2 -1
- package/lib/typescript/credential/presentation/05-verify-request-object.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts +30 -2
- package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/errors.d.ts +6 -7
- package/lib/typescript/credential/presentation/errors.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/index.d.ts +3 -3
- package/lib/typescript/credential/presentation/index.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/types.d.ts +24 -17
- package/lib/typescript/credential/presentation/types.d.ts.map +1 -1
- package/lib/typescript/utils/error-codes.d.ts +8 -0
- package/lib/typescript/utils/error-codes.d.ts.map +1 -1
- package/lib/typescript/utils/errors.d.ts +32 -18
- package/lib/typescript/utils/errors.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/credential/presentation/03-get-request-object.ts +3 -2
- package/src/credential/presentation/05-verify-request-object.ts +73 -15
- package/src/credential/presentation/07-evaluate-dcql-query.ts +7 -17
- package/src/credential/presentation/08-send-authorization-response.ts +110 -3
- package/src/credential/presentation/README.md +14 -4
- package/src/credential/presentation/errors.ts +9 -9
- package/src/credential/presentation/index.ts +4 -0
- package/src/credential/presentation/types.ts +22 -10
- package/src/utils/error-codes.ts +11 -0
- 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 {
|
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
|
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
|
-
|
33
|
-
const pubKey = keys?.find(
|
34
|
-
({ kid }) => kid === requestObjectJwt.protectedHeader.kid
|
35
|
-
);
|
32
|
+
const pubKey = getSigPublicKey(rpConf, requestObjectJwt.protectedHeader.kid);
|
36
33
|
|
37
|
-
|
38
|
-
|
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
|
-
|
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
|
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
|
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
|
173
|
-
message: "
|
174
|
-
|
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
|
-
|
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
|
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
|
-
|
30
|
-
|
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
|
61
|
-
*
|
61
|
+
* When the Request Object sent by the Relying Party is not valid
|
62
62
|
*/
|
63
|
-
export class
|
64
|
-
code = "
|
63
|
+
export class InvalidRequestObjectError extends IoWalletError {
|
64
|
+
code = "ERR_INVALID_REQUEST_OBJECT";
|
65
65
|
|
66
|
-
/**
|
67
|
-
|
68
|
-
|
69
|
-
constructor(
|
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
|
-
*
|
137
|
-
*
|
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
|
140
|
-
|
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
|
-
|
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
|
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.
|
157
|
-
|
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
|
+
]);
|
package/src/utils/error-codes.ts
CHANGED
@@ -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];
|
package/src/utils/errors.ts
CHANGED
@@ -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 {
|
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
|
-
*
|
204
|
-
*
|
205
|
-
* @param
|
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
|
-
|
208
|
-
|
209
|
-
code?:
|
210
|
-
|
211
|
-
|
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:
|
262
|
+
code: ExtractErrorCode<T>;
|
233
263
|
message: string;
|
234
264
|
reason?: GenericErrorReason;
|
235
265
|
};
|