@pagopa/io-react-native-wallet 0.4.3 → 0.6.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 (94) hide show
  1. package/README.md +128 -19
  2. package/lib/commonjs/index.js +16 -23
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/pid/index.js +3 -8
  5. package/lib/commonjs/pid/index.js.map +1 -1
  6. package/lib/commonjs/pid/issuing.js +153 -176
  7. package/lib/commonjs/pid/issuing.js.map +1 -1
  8. package/lib/commonjs/rp/__test__/index.test.js +7 -5
  9. package/lib/commonjs/rp/__test__/index.test.js.map +1 -1
  10. package/lib/commonjs/rp/index.js +145 -155
  11. package/lib/commonjs/rp/index.js.map +1 -1
  12. package/lib/commonjs/rp/types.js +1 -21
  13. package/lib/commonjs/rp/types.js.map +1 -1
  14. package/lib/commonjs/trust/index.js +24 -5
  15. package/lib/commonjs/trust/index.js.map +1 -1
  16. package/lib/commonjs/trust/types.js +102 -9
  17. package/lib/commonjs/trust/types.js.map +1 -1
  18. package/lib/commonjs/utils/crypto.js +46 -0
  19. package/lib/commonjs/utils/crypto.js.map +1 -0
  20. package/lib/commonjs/utils/dpop.js +14 -7
  21. package/lib/commonjs/utils/dpop.js.map +1 -1
  22. package/lib/commonjs/wallet-instance-attestation/index.js +3 -3
  23. package/lib/commonjs/wallet-instance-attestation/issuing.js +42 -60
  24. package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -1
  25. package/lib/module/index.js +4 -6
  26. package/lib/module/index.js.map +1 -1
  27. package/lib/module/pid/index.js +1 -1
  28. package/lib/module/pid/index.js.map +1 -1
  29. package/lib/module/pid/issuing.js +152 -180
  30. package/lib/module/pid/issuing.js.map +1 -1
  31. package/lib/module/rp/__test__/index.test.js +3 -3
  32. package/lib/module/rp/__test__/index.test.js.map +1 -1
  33. package/lib/module/rp/index.js +141 -154
  34. package/lib/module/rp/index.js.map +1 -1
  35. package/lib/module/rp/types.js +0 -20
  36. package/lib/module/rp/types.js.map +1 -1
  37. package/lib/module/trust/index.js +19 -5
  38. package/lib/module/trust/index.js.map +1 -1
  39. package/lib/module/trust/types.js +100 -7
  40. package/lib/module/trust/types.js.map +1 -1
  41. package/lib/module/utils/crypto.js +40 -0
  42. package/lib/module/utils/crypto.js.map +1 -0
  43. package/lib/module/utils/dpop.js +13 -5
  44. package/lib/module/utils/dpop.js.map +1 -1
  45. package/lib/module/wallet-instance-attestation/index.js +2 -2
  46. package/lib/module/wallet-instance-attestation/index.js.map +1 -1
  47. package/lib/module/wallet-instance-attestation/issuing.js +40 -58
  48. package/lib/module/wallet-instance-attestation/issuing.js.map +1 -1
  49. package/lib/typescript/index.d.ts +4 -6
  50. package/lib/typescript/index.d.ts.map +1 -1
  51. package/lib/typescript/pid/index.d.ts +1 -1
  52. package/lib/typescript/pid/index.d.ts.map +1 -1
  53. package/lib/typescript/pid/issuing.d.ts +43 -88
  54. package/lib/typescript/pid/issuing.d.ts.map +1 -1
  55. package/lib/typescript/rp/index.d.ts +41 -87
  56. package/lib/typescript/rp/index.d.ts.map +1 -1
  57. package/lib/typescript/rp/types.d.ts +10 -906
  58. package/lib/typescript/rp/types.d.ts.map +1 -1
  59. package/lib/typescript/sd-jwt/index.d.ts +1 -1
  60. package/lib/typescript/sd-jwt/index.d.ts.map +1 -1
  61. package/lib/typescript/trust/index.d.ts +806 -3
  62. package/lib/typescript/trust/index.d.ts.map +1 -1
  63. package/lib/typescript/trust/types.d.ts +9655 -297
  64. package/lib/typescript/trust/types.d.ts.map +1 -1
  65. package/lib/typescript/utils/crypto.d.ts +10 -0
  66. package/lib/typescript/utils/crypto.d.ts.map +1 -0
  67. package/lib/typescript/utils/dpop.d.ts +10 -2
  68. package/lib/typescript/utils/dpop.d.ts.map +1 -1
  69. package/lib/typescript/wallet-instance-attestation/index.d.ts +2 -2
  70. package/lib/typescript/wallet-instance-attestation/index.d.ts.map +1 -1
  71. package/lib/typescript/wallet-instance-attestation/issuing.d.ts +18 -31
  72. package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -1
  73. package/lib/typescript/wallet-instance-attestation/types.d.ts +4 -4
  74. package/package.json +2 -2
  75. package/src/index.ts +14 -13
  76. package/src/pid/index.ts +1 -1
  77. package/src/pid/issuing.ts +233 -232
  78. package/src/rp/__test__/index.test.ts +3 -3
  79. package/src/rp/index.ts +172 -194
  80. package/src/rp/types.ts +0 -24
  81. package/src/sd-jwt/index.ts +1 -1
  82. package/src/trust/index.ts +106 -5
  83. package/src/trust/types.ts +152 -34
  84. package/src/utils/crypto.ts +41 -0
  85. package/src/utils/dpop.ts +17 -7
  86. package/src/wallet-instance-attestation/index.ts +2 -2
  87. package/src/wallet-instance-attestation/issuing.ts +51 -63
  88. package/lib/commonjs/pid/metadata.js +0 -49
  89. package/lib/commonjs/pid/metadata.js.map +0 -1
  90. package/lib/module/pid/metadata.js +0 -41
  91. package/lib/module/pid/metadata.js.map +0 -1
  92. package/lib/typescript/pid/metadata.d.ts +0 -482
  93. package/lib/typescript/pid/metadata.d.ts.map +0 -1
  94. package/src/pid/metadata.ts +0 -46
package/src/rp/index.ts CHANGED
@@ -10,113 +10,109 @@ import {
10
10
  SignJWT,
11
11
  EncryptJwe,
12
12
  verify,
13
+ type CryptoContext,
13
14
  } from "@pagopa/io-react-native-jwt";
14
- import {
15
- QRCodePayload,
16
- RequestObject,
17
- RpEntityConfiguration,
18
- type Presentation,
19
- } from "./types";
15
+ import { QRCodePayload, RequestObject, type Presentation } from "./types";
20
16
 
21
17
  import uuid from "react-native-uuid";
22
18
  import type { JWK } from "@pagopa/io-react-native-jwt/lib/typescript/types";
23
19
  import { disclose } from "../sd-jwt";
24
- import { getEntityConfiguration } from "../trust";
20
+ import { createDPopToken } from "../utils/dpop";
21
+ import { RelyingPartyEntityConfiguration } from "../trust/types";
22
+ import * as WalletInstanceAttestation from "../wallet-instance-attestation";
25
23
 
26
- export class RelyingPartySolution {
27
- relyingPartyBaseUrl: string;
28
- walletInstanceAttestation: string;
29
- appFetch: GlobalFetch["fetch"];
24
+ /**
25
+ * Select a RSA public key from those provided by the RP to encrypt.
26
+ *
27
+ * @param entity The RP entity configuration
28
+ * @returns A suitable public key with its compatible encryption algorithm
29
+ * @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key suitable for encrypting
30
+ */
31
+ const chooseRSAPublicKeyToEncrypt = (
32
+ entity: RelyingPartyEntityConfiguration
33
+ ): JWK => {
34
+ const [usingRsa256] =
35
+ entity.payload.metadata.wallet_relying_party.jwks.filter(
36
+ (jwk) => jwk.use === "enc" && jwk.kty === "RSA"
37
+ );
30
38
 
31
- constructor(
32
- relyingPartyBaseUrl: string,
33
- walletInstanceAttestation: string,
34
- appFetch: GlobalFetch["fetch"] = fetch
35
- ) {
36
- this.relyingPartyBaseUrl = relyingPartyBaseUrl;
37
- this.walletInstanceAttestation = walletInstanceAttestation;
38
- this.appFetch = appFetch;
39
+ if (usingRsa256) {
40
+ return usingRsa256;
39
41
  }
40
42
 
41
- /**
42
- * Decode a QR code content to an authentication request url.
43
- * @function
44
- * @param qrcode QR code content
45
- *
46
- * @returns The authentication request url
47
- *
48
- */
49
- static decodeAuthRequestQR(qrcode: string): QRCodePayload {
50
- const decoded = decodeBase64(qrcode);
51
- const decodedUrl = new URL(decoded);
52
- const protocol = decodedUrl.protocol;
53
- const resource = decodedUrl.hostname;
54
- const requestURI = decodedUrl.searchParams.get("request_uri");
55
- const clientId = decodedUrl.searchParams.get("client_id");
43
+ // No suitable key has been found
44
+ throw new NoSuitableKeysFoundInEntityConfiguration(
45
+ "Encrypt with RP public key"
46
+ );
47
+ };
56
48
 
57
- const result = QRCodePayload.safeParse({
58
- protocol,
59
- resource,
60
- requestURI,
61
- clientId,
62
- });
49
+ /**
50
+ * Decode a QR code content to an authentication request url.
51
+ * @function
52
+ * @param qrcode QR code content
53
+ *
54
+ * @returns The authentication request url
55
+ *
56
+ */
57
+ export const decodeAuthRequestQR = (qrcode: string): QRCodePayload => {
58
+ const decoded = decodeBase64(qrcode);
59
+ const decodedUrl = new URL(decoded);
60
+ const protocol = decodedUrl.protocol;
61
+ const resource = decodedUrl.hostname;
62
+ const requestURI = decodedUrl.searchParams.get("request_uri");
63
+ const clientId = decodedUrl.searchParams.get("client_id");
63
64
 
64
- if (result.success) {
65
- return result.data;
66
- } else {
67
- throw new AuthRequestDecodeError(result.error.message, `${decodedUrl}`);
68
- }
69
- }
70
- /**
71
- * Obtain the unsigned wallet instance DPoP for authentication request
72
- *
73
- * @function
74
- * @param walletInstanceAttestationJwk JWT of the Wallet Instance Attestation
75
- * @param authRequestUrl authentication request url
76
- *
77
- * @returns The unsigned wallet instance DPoP
78
- *
79
- */
80
- async getUnsignedWalletInstanceDPoP(
81
- walletInstanceAttestationJwk: any,
82
- authRequestUrl: string
83
- ): Promise<string> {
84
- return await new SignJWT({
85
- jti: `${uuid.v4()}`,
86
- htm: "GET",
87
- htu: authRequestUrl,
88
- ath: await sha256ToBase64(this.walletInstanceAttestation),
89
- })
90
- .setProtectedHeader({
91
- alg: "ES256",
92
- jwk: walletInstanceAttestationJwk,
93
- typ: "dpop+jwt",
94
- })
95
- .setIssuedAt()
96
- .setExpirationTime("1h")
97
- .toSign();
65
+ const result = QRCodePayload.safeParse({
66
+ protocol,
67
+ resource,
68
+ requestURI,
69
+ clientId,
70
+ });
71
+
72
+ if (result.success) {
73
+ return result.data;
74
+ } else {
75
+ throw new AuthRequestDecodeError(result.error.message, `${decodedUrl}`);
98
76
  }
77
+ };
78
+
79
+ export type RequestObjectConf = {
80
+ requestObject: RequestObject;
81
+ rpEntityConfiguration: RelyingPartyEntityConfiguration;
82
+ walletInstanceAttestation: string;
83
+ };
99
84
 
100
- /**
101
- * Obtain the Request Object for RP authentication
102
- * @see https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/relying-party-solution.html
103
- *
104
- * @async @function
105
- * @param signedWalletInstanceDPoP JWT of the Wallet Instance Attestation DPoP
106
- *
107
- * @returns The Request Object JWT
108
- * @throws {NoSuitableKeysFoundInEntityConfiguration} When the Request Object is signed with a key not listed in RP's entity configuration
109
- *
110
- */
111
- async getRequestObject(
112
- signedWalletInstanceDPoP: string,
85
+ /**
86
+ * Obtain the Request Object for RP authentication
87
+ * @see https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/relying-party-solution.html
88
+ */
89
+ export const getRequestObject =
90
+ ({
91
+ wiaCryptoContext,
92
+ appFetch = fetch,
93
+ }: {
94
+ wiaCryptoContext: CryptoContext;
95
+ appFetch?: GlobalFetch["fetch"];
96
+ }) =>
97
+ async (
98
+ walletInstanceAttestation: string,
113
99
  requestUri: string,
114
- entity: RpEntityConfiguration
115
- ): Promise<RequestObject> {
116
- const response = await this.appFetch(requestUri, {
100
+ rpEntityConfiguration: RelyingPartyEntityConfiguration
101
+ ): Promise<RequestObjectConf> => {
102
+ const signedWalletInstanceDPoP = await createDPopToken(
103
+ {
104
+ jti: `${uuid.v4()}`,
105
+ htm: "GET",
106
+ htu: requestUri,
107
+ ath: await sha256ToBase64(walletInstanceAttestation),
108
+ },
109
+ wiaCryptoContext
110
+ );
111
+
112
+ const response = await appFetch(requestUri, {
117
113
  method: "GET",
118
114
  headers: {
119
- Authorization: `DPoP ${this.walletInstanceAttestation}`,
115
+ Authorization: `DPoP ${walletInstanceAttestation}`,
120
116
  DPoP: signedWalletInstanceDPoP,
121
117
  },
122
118
  });
@@ -130,9 +126,10 @@ export class RelyingPartySolution {
130
126
  // verify token signature according to RP's entity configuration
131
127
  // to ensure the request object is authentic
132
128
  {
133
- const pubKey = entity.payload.metadata.wallet_relying_party.jwks.find(
134
- ({ kid }) => kid === responseJwt.protectedHeader.kid
135
- );
129
+ const pubKey =
130
+ rpEntityConfiguration.payload.metadata.wallet_relying_party.jwks.find(
131
+ ({ kid }) => kid === responseJwt.protectedHeader.kid
132
+ );
136
133
  if (!pubKey) {
137
134
  throw new NoSuitableKeysFoundInEntityConfiguration(
138
135
  "Request Object signature verification"
@@ -142,67 +139,68 @@ export class RelyingPartySolution {
142
139
  }
143
140
 
144
141
  // parse request object it has the expected shape by specification
145
- const requestObj = RequestObject.parse({
142
+ const requestObject = RequestObject.parse({
146
143
  header: responseJwt.protectedHeader,
147
144
  payload: responseJwt.payload,
148
145
  });
149
146
 
150
- return requestObj;
147
+ return {
148
+ requestObject,
149
+ rpEntityConfiguration,
150
+ walletInstanceAttestation,
151
+ };
151
152
  }
152
153
 
153
154
  throw new IoWalletError(
154
- `Unable to obtain Request Object. Response code: ${response.status}`
155
+ `Unable to obtain Request Object. Response code: ${response.status}
156
+ ${await response.text()}`
155
157
  );
156
- }
158
+ };
157
159
 
158
- /**
159
- * Prepare the Verified Presentation token for a received request object in the context of an authorization request flow.
160
- * The presentation is prepared by disclosing data from provided credentials, according to requested claims
161
- * Each Verified Credential come along with the claims the user accepts to disclose from it.
162
- *
163
- * The returned token is unsigned (sign should be apply by the caller).
164
- *
165
- * @todo accept more than a Verified Credential
166
- *
167
- * @param requestObj The incoming request object, which the requirements for the requested authorization
168
- * @param walletInstanceIdentifier The identifies of the wallt instance that is presenting
169
- * @param presentation The Verified Credential containing user data along with the list of claims to be disclosed.
170
- * @param signKeyId The kid of the key that will be used to sign
171
- * @returns The unsigned Verified Presentation token
172
- * @throws {ClaimsNotFoundBetweenDislosures} If the Verified Credential does not contain one or more requested claims.
173
- *
174
- */
175
- async prepareVpToken(
176
- requestObj: RequestObject,
177
- walletInstanceIdentifier: string,
178
- [vc, claims]: Presentation, // TODO: [SIW-353] support multiple presentations,
179
- signKeyId: string
160
+ /**
161
+ * Prepare the Verified Presentation token for a received request object in the context of an authorization request flow.
162
+ * The presentation is prepared by disclosing data from provided credentials, according to requested claims
163
+ * Each Verified Credential come along with the claims the user accepts to disclose from it.
164
+ *
165
+ * @todo accept more than a Verified Credential
166
+ */
167
+ const prepareVpToken =
168
+ ({ pidCryptoContext }: { pidCryptoContext: CryptoContext }) =>
169
+ async (
170
+ { requestObject, walletInstanceAttestation }: RequestObjectConf,
171
+ [vc, claims]: Presentation // TODO: [SIW-353] support multiple presentations,
180
172
  ): Promise<{
181
173
  vp_token: string;
182
174
  presentation_submission: Record<string, unknown>;
183
- }> {
175
+ }> => {
184
176
  // this throws if vc cannot satisfy all the requested claims
185
177
  const { token: vp, paths } = await disclose(vc, claims);
186
178
 
187
- // TODO: [SIW-359] check all requeste claims of the requestedObj are satisfied
179
+ // obtain issuer from Wallet Instance
180
+ const {
181
+ payload: { iss },
182
+ } = WalletInstanceAttestation.decode(walletInstanceAttestation);
188
183
 
189
- const vp_token = new SignJWT({
190
- vp: vp,
191
- jti: `${uuid.v4()}`,
192
- iss: walletInstanceIdentifier,
193
- nonce: requestObj.payload.nonce,
194
- })
195
- .setAudience(requestObj.payload.response_uri)
196
- .setIssuedAt()
197
- .setExpirationTime("1h")
184
+ const pidKid = await pidCryptoContext.getPublicKey().then((_) => _.kid);
185
+
186
+ // TODO: [SIW-359] check all requeste claims of the requestedObj are satisfied
187
+ const vp_token = await new SignJWT(pidCryptoContext)
198
188
  .setProtectedHeader({
199
189
  typ: "JWT",
200
- alg: "ES256",
201
- kid: signKeyId,
190
+ kid: pidKid,
191
+ })
192
+ .setPayload({
193
+ vp: vp,
194
+ jti: `${uuid.v4()}`,
195
+ iss,
196
+ nonce: requestObject.payload.nonce,
202
197
  })
203
- .toSign();
198
+ .setAudience(requestObject.payload.response_uri)
199
+ .setIssuedAt()
200
+ .setExpirationTime("1h")
201
+ .sign();
204
202
 
205
- const vc_scope = requestObj.payload.scope;
203
+ const vc_scope = requestObject.payload.scope;
206
204
  const presentation_submission = {
207
205
  definition_id: `${uuid.v4()}`,
208
206
  id: `${uuid.v4()}`,
@@ -214,36 +212,49 @@ export class RelyingPartySolution {
214
212
  };
215
213
 
216
214
  return { vp_token, presentation_submission };
217
- }
215
+ };
218
216
 
219
- /**
220
- * Compose and send an Authorization Response in the context of an authorization request flow.
221
- *
222
- * @todo MUST add presentation_submission
223
- *
224
- * @param requestObj The incoming request object, which the requirements for the requested authorization
225
- * @param vp_token The signed Verified Presentation token with data to send.
226
- * @param presentation_submission
227
- * @param entity The RP entity configuration
228
- * @returns The response from the RP
229
- * @throws {IoWalletError} if the submission fails.
230
- * @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key
231
- *
232
- */
233
- async sendAuthorizationResponse(
234
- requestObj: RequestObject,
235
- vp_token: string,
236
- presentation_submission: Record<string, unknown>,
237
- entity: RpEntityConfiguration
238
- ): Promise<string> {
217
+ /**
218
+ * Compose and send an Authorization Response in the context of an authorization request flow.
219
+ *
220
+ * @todo MUST add presentation_submission
221
+ *
222
+ */
223
+ export const sendAuthorizationResponse =
224
+ ({
225
+ pidCryptoContext,
226
+ appFetch = fetch,
227
+ }: {
228
+ pidCryptoContext: CryptoContext;
229
+ appFetch?: GlobalFetch["fetch"];
230
+ }) =>
231
+ async (
232
+ {
233
+ requestObject,
234
+ rpEntityConfiguration,
235
+ walletInstanceAttestation,
236
+ }: RequestObjectConf,
237
+ presentation: Presentation // TODO: [SIW-353] support multiple presentations,
238
+ ): Promise<string> => {
239
239
  // the request is an unsigned jws without iss, aud, exp
240
240
  // https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-signed-and-encrypted-respon
241
- const jwk = this.chooseRSAPublicKeyToEncrypt(entity);
241
+ const jwk = chooseRSAPublicKeyToEncrypt(rpEntityConfiguration);
242
+
243
+ const { vp_token, presentation_submission } = await prepareVpToken({
244
+ pidCryptoContext,
245
+ })(
246
+ {
247
+ requestObject,
248
+ rpEntityConfiguration,
249
+ walletInstanceAttestation,
250
+ },
251
+ presentation
252
+ );
242
253
 
243
254
  const authzResponsePayload = JSON.stringify({
244
- state: requestObj.payload.state,
255
+ state: requestObject.payload.state,
245
256
  presentation_submission,
246
- nonce: requestObj.payload.nonce,
257
+ nonce: requestObject.payload.nonce,
247
258
  vp_token,
248
259
  });
249
260
 
@@ -256,7 +267,7 @@ export class RelyingPartySolution {
256
267
  const formBody = new URLSearchParams({ response: encrypted });
257
268
  const body = formBody.toString();
258
269
 
259
- const response = await this.appFetch(requestObj.payload.response_uri, {
270
+ const response = await appFetch(requestObject.payload.response_uri, {
260
271
  method: "POST",
261
272
  headers: {
262
273
  "Content-Type": "application/x-www-form-urlencoded",
@@ -273,37 +284,4 @@ export class RelyingPartySolution {
273
284
  response.status
274
285
  }`
275
286
  );
276
- }
277
-
278
- /**
279
- * Select a RSA public key from those provided by the RP to encrypt.
280
- *
281
- * @param entity The RP entity configuration
282
- * @returns A suitable public key with its compatible encryption algorithm
283
- * @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key suitable for encrypting
284
- */
285
- private chooseRSAPublicKeyToEncrypt(entity: RpEntityConfiguration): JWK {
286
- const [usingRsa256] =
287
- entity.payload.metadata.wallet_relying_party.jwks.filter(
288
- (jwk) => jwk.use === "enc" && jwk.kty === "RSA"
289
- );
290
-
291
- if (usingRsa256) {
292
- return usingRsa256;
293
- }
294
-
295
- // No suitable key has been found
296
- throw new NoSuitableKeysFoundInEntityConfiguration(
297
- "Encrypt with RP public key"
298
- );
299
- }
300
-
301
- /**
302
- * Obtain the relying party entity configuration.
303
- */
304
- async getEntityConfiguration(): Promise<RpEntityConfiguration> {
305
- return getEntityConfiguration(this.relyingPartyBaseUrl, {
306
- appFetch: this.appFetch,
307
- }).then(RpEntityConfiguration.parse);
308
- }
309
- }
287
+ };
package/src/rp/types.ts CHANGED
@@ -1,7 +1,5 @@
1
- import { JWK } from "../utils/jwk";
2
1
  import { UnixTime } from "../sd-jwt/types";
3
2
  import * as z from "zod";
4
- import { EntityConfiguration } from "../trust/types";
5
3
 
6
4
  export type RequestObject = z.infer<typeof RequestObject>;
7
5
  export const RequestObject = z.object({
@@ -27,28 +25,6 @@ export const RequestObject = z.object({
27
25
  }),
28
26
  });
29
27
 
30
- /**
31
- * EntityConfiguration plus the metadata specific for a Relying Party entity.
32
- */
33
- export type RpEntityConfiguration = z.infer<typeof RpEntityConfiguration>;
34
- export const RpEntityConfiguration = EntityConfiguration.and(
35
- z.object({
36
- payload: z.object({
37
- metadata: z.object({
38
- wallet_relying_party: z
39
- .object({
40
- application_type: z.string().optional(),
41
- client_id: z.string().optional(),
42
- client_name: z.string().optional(),
43
- jwks: z.array(JWK),
44
- contacts: z.array(z.string()).optional(),
45
- })
46
- .passthrough(),
47
- }),
48
- }),
49
- })
50
- );
51
-
52
28
  export type QRCodePayload = z.infer<typeof QRCodePayload>;
53
29
  export const QRCodePayload = z.object({
54
30
  protocol: z.string(),
@@ -7,7 +7,7 @@ import { sha256ToBase64 } from "@pagopa/io-react-native-jwt";
7
7
  import { decodeBase64 } from "@pagopa/io-react-native-jwt";
8
8
  import { Disclosure, SdJwt4VC, type DisclosureWithEncoded } from "./types";
9
9
  import { verifyDisclosure } from "./verifier";
10
- import type { JWK } from "src/utils/jwk";
10
+ import type { JWK } from "../utils/jwk";
11
11
  import {
12
12
  ClaimsNotFoundBetweenDislosures,
13
13
  ClaimsNotFoundInToken,
@@ -1,27 +1,82 @@
1
1
  import { decode as decodeJwt } from "@pagopa/io-react-native-jwt";
2
- import { EntityConfiguration } from "./types";
2
+ import {
3
+ WalletProviderEntityConfiguration,
4
+ TrustAnchorEntityConfiguration,
5
+ CredentialIssuerEntityConfiguration,
6
+ RelyingPartyEntityConfiguration,
7
+ EntityConfiguration,
8
+ } from "./types";
3
9
  import { IoWalletError } from "../utils/errors";
4
10
  import { verifyTrustChain } from "./chain";
5
11
 
6
12
  export { verifyTrustChain };
7
13
 
8
14
  /**
9
- * Fetch and parse teh entity configuration document for a given federation entity
15
+ * Fetch and parse the entity configuration document for a given federation entity.
16
+ * This is an inner method to serve public interfaces.
17
+ *
18
+ * To add another entity configuration type (example: Foo entity type):
19
+ * - create its zod schema and type by inherit from the base type (example: FooEntityConfiguration = BaseEntityConfiguration.and(...))
20
+ * - add such type to EntityConfiguration union
21
+ * - add an overload to this function
22
+ * - create a public function which use such type (example: getFooEntityConfiguration = (url, options) => Promise<FooEntityConfiguration>)
10
23
  *
11
24
  * @param entityBaseUrl The base url of the entity.
25
+ * @param schema The expected schema of the entity configuration, according to the kind of entity we are fetching from.
12
26
  * @param options.appFetch An optional instance of the http client to be used.
13
27
  * @returns The parsed entity configuration object
14
28
  * @throws {IoWalletError} If the http request fails
15
29
  * @throws Parse error if the document is not in the expected shape.
16
30
  */
17
- export async function getEntityConfiguration(
31
+ async function fetchAndParseEntityConfiguration(
32
+ entityBaseUrl: string,
33
+ schema: typeof WalletProviderEntityConfiguration,
34
+ options?: {
35
+ appFetch?: GlobalFetch["fetch"];
36
+ }
37
+ ): Promise<WalletProviderEntityConfiguration>;
38
+ async function fetchAndParseEntityConfiguration(
39
+ entityBaseUrl: string,
40
+ schema: typeof RelyingPartyEntityConfiguration,
41
+ options?: {
42
+ appFetch?: GlobalFetch["fetch"];
43
+ }
44
+ ): Promise<RelyingPartyEntityConfiguration>;
45
+ async function fetchAndParseEntityConfiguration(
46
+ entityBaseUrl: string,
47
+ schema: typeof TrustAnchorEntityConfiguration,
48
+ options?: {
49
+ appFetch?: GlobalFetch["fetch"];
50
+ }
51
+ ): Promise<TrustAnchorEntityConfiguration>;
52
+ async function fetchAndParseEntityConfiguration(
53
+ entityBaseUrl: string,
54
+ schema: typeof CredentialIssuerEntityConfiguration,
55
+ options?: {
56
+ appFetch?: GlobalFetch["fetch"];
57
+ }
58
+ ): Promise<CredentialIssuerEntityConfiguration>;
59
+ async function fetchAndParseEntityConfiguration(
18
60
  entityBaseUrl: string,
61
+ schema: typeof EntityConfiguration,
62
+ options?: {
63
+ appFetch?: GlobalFetch["fetch"];
64
+ }
65
+ ): Promise<EntityConfiguration>;
66
+ async function fetchAndParseEntityConfiguration(
67
+ entityBaseUrl: string,
68
+ schema: /* FIXME: why is it different from "typeof EntityConfiguration"? */
69
+ | typeof CredentialIssuerEntityConfiguration
70
+ | typeof WalletProviderEntityConfiguration
71
+ | typeof RelyingPartyEntityConfiguration
72
+ | typeof TrustAnchorEntityConfiguration
73
+ | typeof EntityConfiguration,
19
74
  {
20
75
  appFetch = fetch,
21
76
  }: {
22
77
  appFetch?: GlobalFetch["fetch"];
23
78
  } = {}
24
- ): Promise<EntityConfiguration> {
79
+ ) {
25
80
  const wellKnownUrl = `${entityBaseUrl}/.well-known/openid-federation`;
26
81
 
27
82
  const response = await appFetch(wellKnownUrl, {
@@ -31,7 +86,7 @@ export async function getEntityConfiguration(
31
86
  if (response.status === 200) {
32
87
  const responseText = await response.text();
33
88
  const responseJwt = decodeJwt(responseText);
34
- return EntityConfiguration.parse({
89
+ return schema.parse({
35
90
  header: responseJwt.protectedHeader,
36
91
  payload: responseJwt.payload,
37
92
  });
@@ -41,3 +96,49 @@ export async function getEntityConfiguration(
41
96
  `Unable to obtain Entity Configuration at ${wellKnownUrl}. Response code: ${response.status}`
42
97
  );
43
98
  }
99
+
100
+ export const getWalletProviderEntityConfiguration = (
101
+ entityBaseUrl: Parameters<typeof fetchAndParseEntityConfiguration>[0],
102
+ options?: Parameters<typeof fetchAndParseEntityConfiguration>[2]
103
+ ) =>
104
+ fetchAndParseEntityConfiguration(
105
+ entityBaseUrl,
106
+ WalletProviderEntityConfiguration,
107
+ options
108
+ );
109
+
110
+ export const getCredentialIssuerEntityConfiguration = (
111
+ entityBaseUrl: Parameters<typeof fetchAndParseEntityConfiguration>[0],
112
+ options?: Parameters<typeof fetchAndParseEntityConfiguration>[2]
113
+ ) =>
114
+ fetchAndParseEntityConfiguration(
115
+ entityBaseUrl,
116
+ CredentialIssuerEntityConfiguration,
117
+ options
118
+ );
119
+
120
+ export const getTrustAnchorEntityConfiguration = (
121
+ entityBaseUrl: Parameters<typeof fetchAndParseEntityConfiguration>[0],
122
+ options?: Parameters<typeof fetchAndParseEntityConfiguration>[2]
123
+ ) =>
124
+ fetchAndParseEntityConfiguration(
125
+ entityBaseUrl,
126
+ TrustAnchorEntityConfiguration,
127
+ options
128
+ );
129
+
130
+ export const getRelyingPartyEntityConfiguration = (
131
+ entityBaseUrl: Parameters<typeof fetchAndParseEntityConfiguration>[0],
132
+ options?: Parameters<typeof fetchAndParseEntityConfiguration>[2]
133
+ ) =>
134
+ fetchAndParseEntityConfiguration(
135
+ entityBaseUrl,
136
+ RelyingPartyEntityConfiguration,
137
+ options
138
+ );
139
+
140
+ export const getEntityConfiguration = (
141
+ entityBaseUrl: Parameters<typeof fetchAndParseEntityConfiguration>[0],
142
+ options?: Parameters<typeof fetchAndParseEntityConfiguration>[2]
143
+ ) =>
144
+ fetchAndParseEntityConfiguration(entityBaseUrl, EntityConfiguration, options);