@pagopa/io-react-native-wallet 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. package/README.md +1 -1
  2. package/lib/commonjs/index.js +12 -5
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/pid/index.js +7 -0
  5. package/lib/commonjs/pid/index.js.map +1 -1
  6. package/lib/commonjs/pid/issuing.js +231 -0
  7. package/lib/commonjs/pid/issuing.js.map +1 -0
  8. package/lib/commonjs/rp/__test__/index.test.js +18 -0
  9. package/lib/commonjs/rp/__test__/index.test.js.map +1 -0
  10. package/lib/commonjs/rp/index.js +116 -0
  11. package/lib/commonjs/rp/index.js.map +1 -0
  12. package/lib/commonjs/rp/types.js +72 -0
  13. package/lib/commonjs/rp/types.js.map +1 -0
  14. package/lib/commonjs/sd-jwt/types.js +1 -1
  15. package/lib/commonjs/sd-jwt/types.js.map +1 -1
  16. package/lib/commonjs/utils/dpop.js +27 -0
  17. package/lib/commonjs/utils/dpop.js.map +1 -0
  18. package/lib/commonjs/utils/errors.js +49 -1
  19. package/lib/commonjs/utils/errors.js.map +1 -1
  20. package/lib/commonjs/wallet-instance-attestation/issuing.js +3 -5
  21. package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -1
  22. package/lib/module/index.js +4 -4
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/pid/index.js +2 -1
  25. package/lib/module/pid/index.js.map +1 -1
  26. package/lib/module/pid/issuing.js +225 -0
  27. package/lib/module/pid/issuing.js.map +1 -0
  28. package/lib/module/rp/__test__/index.test.js +16 -0
  29. package/lib/module/rp/__test__/index.test.js.map +1 -0
  30. package/lib/module/rp/index.js +108 -0
  31. package/lib/module/rp/index.js.map +1 -0
  32. package/lib/module/rp/types.js +63 -0
  33. package/lib/module/rp/types.js.map +1 -0
  34. package/lib/module/sd-jwt/types.js +1 -1
  35. package/lib/module/sd-jwt/types.js.map +1 -1
  36. package/lib/module/utils/dpop.js +17 -0
  37. package/lib/module/utils/dpop.js.map +1 -0
  38. package/lib/module/utils/errors.js +46 -0
  39. package/lib/module/utils/errors.js.map +1 -1
  40. package/lib/module/wallet-instance-attestation/issuing.js +3 -5
  41. package/lib/module/wallet-instance-attestation/issuing.js.map +1 -1
  42. package/lib/typescript/index.d.ts +4 -2
  43. package/lib/typescript/index.d.ts.map +1 -1
  44. package/lib/typescript/pid/index.d.ts +2 -1
  45. package/lib/typescript/pid/index.d.ts.map +1 -1
  46. package/lib/typescript/pid/issuing.d.ts +93 -0
  47. package/lib/typescript/pid/issuing.d.ts.map +1 -0
  48. package/lib/typescript/rp/__test__/index.test.d.ts +2 -0
  49. package/lib/typescript/rp/__test__/index.test.d.ts.map +1 -0
  50. package/lib/typescript/rp/index.d.ts +43 -0
  51. package/lib/typescript/rp/index.d.ts.map +1 -0
  52. package/lib/typescript/rp/types.d.ts +840 -0
  53. package/lib/typescript/rp/types.d.ts.map +1 -0
  54. package/lib/typescript/sd-jwt/types.d.ts +5 -5
  55. package/lib/typescript/utils/dpop.d.ts +21 -0
  56. package/lib/typescript/utils/dpop.d.ts.map +1 -0
  57. package/lib/typescript/utils/errors.d.ts +26 -0
  58. package/lib/typescript/utils/errors.d.ts.map +1 -1
  59. package/lib/typescript/wallet-instance-attestation/issuing.d.ts +3 -3
  60. package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -1
  61. package/lib/typescript/wallet-instance-attestation/types.d.ts +4 -4
  62. package/package.json +4 -2
  63. package/src/index.ts +4 -5
  64. package/src/pid/index.ts +2 -1
  65. package/src/pid/issuing.ts +305 -0
  66. package/src/rp/__test__/index.test.ts +23 -0
  67. package/src/rp/index.ts +150 -0
  68. package/src/rp/types.ts +64 -0
  69. package/src/sd-jwt/types.ts +1 -1
  70. package/src/utils/dpop.ts +25 -0
  71. package/src/utils/errors.ts +48 -0
  72. package/src/wallet-instance-attestation/issuing.ts +9 -7
@@ -0,0 +1,150 @@
1
+ import { AuthRequestDecodeError, IoWalletError } from "../utils/errors";
2
+ import {
3
+ decode as decodeJwt,
4
+ decodeBase64,
5
+ sha256ToBase64,
6
+ SignJWT,
7
+ } from "@pagopa/io-react-native-jwt";
8
+ import { RequestObject, RpEntityConfiguration } from "./types";
9
+
10
+ import uuid from "react-native-uuid";
11
+ import type { JWK } from "@pagopa/io-react-native-jwt/lib/typescript/types";
12
+
13
+ export class RelyingPartySolution {
14
+ relyingPartyBaseUrl: string;
15
+ walletInstanceAttestation: string;
16
+ appFetch: GlobalFetch["fetch"];
17
+
18
+ constructor(
19
+ relyingPartyBaseUrl: string,
20
+ walletInstanceAttestation: string,
21
+ appFetch: GlobalFetch["fetch"] = fetch
22
+ ) {
23
+ this.relyingPartyBaseUrl = relyingPartyBaseUrl;
24
+ this.walletInstanceAttestation = walletInstanceAttestation;
25
+ this.appFetch = appFetch;
26
+ }
27
+
28
+ /**
29
+ * Decode a QR code content to an authentication request url.
30
+ * @function
31
+ * @param qrcode QR code content
32
+ *
33
+ * @returns The authentication request url
34
+ *
35
+ */
36
+ decodeAuthRequestQR(qrcode: string): string {
37
+ try {
38
+ const decoded = decodeBase64(qrcode);
39
+ const decodedUrl = new URL(decoded);
40
+ const requestUri = decodedUrl.searchParams.get("request_uri");
41
+ if (requestUri) {
42
+ return requestUri;
43
+ } else {
44
+ throw new AuthRequestDecodeError(
45
+ "Unable to obtain request_uri from QR code",
46
+ `${decodedUrl}`
47
+ );
48
+ }
49
+ } catch {
50
+ throw new AuthRequestDecodeError(
51
+ "Unable to decode QR code authentication request url",
52
+ qrcode
53
+ );
54
+ }
55
+ }
56
+ /**
57
+ * Obtain the unsigned wallet instance DPoP for authentication request
58
+ *
59
+ * @function
60
+ * @param walletInstanceAttestationJwk JWT of the Wallet Instance Attestation
61
+ * @param authRequestUrl authentication request url
62
+ *
63
+ * @returns The unsigned wallet instance DPoP
64
+ *
65
+ */
66
+ async getUnsignedWalletInstanceDPoP(
67
+ walletInstanceAttestationJwk: JWK,
68
+ authRequestUrl: string
69
+ ): Promise<string> {
70
+ return await new SignJWT({
71
+ jti: `${uuid.v4()}`,
72
+ htm: "GET",
73
+ htu: authRequestUrl,
74
+ ath: await sha256ToBase64(this.walletInstanceAttestation),
75
+ })
76
+ .setProtectedHeader({
77
+ alg: "ES256",
78
+ jwk: walletInstanceAttestationJwk,
79
+ typ: "dpop+jwt",
80
+ })
81
+ .setIssuedAt()
82
+ .setExpirationTime("1h")
83
+ .toSign();
84
+ }
85
+
86
+ /**
87
+ * Obtain the Request Object for RP authentication
88
+ *
89
+ * @function
90
+ * @param signedWalletInstanceDPoP JWT of the Wallet Instance Attestation DPoP
91
+ *
92
+ * @returns The Request Object JWT
93
+ *
94
+ */
95
+ async getRequestObject(
96
+ signedWalletInstanceDPoP: string
97
+ ): Promise<RequestObject> {
98
+ const decodedJwtDPop = await decodeJwt(signedWalletInstanceDPoP);
99
+ const requestUri = decodedJwtDPop.payload.htu as string;
100
+
101
+ const response = await this.appFetch(requestUri, {
102
+ method: "GET",
103
+ headers: {
104
+ Authorization: `DPoP ${this.walletInstanceAttestation}`,
105
+ DPoP: signedWalletInstanceDPoP,
106
+ },
107
+ });
108
+
109
+ if (response.status === 200) {
110
+ const responseText = await response.text();
111
+ const responseJwt = await decodeJwt(responseText);
112
+ const requestObj = RequestObject.parse({
113
+ header: responseJwt.protectedHeader,
114
+ payload: responseJwt.payload,
115
+ });
116
+ return requestObj;
117
+ }
118
+
119
+ throw new IoWalletError(
120
+ `Unable to obtain Request Object. Response code: ${response.status}`
121
+ );
122
+ }
123
+
124
+ /**
125
+ * Obtain the relying party entity configuration.
126
+ */
127
+ async getEntityConfiguration(): Promise<RpEntityConfiguration> {
128
+ const wellKnownUrl = new URL(
129
+ "/.well-known/openid-federation",
130
+ this.relyingPartyBaseUrl
131
+ ).href;
132
+
133
+ const response = await this.appFetch(wellKnownUrl, {
134
+ method: "GET",
135
+ });
136
+
137
+ if (response.status === 200) {
138
+ const responseText = await response.text();
139
+ const responseJwt = await decodeJwt(responseText);
140
+ return RpEntityConfiguration.parse({
141
+ header: responseJwt.protectedHeader,
142
+ payload: responseJwt.payload,
143
+ });
144
+ }
145
+
146
+ throw new IoWalletError(
147
+ `Unable to obtain RP Entity Configuration. Response code: ${response.status}`
148
+ );
149
+ }
150
+ }
@@ -0,0 +1,64 @@
1
+ import { JWK } from "../utils/jwk";
2
+ import { UnixTime } from "../sd-jwt/types";
3
+ import * as z from "zod";
4
+
5
+ export type RequestObject = z.infer<typeof RequestObject>;
6
+ export const RequestObject = z.object({
7
+ header: z.object({
8
+ typ: z.literal("JWT"),
9
+ alg: z.string(),
10
+ kid: z.string(),
11
+ trust_chain: z.array(z.string()),
12
+ }),
13
+ payload: z.object({
14
+ iss: z.string(),
15
+ iat: UnixTime,
16
+ exp: UnixTime,
17
+ state: z.string(),
18
+ nonce: z.string(),
19
+ response_uri: z.string(),
20
+ response_type: z.literal("vp_token"),
21
+ response_mode: z.literal("direct_post.jwt"),
22
+ client_id: z.string(),
23
+ client_id_scheme: z.literal("entity_id"),
24
+ scope: z.string(),
25
+ }),
26
+ });
27
+
28
+ // TODO: This types is WIP in technical rules
29
+ export type RpEntityConfiguration = z.infer<typeof RpEntityConfiguration>;
30
+ export const RpEntityConfiguration = z.object({
31
+ header: z.object({
32
+ typ: z.literal("entity-statement+jwt"),
33
+ alg: z.string(),
34
+ kid: z.string(),
35
+ }),
36
+ payload: z.object({
37
+ exp: UnixTime,
38
+ iat: UnixTime,
39
+ iss: z.string(),
40
+ sub: z.string(),
41
+ jwks: z.object({
42
+ keys: z.array(JWK),
43
+ }),
44
+ metadata: z.object({
45
+ wallet_relying_party: z.object({
46
+ application_type: z.string(),
47
+ client_id: z.string(),
48
+ client_name: z.string(),
49
+ jwks: z.object({
50
+ keys: z.array(JWK),
51
+ }),
52
+ contacts: z.array(z.string()),
53
+ }),
54
+ federation_entity: z.object({
55
+ organization_name: z.string(),
56
+ homepage_uri: z.string(),
57
+ policy_uri: z.string(),
58
+ logo_uri: z.string(),
59
+ contacts: z.array(z.string()),
60
+ }),
61
+ }),
62
+ authority_hints: z.array(z.string()),
63
+ }),
64
+ });
@@ -25,7 +25,7 @@ export const SdJwt4VC = z.object({
25
25
  header: z.object({
26
26
  typ: z.literal("vc+sd-jwt"),
27
27
  alg: z.string(),
28
- kid: z.string(),
28
+ kid: z.string().optional(),
29
29
  trust_chain: z.array(z.string()),
30
30
  }),
31
31
  payload: z.object({
@@ -0,0 +1,25 @@
1
+ import * as z from "zod";
2
+
3
+ import { SignJWT } from "@pagopa/io-react-native-jwt";
4
+ import type { JWK } from "./jwk";
5
+
6
+ export const getUnsignedDPop = (jwk: JWK, payload: DPoPPayload): string => {
7
+ const dPop = new SignJWT(payload)
8
+ .setProtectedHeader({
9
+ alg: "ES256",
10
+ typ: "dpop+jwt",
11
+ jwk,
12
+ })
13
+ .setIssuedAt()
14
+ .setExpirationTime("1h")
15
+ .toSign();
16
+ return dPop;
17
+ };
18
+
19
+ export type DPoPPayload = z.infer<typeof DPoPPayload>;
20
+ export const DPoPPayload = z.object({
21
+ jti: z.string(),
22
+ htm: z.union([z.literal("POST"), z.literal("GET")]),
23
+ htu: z.string(),
24
+ ath: z.string().optional(),
25
+ });
@@ -72,3 +72,51 @@ export class WalletInstanceAttestationIssuingError extends IoWalletError {
72
72
  this.reason = reason;
73
73
  }
74
74
  }
75
+
76
+ /**
77
+ * An error subclass thrown when auth request decode fail
78
+ *
79
+ */
80
+ export class AuthRequestDecodeError extends IoWalletError {
81
+ static get code(): "ERR_IO_WALLET_AUTHENTICATION_REQUEST_DECODE_FAILED" {
82
+ return "ERR_IO_WALLET_AUTHENTICATION_REQUEST_DECODE_FAILED";
83
+ }
84
+
85
+ code = "ERR_IO_WALLET_AUTHENTICATION_REQUEST_DECODE_FAILED";
86
+
87
+ /** The Claim for which the validation failed. */
88
+ claim: string;
89
+
90
+ /** Reason code for the validation failure. */
91
+ reason: string;
92
+
93
+ constructor(message: string, claim = "unspecified", reason = "unspecified") {
94
+ super(message);
95
+ this.claim = claim;
96
+ this.reason = reason;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * An error subclass thrown when validation fail
102
+ *
103
+ */
104
+ export class PidIssuingError extends IoWalletError {
105
+ static get code(): "ERR_IO_WALLET_PID_ISSUING_FAILED" {
106
+ return "ERR_IO_WALLET_PID_ISSUING_FAILED";
107
+ }
108
+
109
+ code = "ERR_IO_WALLET_PID_ISSUING_FAILED";
110
+
111
+ /** The Claim for which the validation failed. */
112
+ claim: string;
113
+
114
+ /** Reason code for the validation failure. */
115
+ reason: string;
116
+
117
+ constructor(message: string, claim = "unspecified", reason = "unspecified") {
118
+ super(message);
119
+ this.claim = claim;
120
+ this.reason = reason;
121
+ }
122
+ }
@@ -8,9 +8,13 @@ import { WalletInstanceAttestationIssuingError } from "../utils/errors";
8
8
 
9
9
  export class Issuing {
10
10
  walletProviderBaseUrl: string;
11
-
12
- constructor(walletProviderBaseUrl: string) {
11
+ appFetch: GlobalFetch["fetch"];
12
+ constructor(
13
+ walletProviderBaseUrl: string,
14
+ appFetch: GlobalFetch["fetch"] = fetch
15
+ ) {
13
16
  this.walletProviderBaseUrl = walletProviderBaseUrl;
17
+ this.appFetch = appFetch;
14
18
  }
15
19
 
16
20
  /**
@@ -58,16 +62,14 @@ export class Issuing {
58
62
  * @param attestationRequest Wallet Instance Attestaion Request
59
63
  * obtained with {@link getAttestationRequestToSign}
60
64
  * @param signature Signature of the Wallet Instance Attestaion Request
61
- * @param appFetch Optional object with fetch function to use
62
65
  *
63
66
  * @returns {string} Wallet Instance Attestation
64
67
  *
65
68
  */
66
69
  async getAttestation(
67
70
  attestationRequest: string,
68
- signature: string,
69
- appFetch: GlobalFetch = { fetch }
70
- ): Promise<String> {
71
+ signature: string
72
+ ): Promise<string> {
71
73
  const signedAttestationRequest = await SignJWT.appendSignature(
72
74
  attestationRequest,
73
75
  signature
@@ -87,7 +89,7 @@ export class Issuing {
87
89
  "urn:ietf:params:oauth:client-assertion-type:jwt-key-attestation",
88
90
  assertion: signedAttestationRequest,
89
91
  };
90
- const response = await appFetch.fetch(tokenUrl, {
92
+ const response = await this.appFetch(tokenUrl, {
91
93
  method: "POST",
92
94
  headers: {
93
95
  "Content-Type": "application/json",