@pagopa/io-react-native-wallet 0.21.0 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- package/lib/commonjs/credential/issuance/06-obtain-credential.js +5 -4
- package/lib/commonjs/credential/issuance/06-obtain-credential.js.map +1 -1
- package/lib/commonjs/credential/issuance/README.md +7 -5
- package/lib/commonjs/credential/issuance/types.js +5 -1
- package/lib/commonjs/credential/issuance/types.js.map +1 -1
- package/lib/commonjs/credential/status/02-status-attestation.js +4 -3
- package/lib/commonjs/credential/status/02-status-attestation.js.map +1 -1
- package/lib/commonjs/credential/status/README.md +1 -1
- package/lib/commonjs/credential/status/types.js +14 -1
- package/lib/commonjs/credential/status/types.js.map +1 -1
- package/lib/commonjs/sd-jwt/index.js +3 -1
- package/lib/commonjs/sd-jwt/index.js.map +1 -1
- package/lib/commonjs/trust/types.js +9 -1
- package/lib/commonjs/trust/types.js.map +1 -1
- package/lib/commonjs/utils/errors.js +57 -29
- package/lib/commonjs/utils/errors.js.map +1 -1
- package/lib/commonjs/utils/misc.js +11 -2
- package/lib/commonjs/utils/misc.js.map +1 -1
- package/lib/module/credential/issuance/06-obtain-credential.js +8 -7
- package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -1
- package/lib/module/credential/issuance/README.md +7 -5
- package/lib/module/credential/issuance/types.js +3 -0
- package/lib/module/credential/issuance/types.js.map +1 -1
- package/lib/module/credential/status/02-status-attestation.js +7 -6
- package/lib/module/credential/status/02-status-attestation.js.map +1 -1
- package/lib/module/credential/status/README.md +1 -1
- package/lib/module/credential/status/types.js +12 -0
- package/lib/module/credential/status/types.js.map +1 -1
- package/lib/module/sd-jwt/index.js +3 -2
- package/lib/module/sd-jwt/index.js.map +1 -1
- package/lib/module/trust/types.js +9 -1
- package/lib/module/trust/types.js.map +1 -1
- package/lib/module/utils/errors.js +52 -25
- package/lib/module/utils/errors.js.map +1 -1
- package/lib/module/utils/misc.js +9 -1
- package/lib/module/utils/misc.js.map +1 -1
- package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -1
- package/lib/typescript/credential/issuance/types.d.ts +8 -0
- package/lib/typescript/credential/issuance/types.d.ts.map +1 -1
- package/lib/typescript/credential/status/02-status-attestation.d.ts +1 -1
- package/lib/typescript/credential/status/02-status-attestation.d.ts.map +1 -1
- package/lib/typescript/credential/status/types.d.ts +15 -0
- package/lib/typescript/credential/status/types.d.ts.map +1 -1
- package/lib/typescript/sd-jwt/index.d.ts.map +1 -1
- package/lib/typescript/trust/index.d.ts +14 -0
- package/lib/typescript/trust/index.d.ts.map +1 -1
- package/lib/typescript/trust/types.d.ts +194 -0
- package/lib/typescript/trust/types.d.ts.map +1 -1
- package/lib/typescript/utils/errors.d.ts +31 -14
- package/lib/typescript/utils/errors.d.ts.map +1 -1
- package/lib/typescript/utils/misc.d.ts +1 -0
- package/lib/typescript/utils/misc.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/credential/issuance/06-obtain-credential.ts +11 -7
- package/src/credential/issuance/README.md +7 -5
- package/src/credential/issuance/types.ts +8 -0
- package/src/credential/status/02-status-attestation.ts +13 -5
- package/src/credential/status/README.md +1 -1
- package/src/credential/status/types.ts +15 -0
- package/src/sd-jwt/index.ts +3 -3
- package/src/trust/types.ts +12 -0
- package/src/utils/errors.ts +72 -26
- package/src/utils/misc.ts +12 -4
@@ -5,16 +5,16 @@ import {
|
|
5
5
|
} from "@pagopa/io-react-native-jwt";
|
6
6
|
import type { AuthorizeAccess } from "./05-authorize-access";
|
7
7
|
import type { EvaluateIssuerTrust } from "./02-evaluate-issuer-trust";
|
8
|
-
import { hasStatus, type Out } from "../../utils/misc";
|
8
|
+
import { hasStatus, safeJsonParse, type Out } from "../../utils/misc";
|
9
9
|
import type { StartUserAuthorization } from "./03-start-user-authorization";
|
10
10
|
import {
|
11
|
+
CredentialInvalidStatusError,
|
11
12
|
CredentialIssuingNotSynchronousError,
|
12
|
-
CredentialNotEntitledError,
|
13
13
|
CredentialRequestError,
|
14
14
|
UnexpectedStatusCodeError,
|
15
15
|
ValidationFailed,
|
16
16
|
} from "../../utils/errors";
|
17
|
-
import { CredentialResponse } from "./types";
|
17
|
+
import { CredentialIssuanceFailureResponse, CredentialResponse } from "./types";
|
18
18
|
|
19
19
|
import { createDPopToken } from "../../utils/dpop";
|
20
20
|
import uuid from "react-native-uuid";
|
@@ -157,8 +157,8 @@ export const obtainCredential: ObtainCredential = async (
|
|
157
157
|
* Handle the credential error by mapping it to a custom exception.
|
158
158
|
* If the error is not an instance of {@link UnexpectedStatusCodeError}, it is thrown as is.
|
159
159
|
* @param e - The error to be handled
|
160
|
-
* @throws {@link
|
161
|
-
* @throws {@link
|
160
|
+
* @throws {@link CredentialRequestError} if the status code is different from 404
|
161
|
+
* @throws {@link CredentialInvalidStatusError} if the status code is 404 (meaning the credential is invalid)
|
162
162
|
*/
|
163
163
|
const handleObtainCredentialError = (e: unknown) => {
|
164
164
|
if (!(e instanceof UnexpectedStatusCodeError)) {
|
@@ -174,9 +174,13 @@ const handleObtainCredentialError = (e: unknown) => {
|
|
174
174
|
);
|
175
175
|
}
|
176
176
|
|
177
|
-
if (e.statusCode
|
178
|
-
|
177
|
+
if ([403, 404].includes(e.statusCode)) {
|
178
|
+
const maybeError = CredentialIssuanceFailureResponse.safeParse(
|
179
|
+
safeJsonParse(e.responseBody)
|
180
|
+
);
|
181
|
+
throw new CredentialInvalidStatusError(
|
179
182
|
"Invalid status found for the given credential",
|
183
|
+
maybeError.success ? maybeError.data.error : "unknown",
|
180
184
|
e.message
|
181
185
|
);
|
182
186
|
}
|
@@ -43,14 +43,16 @@ graph TD;
|
|
43
43
|
|
44
44
|
A `201 Created` response is returned by the credential issuer when the request has been queued because the credential cannot be issued synchronously. The consumer should try to obtain the credential at a later time.
|
45
45
|
|
46
|
-
|
46
|
+
Although `201 Created` is not considered an error, it is mapped as an error in this context in order to handle the case where the credential issuance is not synchronous.
|
47
|
+
This allows keeping the flow consistent and handle the case where the credential is not immediately available.
|
47
48
|
|
48
|
-
|
49
|
+
### 403 Forbidden (CredentialInvalidStatusError)
|
49
50
|
|
50
|
-
|
51
|
+
A `403 Forbidden` response is returned by the credential issuer when the requested credential has an invalid status. It might contain more details in the `errorCode` property.
|
51
52
|
|
52
|
-
|
53
|
-
|
53
|
+
### 404 Not Found (CredentialInvalidStatusError)
|
54
|
+
|
55
|
+
A `404 Not Found` response is returned by the credential issuer when the authenticated user is not entitled to receive the requested credential. It might contain more details in the `errorCode` property.
|
54
56
|
|
55
57
|
## Strong authentication for eID issuance (Query Mode)
|
56
58
|
|
@@ -30,3 +30,11 @@ export const ResponseUriResultShape = z.object({
|
|
30
30
|
});
|
31
31
|
|
32
32
|
export type ResponseMode = "query" | "form_post.jwt";
|
33
|
+
|
34
|
+
export const CredentialIssuanceFailureResponse = z.object({
|
35
|
+
error: z.string(),
|
36
|
+
});
|
37
|
+
|
38
|
+
export type CredentialIssuanceFailureResponse = z.infer<
|
39
|
+
typeof CredentialIssuanceFailureResponse
|
40
|
+
>;
|
@@ -1,15 +1,19 @@
|
|
1
1
|
import {
|
2
2
|
getCredentialHashWithouDiscloures,
|
3
3
|
hasStatus,
|
4
|
+
safeJsonParse,
|
4
5
|
type Out,
|
5
6
|
} from "../../utils/misc";
|
6
7
|
import type { EvaluateIssuerTrust, ObtainCredential } from "../issuance";
|
7
8
|
import { SignJWT, type CryptoContext } from "@pagopa/io-react-native-jwt";
|
8
9
|
import uuid from "react-native-uuid";
|
9
|
-
import {
|
10
|
+
import {
|
11
|
+
InvalidStatusAttestationResponse,
|
12
|
+
StatusAttestationResponse,
|
13
|
+
} from "./types";
|
10
14
|
import {
|
11
15
|
StatusAttestationError,
|
12
|
-
|
16
|
+
CredentialInvalidStatusError,
|
13
17
|
UnexpectedStatusCodeError,
|
14
18
|
} from "../../utils/errors";
|
15
19
|
|
@@ -29,7 +33,7 @@ export type StatusAttestation = (
|
|
29
33
|
* @param credential - The credential to be verified
|
30
34
|
* @param credentialCryptoContext - The credential's crypto context
|
31
35
|
* @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
|
32
|
-
* @throws {@link
|
36
|
+
* @throws {@link CredentialInvalidStatusError} if the status attestation is invalid and thus the credential is not valid
|
33
37
|
* @throws {@link StatusAttestationError} if an error occurs during the status attestation
|
34
38
|
* @returns The credential status attestation
|
35
39
|
*/
|
@@ -83,7 +87,7 @@ export const statusAttestation: StatusAttestation = async (
|
|
83
87
|
* If the error is not an instance of {@link UnexpectedStatusCodeError}, it is thrown as is.
|
84
88
|
* @param e - The error to be handled
|
85
89
|
* @throws {@link StatusAttestationError} if the status code is different from 404
|
86
|
-
* @throws {@link
|
90
|
+
* @throws {@link CredentialInvalidStatusError} if the status code is 404 (meaning the credential is invalid)
|
87
91
|
*/
|
88
92
|
const handleStatusAttestationError = (e: unknown) => {
|
89
93
|
if (!(e instanceof UnexpectedStatusCodeError)) {
|
@@ -91,8 +95,12 @@ const handleStatusAttestationError = (e: unknown) => {
|
|
91
95
|
}
|
92
96
|
|
93
97
|
if (e.statusCode === 404) {
|
94
|
-
|
98
|
+
const maybeError = InvalidStatusAttestationResponse.safeParse(
|
99
|
+
safeJsonParse(e.responseBody)
|
100
|
+
);
|
101
|
+
throw new CredentialInvalidStatusError(
|
95
102
|
"Invalid status found for the given credential",
|
103
|
+
maybeError.success ? maybeError.data.error : "unknown",
|
96
104
|
e.message
|
97
105
|
);
|
98
106
|
}
|
@@ -41,3 +41,18 @@ export const ParsedStatusAttestation = z.object({
|
|
41
41
|
iat: UnixTime,
|
42
42
|
}),
|
43
43
|
});
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Shape from parsing a status attestation response in case of error.
|
47
|
+
*/
|
48
|
+
export const InvalidStatusAttestationResponse = z.object({
|
49
|
+
error: z.string(),
|
50
|
+
});
|
51
|
+
|
52
|
+
/**
|
53
|
+
* Type from parsing a status attestation response in case of error.
|
54
|
+
* Inferred from {@link InvalidStatusAttestationResponse}.
|
55
|
+
*/
|
56
|
+
export type InvalidStatusAttestationResponse = z.infer<
|
57
|
+
typeof InvalidStatusAttestationResponse
|
58
|
+
>;
|
package/src/sd-jwt/index.ts
CHANGED
@@ -3,8 +3,6 @@ import { z } from "zod";
|
|
3
3
|
import { decode as decodeJwt } from "@pagopa/io-react-native-jwt";
|
4
4
|
import { verify as verifyJwt } from "@pagopa/io-react-native-jwt";
|
5
5
|
import { sha256ToBase64 } from "@pagopa/io-react-native-jwt";
|
6
|
-
|
7
|
-
import { decodeBase64 } from "@pagopa/io-react-native-jwt";
|
8
6
|
import { Disclosure, SdJwt4VC, type DisclosureWithEncoded } from "./types";
|
9
7
|
import { verifyDisclosure } from "./verifier";
|
10
8
|
import type { JWK } from "../utils/jwk";
|
@@ -12,9 +10,11 @@ import {
|
|
12
10
|
ClaimsNotFoundBetweenDislosures,
|
13
11
|
ClaimsNotFoundInToken,
|
14
12
|
} from "../utils/errors";
|
13
|
+
import { Base64 } from "js-base64";
|
15
14
|
|
16
15
|
const decodeDisclosure = (encoded: string): DisclosureWithEncoded => {
|
17
|
-
const
|
16
|
+
const utf8String = Base64.decode(encoded); // Decode Base64 into UTF-8 string
|
17
|
+
const decoded = Disclosure.parse(JSON.parse(utf8String));
|
18
18
|
return { decoded, encoded };
|
19
19
|
};
|
20
20
|
|
package/src/trust/types.ts
CHANGED
@@ -53,6 +53,17 @@ const ClaimsMetadata = z.record(
|
|
53
53
|
})
|
54
54
|
);
|
55
55
|
|
56
|
+
type IssuanceErrorSupported = z.infer<typeof IssuanceErrorSupported>;
|
57
|
+
const IssuanceErrorSupported = z.object({
|
58
|
+
display: z.array(
|
59
|
+
z.object({
|
60
|
+
title: z.string(),
|
61
|
+
description: z.string(),
|
62
|
+
locale: z.string(),
|
63
|
+
})
|
64
|
+
),
|
65
|
+
});
|
66
|
+
|
56
67
|
// Metadata for a credentia which is supported by a Issuer
|
57
68
|
type SupportedCredentialMetadata = z.infer<typeof SupportedCredentialMetadata>;
|
58
69
|
const SupportedCredentialMetadata = z.object({
|
@@ -63,6 +74,7 @@ const SupportedCredentialMetadata = z.object({
|
|
63
74
|
cryptographic_binding_methods_supported: z.array(z.string()),
|
64
75
|
credential_signing_alg_values_supported: z.array(z.string()),
|
65
76
|
authentic_source: z.string().optional(),
|
77
|
+
issuance_errors_supported: z.record(IssuanceErrorSupported).optional(),
|
66
78
|
});
|
67
79
|
|
68
80
|
export type EntityStatement = z.infer<typeof EntityStatement>;
|
package/src/utils/errors.ts
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
import type { CredentialIssuerEntityConfiguration } from "../trust/types";
|
2
|
+
|
1
3
|
/**
|
2
4
|
* utility to format a set of attributes into an error message string
|
3
5
|
*
|
@@ -56,8 +58,10 @@ export class UnexpectedStatusCodeError extends IoWalletError {
|
|
56
58
|
|
57
59
|
/** HTTP status code */
|
58
60
|
statusCode: number;
|
61
|
+
/** The stringified response body, useful to process the error response */
|
62
|
+
responseBody: string;
|
59
63
|
|
60
|
-
constructor(message: string, statusCode: number) {
|
64
|
+
constructor(message: string, statusCode: number, responseBody: string) {
|
61
65
|
super(
|
62
66
|
serializeAttrs({
|
63
67
|
message,
|
@@ -65,6 +69,7 @@ export class UnexpectedStatusCodeError extends IoWalletError {
|
|
65
69
|
})
|
66
70
|
);
|
67
71
|
this.statusCode = statusCode;
|
72
|
+
this.responseBody = responseBody;
|
68
73
|
}
|
69
74
|
}
|
70
75
|
/**
|
@@ -461,19 +466,28 @@ export class OperationAbortedError extends IoWalletError {
|
|
461
466
|
}
|
462
467
|
|
463
468
|
/**
|
464
|
-
* Error subclass thrown when
|
469
|
+
* Error subclass thrown when a credential status is invalid, either during issuance or when requesting a status attestation.
|
465
470
|
*/
|
466
|
-
export class
|
467
|
-
static get code(): "
|
468
|
-
return "
|
471
|
+
export class CredentialInvalidStatusError extends IoWalletError {
|
472
|
+
static get code(): "ERR_CREDENTIAL_INVALID_STATUS" {
|
473
|
+
return "ERR_CREDENTIAL_INVALID_STATUS";
|
469
474
|
}
|
470
475
|
|
471
|
-
code = "
|
476
|
+
code = "ERR_CREDENTIAL_INVALID_STATUS";
|
472
477
|
|
478
|
+
/**
|
479
|
+
* The error code that should be mapped with one of the `issuance_errors_supported` in the EC.
|
480
|
+
*/
|
481
|
+
errorCode: string;
|
473
482
|
reason: string;
|
474
483
|
|
475
|
-
constructor(
|
476
|
-
|
484
|
+
constructor(
|
485
|
+
message: string,
|
486
|
+
errorCode: string,
|
487
|
+
reason: string = "unspecified"
|
488
|
+
) {
|
489
|
+
super(serializeAttrs({ message, errorCode, reason }));
|
490
|
+
this.errorCode = errorCode;
|
477
491
|
this.reason = reason;
|
478
492
|
}
|
479
493
|
}
|
@@ -496,24 +510,6 @@ export class StatusAttestationError extends IoWalletError {
|
|
496
510
|
}
|
497
511
|
}
|
498
512
|
|
499
|
-
/**
|
500
|
-
* Error subclass thrown when the the user is not entitled to receive the requested credential.
|
501
|
-
*/
|
502
|
-
export class CredentialNotEntitledError extends IoWalletError {
|
503
|
-
static get code(): "CREDENTIAL_NOT_ENTITLED_ERROR" {
|
504
|
-
return "CREDENTIAL_NOT_ENTITLED_ERROR";
|
505
|
-
}
|
506
|
-
|
507
|
-
code = "CREDENTIAL_NOT_ENTITLED_ERROR";
|
508
|
-
|
509
|
-
reason: string;
|
510
|
-
|
511
|
-
constructor(message: string, reason: string = "unspecified") {
|
512
|
-
super(serializeAttrs({ message, reason }));
|
513
|
-
this.reason = reason;
|
514
|
-
}
|
515
|
-
}
|
516
|
-
|
517
513
|
/**
|
518
514
|
* Error subclass thrown when an error occurs while requesting a credential.
|
519
515
|
*/
|
@@ -549,3 +545,53 @@ export class CredentialIssuingNotSynchronousError extends IoWalletError {
|
|
549
545
|
this.reason = reason;
|
550
546
|
}
|
551
547
|
}
|
548
|
+
|
549
|
+
type LocalizedIssuanceError = {
|
550
|
+
[locale: string]: {
|
551
|
+
title: string;
|
552
|
+
description: string;
|
553
|
+
};
|
554
|
+
};
|
555
|
+
|
556
|
+
/**
|
557
|
+
* Function to extract the error message from the Entity Configuration's supported error codes.
|
558
|
+
* @param errorCode The error code to map to a meaningful message
|
559
|
+
* @param params.issuerConf The entity configuration for credentials
|
560
|
+
* @param params.credentialType The type of credential the error belongs to
|
561
|
+
* @returns A localized error {@link LocalizedIssuanceError} or undefined
|
562
|
+
* @throws {Error} When no credential config is found
|
563
|
+
*/
|
564
|
+
export function extractErrorMessageFromIssuerConf(
|
565
|
+
errorCode: string,
|
566
|
+
{
|
567
|
+
issuerConf,
|
568
|
+
credentialType,
|
569
|
+
}: {
|
570
|
+
issuerConf: CredentialIssuerEntityConfiguration["payload"]["metadata"];
|
571
|
+
credentialType: string;
|
572
|
+
}
|
573
|
+
): LocalizedIssuanceError | undefined {
|
574
|
+
const credentialConfiguration =
|
575
|
+
issuerConf.openid_credential_issuer.credential_configurations_supported[
|
576
|
+
credentialType
|
577
|
+
];
|
578
|
+
|
579
|
+
if (!credentialConfiguration) {
|
580
|
+
throw new Error(
|
581
|
+
`No configuration found for ${credentialType} in the provided EC`
|
582
|
+
);
|
583
|
+
}
|
584
|
+
|
585
|
+
const { issuance_errors_supported } = credentialConfiguration;
|
586
|
+
|
587
|
+
if (!issuance_errors_supported?.[errorCode]) {
|
588
|
+
return undefined;
|
589
|
+
}
|
590
|
+
|
591
|
+
const localesList = issuance_errors_supported[errorCode]!.display;
|
592
|
+
|
593
|
+
return localesList.reduce(
|
594
|
+
(acc, { locale, ...rest }) => ({ ...acc, [locale]: rest }),
|
595
|
+
{} as LocalizedIssuanceError
|
596
|
+
);
|
597
|
+
}
|
package/src/utils/misc.ts
CHANGED
@@ -11,11 +11,11 @@ export const hasStatus =
|
|
11
11
|
(status: number) =>
|
12
12
|
async (res: Response): Promise<Response> => {
|
13
13
|
if (res.status !== status) {
|
14
|
+
const responseBody = await res.text();
|
14
15
|
throw new UnexpectedStatusCodeError(
|
15
|
-
`Http request failed. Expected ${status}, got ${res.status}, url: ${
|
16
|
-
|
17
|
-
|
18
|
-
res.status
|
16
|
+
`Http request failed. Expected ${status}, got ${res.status}, url: ${res.url} with response: ${responseBody}`,
|
17
|
+
res.status,
|
18
|
+
responseBody
|
19
19
|
);
|
20
20
|
}
|
21
21
|
return res;
|
@@ -109,3 +109,11 @@ export const createAbortPromiseFromSignal = (signal: AbortSignal) => {
|
|
109
109
|
|
110
110
|
export const isDefined = <T>(x: T | undefined | null | ""): x is T =>
|
111
111
|
Boolean(x);
|
112
|
+
|
113
|
+
export const safeJsonParse = <T>(text: string, withDefault?: T): T | null => {
|
114
|
+
try {
|
115
|
+
return JSON.parse(text);
|
116
|
+
} catch (_) {
|
117
|
+
return withDefault ?? null;
|
118
|
+
}
|
119
|
+
};
|