@pagopa/io-react-native-wallet 0.28.2 → 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 (173) hide show
  1. package/README.md +43 -0
  2. package/lib/commonjs/credential/issuance/03-start-user-authorization.js +5 -0
  3. package/lib/commonjs/credential/issuance/03-start-user-authorization.js.map +1 -1
  4. package/lib/commonjs/credential/issuance/04-complete-user-authorization.js +12 -0
  5. package/lib/commonjs/credential/issuance/04-complete-user-authorization.js.map +1 -1
  6. package/lib/commonjs/credential/issuance/05-authorize-access.js +5 -0
  7. package/lib/commonjs/credential/issuance/05-authorize-access.js.map +1 -1
  8. package/lib/commonjs/credential/issuance/06-obtain-credential.js +13 -2
  9. package/lib/commonjs/credential/issuance/06-obtain-credential.js.map +1 -1
  10. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +10 -0
  11. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  12. package/lib/commonjs/credential/presentation/01-start-flow.js +9 -8
  13. package/lib/commonjs/credential/presentation/01-start-flow.js.map +1 -1
  14. package/lib/commonjs/credential/presentation/03-get-request-object.js +3 -2
  15. package/lib/commonjs/credential/presentation/03-get-request-object.js.map +1 -1
  16. package/lib/commonjs/credential/presentation/05-verify-request-object.js +57 -22
  17. package/lib/commonjs/credential/presentation/05-verify-request-object.js.map +1 -1
  18. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js +43 -16
  19. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
  20. package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js +16 -4
  21. package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
  22. package/lib/commonjs/credential/presentation/08-send-authorization-response.js +85 -3
  23. package/lib/commonjs/credential/presentation/08-send-authorization-response.js.map +1 -1
  24. package/lib/commonjs/credential/presentation/README.md +27 -9
  25. package/lib/commonjs/credential/presentation/errors.js +28 -23
  26. package/lib/commonjs/credential/presentation/errors.js.map +1 -1
  27. package/lib/commonjs/credential/presentation/index.js +6 -0
  28. package/lib/commonjs/credential/presentation/index.js.map +1 -1
  29. package/lib/commonjs/credential/presentation/types.js +14 -7
  30. package/lib/commonjs/credential/presentation/types.js.map +1 -1
  31. package/lib/commonjs/credential/status/02-status-attestation.js +2 -0
  32. package/lib/commonjs/credential/status/02-status-attestation.js.map +1 -1
  33. package/lib/commonjs/credential/status/03-verify-and-parse-status-attestation.js +3 -0
  34. package/lib/commonjs/credential/status/03-verify-and-parse-status-attestation.js.map +1 -1
  35. package/lib/commonjs/credential/trustmark/get-credential-trustmark.js +5 -0
  36. package/lib/commonjs/credential/trustmark/get-credential-trustmark.js.map +1 -1
  37. package/lib/commonjs/index.js +3 -1
  38. package/lib/commonjs/index.js.map +1 -1
  39. package/lib/commonjs/utils/decoder.js +2 -0
  40. package/lib/commonjs/utils/decoder.js.map +1 -1
  41. package/lib/commonjs/utils/error-codes.js +9 -1
  42. package/lib/commonjs/utils/error-codes.js.map +1 -1
  43. package/lib/commonjs/utils/errors.js +31 -14
  44. package/lib/commonjs/utils/errors.js.map +1 -1
  45. package/lib/commonjs/utils/logging.js +68 -0
  46. package/lib/commonjs/utils/logging.js.map +1 -0
  47. package/lib/commonjs/utils/misc.js +2 -0
  48. package/lib/commonjs/utils/misc.js.map +1 -1
  49. package/lib/commonjs/utils/par.js +2 -0
  50. package/lib/commonjs/utils/par.js.map +1 -1
  51. package/lib/commonjs/wallet-instance/index.js +4 -0
  52. package/lib/commonjs/wallet-instance/index.js.map +1 -1
  53. package/lib/commonjs/wallet-instance-attestation/issuing.js +5 -0
  54. package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -1
  55. package/lib/module/credential/issuance/03-start-user-authorization.js +5 -0
  56. package/lib/module/credential/issuance/03-start-user-authorization.js.map +1 -1
  57. package/lib/module/credential/issuance/04-complete-user-authorization.js +12 -0
  58. package/lib/module/credential/issuance/04-complete-user-authorization.js.map +1 -1
  59. package/lib/module/credential/issuance/05-authorize-access.js +5 -0
  60. package/lib/module/credential/issuance/05-authorize-access.js.map +1 -1
  61. package/lib/module/credential/issuance/06-obtain-credential.js +13 -2
  62. package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -1
  63. package/lib/module/credential/issuance/07-verify-and-parse-credential.js +10 -0
  64. package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  65. package/lib/module/credential/presentation/01-start-flow.js +9 -8
  66. package/lib/module/credential/presentation/01-start-flow.js.map +1 -1
  67. package/lib/module/credential/presentation/03-get-request-object.js +3 -2
  68. package/lib/module/credential/presentation/03-get-request-object.js.map +1 -1
  69. package/lib/module/credential/presentation/05-verify-request-object.js +58 -23
  70. package/lib/module/credential/presentation/05-verify-request-object.js.map +1 -1
  71. package/lib/module/credential/presentation/07-evaluate-dcql-query.js +44 -17
  72. package/lib/module/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
  73. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js +17 -5
  74. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
  75. package/lib/module/credential/presentation/08-send-authorization-response.js +82 -1
  76. package/lib/module/credential/presentation/08-send-authorization-response.js.map +1 -1
  77. package/lib/module/credential/presentation/README.md +27 -9
  78. package/lib/module/credential/presentation/errors.js +17 -19
  79. package/lib/module/credential/presentation/errors.js.map +1 -1
  80. package/lib/module/credential/presentation/index.js +2 -2
  81. package/lib/module/credential/presentation/index.js.map +1 -1
  82. package/lib/module/credential/presentation/types.js +12 -6
  83. package/lib/module/credential/presentation/types.js.map +1 -1
  84. package/lib/module/credential/status/02-status-attestation.js +2 -0
  85. package/lib/module/credential/status/02-status-attestation.js.map +1 -1
  86. package/lib/module/credential/status/03-verify-and-parse-status-attestation.js +3 -0
  87. package/lib/module/credential/status/03-verify-and-parse-status-attestation.js.map +1 -1
  88. package/lib/module/credential/trustmark/get-credential-trustmark.js +5 -0
  89. package/lib/module/credential/trustmark/get-credential-trustmark.js.map +1 -1
  90. package/lib/module/index.js +2 -1
  91. package/lib/module/index.js.map +1 -1
  92. package/lib/module/utils/decoder.js +2 -0
  93. package/lib/module/utils/decoder.js.map +1 -1
  94. package/lib/module/utils/error-codes.js +7 -0
  95. package/lib/module/utils/error-codes.js.map +1 -1
  96. package/lib/module/utils/errors.js +23 -14
  97. package/lib/module/utils/errors.js.map +1 -1
  98. package/lib/module/utils/logging.js +62 -0
  99. package/lib/module/utils/logging.js.map +1 -0
  100. package/lib/module/utils/misc.js +2 -0
  101. package/lib/module/utils/misc.js.map +1 -1
  102. package/lib/module/utils/par.js +2 -0
  103. package/lib/module/utils/par.js.map +1 -1
  104. package/lib/module/wallet-instance/index.js +4 -0
  105. package/lib/module/wallet-instance/index.js.map +1 -1
  106. package/lib/module/wallet-instance-attestation/issuing.js +5 -0
  107. package/lib/module/wallet-instance-attestation/issuing.js.map +1 -1
  108. package/lib/typescript/credential/issuance/03-start-user-authorization.d.ts.map +1 -1
  109. package/lib/typescript/credential/issuance/04-complete-user-authorization.d.ts.map +1 -1
  110. package/lib/typescript/credential/issuance/05-authorize-access.d.ts.map +1 -1
  111. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts +1 -1
  112. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -1
  113. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
  114. package/lib/typescript/credential/presentation/01-start-flow.d.ts +17 -14
  115. package/lib/typescript/credential/presentation/01-start-flow.d.ts.map +1 -1
  116. package/lib/typescript/credential/presentation/03-get-request-object.d.ts +1 -3
  117. package/lib/typescript/credential/presentation/03-get-request-object.d.ts.map +1 -1
  118. package/lib/typescript/credential/presentation/05-verify-request-object.d.ts +2 -1
  119. package/lib/typescript/credential/presentation/05-verify-request-object.d.ts.map +1 -1
  120. package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts.map +1 -1
  121. package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts.map +1 -1
  122. package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts +30 -2
  123. package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts.map +1 -1
  124. package/lib/typescript/credential/presentation/errors.d.ts +17 -12
  125. package/lib/typescript/credential/presentation/errors.d.ts.map +1 -1
  126. package/lib/typescript/credential/presentation/index.d.ts +3 -3
  127. package/lib/typescript/credential/presentation/index.d.ts.map +1 -1
  128. package/lib/typescript/credential/presentation/types.d.ts +24 -17
  129. package/lib/typescript/credential/presentation/types.d.ts.map +1 -1
  130. package/lib/typescript/credential/status/02-status-attestation.d.ts.map +1 -1
  131. package/lib/typescript/credential/status/03-verify-and-parse-status-attestation.d.ts.map +1 -1
  132. package/lib/typescript/credential/trustmark/get-credential-trustmark.d.ts.map +1 -1
  133. package/lib/typescript/index.d.ts +2 -1
  134. package/lib/typescript/index.d.ts.map +1 -1
  135. package/lib/typescript/utils/decoder.d.ts.map +1 -1
  136. package/lib/typescript/utils/error-codes.d.ts +8 -0
  137. package/lib/typescript/utils/error-codes.d.ts.map +1 -1
  138. package/lib/typescript/utils/errors.d.ts +32 -18
  139. package/lib/typescript/utils/errors.d.ts.map +1 -1
  140. package/lib/typescript/utils/logging.d.ts +35 -0
  141. package/lib/typescript/utils/logging.d.ts.map +1 -0
  142. package/lib/typescript/utils/misc.d.ts.map +1 -1
  143. package/lib/typescript/utils/par.d.ts.map +1 -1
  144. package/lib/typescript/wallet-instance/index.d.ts.map +1 -1
  145. package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -1
  146. package/package.json +1 -1
  147. package/src/credential/issuance/03-start-user-authorization.ts +18 -0
  148. package/src/credential/issuance/04-complete-user-authorization.ts +51 -0
  149. package/src/credential/issuance/05-authorize-access.ts +16 -0
  150. package/src/credential/issuance/06-obtain-credential.ts +31 -2
  151. package/src/credential/issuance/07-verify-and-parse-credential.ts +27 -1
  152. package/src/credential/presentation/01-start-flow.ts +12 -11
  153. package/src/credential/presentation/03-get-request-object.ts +5 -5
  154. package/src/credential/presentation/05-verify-request-object.ts +73 -15
  155. package/src/credential/presentation/07-evaluate-dcql-query.ts +43 -18
  156. package/src/credential/presentation/07-evaluate-input-descriptor.ts +25 -13
  157. package/src/credential/presentation/08-send-authorization-response.ts +110 -3
  158. package/src/credential/presentation/README.md +27 -9
  159. package/src/credential/presentation/errors.ts +24 -17
  160. package/src/credential/presentation/index.ts +4 -0
  161. package/src/credential/presentation/types.ts +22 -10
  162. package/src/credential/status/02-status-attestation.ts +3 -0
  163. package/src/credential/status/03-verify-and-parse-status-attestation.ts +10 -0
  164. package/src/credential/trustmark/get-credential-trustmark.ts +19 -0
  165. package/src/index.ts +2 -0
  166. package/src/utils/decoder.ts +5 -0
  167. package/src/utils/error-codes.ts +11 -0
  168. package/src/utils/errors.ts +59 -29
  169. package/src/utils/logging.ts +68 -0
  170. package/src/utils/misc.ts +5 -0
  171. package/src/utils/par.ts +6 -0
  172. package/src/wallet-instance/index.ts +17 -1
  173. package/src/wallet-instance-attestation/issuing.ts +19 -0
@@ -17,6 +17,7 @@ import {
17
17
  import { CredentialResponse } from "./types";
18
18
  import { createDPopToken } from "../../utils/dpop";
19
19
  import { v4 as uuidv4 } from "uuid";
20
+ import { LogLevel, Logger } from "../../utils/logging";
20
21
 
21
22
  export type ObtainCredential = (
22
23
  issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
@@ -27,7 +28,8 @@ export type ObtainCredential = (
27
28
  dPopCryptoContext: CryptoContext;
28
29
  credentialCryptoContext: CryptoContext;
29
30
  appFetch?: GlobalFetch["fetch"];
30
- }
31
+ },
32
+ operationType?: "reissuing"
31
33
  ) => Promise<CredentialResponse>;
32
34
 
33
35
  export const createNonceProof = async (
@@ -73,7 +75,8 @@ export const obtainCredential: ObtainCredential = async (
73
75
  accessToken,
74
76
  clientId,
75
77
  credentialDefinition,
76
- context
78
+ context,
79
+ operationType
77
80
  ) => {
78
81
  const {
79
82
  credentialCryptoContext,
@@ -95,6 +98,8 @@ export const obtainCredential: ObtainCredential = async (
95
98
  credentialCryptoContext
96
99
  );
97
100
 
101
+ Logger.log(LogLevel.DEBUG, `Signed nonce proof: ${signedNonceProof}`);
102
+
98
103
  // Validation of accessTokenResponse.authorization_details if contain credentialDefinition
99
104
  const containsCredentialDefinition = accessToken.authorization_details.some(
100
105
  (c) =>
@@ -105,6 +110,10 @@ export const obtainCredential: ObtainCredential = async (
105
110
  );
106
111
 
107
112
  if (!containsCredentialDefinition) {
113
+ Logger.log(
114
+ LogLevel.ERROR,
115
+ `Credential definition not found in the access token response ${accessToken.authorization_details}`
116
+ );
108
117
  throw new ValidationFailed({
109
118
  message:
110
119
  "The access token response does not contain the requested credential",
@@ -123,6 +132,11 @@ export const obtainCredential: ObtainCredential = async (
123
132
  },
124
133
  };
125
134
 
135
+ Logger.log(
136
+ LogLevel.DEBUG,
137
+ `Credential request body: ${JSON.stringify(credentialRequestFormBody)}`
138
+ );
139
+
126
140
  const tokenRequestSignedDPop = await createDPopToken(
127
141
  {
128
142
  htm: "POST",
@@ -132,12 +146,16 @@ export const obtainCredential: ObtainCredential = async (
132
146
  },
133
147
  dPopCryptoContext
134
148
  );
149
+
150
+ Logger.log(LogLevel.DEBUG, `Token request DPoP: ${tokenRequestSignedDPop}`);
151
+
135
152
  const credentialRes = await appFetch(credentialUrl, {
136
153
  method: "POST",
137
154
  headers: {
138
155
  "Content-Type": "application/json",
139
156
  DPoP: tokenRequestSignedDPop,
140
157
  Authorization: `${accessToken.token_type} ${accessToken.access_token}`,
158
+ ...(operationType === "reissuing" && { operationType }),
141
159
  },
142
160
  body: JSON.stringify(credentialRequestFormBody),
143
161
  })
@@ -147,12 +165,21 @@ export const obtainCredential: ObtainCredential = async (
147
165
  .catch(handleObtainCredentialError);
148
166
 
149
167
  if (!credentialRes.success) {
168
+ Logger.log(
169
+ LogLevel.ERROR,
170
+ `Credential Response validation failed: ${credentialRes.error.message}`
171
+ );
150
172
  throw new ValidationFailed({
151
173
  message: "Credential Response validation failed",
152
174
  reason: credentialRes.error.message,
153
175
  });
154
176
  }
155
177
 
178
+ Logger.log(
179
+ LogLevel.DEBUG,
180
+ `Credential Response: ${JSON.stringify(credentialRes.data)}`
181
+ );
182
+
156
183
  return credentialRes.data;
157
184
  };
158
185
 
@@ -163,6 +190,8 @@ export const obtainCredential: ObtainCredential = async (
163
190
  * @throws {IssuerResponseError} with a specific code for more context
164
191
  */
165
192
  const handleObtainCredentialError = (e: unknown) => {
193
+ Logger.log(LogLevel.ERROR, `Error occurred while obtaining credential: ${e}`);
194
+
166
195
  if (!(e instanceof UnexpectedStatusCodeError)) {
167
196
  throw e;
168
197
  }
@@ -7,6 +7,7 @@ import { verify as verifySdJwt } from "../../sd-jwt";
7
7
  import { getValueFromDisclosures } from "../../sd-jwt/converters";
8
8
  import type { JWK } from "../../utils/jwk";
9
9
  import type { ObtainCredential } from "./06-obtain-credential";
10
+ import { LogLevel, Logger } from "../../utils/logging";
10
11
 
11
12
  export type VerifyAndParseCredential = (
12
13
  issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
@@ -62,10 +63,18 @@ const parseCredentialSdJwt = (
62
63
  const credentialSubject = credentials_supported[sdJwt.payload.vct];
63
64
 
64
65
  if (!credentialSubject) {
66
+ Logger.log(
67
+ LogLevel.ERROR,
68
+ `Credential type not supported by the issuer: ${sdJwt.payload.vct}`
69
+ );
65
70
  throw new IoWalletError("Credential type not supported by the issuer");
66
71
  }
67
72
 
68
73
  if (credentialSubject.format !== sdJwt.header.typ) {
74
+ Logger.log(
75
+ LogLevel.ERROR,
76
+ `Received credential is of an unknwown type. Expected one of [${credentialSubject.format}], received '${sdJwt.header.typ}'`
77
+ );
69
78
  throw new IoWalletError(
70
79
  `Received credential is of an unknwown type. Expected one of [${credentialSubject.format}], received '${sdJwt.header.typ}', `
71
80
  );
@@ -73,6 +82,7 @@ const parseCredentialSdJwt = (
73
82
 
74
83
  // transfrom a record { key: value } in an iterable of pairs [key, value]
75
84
  if (!credentialSubject.claims) {
85
+ Logger.log(LogLevel.ERROR, "Missing claims in the credential subject");
76
86
  throw new IoWalletError("Missing claims in the credential subject"); // TODO [SIW-1268]: should not be optional
77
87
  }
78
88
  const attrDefinitions = Object.entries(credentialSubject.claims);
@@ -85,6 +95,10 @@ const parseCredentialSdJwt = (
85
95
  const missing = attrsNotInDisclosures.map((_) => _[0 /* key */]).join(", ");
86
96
  const received = disclosures.map((_) => _[1 /* name */]).join(", ");
87
97
  if (!ignoreMissingAttributes) {
98
+ Logger.log(
99
+ LogLevel.ERROR,
100
+ `Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`
101
+ );
88
102
  throw new IoWalletError(
89
103
  `Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`
90
104
  );
@@ -172,6 +186,10 @@ async function verifyCredentialSdJwt(
172
186
  const { cnf } = decodedCredential.sdJwt.payload;
173
187
 
174
188
  if (!cnf.jwk.kid || cnf.jwk.kid !== holderBindingKey.kid) {
189
+ Logger.log(
190
+ LogLevel.ERROR,
191
+ `Failed to verify holder binding, expected kid: ${holderBindingKey.kid}, got: ${decodedCredential.sdJwt.payload.cnf.jwk.kid}`
192
+ );
175
193
  throw new IoWalletError(
176
194
  `Failed to verify holder binding, expected kid: ${holderBindingKey.kid}, got: ${decodedCredential.sdJwt.payload.cnf.jwk.kid}`
177
195
  );
@@ -204,15 +222,21 @@ const verifyAndParseCredentialSdJwt: WithFormat<"vc+sd-jwt"> = async (
204
222
  credentialCryptoContext
205
223
  );
206
224
 
225
+ Logger.log(LogLevel.DEBUG, `Decoded credential: ${JSON.stringify(decoded)}`);
226
+
207
227
  const parsedCredential = parseCredentialSdJwt(
208
228
  issuerConf.openid_credential_issuer.credential_configurations_supported,
209
229
  decoded,
210
230
  ignoreMissingAttributes,
211
231
  includeUndefinedAttributes
212
232
  );
213
-
214
233
  const maybeIssuedAt = getValueFromDisclosures(decoded.disclosures, "iat");
215
234
 
235
+ Logger.log(
236
+ LogLevel.DEBUG,
237
+ `Parsed credential: ${JSON.stringify(parsedCredential)}\nIssued at: ${maybeIssuedAt}`
238
+ );
239
+
216
240
  return {
217
241
  parsedCredential,
218
242
  expiration: new Date(decoded.sdJwt.payload.exp * 1000),
@@ -243,6 +267,7 @@ export const verifyAndParseCredential: VerifyAndParseCredential = async (
243
267
  context
244
268
  ) => {
245
269
  if (format === "vc+sd-jwt") {
270
+ Logger.log(LogLevel.DEBUG, "Parsing credential in vc+sd-jwt format");
246
271
  return verifyAndParseCredentialSdJwt(
247
272
  issuerConf,
248
273
  credential,
@@ -251,5 +276,6 @@ export const verifyAndParseCredential: VerifyAndParseCredential = async (
251
276
  );
252
277
  }
253
278
 
279
+ Logger.log(LogLevel.ERROR, `Unsupported credential format: ${format}`);
254
280
  throw new IoWalletError(`Unsupported credential format: ${format}`);
255
281
  };
@@ -2,9 +2,9 @@ import * as z from "zod";
2
2
  import { InvalidQRCodeError } from "./errors";
3
3
 
4
4
  const PresentationParams = z.object({
5
- clientId: z.string().nonempty(),
6
- requestUri: z.string().url(),
7
- requestUriMethod: z.enum(["get", "post"]),
5
+ client_id: z.string().nonempty(),
6
+ request_uri: z.string().url(),
7
+ request_uri_method: z.enum(["get", "post"]),
8
8
  state: z.string().optional(),
9
9
  });
10
10
  export type PresentationParams = z.infer<typeof PresentationParams>;
@@ -13,24 +13,25 @@ export type PresentationParams = z.infer<typeof PresentationParams>;
13
13
  * The beginning of the presentation flow.
14
14
  * To be implemented accordind to the user touchpoint
15
15
  *
16
- * @param params Presentation parameters, depending on the starting touchoint
16
+ * @param params Presentation parameters, depending on the starting touchpoint
17
17
  * @returns The url for the Relying Party to connect with
18
18
  */
19
- export type StartFlow = (
20
- params: Partial<PresentationParams>
21
- ) => PresentationParams;
19
+ export type StartFlow = (params: {
20
+ [K in keyof PresentationParams]?: PresentationParams[K] | null;
21
+ }) => PresentationParams;
22
22
 
23
23
  /**
24
- * Start a presentation flow by decoding an incoming QR-code
24
+ * Start a presentation flow by validating the required parameters.
25
+ * Parameters are extracted from a url encoded in a QR code or in a deep link.
25
26
  *
26
- * @param params The encoded QR-code content
27
+ * @param params The parameters to be validated
27
28
  * @returns The url for the Relying Party to connect with
28
- * @throws If the provided qr code fails to be decoded
29
+ * @throws If the provided parameters are not valid
29
30
  */
30
31
  export const startFlowFromQR: StartFlow = (params) => {
31
32
  const result = PresentationParams.safeParse({
32
33
  ...params,
33
- requestUriMethod: params.requestUriMethod ?? "get",
34
+ request_uri_method: params.request_uri_method ?? "get",
34
35
  });
35
36
 
36
37
  if (result.success) {
@@ -1,9 +1,9 @@
1
- import { hasStatusOrThrow, type Out } from "../../utils/misc";
2
- import type { StartFlow } from "./01-start-flow";
1
+ import { RelyingPartyResponseError } from "../../utils/errors";
2
+ import { hasStatusOrThrow } from "../../utils/misc";
3
3
  import { RequestObjectWalletCapabilities } from "./types";
4
4
 
5
5
  export type GetRequestObject = (
6
- requestUri: Out<StartFlow>["requestUri"],
6
+ requestUri: string,
7
7
  context?: {
8
8
  appFetch?: GlobalFetch["fetch"];
9
9
  walletCapabilities?: RequestObjectWalletCapabilities;
@@ -41,7 +41,7 @@ export const getRequestObject: GetRequestObject = async (
41
41
  },
42
42
  body: formUrlEncodedBody.toString(),
43
43
  })
44
- .then(hasStatusOrThrow(200))
44
+ .then(hasStatusOrThrow(200, RelyingPartyResponseError))
45
45
  .then((res) => res.text());
46
46
 
47
47
  return {
@@ -52,7 +52,7 @@ export const getRequestObject: GetRequestObject = async (
52
52
  const requestObjectEncodedJwt = await appFetch(requestUri, {
53
53
  method: "GET",
54
54
  })
55
- .then(hasStatusOrThrow(200))
55
+ .then(hasStatusOrThrow(200, RelyingPartyResponseError))
56
56
  .then((res) => res.text());
57
57
 
58
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,15 +1,10 @@
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";
7
+ import { CredentialsNotFoundError, type NotFoundDetail } from "./errors";
13
8
 
14
9
  /**
15
10
  * The purpose for the credential request by the RP.
@@ -47,6 +42,11 @@ type DcqlMatchSuccess = Extract<
47
42
  { success: true }
48
43
  >;
49
44
 
45
+ type DcqlMatchFailure = Extract<
46
+ DcqlQueryResult.CredentialMatch,
47
+ { success: false }
48
+ >;
49
+
50
50
  /**
51
51
  * Convert a credential in JWT format to an object with claims
52
52
  * for correct parsing by the `dcql` library.
@@ -81,6 +81,32 @@ const getDcqlQueryMatches = (result: DcqlQueryResult) =>
81
81
  ([, match]) => match.success === true
82
82
  ) as [string, DcqlMatchSuccess][];
83
83
 
84
+ /**
85
+ * Extract only failed matches from the DCQL query result.
86
+ */
87
+ const getDcqlQueryFailedMatches = (result: DcqlQueryResult) =>
88
+ Object.entries(result.credential_matches).filter(
89
+ ([, match]) => match.success === false
90
+ ) as [string, DcqlMatchFailure][];
91
+
92
+ /**
93
+ * Extract missing credentials from the DCQL query result.
94
+ * Note: here we are assuming a failed match is a missing credential,
95
+ * but there might be other reasons for its failure.
96
+ */
97
+ const extractMissingCredentials = (
98
+ queryResult: DcqlQueryResult,
99
+ originalQuery: DcqlQuery
100
+ ): NotFoundDetail[] => {
101
+ return getDcqlQueryFailedMatches(queryResult).map(([id]) => {
102
+ const credential = originalQuery.credentials.find((c) => c.id === id);
103
+ if (credential?.format !== "vc+sd-jwt") {
104
+ throw new Error("Unsupported format"); // TODO [SIW-2082]: support MDOC credentials
105
+ }
106
+ return { id, vctValues: credential.meta?.vct_values };
107
+ });
108
+ };
109
+
84
110
  export const evaluateDcqlQuery: EvaluateDcqlQuery = (
85
111
  credentialsSdJwt,
86
112
  query
@@ -97,8 +123,11 @@ export const evaluateDcqlQuery: EvaluateDcqlQuery = (
97
123
  const queryResult = DcqlQuery.query(parsedQuery, credentials);
98
124
 
99
125
  if (!queryResult.canBeSatisfied) {
100
- throw new Error("No credential can satisfy the provided DCQL query");
126
+ throw new CredentialsNotFoundError(
127
+ extractMissingCredentials(queryResult, parsedQuery)
128
+ );
101
129
  }
130
+
102
131
  // Build an object vct:credentialJwt to map matched credentials to their JWT
103
132
  const credentialsSdJwtByVct = credentials.reduce(
104
133
  (acc, c, i) => ({ ...acc, [c.vct]: credentialsSdJwt[i]! }),
@@ -132,20 +161,16 @@ export const evaluateDcqlQuery: EvaluateDcqlQuery = (
132
161
  };
133
162
  });
134
163
  } catch (error) {
135
- // Invalid DCQL query structure
164
+ // Invalid DCQL query structure. Remap to `DcqlError` for consistency.
136
165
  if (isValiError(error)) {
137
- throw new ValidationFailed({
138
- message: "Invalid DCQL query",
139
- 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,
140
170
  });
141
171
  }
142
172
 
143
- if (error instanceof DcqlError) {
144
- // TODO [SIW-2110]: handle invalid DQCL query or let the error propagate
145
- }
146
- if (error instanceof DcqlCredentialSetError) {
147
- // TODO [SIW-2110]: handle missing credentials or let the error propagate
148
- }
173
+ // Let other errors propagate so they can be caught with `err instanceof DcqlError`
149
174
  throw error;
150
175
  }
151
176
  };
@@ -3,7 +3,7 @@ import { SdJwt4VC, type DisclosureWithEncoded } from "../../sd-jwt/types";
3
3
  import { decode, prepareVpToken } from "../../sd-jwt";
4
4
  import { createCryptoContextFor } from "../../utils/crypto";
5
5
  import { JSONPath } from "jsonpath-plus";
6
- import { CredentialNotFoundError, MissingDataError } from "./errors";
6
+ import { CredentialsNotFoundError, MissingDataError } from "./errors";
7
7
  import Ajv from "ajv";
8
8
 
9
9
  const ajv = new Ajv({ allErrors: true });
@@ -291,9 +291,12 @@ export const findCredentialSdJwt = (
291
291
  }
292
292
  }
293
293
 
294
- throw new CredentialNotFoundError(
295
- "None of the vc+sd-jwt credentials satisfy the requirements."
296
- );
294
+ throw new CredentialsNotFoundError([
295
+ {
296
+ id: "",
297
+ reason: "None of the vc+sd-jwt credentials satisfy the requirements.",
298
+ },
299
+ ]);
297
300
  };
298
301
 
299
302
  /**
@@ -325,9 +328,12 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
325
328
  inputDescriptors.map(async (descriptor) => {
326
329
  if (descriptor.format?.["vc+sd-jwt"]) {
327
330
  if (!decodedSdJwtCredentials.length) {
328
- throw new CredentialNotFoundError(
329
- "vc+sd-jwt credential is not supported."
330
- );
331
+ throw new CredentialsNotFoundError([
332
+ {
333
+ id: descriptor.id,
334
+ reason: "vc+sd-jwt credential is not supported.",
335
+ },
336
+ ]);
331
337
  }
332
338
 
333
339
  const { matchedEvaluation, matchedKeyTag, matchedCredential } =
@@ -341,9 +347,12 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
341
347
  };
342
348
  }
343
349
 
344
- throw new CredentialNotFoundError(
345
- `${descriptor.format} format is not supported.`
346
- );
350
+ throw new CredentialsNotFoundError([
351
+ {
352
+ id: descriptor.id,
353
+ reason: `${descriptor.format} format is not supported.`,
354
+ },
355
+ ]);
347
356
  })
348
357
  );
349
358
  };
@@ -385,9 +394,12 @@ export const prepareLegacyRemotePresentations: PrepareLegacyRemotePresentations
385
394
  };
386
395
  }
387
396
 
388
- throw new CredentialNotFoundError(
389
- `${descriptor.format} format is not supported.`
390
- );
397
+ throw new CredentialsNotFoundError([
398
+ {
399
+ id: descriptor.id,
400
+ reason: `${descriptor.format} format is not supported.`,
401
+ },
402
+ ]);
391
403
  })
392
404
  );
393
405
  };