@pagopa/io-react-native-wallet 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/commonjs/index.js +12 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/pid/index.js +7 -0
- package/lib/commonjs/pid/index.js.map +1 -1
- package/lib/commonjs/pid/issuing.js +231 -0
- package/lib/commonjs/pid/issuing.js.map +1 -0
- package/lib/commonjs/rp/__test__/index.test.js +18 -0
- package/lib/commonjs/rp/__test__/index.test.js.map +1 -0
- package/lib/commonjs/rp/index.js +116 -0
- package/lib/commonjs/rp/index.js.map +1 -0
- package/lib/commonjs/rp/types.js +72 -0
- package/lib/commonjs/rp/types.js.map +1 -0
- package/lib/commonjs/sd-jwt/types.js +1 -1
- package/lib/commonjs/sd-jwt/types.js.map +1 -1
- package/lib/commonjs/utils/dpop.js +27 -0
- package/lib/commonjs/utils/dpop.js.map +1 -0
- package/lib/commonjs/utils/errors.js +49 -1
- package/lib/commonjs/utils/errors.js.map +1 -1
- package/lib/commonjs/wallet-instance-attestation/issuing.js +3 -5
- package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -1
- package/lib/module/index.js +4 -4
- package/lib/module/index.js.map +1 -1
- package/lib/module/pid/index.js +2 -1
- package/lib/module/pid/index.js.map +1 -1
- package/lib/module/pid/issuing.js +225 -0
- package/lib/module/pid/issuing.js.map +1 -0
- package/lib/module/rp/__test__/index.test.js +16 -0
- package/lib/module/rp/__test__/index.test.js.map +1 -0
- package/lib/module/rp/index.js +108 -0
- package/lib/module/rp/index.js.map +1 -0
- package/lib/module/rp/types.js +63 -0
- package/lib/module/rp/types.js.map +1 -0
- package/lib/module/sd-jwt/types.js +1 -1
- package/lib/module/sd-jwt/types.js.map +1 -1
- package/lib/module/utils/dpop.js +17 -0
- package/lib/module/utils/dpop.js.map +1 -0
- package/lib/module/utils/errors.js +46 -0
- package/lib/module/utils/errors.js.map +1 -1
- package/lib/module/wallet-instance-attestation/issuing.js +3 -5
- package/lib/module/wallet-instance-attestation/issuing.js.map +1 -1
- package/lib/typescript/index.d.ts +4 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/pid/index.d.ts +2 -1
- package/lib/typescript/pid/index.d.ts.map +1 -1
- package/lib/typescript/pid/issuing.d.ts +93 -0
- package/lib/typescript/pid/issuing.d.ts.map +1 -0
- package/lib/typescript/rp/__test__/index.test.d.ts +2 -0
- package/lib/typescript/rp/__test__/index.test.d.ts.map +1 -0
- package/lib/typescript/rp/index.d.ts +43 -0
- package/lib/typescript/rp/index.d.ts.map +1 -0
- package/lib/typescript/rp/types.d.ts +840 -0
- package/lib/typescript/rp/types.d.ts.map +1 -0
- package/lib/typescript/sd-jwt/types.d.ts +5 -5
- package/lib/typescript/utils/dpop.d.ts +21 -0
- package/lib/typescript/utils/dpop.d.ts.map +1 -0
- package/lib/typescript/utils/errors.d.ts +26 -0
- package/lib/typescript/utils/errors.d.ts.map +1 -1
- package/lib/typescript/wallet-instance-attestation/issuing.d.ts +3 -3
- package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -1
- package/lib/typescript/wallet-instance-attestation/types.d.ts +4 -4
- package/package.json +4 -2
- package/src/index.ts +4 -5
- package/src/pid/index.ts +2 -1
- package/src/pid/issuing.ts +305 -0
- package/src/rp/__test__/index.test.ts +23 -0
- package/src/rp/index.ts +150 -0
- package/src/rp/types.ts +64 -0
- package/src/sd-jwt/types.ts +1 -1
- package/src/utils/dpop.ts +25 -0
- package/src/utils/errors.ts +48 -0
- package/src/wallet-instance-attestation/issuing.ts +9 -7
package/src/rp/index.ts
ADDED
@@ -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
|
+
}
|
package/src/rp/types.ts
ADDED
@@ -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
|
+
});
|
package/src/sd-jwt/types.ts
CHANGED
@@ -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
|
+
});
|
package/src/utils/errors.ts
CHANGED
@@ -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(
|
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
|
-
|
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
|
92
|
+
const response = await this.appFetch(tokenUrl, {
|
91
93
|
method: "POST",
|
92
94
|
headers: {
|
93
95
|
"Content-Type": "application/json",
|