@pagopa/io-react-native-wallet 2.0.0-next.8 → 2.0.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 +3 -3
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +148 -86
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js +3 -2
- package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
- package/lib/commonjs/credential/status/02-status-assertion.js.map +1 -1
- package/lib/commonjs/credential/status/03-verify-and-parse-status-assertion.js.map +1 -1
- package/lib/commonjs/sd-jwt/types.js +11 -4
- package/lib/commonjs/sd-jwt/types.js.map +1 -1
- package/lib/commonjs/trust/types.js +6 -0
- package/lib/commonjs/trust/types.js.map +1 -1
- package/lib/commonjs/utils/credentials.js +2 -1
- package/lib/commonjs/utils/credentials.js.map +1 -1
- package/lib/commonjs/utils/misc.js +3 -1
- package/lib/commonjs/utils/misc.js.map +1 -1
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js +148 -86
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/module/credential/presentation/07-evaluate-dcql-query.js +3 -2
- package/lib/module/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
- package/lib/module/credential/status/02-status-assertion.js.map +1 -1
- package/lib/module/credential/status/03-verify-and-parse-status-assertion.js.map +1 -1
- package/lib/module/sd-jwt/types.js +9 -2
- package/lib/module/sd-jwt/types.js.map +1 -1
- package/lib/module/trust/types.js +6 -0
- package/lib/module/trust/types.js.map +1 -1
- package/lib/module/utils/credentials.js +2 -1
- package/lib/module/utils/credentials.js.map +1 -1
- package/lib/module/utils/misc.js +1 -0
- package/lib/module/utils/misc.js.map +1 -1
- package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts +1 -1
- package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts +2 -2
- package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts.map +1 -1
- package/lib/typescript/credential/status/02-status-assertion.d.ts +2 -1
- package/lib/typescript/credential/status/02-status-assertion.d.ts.map +1 -1
- package/lib/typescript/credential/status/03-verify-and-parse-status-assertion.d.ts +2 -1
- package/lib/typescript/credential/status/03-verify-and-parse-status-assertion.d.ts.map +1 -1
- package/lib/typescript/sd-jwt/types.d.ts +7 -1
- package/lib/typescript/sd-jwt/types.d.ts.map +1 -1
- package/lib/typescript/trust/types.d.ts +4 -0
- package/lib/typescript/trust/types.d.ts.map +1 -1
- package/lib/typescript/utils/credentials.d.ts +2 -1
- package/lib/typescript/utils/credentials.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 +1 -1
- package/src/credential/issuance/07-verify-and-parse-credential.ts +142 -44
- package/src/credential/presentation/07-evaluate-dcql-query.ts +4 -4
- package/src/credential/status/02-status-assertion.ts +2 -1
- package/src/credential/status/03-verify-and-parse-status-assertion.ts +2 -1
- package/src/sd-jwt/types.ts +9 -2
- package/src/trust/types.ts +4 -0
- package/src/utils/credentials.ts +6 -2
- package/src/utils/misc.ts +3 -0
@@ -1,5 +1,5 @@
|
|
1
1
|
import type { CryptoContext } from "@pagopa/io-react-native-jwt";
|
2
|
-
import type
|
2
|
+
import { isObject, type Out } from "../../utils/misc";
|
3
3
|
import type { EvaluateIssuerTrust } from "./02-evaluate-issuer-trust";
|
4
4
|
import { IoWalletError } from "../../utils/errors";
|
5
5
|
import { SdJwt4VC, verify as verifySdJwt } from "../../sd-jwt";
|
@@ -64,6 +64,118 @@ type DecodedSdJwtCredential = Out<typeof verifySdJwt> & {
|
|
64
64
|
sdJwt: SdJwt4VC;
|
65
65
|
};
|
66
66
|
|
67
|
+
// The data used to create localized names
|
68
|
+
type DisplayData = { locale: string; name: string }[];
|
69
|
+
|
70
|
+
// The resulting object of localized names { en: "Name", it: "Nome" }
|
71
|
+
type LocalizedNames = Record<string, string>;
|
72
|
+
|
73
|
+
// The core structure being built: a node containing the actual value and its localized names
|
74
|
+
type PropertyNode<T> = {
|
75
|
+
value: T;
|
76
|
+
name: LocalizedNames;
|
77
|
+
};
|
78
|
+
|
79
|
+
// A path can consist of object keys, array indices, or null for mapping
|
80
|
+
type Path = (string | number | null)[];
|
81
|
+
|
82
|
+
// A union of all possible shapes. It can be a custom PropertyNode or a standard object/array structure
|
83
|
+
type NodeOrStructure = Partial<PropertyNode<any>> | Record<string, any> | any[];
|
84
|
+
|
85
|
+
// Helper to build localized names from the display data.
|
86
|
+
const buildName = (display: DisplayData): LocalizedNames =>
|
87
|
+
display.reduce(
|
88
|
+
(names, { locale, name }) => ({ ...names, [locale]: name }),
|
89
|
+
{}
|
90
|
+
);
|
91
|
+
|
92
|
+
/**
|
93
|
+
* Recursively constructs a nested object with descriptive properties from a path.
|
94
|
+
*
|
95
|
+
* @param currentObject - The object or array being built upon.
|
96
|
+
* @param path - The path segments to follow.
|
97
|
+
* @param sourceValue - The raw value to place at the end of the path.
|
98
|
+
* @param displayData - The data for generating localized names.
|
99
|
+
* @returns The new object or array structure.
|
100
|
+
*/
|
101
|
+
const createNestedProperty = (
|
102
|
+
currentObject: NodeOrStructure,
|
103
|
+
path: Path,
|
104
|
+
sourceValue: unknown, // Use `unknown` for type-safe input
|
105
|
+
displayData: DisplayData
|
106
|
+
): NodeOrStructure => {
|
107
|
+
const [key, ...rest] = path;
|
108
|
+
|
109
|
+
// Case 1: Map over an array (key is null)
|
110
|
+
if (key === null) {
|
111
|
+
if (!Array.isArray(sourceValue)) return currentObject;
|
112
|
+
|
113
|
+
// We assert the type here because we know this branch handles PropertyNodes
|
114
|
+
const node = currentObject as Partial<PropertyNode<unknown[]>>;
|
115
|
+
const existingValue = Array.isArray(node.value) ? node.value : [];
|
116
|
+
|
117
|
+
const mappedArray = sourceValue.map((item, idx) =>
|
118
|
+
createNestedProperty(existingValue[idx] || {}, rest, item, displayData)
|
119
|
+
);
|
120
|
+
|
121
|
+
return {
|
122
|
+
...node,
|
123
|
+
value: mappedArray,
|
124
|
+
name: node.name ?? buildName(displayData),
|
125
|
+
};
|
126
|
+
}
|
127
|
+
|
128
|
+
// Case 2: Handle an object key (key is a string)
|
129
|
+
if (typeof key === "string") {
|
130
|
+
let nextSourceValue = sourceValue;
|
131
|
+
|
132
|
+
if (isObject(sourceValue)) {
|
133
|
+
// Skip processing when the key is not found within the claim object
|
134
|
+
if (!(key in sourceValue)) return currentObject;
|
135
|
+
|
136
|
+
nextSourceValue = sourceValue[key];
|
137
|
+
}
|
138
|
+
|
139
|
+
// base case
|
140
|
+
if (rest.length === 0) {
|
141
|
+
return {
|
142
|
+
...currentObject,
|
143
|
+
[key]: { value: nextSourceValue, name: buildName(displayData) },
|
144
|
+
};
|
145
|
+
}
|
146
|
+
|
147
|
+
// recursive step
|
148
|
+
const nextObject =
|
149
|
+
(currentObject as Record<string, NodeOrStructure>)[key] || {};
|
150
|
+
|
151
|
+
return {
|
152
|
+
...currentObject,
|
153
|
+
[key]: createNestedProperty(
|
154
|
+
nextObject,
|
155
|
+
rest,
|
156
|
+
nextSourceValue,
|
157
|
+
displayData
|
158
|
+
),
|
159
|
+
};
|
160
|
+
}
|
161
|
+
|
162
|
+
// Case 3: Handle a specific array index (key is a number)
|
163
|
+
if (typeof key === "number") {
|
164
|
+
const newArray = Array.isArray(currentObject) ? [...currentObject] : [];
|
165
|
+
const nextValue = Array.isArray(sourceValue) ? sourceValue[key] : undefined;
|
166
|
+
|
167
|
+
newArray[key] = createNestedProperty(
|
168
|
+
newArray[key] || {},
|
169
|
+
rest,
|
170
|
+
nextValue,
|
171
|
+
displayData
|
172
|
+
);
|
173
|
+
return newArray;
|
174
|
+
}
|
175
|
+
|
176
|
+
return currentObject;
|
177
|
+
};
|
178
|
+
|
67
179
|
const parseCredentialSdJwt = (
|
68
180
|
// The credential configuration to use to parse the provided credential
|
69
181
|
credentialConfig: CredentialConf,
|
@@ -72,7 +184,7 @@ const parseCredentialSdJwt = (
|
|
72
184
|
includeUndefinedAttributes: boolean = false
|
73
185
|
): ParsedCredential => {
|
74
186
|
if (credentialConfig.format !== sdJwt.header.typ) {
|
75
|
-
const message = `Received credential is of an
|
187
|
+
const message = `Received credential is of an unknown type. Expected one of [${credentialConfig.format}], received '${sdJwt.header.typ}'`;
|
76
188
|
Logger.log(LogLevel.ERROR, message);
|
77
189
|
throw new IoWalletError(message);
|
78
190
|
}
|
@@ -81,55 +193,40 @@ const parseCredentialSdJwt = (
|
|
81
193
|
Logger.log(LogLevel.ERROR, "Missing claims in the credential subject");
|
82
194
|
throw new IoWalletError("Missing claims in the credential subject"); // TODO [SIW-1268]: should not be optional
|
83
195
|
}
|
196
|
+
|
84
197
|
const attrDefinitions = credentialConfig.claims;
|
85
198
|
|
86
|
-
//
|
199
|
+
// Validate that all attributes from the config exist in the disclosures
|
87
200
|
const attrsNotInDisclosures = attrDefinitions.filter(
|
88
|
-
(definition) => !disclosures.some(([, name]) => name === definition.path[0])
|
201
|
+
(definition) => !disclosures.some(([, name]) => name === definition.path[0])
|
89
202
|
);
|
90
|
-
|
203
|
+
|
204
|
+
if (attrsNotInDisclosures.length > 0 && !ignoreMissingAttributes) {
|
91
205
|
const missing = attrsNotInDisclosures.map((_) => _.path[0]).join(", ");
|
92
|
-
const received = disclosures.map((_) => _[1
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
throw new IoWalletError(message);
|
97
|
-
}
|
206
|
+
const received = disclosures.map((_) => _[1]).join(", ");
|
207
|
+
const message = `Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`;
|
208
|
+
Logger.log(LogLevel.ERROR, message);
|
209
|
+
throw new IoWalletError(message);
|
98
210
|
}
|
99
211
|
|
100
|
-
|
101
|
-
|
102
|
-
const
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
// example: { "it-IT": "Nome", "en-EN": "Name", "es-ES": "Nombre" }
|
119
|
-
.map(
|
120
|
-
([attrKey, { display, ...definition }]) =>
|
121
|
-
[
|
122
|
-
attrKey,
|
123
|
-
{
|
124
|
-
...definition,
|
125
|
-
name: display.reduce(
|
126
|
-
(names, { locale, name }) => ({ ...names, [locale]: name }),
|
127
|
-
{} as Record<string, string>
|
128
|
-
),
|
129
|
-
},
|
130
|
-
] as const
|
131
|
-
)
|
132
|
-
);
|
212
|
+
const definedValues: ParsedCredential = {};
|
213
|
+
|
214
|
+
for (const { path, display } of attrDefinitions) {
|
215
|
+
const attrKey = path[0];
|
216
|
+
const disclosureValue = disclosures.find(
|
217
|
+
([, name]) => name === attrKey
|
218
|
+
)?.[2];
|
219
|
+
|
220
|
+
if (disclosureValue !== undefined) {
|
221
|
+
const enriched = createNestedProperty(
|
222
|
+
definedValues,
|
223
|
+
path,
|
224
|
+
disclosureValue,
|
225
|
+
display
|
226
|
+
);
|
227
|
+
Object.assign(definedValues, enriched);
|
228
|
+
}
|
229
|
+
}
|
133
230
|
|
134
231
|
if (includeUndefinedAttributes) {
|
135
232
|
// attributes that are in the disclosure set
|
@@ -139,6 +236,7 @@ const parseCredentialSdJwt = (
|
|
139
236
|
.filter((_) => !Object.keys(definedValues).includes(_[1]))
|
140
237
|
.map(([, key, value]) => [key, { value, name: key }])
|
141
238
|
);
|
239
|
+
|
142
240
|
return {
|
143
241
|
...definedValues,
|
144
242
|
...undefinedValues,
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import { DcqlQuery, DcqlError, DcqlQueryResult } from "dcql";
|
2
2
|
import { isValiError } from "valibot";
|
3
|
+
import type { CryptoContext } from "@pagopa/io-react-native-jwt";
|
3
4
|
import { decode, prepareVpToken } from "../../sd-jwt";
|
4
|
-
import type
|
5
|
+
import { LEGACY_SD_JWT, type Disclosure } from "../../sd-jwt/types";
|
5
6
|
import type { RemotePresentation } from "./types";
|
6
7
|
import { CredentialsNotFoundError, type NotFoundDetail } from "./errors";
|
7
|
-
import type { CryptoContext } from "@pagopa/io-react-native-jwt";
|
8
8
|
|
9
9
|
/**
|
10
10
|
* The purpose for the credential request by the RP.
|
@@ -97,7 +97,7 @@ const extractMissingCredentials = (
|
|
97
97
|
const credential = originalQuery.credentials.find((c) => c.id === id);
|
98
98
|
if (
|
99
99
|
credential?.format !== "dc+sd-jwt" &&
|
100
|
-
credential?.format !==
|
100
|
+
credential?.format !== LEGACY_SD_JWT
|
101
101
|
) {
|
102
102
|
throw new Error("Unsupported format"); // TODO [SIW-2082]: support MDOC credentials
|
103
103
|
}
|
@@ -134,7 +134,7 @@ export const evaluateDcqlQuery: EvaluateDcqlQuery = (
|
|
134
134
|
return getDcqlQueryMatches(queryResult).map(([id, match]) => {
|
135
135
|
if (
|
136
136
|
match.output.credential_format !== "dc+sd-jwt" &&
|
137
|
-
match.output.credential_format !==
|
137
|
+
match.output.credential_format !== LEGACY_SD_JWT
|
138
138
|
) {
|
139
139
|
throw new Error("Unsupported format"); // TODO [SIW-2082]: support MDOC credentials
|
140
140
|
}
|
@@ -15,11 +15,12 @@ import {
|
|
15
15
|
} from "../../utils/errors";
|
16
16
|
import { Logger, LogLevel } from "../../utils/logging";
|
17
17
|
import { extractJwkFromCredential } from "../../utils/credentials";
|
18
|
+
import type { SupportedSdJwtLegacyFormat } from "../../sd-jwt/types";
|
18
19
|
|
19
20
|
export type StatusAssertion = (
|
20
21
|
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
|
21
22
|
credential: Out<ObtainCredential>["credential"],
|
22
|
-
format: Out<ObtainCredential>["format"],
|
23
|
+
format: Out<ObtainCredential>["format"] | SupportedSdJwtLegacyFormat,
|
23
24
|
context: {
|
24
25
|
credentialCryptoContext: CryptoContext;
|
25
26
|
wiaCryptoContext: CryptoContext;
|
@@ -17,12 +17,13 @@ import { Logger, LogLevel } from "../../utils/logging";
|
|
17
17
|
import type { ObtainCredential } from "../issuance";
|
18
18
|
import { extractJwkFromCredential } from "../../utils/credentials";
|
19
19
|
import { isSameThumbprint } from "../../utils/jwk";
|
20
|
+
import type { SupportedSdJwtLegacyFormat } from "../../sd-jwt/types";
|
20
21
|
|
21
22
|
export type VerifyAndParseStatusAssertion = (
|
22
23
|
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
|
23
24
|
statusAssertion: Out<StatusAssertion>,
|
24
25
|
credential: Out<ObtainCredential>["credential"],
|
25
|
-
format: Out<ObtainCredential>["format"]
|
26
|
+
format: Out<ObtainCredential>["format"] | SupportedSdJwtLegacyFormat
|
26
27
|
) => Promise<{ parsedStatusAssertion: ParsedStatusAssertion }>;
|
27
28
|
|
28
29
|
/**
|
package/src/sd-jwt/types.ts
CHANGED
@@ -20,6 +20,13 @@ export const Disclosure = z.tuple([
|
|
20
20
|
/* claim value */ z.unknown(),
|
21
21
|
]);
|
22
22
|
|
23
|
+
/**
|
24
|
+
* For backward compatibility reasons it is still necessary to support the legacy SD-JWT
|
25
|
+
* in a few flows (for instance status assertion and presentation of the old eID).
|
26
|
+
*/
|
27
|
+
export type SupportedSdJwtLegacyFormat = typeof LEGACY_SD_JWT;
|
28
|
+
export const LEGACY_SD_JWT = "vc+sd-jwt";
|
29
|
+
|
23
30
|
/**
|
24
31
|
* Encoding depends on the serialization algorithm used when generating the disclosure tokens.
|
25
32
|
* The SD-JWT reference itself take no decision about how to handle whitespaces in serialized objects.
|
@@ -44,7 +51,7 @@ const StatusAssertion = z.object({
|
|
44
51
|
export type SdJwt4VC = z.infer<typeof SdJwt4VC>;
|
45
52
|
export const SdJwt4VC = z.object({
|
46
53
|
header: z.object({
|
47
|
-
typ: z.enum(["
|
54
|
+
typ: z.enum(["dc+sd-jwt", LEGACY_SD_JWT]),
|
48
55
|
alg: z.string(),
|
49
56
|
kid: z.string(),
|
50
57
|
trust_chain: z.array(z.string()).optional(),
|
@@ -62,7 +69,7 @@ export const SdJwt4VC = z.object({
|
|
62
69
|
.union([
|
63
70
|
// Credentials v1.0
|
64
71
|
z.object({ status_assertion: StatusAssertion }),
|
65
|
-
//
|
72
|
+
// Legacy credentials v0.7.1
|
66
73
|
z.object({ status_attestation: StatusAssertion }),
|
67
74
|
])
|
68
75
|
.optional(),
|
package/src/trust/types.ts
CHANGED
@@ -71,6 +71,10 @@ const SupportedCredentialMetadata = z.intersection(
|
|
71
71
|
})
|
72
72
|
);
|
73
73
|
|
74
|
+
/**
|
75
|
+
* Supported formats for credentials issued by the Issuer API 1.0,
|
76
|
+
* compliant with IT-Wallet technical specifications 1.0.
|
77
|
+
*/
|
74
78
|
export type SupportedCredentialFormat = z.infer<
|
75
79
|
typeof SupportedCredentialMetadata
|
76
80
|
>["format"];
|
package/src/utils/credentials.ts
CHANGED
@@ -4,8 +4,12 @@ import type { Out } from "./misc";
|
|
4
4
|
import type { ObtainCredential } from "../credential/issuance";
|
5
5
|
import type { JWK } from "./jwk";
|
6
6
|
import { IoWalletError } from "./errors";
|
7
|
+
import {
|
8
|
+
LEGACY_SD_JWT,
|
9
|
+
type SupportedSdJwtLegacyFormat,
|
10
|
+
} from "../sd-jwt/types";
|
7
11
|
|
8
|
-
const SD_JWT = ["
|
12
|
+
const SD_JWT = ["dc+sd-jwt", LEGACY_SD_JWT];
|
9
13
|
|
10
14
|
/**
|
11
15
|
* Extracts a JWK from a credential.
|
@@ -15,7 +19,7 @@ const SD_JWT = ["vc+sd-jwt", "dc+sd-jwt"];
|
|
15
19
|
*/
|
16
20
|
export const extractJwkFromCredential = async (
|
17
21
|
credential: Out<ObtainCredential>["credential"],
|
18
|
-
format: Out<ObtainCredential>["format"]
|
22
|
+
format: Out<ObtainCredential>["format"] | SupportedSdJwtLegacyFormat
|
19
23
|
): Promise<JWK> => {
|
20
24
|
if (SD_JWT.includes(format)) {
|
21
25
|
// 1. SD-JWT case
|
package/src/utils/misc.ts
CHANGED
@@ -78,3 +78,6 @@ export const safeJsonParse = <T>(text: string, withDefault?: T): T | null => {
|
|
78
78
|
return withDefault ?? null;
|
79
79
|
}
|
80
80
|
};
|
81
|
+
|
82
|
+
export const isObject = (value: unknown): value is Record<string, unknown> =>
|
83
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|