@pagopa/io-react-native-wallet 0.21.0 → 0.23.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/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 +5 -4
- 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
|
+
};
|