@pagopa/io-react-native-wallet 0.28.2 → 0.29.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 (129) 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.map +1 -1
  15. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js +36 -5
  16. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
  17. package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js +16 -4
  18. package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
  19. package/lib/commonjs/credential/presentation/README.md +17 -9
  20. package/lib/commonjs/credential/presentation/errors.js +11 -13
  21. package/lib/commonjs/credential/presentation/errors.js.map +1 -1
  22. package/lib/commonjs/credential/status/02-status-attestation.js +2 -0
  23. package/lib/commonjs/credential/status/02-status-attestation.js.map +1 -1
  24. package/lib/commonjs/credential/status/03-verify-and-parse-status-attestation.js +3 -0
  25. package/lib/commonjs/credential/status/03-verify-and-parse-status-attestation.js.map +1 -1
  26. package/lib/commonjs/credential/trustmark/get-credential-trustmark.js +5 -0
  27. package/lib/commonjs/credential/trustmark/get-credential-trustmark.js.map +1 -1
  28. package/lib/commonjs/index.js +3 -1
  29. package/lib/commonjs/index.js.map +1 -1
  30. package/lib/commonjs/utils/decoder.js +2 -0
  31. package/lib/commonjs/utils/decoder.js.map +1 -1
  32. package/lib/commonjs/utils/logging.js +68 -0
  33. package/lib/commonjs/utils/logging.js.map +1 -0
  34. package/lib/commonjs/utils/misc.js +2 -0
  35. package/lib/commonjs/utils/misc.js.map +1 -1
  36. package/lib/commonjs/utils/par.js +2 -0
  37. package/lib/commonjs/utils/par.js.map +1 -1
  38. package/lib/commonjs/wallet-instance/index.js +4 -0
  39. package/lib/commonjs/wallet-instance/index.js.map +1 -1
  40. package/lib/commonjs/wallet-instance-attestation/issuing.js +5 -0
  41. package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -1
  42. package/lib/module/credential/issuance/03-start-user-authorization.js +5 -0
  43. package/lib/module/credential/issuance/03-start-user-authorization.js.map +1 -1
  44. package/lib/module/credential/issuance/04-complete-user-authorization.js +12 -0
  45. package/lib/module/credential/issuance/04-complete-user-authorization.js.map +1 -1
  46. package/lib/module/credential/issuance/05-authorize-access.js +5 -0
  47. package/lib/module/credential/issuance/05-authorize-access.js.map +1 -1
  48. package/lib/module/credential/issuance/06-obtain-credential.js +13 -2
  49. package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -1
  50. package/lib/module/credential/issuance/07-verify-and-parse-credential.js +10 -0
  51. package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  52. package/lib/module/credential/presentation/01-start-flow.js +9 -8
  53. package/lib/module/credential/presentation/01-start-flow.js.map +1 -1
  54. package/lib/module/credential/presentation/03-get-request-object.js.map +1 -1
  55. package/lib/module/credential/presentation/07-evaluate-dcql-query.js +36 -5
  56. package/lib/module/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
  57. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js +17 -5
  58. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
  59. package/lib/module/credential/presentation/README.md +17 -9
  60. package/lib/module/credential/presentation/errors.js +8 -10
  61. package/lib/module/credential/presentation/errors.js.map +1 -1
  62. package/lib/module/credential/status/02-status-attestation.js +2 -0
  63. package/lib/module/credential/status/02-status-attestation.js.map +1 -1
  64. package/lib/module/credential/status/03-verify-and-parse-status-attestation.js +3 -0
  65. package/lib/module/credential/status/03-verify-and-parse-status-attestation.js.map +1 -1
  66. package/lib/module/credential/trustmark/get-credential-trustmark.js +5 -0
  67. package/lib/module/credential/trustmark/get-credential-trustmark.js.map +1 -1
  68. package/lib/module/index.js +2 -1
  69. package/lib/module/index.js.map +1 -1
  70. package/lib/module/utils/decoder.js +2 -0
  71. package/lib/module/utils/decoder.js.map +1 -1
  72. package/lib/module/utils/logging.js +62 -0
  73. package/lib/module/utils/logging.js.map +1 -0
  74. package/lib/module/utils/misc.js +2 -0
  75. package/lib/module/utils/misc.js.map +1 -1
  76. package/lib/module/utils/par.js +2 -0
  77. package/lib/module/utils/par.js.map +1 -1
  78. package/lib/module/wallet-instance/index.js +4 -0
  79. package/lib/module/wallet-instance/index.js.map +1 -1
  80. package/lib/module/wallet-instance-attestation/issuing.js +5 -0
  81. package/lib/module/wallet-instance-attestation/issuing.js.map +1 -1
  82. package/lib/typescript/credential/issuance/03-start-user-authorization.d.ts.map +1 -1
  83. package/lib/typescript/credential/issuance/04-complete-user-authorization.d.ts.map +1 -1
  84. package/lib/typescript/credential/issuance/05-authorize-access.d.ts.map +1 -1
  85. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts +1 -1
  86. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -1
  87. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
  88. package/lib/typescript/credential/presentation/01-start-flow.d.ts +17 -14
  89. package/lib/typescript/credential/presentation/01-start-flow.d.ts.map +1 -1
  90. package/lib/typescript/credential/presentation/03-get-request-object.d.ts +1 -3
  91. package/lib/typescript/credential/presentation/03-get-request-object.d.ts.map +1 -1
  92. package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts.map +1 -1
  93. package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts.map +1 -1
  94. package/lib/typescript/credential/presentation/errors.d.ts +11 -5
  95. package/lib/typescript/credential/presentation/errors.d.ts.map +1 -1
  96. package/lib/typescript/credential/status/02-status-attestation.d.ts.map +1 -1
  97. package/lib/typescript/credential/status/03-verify-and-parse-status-attestation.d.ts.map +1 -1
  98. package/lib/typescript/credential/trustmark/get-credential-trustmark.d.ts.map +1 -1
  99. package/lib/typescript/index.d.ts +2 -1
  100. package/lib/typescript/index.d.ts.map +1 -1
  101. package/lib/typescript/utils/decoder.d.ts.map +1 -1
  102. package/lib/typescript/utils/logging.d.ts +35 -0
  103. package/lib/typescript/utils/logging.d.ts.map +1 -0
  104. package/lib/typescript/utils/misc.d.ts.map +1 -1
  105. package/lib/typescript/utils/par.d.ts.map +1 -1
  106. package/lib/typescript/wallet-instance/index.d.ts.map +1 -1
  107. package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -1
  108. package/package.json +1 -1
  109. package/src/credential/issuance/03-start-user-authorization.ts +18 -0
  110. package/src/credential/issuance/04-complete-user-authorization.ts +51 -0
  111. package/src/credential/issuance/05-authorize-access.ts +16 -0
  112. package/src/credential/issuance/06-obtain-credential.ts +31 -2
  113. package/src/credential/issuance/07-verify-and-parse-credential.ts +27 -1
  114. package/src/credential/presentation/01-start-flow.ts +12 -11
  115. package/src/credential/presentation/03-get-request-object.ts +2 -3
  116. package/src/credential/presentation/07-evaluate-dcql-query.ts +36 -1
  117. package/src/credential/presentation/07-evaluate-input-descriptor.ts +25 -13
  118. package/src/credential/presentation/README.md +17 -9
  119. package/src/credential/presentation/errors.ts +15 -8
  120. package/src/credential/status/02-status-attestation.ts +3 -0
  121. package/src/credential/status/03-verify-and-parse-status-attestation.ts +10 -0
  122. package/src/credential/trustmark/get-credential-trustmark.ts +19 -0
  123. package/src/index.ts +2 -0
  124. package/src/utils/decoder.ts +5 -0
  125. package/src/utils/logging.ts +68 -0
  126. package/src/utils/misc.ts +5 -0
  127. package/src/utils/par.ts +6 -0
  128. package/src/wallet-instance/index.ts +17 -1
  129. package/src/wallet-instance-attestation/issuing.ts +19 -0
@@ -19,6 +19,7 @@ import { v4 as uuidv4 } from "uuid";
19
19
  import { ResponseUriResultShape } from "./types";
20
20
  import { getJwtFromFormPost } from "../../utils/decoder";
21
21
  import { AuthorizationError, AuthorizationIdpError } from "./errors";
22
+ import { LogLevel, Logger } from "../../utils/logging";
22
23
 
23
24
  /**
24
25
  * The interface of the phase to complete User authorization via strong identification when the response mode is "query" and the request credential is a PersonIdentificationData.
@@ -95,6 +96,10 @@ export const buildAuthorizationUrl: BuildAuthorizationUrl = async (
95
96
  */
96
97
  export const completeUserAuthorizationWithQueryMode: CompleteUserAuthorizationWithQueryMode =
97
98
  async (authRedirectUrl) => {
99
+ Logger.log(
100
+ LogLevel.DEBUG,
101
+ `The requeste credential is a PersonIdentificationData, completing the user authorization with query mode`
102
+ );
98
103
  const query = parseUrl(authRedirectUrl).query;
99
104
 
100
105
  return parseAuthorizationResponse(query);
@@ -114,6 +119,10 @@ export const completeUserAuthorizationWithQueryMode: CompleteUserAuthorizationWi
114
119
  */
115
120
  export const getRequestedCredentialToBePresented: GetRequestedCredentialToBePresented =
116
121
  async (issuerRequestUri, clientId, issuerConf, appFetch = fetch) => {
122
+ Logger.log(
123
+ LogLevel.DEBUG,
124
+ `The requeste credential is not a PersonIdentificationData, requesting the credential to be presented`
125
+ );
117
126
  const authzRequestEndpoint =
118
127
  issuerConf.oauth_authorization_server.authorization_endpoint;
119
128
  const params = new URLSearchParams({
@@ -121,6 +130,11 @@ export const getRequestedCredentialToBePresented: GetRequestedCredentialToBePres
121
130
  request_uri: issuerRequestUri,
122
131
  });
123
132
 
133
+ Logger.log(
134
+ LogLevel.DEBUG,
135
+ `Requesting the request object to ${authzRequestEndpoint}?${params.toString()}`
136
+ );
137
+
124
138
  const requestObject = await appFetch(
125
139
  `${authzRequestEndpoint}?${params.toString()}`,
126
140
  { method: "GET" }
@@ -131,6 +145,10 @@ export const getRequestedCredentialToBePresented: GetRequestedCredentialToBePres
131
145
  .then((reqObj) => RequestObject.safeParse(reqObj.payload));
132
146
 
133
147
  if (!requestObject.success) {
148
+ Logger.log(
149
+ LogLevel.ERROR,
150
+ `Error while validating the response object: ${requestObject.error.message}`
151
+ );
134
152
  throw new ValidationFailed({
135
153
  message: "Request Object validation failed",
136
154
  reason: requestObject.error.message,
@@ -157,6 +175,11 @@ export const getRequestedCredentialToBePresented: GetRequestedCredentialToBePres
157
175
  */
158
176
  export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthorizationWithFormPostJwtMode =
159
177
  async (requestObject, ctx) => {
178
+ Logger.log(
179
+ LogLevel.DEBUG,
180
+ `The requeste credential is not a PersonIdentificationData, completing the user authorization with form_post.jwt mode`
181
+ );
182
+
160
183
  const {
161
184
  wiaCryptoContext,
162
185
  pidCryptoContext,
@@ -195,6 +218,11 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
195
218
  .setAudience(requestObject.response_uri)
196
219
  .sign();
197
220
 
221
+ Logger.log(
222
+ LogLevel.DEBUG,
223
+ `Wallet instance attestation JWT token: ${wiaWpToken}`
224
+ );
225
+
198
226
  /* The path parameter refers to the vp_token variable of the authzResponsePayload and must point to the plain credential which
199
227
  * is cointaned in the `vp` property of the signed jwt token payload
200
228
  */
@@ -215,6 +243,11 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
215
243
  ],
216
244
  };
217
245
 
246
+ Logger.log(
247
+ LogLevel.DEBUG,
248
+ `Presentation submission: ${JSON.stringify(presentationSubmission)}`
249
+ );
250
+
218
251
  const authzResponsePayload = encodeBase64(
219
252
  JSON.stringify({
220
253
  state: requestObject.state,
@@ -223,6 +256,11 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
223
256
  })
224
257
  );
225
258
 
259
+ Logger.log(
260
+ LogLevel.DEBUG,
261
+ `Authz response payload: ${authzResponsePayload}`
262
+ );
263
+
226
264
  // Note: according to the spec, the response should be encrypted with the public key of the RP however this is not implemented yet
227
265
  // https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-signed-and-encrypted-response
228
266
  // const rsaPublicJwk = chooseRSAPublicKeyToEncrypt(rpConf);
@@ -235,6 +273,7 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
235
273
  const body = new URLSearchParams({
236
274
  response: authzResponsePayload,
237
275
  }).toString();
276
+
238
277
  const resUriRes = await appFetch(requestObject.response_uri, {
239
278
  method: "POST",
240
279
  headers: {
@@ -247,6 +286,10 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
247
286
 
248
287
  const responseUri = ResponseUriResultShape.safeParse(resUriRes);
249
288
  if (!responseUri.success) {
289
+ Logger.log(
290
+ LogLevel.ERROR,
291
+ `Error while validating the response uri: ${responseUri.error.message}`
292
+ );
250
293
  throw new ValidationFailed({
251
294
  message: "Response Uri validation failed",
252
295
  reason: responseUri.error.message,
@@ -274,8 +317,16 @@ export const parseAuthorizationResponse = (
274
317
  if (!authResParsed.success) {
275
318
  const authErr = AuthorizationErrorShape.safeParse(authRes);
276
319
  if (!authErr.success) {
320
+ Logger.log(
321
+ LogLevel.ERROR,
322
+ `Error while parsing the authorization response: ${authResParsed.error.message}`
323
+ );
277
324
  throw new AuthorizationError(authResParsed.error.message); // an error occured while parsing the result and the error
278
325
  }
326
+ Logger.log(
327
+ LogLevel.ERROR,
328
+ `Error while authorizating with the idp: ${JSON.stringify(authErr)}`
329
+ );
279
330
  throw new AuthorizationIdpError(
280
331
  authErr.data.error,
281
332
  authErr.data.error_description
@@ -10,6 +10,7 @@ import { ASSERTION_TYPE } from "./const";
10
10
  import { TokenResponse } from "./types";
11
11
  import { IssuerResponseError, ValidationFailed } from "../../utils/errors";
12
12
  import type { CompleteUserAuthorizationWithQueryMode } from "./04-complete-user-authorization";
13
+ import { LogLevel, Logger } from "../../utils/logging";
13
14
 
14
15
  export type AuthorizeAccess = (
15
16
  issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
@@ -76,6 +77,8 @@ export const authorizeAccess: AuthorizeAccess = async (
76
77
  dPopCryptoContext
77
78
  );
78
79
 
80
+ Logger.log(LogLevel.DEBUG, `Token request DPoP: ${tokenRequestSignedDPop}`);
81
+
79
82
  const signedWiaPoP = await createPopToken(
80
83
  {
81
84
  jti: `${uuidv4()}`,
@@ -85,6 +88,8 @@ export const authorizeAccess: AuthorizeAccess = async (
85
88
  wiaCryptoContext
86
89
  );
87
90
 
91
+ Logger.log(LogLevel.DEBUG, `WIA DPoP token: ${signedWiaPoP}`);
92
+
88
93
  const requestBody = {
89
94
  grant_type: "authorization_code",
90
95
  client_id: clientId,
@@ -96,6 +101,12 @@ export const authorizeAccess: AuthorizeAccess = async (
96
101
  };
97
102
 
98
103
  const authorizationRequestFormBody = new URLSearchParams(requestBody);
104
+
105
+ Logger.log(
106
+ LogLevel.DEBUG,
107
+ `Auth form request body: ${authorizationRequestFormBody}`
108
+ );
109
+
99
110
  const tokenRes = await appFetch(tokenUrl, {
100
111
  method: "POST",
101
112
  headers: {
@@ -109,6 +120,11 @@ export const authorizeAccess: AuthorizeAccess = async (
109
120
  .then((body) => TokenResponse.safeParse(body));
110
121
 
111
122
  if (!tokenRes.success) {
123
+ Logger.log(
124
+ LogLevel.ERROR,
125
+ `Token Response validation failed: ${tokenRes.error.message}`
126
+ );
127
+
112
128
  throw new ValidationFailed({
113
129
  message: "Token Response validation failed",
114
130
  reason: tokenRes.error.message,
@@ -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,8 @@
1
- import { hasStatusOrThrow, type Out } from "../../utils/misc";
2
- import type { StartFlow } from "./01-start-flow";
1
+ import { hasStatusOrThrow } from "../../utils/misc";
3
2
  import { RequestObjectWalletCapabilities } from "./types";
4
3
 
5
4
  export type GetRequestObject = (
6
- requestUri: Out<StartFlow>["requestUri"],
5
+ requestUri: string,
7
6
  context?: {
8
7
  appFetch?: GlobalFetch["fetch"];
9
8
  walletCapabilities?: RequestObjectWalletCapabilities;
@@ -10,6 +10,7 @@ import type { Disclosure } from "../../sd-jwt/types";
10
10
  import { ValidationFailed } from "../../utils/errors";
11
11
  import { createCryptoContextFor } from "../../utils/crypto";
12
12
  import type { RemotePresentation } from "./types";
13
+ import { CredentialsNotFoundError, type NotFoundDetail } from "./errors";
13
14
 
14
15
  /**
15
16
  * The purpose for the credential request by the RP.
@@ -47,6 +48,11 @@ type DcqlMatchSuccess = Extract<
47
48
  { success: true }
48
49
  >;
49
50
 
51
+ type DcqlMatchFailure = Extract<
52
+ DcqlQueryResult.CredentialMatch,
53
+ { success: false }
54
+ >;
55
+
50
56
  /**
51
57
  * Convert a credential in JWT format to an object with claims
52
58
  * for correct parsing by the `dcql` library.
@@ -81,6 +87,32 @@ const getDcqlQueryMatches = (result: DcqlQueryResult) =>
81
87
  ([, match]) => match.success === true
82
88
  ) as [string, DcqlMatchSuccess][];
83
89
 
90
+ /**
91
+ * Extract only failed matches from the DCQL query result.
92
+ */
93
+ const getDcqlQueryFailedMatches = (result: DcqlQueryResult) =>
94
+ Object.entries(result.credential_matches).filter(
95
+ ([, match]) => match.success === false
96
+ ) as [string, DcqlMatchFailure][];
97
+
98
+ /**
99
+ * Extract missing credentials from the DCQL query result.
100
+ * Note: here we are assuming a failed match is a missing credential,
101
+ * but there might be other reasons for its failure.
102
+ */
103
+ const extractMissingCredentials = (
104
+ queryResult: DcqlQueryResult,
105
+ originalQuery: DcqlQuery
106
+ ): NotFoundDetail[] => {
107
+ return getDcqlQueryFailedMatches(queryResult).map(([id]) => {
108
+ const credential = originalQuery.credentials.find((c) => c.id === id);
109
+ if (credential?.format !== "vc+sd-jwt") {
110
+ throw new Error("Unsupported format"); // TODO [SIW-2082]: support MDOC credentials
111
+ }
112
+ return { id, vctValues: credential.meta?.vct_values };
113
+ });
114
+ };
115
+
84
116
  export const evaluateDcqlQuery: EvaluateDcqlQuery = (
85
117
  credentialsSdJwt,
86
118
  query
@@ -97,8 +129,11 @@ export const evaluateDcqlQuery: EvaluateDcqlQuery = (
97
129
  const queryResult = DcqlQuery.query(parsedQuery, credentials);
98
130
 
99
131
  if (!queryResult.canBeSatisfied) {
100
- throw new Error("No credential can satisfy the provided DCQL query");
132
+ throw new CredentialsNotFoundError(
133
+ extractMissingCredentials(queryResult, parsedQuery)
134
+ );
101
135
  }
136
+
102
137
  // Build an object vct:credentialJwt to map matched credentials to their JWT
103
138
  const credentialsSdJwtByVct = credentials.reduce(
104
139
  (acc, c, i) => ({ ...acc, [c.vct]: credentialsSdJwt[i]! }),
@@ -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
  };
@@ -15,12 +15,20 @@ sequenceDiagram
15
15
  O->>+I: QR-CODE: Authorization Request (`request_uri`)
16
16
  I->>+O: GET: Verifier's Entity Configuration
17
17
  O->>+I: Respond with metadata (including public keys)
18
- I->>+O: GET: Request Object, resolved from the `request_uri`
18
+ I->>+O: GET: Request Object, resolved from `request_uri`
19
19
  O->>+I: Respond with the Request Object
20
- I->>+O: POST: VP token encrypted response
21
- O->>+I: Redirect: Authorization Response
20
+ I->>+I: Validate Request Object and give consent
21
+ I->>+O: POST: Authorization Response with encrypted VP token
22
+ O->>+I: Respond with optional `redirect_uri`
22
23
  ```
23
24
 
25
+ ## Mapped results
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`.|
31
+
24
32
 
25
33
  ## Examples
26
34
 
@@ -35,23 +43,23 @@ const qrCodeParams = decodeQrCode(qrCode)
35
43
 
36
44
  // Start the issuance flow
37
45
  const {
38
- requestUri,
39
- clientId,
40
- requestUriMethod,
46
+ request_uri,
47
+ client_id,
48
+ request_uri_method,
41
49
  state
42
50
  } = Credential.Presentation.startFlowFromQR(qrCodeParams);
43
51
 
44
52
  // Get the Relying Party's Entity Configuration and evaluate trust
45
- const { rpConf } = await Credential.Presentation.evaluateRelyingPartyTrust(clientId);
53
+ const { rpConf } = await Credential.Presentation.evaluateRelyingPartyTrust(client_id);
46
54
 
47
55
  // Get the Request Object from the RP
48
56
  const { requestObjectEncodedJwt } =
49
- await Credential.Presentation.getRequestObject(requestUri);
57
+ await Credential.Presentation.getRequestObject(request_uri);
50
58
 
51
59
  // Validate the Request Object
52
60
  const { requestObject } = await Credential.Presentation.verifyRequestObject(
53
61
  requestObjectEncodedJwt,
54
- { clientId, rpConf }
62
+ { clientId: client_id, rpConf }
55
63
  );
56
64
 
57
65
  // All the credentials that might be requested by the Relying Party
@@ -88,18 +88,25 @@ export class MissingDataError extends IoWalletError {
88
88
  }
89
89
  }
90
90
 
91
+ export type NotFoundDetail = {
92
+ id: string;
93
+ reason?: string;
94
+ vctValues?: string[];
95
+ };
96
+
91
97
  /**
92
- * When a credential is not found in the wallet.
93
- *
98
+ * Error thrown when one or more credentials cannot be found in the wallet
99
+ * and the presentation request cannot be satisfied.
94
100
  */
95
- export class CredentialNotFoundError extends IoWalletError {
96
- code = "ERR_CREDENTIAL_NOT_FOUND";
101
+ export class CredentialsNotFoundError extends IoWalletError {
102
+ code = "ERR_CREDENTIALS_NOT_FOUND";
103
+ details: NotFoundDetail[];
97
104
 
98
105
  /**
99
- * @param credentialId The ID of the credential that was not found.
106
+ * @param details The details of the credentials that could not be found.
100
107
  */
101
- constructor(credentialId: string) {
102
- const message = `Credential not found: ${credentialId}.`;
103
- super(message);
108
+ constructor(details: NotFoundDetail[]) {
109
+ super("One or more credentials cannot be found in the wallet");
110
+ this.details = details;
104
111
  }
105
112
  }