dataspace-client-sdk-node 0.2.1 → 0.2.3
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/LICENSE +201 -0
- package/README.md +12 -14
- package/package.json +12 -2
- package/TODO_PROMPT_NEXT_STEPS.md +0 -185
- package/artifacts/update-smart-wallet.js +0 -1016
- package/dist/builders.d.ts +0 -12
- package/dist/builders.js +0 -17
- package/dist/client.d.ts +0 -453
- package/dist/client.js +0 -1755
- package/dist/consent/pdfSignatureVerification.d.ts +0 -18
- package/dist/consent/pdfSignatureVerification.js +0 -23
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -9
- package/dist/sdk/dataspace-wallet-sdk-node/MultiWalletClient.d.ts +0 -9
- package/dist/sdk/dataspace-wallet-sdk-node/MultiWalletClient.js +0 -21
- package/dist/sdk/dataspace-wallet-sdk-node/WalletClient.d.ts +0 -26
- package/dist/sdk/dataspace-wallet-sdk-node/WalletClient.js +0 -36
- package/dist/sdk/dataspace-wallet-sdk-node/index.d.ts +0 -6
- package/dist/sdk/dataspace-wallet-sdk-node/index.js +0 -6
- package/dist/sdk/dataspace-wallet-sdk-node/provider.d.ts +0 -24
- package/dist/sdk/dataspace-wallet-sdk-node/provider.js +0 -1
- package/dist/sdk/dataspace-wallet-sdk-node/providers/memory-provider.d.ts +0 -41
- package/dist/sdk/dataspace-wallet-sdk-node/providers/memory-provider.js +0 -216
- package/dist/sdk/dataspace-wallet-sdk-node/providers/seed-provider.d.ts +0 -22
- package/dist/sdk/dataspace-wallet-sdk-node/providers/seed-provider.js +0 -28
- package/dist/sdk/dataspace-wallet-sdk-node/types.d.ts +0 -51
- package/dist/sdk/dataspace-wallet-sdk-node/types.js +0 -1
- package/dist/types.d.ts +0 -556
- package/dist/types.js +0 -1
- package/dist/vp-token.d.ts +0 -37
- package/dist/vp-token.js +0 -56
|
@@ -1,1016 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
|
|
3
|
-
function replace(file, oldStr, newStr) {
|
|
4
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
5
|
-
if (!content.includes(oldStr)) {
|
|
6
|
-
throw new Error(`Pattern not found in ${file}`);
|
|
7
|
-
}
|
|
8
|
-
fs.writeFileSync(file, content.replace(oldStr, newStr));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
fs.writeFileSync('src/sdk/dataspace-wallet-sdk-node/types.ts', String.raw`export type WalletContext = {
|
|
12
|
-
tenantId: string;
|
|
13
|
-
jurisdiction: string;
|
|
14
|
-
sector: string;
|
|
15
|
-
walletId?: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type PublicJwk = JsonWebKey & {
|
|
19
|
-
kid?: string;
|
|
20
|
-
alg?: string;
|
|
21
|
-
use?: string;
|
|
22
|
-
key_ops?: string[];
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type PrivateKeyRef = {
|
|
26
|
-
kid: string;
|
|
27
|
-
kind: 'managed';
|
|
28
|
-
algorithm: 'ES384' | 'RSA-OAEP-256/RS256';
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type SignOptions = {
|
|
32
|
-
keyId?: string;
|
|
33
|
-
algorithm?: 'ES384' | 'RS256';
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type VerifyOptions = {
|
|
37
|
-
algorithm?: 'ES384' | 'RS256';
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export type EncryptOptions = {
|
|
41
|
-
keyId?: string;
|
|
42
|
-
algorithm?: 'RSA-OAEP-256';
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type DecryptOptions = {
|
|
46
|
-
keyId?: string;
|
|
47
|
-
algorithm?: 'RSA-OAEP-256';
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export type WalletProviderKind = 'mem' | 'seed' | 'external';
|
|
51
|
-
|
|
52
|
-
export type WalletInitOptions = {
|
|
53
|
-
contexts?: WalletContext[];
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export type CompactJwsHeader = {
|
|
57
|
-
alg: string;
|
|
58
|
-
typ?: string;
|
|
59
|
-
kid?: string;
|
|
60
|
-
[key: string]: unknown;
|
|
61
|
-
};
|
|
62
|
-
`);
|
|
63
|
-
|
|
64
|
-
fs.writeFileSync('src/sdk/dataspace-wallet-sdk-node/provider.ts', String.raw`import type {
|
|
65
|
-
CompactJwsHeader,
|
|
66
|
-
DecryptOptions,
|
|
67
|
-
EncryptOptions,
|
|
68
|
-
PublicJwk,
|
|
69
|
-
SignOptions,
|
|
70
|
-
VerifyOptions,
|
|
71
|
-
WalletContext,
|
|
72
|
-
WalletInitOptions,
|
|
73
|
-
WalletProviderKind,
|
|
74
|
-
} from './types.js';
|
|
75
|
-
|
|
76
|
-
export interface WalletProvider {
|
|
77
|
-
readonly kind: WalletProviderKind;
|
|
78
|
-
init?(options?: WalletInitOptions): Promise<void> | void;
|
|
79
|
-
getPublicJwks(context: WalletContext): Promise<PublicJwk[]>;
|
|
80
|
-
sign(payload: Uint8Array | string, context: WalletContext, options?: SignOptions): Promise<string>;
|
|
81
|
-
verify(
|
|
82
|
-
payload: Uint8Array | string,
|
|
83
|
-
signature: string,
|
|
84
|
-
jwk: PublicJwk,
|
|
85
|
-
options?: VerifyOptions,
|
|
86
|
-
): Promise<boolean>;
|
|
87
|
-
signCompactJws(
|
|
88
|
-
context: WalletContext,
|
|
89
|
-
params: { header: CompactJwsHeader; claims: Record<string, unknown> },
|
|
90
|
-
): Promise<string>;
|
|
91
|
-
encrypt(
|
|
92
|
-
plaintext: Uint8Array | string,
|
|
93
|
-
recipientJwk: PublicJwk,
|
|
94
|
-
options?: EncryptOptions,
|
|
95
|
-
): Promise<string>;
|
|
96
|
-
decrypt(ciphertext: string, context: WalletContext, options?: DecryptOptions): Promise<Uint8Array>;
|
|
97
|
-
}
|
|
98
|
-
`);
|
|
99
|
-
|
|
100
|
-
fs.writeFileSync('src/sdk/dataspace-wallet-sdk-node/WalletClient.ts', String.raw`import type { WalletProvider } from './provider.js';
|
|
101
|
-
import type {
|
|
102
|
-
CompactJwsHeader,
|
|
103
|
-
DecryptOptions,
|
|
104
|
-
EncryptOptions,
|
|
105
|
-
PublicJwk,
|
|
106
|
-
SignOptions,
|
|
107
|
-
VerifyOptions,
|
|
108
|
-
WalletContext,
|
|
109
|
-
} from './types.js';
|
|
110
|
-
|
|
111
|
-
export class WalletClient {
|
|
112
|
-
public constructor(
|
|
113
|
-
private readonly provider: WalletProvider,
|
|
114
|
-
private readonly context: WalletContext,
|
|
115
|
-
) {}
|
|
116
|
-
|
|
117
|
-
public getPublicJwks(): Promise<PublicJwk[]> {
|
|
118
|
-
return this.provider.getPublicJwks(this.context);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
public sign(payload: Uint8Array | string, options?: SignOptions): Promise<string> {
|
|
122
|
-
return this.provider.sign(payload, this.context, options);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
public verify(
|
|
126
|
-
payload: Uint8Array | string,
|
|
127
|
-
signature: string,
|
|
128
|
-
jwk: PublicJwk,
|
|
129
|
-
options?: VerifyOptions,
|
|
130
|
-
): Promise<boolean> {
|
|
131
|
-
return this.provider.verify(payload, signature, jwk, options);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public signCompactJws(params: { header: CompactJwsHeader; claims: Record<string, unknown> }): Promise<string> {
|
|
135
|
-
return this.provider.signCompactJws(this.context, params);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
public encrypt(plaintext: Uint8Array | string, recipientJwk: PublicJwk, options?: EncryptOptions): Promise<string> {
|
|
139
|
-
return this.provider.encrypt(plaintext, recipientJwk, options);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
public decrypt(ciphertext: string, options?: DecryptOptions): Promise<Uint8Array> {
|
|
143
|
-
return this.provider.decrypt(ciphertext, this.context, options);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
`);
|
|
147
|
-
|
|
148
|
-
fs.writeFileSync('src/sdk/dataspace-wallet-sdk-node/providers/memory-provider.ts', String.raw`import {
|
|
149
|
-
constants as cryptoConstants,
|
|
150
|
-
createHash,
|
|
151
|
-
createPublicKey,
|
|
152
|
-
generateKeyPairSync,
|
|
153
|
-
privateDecrypt,
|
|
154
|
-
publicEncrypt,
|
|
155
|
-
sign as cryptoSign,
|
|
156
|
-
verify as cryptoVerify,
|
|
157
|
-
type KeyObject,
|
|
158
|
-
} from 'node:crypto';
|
|
159
|
-
import type {
|
|
160
|
-
CompactJwsHeader,
|
|
161
|
-
DecryptOptions,
|
|
162
|
-
EncryptOptions,
|
|
163
|
-
PrivateKeyRef,
|
|
164
|
-
PublicJwk,
|
|
165
|
-
SignOptions,
|
|
166
|
-
VerifyOptions,
|
|
167
|
-
WalletContext,
|
|
168
|
-
WalletInitOptions,
|
|
169
|
-
WalletProviderKind,
|
|
170
|
-
} from '../types.js';
|
|
171
|
-
import type { WalletProvider } from '../provider.js';
|
|
172
|
-
|
|
173
|
-
type StoredKeyPair = {
|
|
174
|
-
signingPublicJwk: PublicJwk;
|
|
175
|
-
signingPublicKey: KeyObject;
|
|
176
|
-
signingPrivateKey: KeyObject;
|
|
177
|
-
signingPrivateKeyRef: PrivateKeyRef;
|
|
178
|
-
encryptionPublicJwk: PublicJwk;
|
|
179
|
-
encryptionPublicKey: KeyObject;
|
|
180
|
-
encryptionPrivateKey: KeyObject;
|
|
181
|
-
encryptionPrivateKeyRef: PrivateKeyRef;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
function contextKey(context: WalletContext): string {
|
|
185
|
-
return [context.tenantId, context.jurisdiction, context.sector, context.walletId ?? 'default'].join(':');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function normalizeBytes(input: Uint8Array | string): Uint8Array {
|
|
189
|
-
return typeof input === 'string' ? Buffer.from(input, 'utf8') : input;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function toBase64Url(buffer: Uint8Array): string {
|
|
193
|
-
return Buffer.from(buffer).toString('base64url');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function fromBase64Url(value: string): Buffer {
|
|
197
|
-
return Buffer.from(value, 'base64url');
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function encodeJsonBase64Url(value: Record<string, unknown>): string {
|
|
201
|
-
return toBase64Url(Buffer.from(JSON.stringify(value), 'utf8'));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function buildKid(context: WalletContext, jwk: PublicJwk): string {
|
|
205
|
-
const thumbprint = createHash('sha256')
|
|
206
|
-
.update(JSON.stringify({
|
|
207
|
-
crv: jwk.crv,
|
|
208
|
-
e: jwk.e,
|
|
209
|
-
kty: jwk.kty,
|
|
210
|
-
n: jwk.n,
|
|
211
|
-
x: jwk.x,
|
|
212
|
-
y: jwk.y,
|
|
213
|
-
}))
|
|
214
|
-
.digest('base64url')
|
|
215
|
-
.slice(0, 16);
|
|
216
|
-
|
|
217
|
-
return `wallet:${context.tenantId}:${context.jurisdiction}:${context.sector}:${thumbprint}`;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function buildPrivateKeyRef(kid: string, algorithm: PrivateKeyRef['algorithm']): PrivateKeyRef {
|
|
221
|
-
return {
|
|
222
|
-
kid,
|
|
223
|
-
kind: 'managed',
|
|
224
|
-
algorithm,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function buildSigningKeyPair(context: WalletContext): {
|
|
229
|
-
publicJwk: PublicJwk;
|
|
230
|
-
publicKey: KeyObject;
|
|
231
|
-
privateKey: KeyObject;
|
|
232
|
-
privateKeyRef: PrivateKeyRef;
|
|
233
|
-
} {
|
|
234
|
-
const generated = generateKeyPairSync('ec', {
|
|
235
|
-
namedCurve: 'secp384r1',
|
|
236
|
-
});
|
|
237
|
-
const publicJwk = generated.publicKey.export({ format: 'jwk' }) as PublicJwk;
|
|
238
|
-
const kid = buildKid(context, publicJwk);
|
|
239
|
-
const normalizedPublicJwk: PublicJwk = {
|
|
240
|
-
...publicJwk,
|
|
241
|
-
kid,
|
|
242
|
-
alg: 'ES384',
|
|
243
|
-
use: 'sig',
|
|
244
|
-
key_ops: ['verify'],
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
return {
|
|
248
|
-
publicJwk: normalizedPublicJwk,
|
|
249
|
-
publicKey: createPublicKey({ key: normalizedPublicJwk, format: 'jwk' }),
|
|
250
|
-
privateKey: generated.privateKey,
|
|
251
|
-
privateKeyRef: buildPrivateKeyRef(kid, 'ES384'),
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function buildEncryptionKeyPair(context: WalletContext): {
|
|
256
|
-
publicJwk: PublicJwk;
|
|
257
|
-
publicKey: KeyObject;
|
|
258
|
-
privateKey: KeyObject;
|
|
259
|
-
privateKeyRef: PrivateKeyRef;
|
|
260
|
-
} {
|
|
261
|
-
const generated = generateKeyPairSync('rsa', {
|
|
262
|
-
modulusLength: 2048,
|
|
263
|
-
publicExponent: 0x10001,
|
|
264
|
-
});
|
|
265
|
-
const publicJwk = generated.publicKey.export({ format: 'jwk' }) as PublicJwk;
|
|
266
|
-
const kid = buildKid(context, publicJwk);
|
|
267
|
-
const normalizedPublicJwk: PublicJwk = {
|
|
268
|
-
...publicJwk,
|
|
269
|
-
kid,
|
|
270
|
-
alg: 'RSA-OAEP-256',
|
|
271
|
-
use: 'enc',
|
|
272
|
-
key_ops: ['encrypt'],
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
publicJwk: normalizedPublicJwk,
|
|
277
|
-
publicKey: createPublicKey({ key: normalizedPublicJwk, format: 'jwk' }),
|
|
278
|
-
privateKey: generated.privateKey,
|
|
279
|
-
privateKeyRef: buildPrivateKeyRef(kid, 'RSA-OAEP-256/RS256'),
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export class MemoryWalletProvider implements WalletProvider {
|
|
284
|
-
public readonly kind: WalletProviderKind = 'mem';
|
|
285
|
-
private readonly keysByContext = new Map<string, StoredKeyPair>();
|
|
286
|
-
|
|
287
|
-
public init(options?: WalletInitOptions): void {
|
|
288
|
-
for (const context of options?.contexts ?? []) {
|
|
289
|
-
this.ensureKeyPair(context);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
public async getPublicJwks(context: WalletContext): Promise<PublicJwk[]> {
|
|
294
|
-
const pair = this.ensureKeyPair(context);
|
|
295
|
-
return [pair.signingPublicJwk, pair.encryptionPublicJwk];
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
public async sign(payload: Uint8Array | string, context: WalletContext, options?: SignOptions): Promise<string> {
|
|
299
|
-
const pair = this.selectSigningKeyPair(context, options?.keyId);
|
|
300
|
-
const signature = cryptoSign('sha384', normalizeBytes(payload), pair.signingPrivateKey);
|
|
301
|
-
return toBase64Url(signature);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
public async verify(
|
|
305
|
-
payload: Uint8Array | string,
|
|
306
|
-
signature: string,
|
|
307
|
-
jwk: PublicJwk,
|
|
308
|
-
_options?: VerifyOptions,
|
|
309
|
-
): Promise<boolean> {
|
|
310
|
-
const publicKey = createPublicKey({ key: jwk, format: 'jwk' });
|
|
311
|
-
return cryptoVerify('sha384', normalizeBytes(payload), publicKey, fromBase64Url(signature));
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
public async signCompactJws(
|
|
315
|
-
context: WalletContext,
|
|
316
|
-
params: { header: CompactJwsHeader; claims: Record<string, unknown> },
|
|
317
|
-
): Promise<string> {
|
|
318
|
-
const pair = this.selectSigningKeyPair(context, params.header.kid);
|
|
319
|
-
const header: CompactJwsHeader = {
|
|
320
|
-
...params.header,
|
|
321
|
-
alg: params.header.alg || 'ES384',
|
|
322
|
-
kid: params.header.kid || pair.signingPublicJwk.kid,
|
|
323
|
-
};
|
|
324
|
-
const signingInput = `${encodeJsonBase64Url(header as Record<string, unknown>)}.${encodeJsonBase64Url(params.claims)}`;
|
|
325
|
-
const signature = cryptoSign('sha384', Buffer.from(signingInput, 'ascii'), pair.signingPrivateKey);
|
|
326
|
-
return `${signingInput}.${toBase64Url(signature)}`;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
public async encrypt(
|
|
330
|
-
plaintext: Uint8Array | string,
|
|
331
|
-
recipientJwk: PublicJwk,
|
|
332
|
-
_options?: EncryptOptions,
|
|
333
|
-
): Promise<string> {
|
|
334
|
-
const recipientKey = createPublicKey({ key: recipientJwk, format: 'jwk' });
|
|
335
|
-
const ciphertext = publicEncrypt(
|
|
336
|
-
{
|
|
337
|
-
key: recipientKey,
|
|
338
|
-
oaepHash: 'sha256',
|
|
339
|
-
padding: cryptoConstants.RSA_PKCS1_OAEP_PADDING,
|
|
340
|
-
},
|
|
341
|
-
normalizeBytes(plaintext),
|
|
342
|
-
);
|
|
343
|
-
return toBase64Url(ciphertext);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
public async decrypt(ciphertext: string, context: WalletContext, options?: DecryptOptions): Promise<Uint8Array> {
|
|
347
|
-
const pair = this.selectEncryptionKeyPair(context, options?.keyId);
|
|
348
|
-
return privateDecrypt(
|
|
349
|
-
{
|
|
350
|
-
key: pair.encryptionPrivateKey,
|
|
351
|
-
oaepHash: 'sha256',
|
|
352
|
-
padding: cryptoConstants.RSA_PKCS1_OAEP_PADDING,
|
|
353
|
-
},
|
|
354
|
-
fromBase64Url(ciphertext),
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
protected ensureKeyPair(context: WalletContext): StoredKeyPair {
|
|
359
|
-
const key = contextKey(context);
|
|
360
|
-
const existing = this.keysByContext.get(key);
|
|
361
|
-
if (existing) {
|
|
362
|
-
return existing;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const signingKeyPair = buildSigningKeyPair(context);
|
|
366
|
-
const encryptionKeyPair = buildEncryptionKeyPair(context);
|
|
367
|
-
|
|
368
|
-
const pair: StoredKeyPair = {
|
|
369
|
-
signingPublicJwk: signingKeyPair.publicJwk,
|
|
370
|
-
signingPublicKey: signingKeyPair.publicKey,
|
|
371
|
-
signingPrivateKey: signingKeyPair.privateKey,
|
|
372
|
-
signingPrivateKeyRef: signingKeyPair.privateKeyRef,
|
|
373
|
-
encryptionPublicJwk: encryptionKeyPair.publicJwk,
|
|
374
|
-
encryptionPublicKey: encryptionKeyPair.publicKey,
|
|
375
|
-
encryptionPrivateKey: encryptionKeyPair.privateKey,
|
|
376
|
-
encryptionPrivateKeyRef: encryptionKeyPair.privateKeyRef,
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
this.keysByContext.set(key, pair);
|
|
380
|
-
return pair;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
protected selectSigningKeyPair(context: WalletContext, keyId?: string): StoredKeyPair {
|
|
384
|
-
const pair = this.ensureKeyPair(context);
|
|
385
|
-
if (keyId && pair.signingPrivateKeyRef.kid !== keyId) {
|
|
386
|
-
throw new Error(`Wallet key not found for kid: ${keyId}`);
|
|
387
|
-
}
|
|
388
|
-
return pair;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
protected selectEncryptionKeyPair(context: WalletContext, keyId?: string): StoredKeyPair {
|
|
392
|
-
const pair = this.ensureKeyPair(context);
|
|
393
|
-
if (keyId && pair.encryptionPrivateKeyRef.kid !== keyId) {
|
|
394
|
-
throw new Error(`Wallet key not found for kid: ${keyId}`);
|
|
395
|
-
}
|
|
396
|
-
return pair;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
`);
|
|
400
|
-
|
|
401
|
-
replace(
|
|
402
|
-
'src/types.ts',
|
|
403
|
-
`export type BackendPkceAuthResult = {
|
|
404
|
-
/** \`fetched\`: new token obtained. \`cached\`: valid token already in cache. \`failed\`: flow error. */
|
|
405
|
-
status: 'fetched' | 'cached' | 'failed';
|
|
406
|
-
endpointId: string;
|
|
407
|
-
accessToken: string;
|
|
408
|
-
tokenType: string;
|
|
409
|
-
scopes: string[];
|
|
410
|
-
/** Present on failure: name of the step that failed (\`_dcr\`, \`_code\`, \`_token\`, \`_exchange\`). */
|
|
411
|
-
step?: string;
|
|
412
|
-
};
|
|
413
|
-
`,
|
|
414
|
-
`export type BackendPkceAuthResult = {
|
|
415
|
-
/** \`fetched\`: new token obtained. \`cached\`: valid token already in cache. \`failed\`: flow error. */
|
|
416
|
-
status: 'fetched' | 'cached' | 'failed';
|
|
417
|
-
endpointId: string;
|
|
418
|
-
accessToken: string;
|
|
419
|
-
tokenType: string;
|
|
420
|
-
scopes: string[];
|
|
421
|
-
/** Present on failure: name of the step that failed (\`_dcr\`, \`_code\`, \`_token\`, \`_exchange\`). */
|
|
422
|
-
step?: string;
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
export type BackendSmartAuthOptions = {
|
|
426
|
-
clientId: string;
|
|
427
|
-
scopes: string[];
|
|
428
|
-
endpointId?: string;
|
|
429
|
-
tokenUrl?: string;
|
|
430
|
-
tokenPath?: string;
|
|
431
|
-
audience?: string;
|
|
432
|
-
assertionTtlSeconds?: number;
|
|
433
|
-
additionalTokenFields?: Record<string, string>;
|
|
434
|
-
publicJwk?: PublicJwk | Record<string, unknown>;
|
|
435
|
-
walletContext?: WalletContext;
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
export type BackendSmartAuthResult = {
|
|
439
|
-
status: 'fetched' | 'cached' | 'failed';
|
|
440
|
-
profile: 'smart-backend.v1';
|
|
441
|
-
endpointId: string;
|
|
442
|
-
accessToken?: string;
|
|
443
|
-
tokenType?: string;
|
|
444
|
-
scopes?: string[];
|
|
445
|
-
expiresAt?: string;
|
|
446
|
-
statusCode?: number;
|
|
447
|
-
response?: unknown;
|
|
448
|
-
};
|
|
449
|
-
`
|
|
450
|
-
);
|
|
451
|
-
|
|
452
|
-
replace(
|
|
453
|
-
'src/client.ts',
|
|
454
|
-
` BackendPkceAuthOptions,
|
|
455
|
-
BackendPkceAuthResult,
|
|
456
|
-
ClientOptions,
|
|
457
|
-
`,
|
|
458
|
-
` BackendPkceAuthOptions,
|
|
459
|
-
BackendPkceAuthResult,
|
|
460
|
-
BackendSmartAuthOptions,
|
|
461
|
-
BackendSmartAuthResult,
|
|
462
|
-
ClientOptions,
|
|
463
|
-
`
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
replace(
|
|
467
|
-
'src/client.ts',
|
|
468
|
-
` public getCachedBearerToken(endpointId: string): string | undefined {
|
|
469
|
-
const cached = this._tokenCache.get(endpointId);
|
|
470
|
-
if (cached && cached.expiresAt > Date.now() + 30_000) {
|
|
471
|
-
return cached.accessToken;
|
|
472
|
-
}
|
|
473
|
-
return undefined;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// ---- Private auth helpers ----------------------------------------------
|
|
477
|
-
`,
|
|
478
|
-
` public getCachedBearerToken(endpointId: string): string | undefined {
|
|
479
|
-
const cached = this._tokenCache.get(endpointId);
|
|
480
|
-
if (cached && cached.expiresAt > Date.now() + 30_000) {
|
|
481
|
-
return cached.accessToken;
|
|
482
|
-
}
|
|
483
|
-
return undefined;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* smart-backend.v1: obtain an OAuth2 backend token using client_credentials + private_key_jwt.
|
|
488
|
-
*/
|
|
489
|
-
public async authenticateBackendSmartStandard(
|
|
490
|
-
options: BackendSmartAuthOptions,
|
|
491
|
-
): Promise<BackendSmartAuthResult> {
|
|
492
|
-
const {
|
|
493
|
-
clientId,
|
|
494
|
-
scopes,
|
|
495
|
-
endpointId = `smart-backend:${clientId}`,
|
|
496
|
-
tokenUrl,
|
|
497
|
-
tokenPath = '/token',
|
|
498
|
-
audience,
|
|
499
|
-
assertionTtlSeconds = 300,
|
|
500
|
-
additionalTokenFields,
|
|
501
|
-
} = options;
|
|
502
|
-
|
|
503
|
-
const cached = this._tokenCache.get(endpointId);
|
|
504
|
-
if (cached && cached.expiresAt > Date.now() + 30_000) {
|
|
505
|
-
return {
|
|
506
|
-
status: 'cached',
|
|
507
|
-
profile: 'smart-backend.v1',
|
|
508
|
-
endpointId,
|
|
509
|
-
accessToken: cached.accessToken,
|
|
510
|
-
tokenType: cached.tokenType,
|
|
511
|
-
scopes: cached.scopes,
|
|
512
|
-
expiresAt: new Date(cached.expiresAt).toISOString(),
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const resolvedTokenUrl = this.resolveStandardTokenUrl(tokenUrl, tokenPath);
|
|
517
|
-
const publicJwk = await this.resolveSmartAuthPublicJwk(options);
|
|
518
|
-
const clientAssertion = await this.signSmartBackendClientAssertion({
|
|
519
|
-
clientId,
|
|
520
|
-
audience: audience ?? resolvedTokenUrl,
|
|
521
|
-
publicJwk,
|
|
522
|
-
ttlSeconds: assertionTtlSeconds,
|
|
523
|
-
walletContext: options.walletContext,
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
const tokenRequest: Record<string, string> = {
|
|
527
|
-
grant_type: 'client_credentials',
|
|
528
|
-
client_id: clientId,
|
|
529
|
-
scope: scopes.join(' '),
|
|
530
|
-
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
|
531
|
-
client_assertion: clientAssertion,
|
|
532
|
-
...(additionalTokenFields ?? {}),
|
|
533
|
-
};
|
|
534
|
-
const response = await this.postJson(tokenUrl ?? tokenPath, tokenRequest);
|
|
535
|
-
const body = (response.body as Record<string, unknown>) ?? {};
|
|
536
|
-
const accessToken = String(body.access_token ?? '').trim();
|
|
537
|
-
|
|
538
|
-
if (response.status >= 400 || !accessToken) {
|
|
539
|
-
return {
|
|
540
|
-
status: 'failed',
|
|
541
|
-
profile: 'smart-backend.v1',
|
|
542
|
-
endpointId,
|
|
543
|
-
statusCode: response.status,
|
|
544
|
-
response,
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const tokenType = String(body.token_type ?? 'Bearer');
|
|
549
|
-
const grantedScope = String(body.scope ?? '').trim();
|
|
550
|
-
const grantedScopes = grantedScope ? grantedScope.split(' ').filter(Boolean) : scopes;
|
|
551
|
-
const expiresIn = Number(body.expires_in ?? 0);
|
|
552
|
-
const expiresAt = Date.now() + expiresIn * 1000;
|
|
553
|
-
|
|
554
|
-
this._tokenCache.set(endpointId, {
|
|
555
|
-
accessToken,
|
|
556
|
-
tokenType,
|
|
557
|
-
scopes: grantedScopes,
|
|
558
|
-
expiresAt,
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
return {
|
|
562
|
-
status: 'fetched',
|
|
563
|
-
profile: 'smart-backend.v1',
|
|
564
|
-
endpointId,
|
|
565
|
-
statusCode: response.status,
|
|
566
|
-
accessToken,
|
|
567
|
-
tokenType,
|
|
568
|
-
scopes: grantedScopes,
|
|
569
|
-
expiresAt: new Date(expiresAt).toISOString(),
|
|
570
|
-
response,
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// ---- Private auth helpers ----------------------------------------------
|
|
575
|
-
`
|
|
576
|
-
);
|
|
577
|
-
|
|
578
|
-
replace(
|
|
579
|
-
'src/client.ts',
|
|
580
|
-
` private async resolveControllerPublicJwk(
|
|
581
|
-
options: BackendPkceAuthOptions,
|
|
582
|
-
): Promise<PublicJwk | Record<string, unknown>> {
|
|
583
|
-
if (options.controllerPublicJwk) {
|
|
584
|
-
return options.controllerPublicJwk;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (!this.wallet) {
|
|
588
|
-
throw new Error('authenticateBackendPkceAndExchange requires controllerPublicJwk or a configured wallet provider.');
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
const walletContext: WalletContext = options.walletContext ?? {
|
|
592
|
-
tenantId: options.ctx.tenantId,
|
|
593
|
-
jurisdiction: options.ctx.jurisdiction,
|
|
594
|
-
sector: options.ctx.sector,
|
|
595
|
-
};
|
|
596
|
-
const publicJwks = await this.wallet.getPublicJwks(walletContext);
|
|
597
|
-
const controllerPublicJwk = publicJwks[0];
|
|
598
|
-
|
|
599
|
-
if (!controllerPublicJwk) {
|
|
600
|
-
throw new Error('Wallet provider returned no public JWKs for the requested context.');
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
return controllerPublicJwk;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// ---- Generic batch API --------------------------------------------------
|
|
607
|
-
`,
|
|
608
|
-
` private async resolveControllerPublicJwk(
|
|
609
|
-
options: BackendPkceAuthOptions,
|
|
610
|
-
): Promise<PublicJwk | Record<string, unknown>> {
|
|
611
|
-
if (options.controllerPublicJwk) {
|
|
612
|
-
return options.controllerPublicJwk;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
if (!this.wallet) {
|
|
616
|
-
throw new Error('authenticateBackendPkceAndExchange requires controllerPublicJwk or a configured wallet provider.');
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
const walletContext: WalletContext = options.walletContext ?? {
|
|
620
|
-
tenantId: options.ctx.tenantId,
|
|
621
|
-
jurisdiction: options.ctx.jurisdiction,
|
|
622
|
-
sector: options.ctx.sector,
|
|
623
|
-
};
|
|
624
|
-
const publicJwks = await this.wallet.getPublicJwks(walletContext);
|
|
625
|
-
const controllerPublicJwk = publicJwks.find((jwk) => jwk.use === 'sig' || jwk.alg === 'ES384') ?? publicJwks[0];
|
|
626
|
-
|
|
627
|
-
if (!controllerPublicJwk) {
|
|
628
|
-
throw new Error('Wallet provider returned no public JWKs for the requested context.');
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
return controllerPublicJwk;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
private resolveStandardTokenUrl(tokenUrl: string | undefined, tokenPath: string): string {
|
|
635
|
-
if (tokenUrl && tokenUrl.trim()) {
|
|
636
|
-
return tokenUrl.trim();
|
|
637
|
-
}
|
|
638
|
-
return `${this.baseUrl}${tokenPath.startsWith('/') ? tokenPath : `/${tokenPath}`}`;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
private async resolveSmartAuthPublicJwk(
|
|
642
|
-
options: BackendSmartAuthOptions,
|
|
643
|
-
): Promise<PublicJwk | Record<string, unknown>> {
|
|
644
|
-
if (options.publicJwk) {
|
|
645
|
-
return options.publicJwk;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
if (!this.wallet) {
|
|
649
|
-
throw new Error('authenticateBackendSmartStandard requires publicJwk or a configured wallet provider.');
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
const walletContext: WalletContext = options.walletContext ?? {
|
|
653
|
-
tenantId: options.clientId,
|
|
654
|
-
jurisdiction: 'global',
|
|
655
|
-
sector: 'backend',
|
|
656
|
-
};
|
|
657
|
-
const publicJwks = await this.wallet.getPublicJwks(walletContext);
|
|
658
|
-
const signingJwk = publicJwks.find((jwk) => jwk.use === 'sig' || jwk.alg === 'ES384') ?? publicJwks[0];
|
|
659
|
-
|
|
660
|
-
if (!signingJwk) {
|
|
661
|
-
throw new Error('Wallet provider returned no public JWKs for smart-backend.v1.');
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
return signingJwk;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
private async signSmartBackendClientAssertion(params: {
|
|
668
|
-
clientId: string;
|
|
669
|
-
audience: string;
|
|
670
|
-
publicJwk: PublicJwk | Record<string, unknown>;
|
|
671
|
-
ttlSeconds: number;
|
|
672
|
-
walletContext?: WalletContext;
|
|
673
|
-
}): Promise<string> {
|
|
674
|
-
if (!this.wallet) {
|
|
675
|
-
throw new Error('smart-backend.v1 signing requires a configured wallet provider.');
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
const now = Math.floor(Date.now() / 1000);
|
|
679
|
-
const walletContext: WalletContext = params.walletContext ?? {
|
|
680
|
-
tenantId: params.clientId,
|
|
681
|
-
jurisdiction: 'global',
|
|
682
|
-
sector: 'backend',
|
|
683
|
-
};
|
|
684
|
-
const kid = String((params.publicJwk as { kid?: string }).kid ?? '').trim();
|
|
685
|
-
|
|
686
|
-
return this.wallet.signCompactJws(walletContext, {
|
|
687
|
-
header: {
|
|
688
|
-
typ: 'JWT',
|
|
689
|
-
alg: this.preferredJwtAlg(params.publicJwk),
|
|
690
|
-
...(kid ? { kid } : {}),
|
|
691
|
-
},
|
|
692
|
-
claims: {
|
|
693
|
-
iss: params.clientId,
|
|
694
|
-
sub: params.clientId,
|
|
695
|
-
aud: params.audience,
|
|
696
|
-
iat: now,
|
|
697
|
-
exp: now + Math.max(params.ttlSeconds, 1),
|
|
698
|
-
jti: `jwt-${randomUUID()}`,
|
|
699
|
-
},
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
private preferredJwtAlg(publicJwk: PublicJwk | Record<string, unknown>): string {
|
|
704
|
-
const jwk = publicJwk as Record<string, unknown>;
|
|
705
|
-
const alg = String(jwk.alg ?? '').trim();
|
|
706
|
-
if (alg) {
|
|
707
|
-
return alg;
|
|
708
|
-
}
|
|
709
|
-
const kty = String(jwk.kty ?? '').toUpperCase();
|
|
710
|
-
const crv = String(jwk.crv ?? '').toUpperCase();
|
|
711
|
-
if (kty === 'EC' && crv === 'P-256') {
|
|
712
|
-
return 'ES256';
|
|
713
|
-
}
|
|
714
|
-
if (kty === 'EC' && crv === 'P-384') {
|
|
715
|
-
return 'ES384';
|
|
716
|
-
}
|
|
717
|
-
if (kty === 'RSA') {
|
|
718
|
-
return 'RS384';
|
|
719
|
-
}
|
|
720
|
-
return 'ES384';
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// ---- Generic batch API --------------------------------------------------
|
|
724
|
-
`
|
|
725
|
-
);
|
|
726
|
-
|
|
727
|
-
replace(
|
|
728
|
-
'src/client.ts',
|
|
729
|
-
" const url = `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n\n const headers: Record<string, string> = {\n",
|
|
730
|
-
" const url = /^https?:\\/\\//.test(path)\n ? path\n : `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n\n const headers: Record<string, string> = {\n"
|
|
731
|
-
);
|
|
732
|
-
|
|
733
|
-
replace(
|
|
734
|
-
'src/client.ts',
|
|
735
|
-
" const url = `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n\n const headers: Record<string, string> = {\n ...this.defaultHeaders,\n 'Content-Type': contentType,\n",
|
|
736
|
-
" const url = /^https?:\\/\\//.test(path)\n ? path\n : `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n\n const headers: Record<string, string> = {\n ...this.defaultHeaders,\n 'Content-Type': contentType,\n"
|
|
737
|
-
);
|
|
738
|
-
|
|
739
|
-
fs.writeFileSync('tests/client.test.mjs', String.raw`import test from 'node:test';
|
|
740
|
-
import assert from 'node:assert/strict';
|
|
741
|
-
import {
|
|
742
|
-
DataspaceNodeClient,
|
|
743
|
-
MemoryWalletProvider,
|
|
744
|
-
MultiWalletClient,
|
|
745
|
-
createDidcommPlainMessage,
|
|
746
|
-
} from '../dist/index.js';
|
|
747
|
-
|
|
748
|
-
function jsonResponse(body, status = 200) {
|
|
749
|
-
return new Response(JSON.stringify(body), {
|
|
750
|
-
status,
|
|
751
|
-
headers: { 'content-type': 'application/json' },
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
test('builds canonical v1 and host registry paths', () => {
|
|
756
|
-
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000' });
|
|
757
|
-
const ctx = { tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' };
|
|
758
|
-
|
|
759
|
-
assert.equal(
|
|
760
|
-
client.v1Path(ctx, 'individual', 'org.hl7.fhir.api', 'Task', '_batch'),
|
|
761
|
-
'/acme/cds-ES/v1/health-care/individual/org.hl7.fhir.api/Task/_batch',
|
|
762
|
-
);
|
|
763
|
-
assert.equal(
|
|
764
|
-
client.hostRegistryPath({ jurisdiction: 'ES', sector: 'test-network' }, 'Organization', '_activate'),
|
|
765
|
-
'/host/cds-ES/v1/test-network/registry/org.schema/Organization/_activate',
|
|
766
|
-
);
|
|
767
|
-
assert.equal(
|
|
768
|
-
client.identityDeviceDcrPath(ctx),
|
|
769
|
-
'/host/cds-ES/v1/health-care/acme/identity/auth/_dcr',
|
|
770
|
-
);
|
|
771
|
-
assert.equal(
|
|
772
|
-
client.conversionUploadPath(ctx, 'excel-adapter', 'xlsx'),
|
|
773
|
-
'/acme/cds-ES/v1/health-care/conversion/excel-adapter/xlsx/_upload',
|
|
774
|
-
);
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
test('accepts an optional wallet provider without breaking client construction', () => {
|
|
778
|
-
const wallet = new MemoryWalletProvider();
|
|
779
|
-
const client = new DataspaceNodeClient({
|
|
780
|
-
baseUrl: 'http://localhost:3000',
|
|
781
|
-
wallet,
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
assert.equal(client.getWallet(), wallet);
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
test('wallet provider supports ES384 sign/verify, compact JWS, and RSA encrypt/decrypt roundtrips', async () => {
|
|
788
|
-
const provider = new MemoryWalletProvider();
|
|
789
|
-
const wallets = new MultiWalletClient(provider);
|
|
790
|
-
const context = { tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' };
|
|
791
|
-
const wallet = wallets.forContext(context);
|
|
792
|
-
const publicJwks = await wallet.getPublicJwks();
|
|
793
|
-
const signingJwk = publicJwks.find((jwk) => jwk.use === 'sig');
|
|
794
|
-
const encryptionJwk = publicJwks.find((jwk) => jwk.use === 'enc');
|
|
795
|
-
|
|
796
|
-
assert.ok(signingJwk);
|
|
797
|
-
assert.ok(encryptionJwk);
|
|
798
|
-
|
|
799
|
-
const signature = await wallet.sign('hello-wallet');
|
|
800
|
-
assert.equal(await wallet.verify('hello-wallet', signature, signingJwk), true);
|
|
801
|
-
assert.equal(await wallet.verify('tampered', signature, signingJwk), false);
|
|
802
|
-
|
|
803
|
-
const compactJws = await wallet.signCompactJws({
|
|
804
|
-
header: { typ: 'JWT', alg: 'ES384' },
|
|
805
|
-
claims: { sub: 'acme-service', aud: 'https://gw.example.com/token' },
|
|
806
|
-
});
|
|
807
|
-
const [encodedHeader, encodedClaims, encodedSignature] = compactJws.split('.');
|
|
808
|
-
assert.ok(encodedHeader);
|
|
809
|
-
assert.ok(encodedClaims);
|
|
810
|
-
assert.ok(encodedSignature);
|
|
811
|
-
assert.equal(
|
|
812
|
-
await wallet.verify(`${encodedHeader}.${encodedClaims}`, encodedSignature, signingJwk),
|
|
813
|
-
true,
|
|
814
|
-
);
|
|
815
|
-
|
|
816
|
-
const ciphertext = await wallet.encrypt('secret-payload', encryptionJwk);
|
|
817
|
-
const plaintext = await wallet.decrypt(ciphertext);
|
|
818
|
-
assert.equal(Buffer.from(plaintext).toString('utf8'), 'secret-payload');
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
test('authenticateBackendPkceAndExchange resolves controller JWK from wallet when omitted', async () => {
|
|
822
|
-
const provider = new MemoryWalletProvider();
|
|
823
|
-
const client = new DataspaceNodeClient({
|
|
824
|
-
baseUrl: 'http://localhost:3000',
|
|
825
|
-
wallet: provider,
|
|
826
|
-
});
|
|
827
|
-
const ctx = { tenantId: 'acme', jurisdiction: 'ES', sector: 'health-care' };
|
|
828
|
-
const [walletJwk] = await provider.getPublicJwks(ctx);
|
|
829
|
-
const calls = [];
|
|
830
|
-
const originalFetch = globalThis.fetch;
|
|
831
|
-
|
|
832
|
-
globalThis.fetch = async (url, options) => {
|
|
833
|
-
calls.push({ url: String(url), options });
|
|
834
|
-
|
|
835
|
-
switch (calls.length) {
|
|
836
|
-
case 1:
|
|
837
|
-
return jsonResponse({ accepted: true }, 202);
|
|
838
|
-
case 2:
|
|
839
|
-
return jsonResponse({ status: 'COMPLETED' }, 200);
|
|
840
|
-
case 3:
|
|
841
|
-
return jsonResponse({ accepted: true }, 202);
|
|
842
|
-
case 4:
|
|
843
|
-
return jsonResponse({ code: 'pkce-code-001' }, 200);
|
|
844
|
-
case 5:
|
|
845
|
-
return jsonResponse({ accepted: true }, 202);
|
|
846
|
-
case 6:
|
|
847
|
-
return jsonResponse({ id_token: 'id-token-001' }, 200);
|
|
848
|
-
case 7:
|
|
849
|
-
return jsonResponse({ accepted: true }, 202);
|
|
850
|
-
default:
|
|
851
|
-
return jsonResponse({
|
|
852
|
-
access_token: 'access-token-001',
|
|
853
|
-
token_type: 'Bearer',
|
|
854
|
-
scope: 'onboarding family-registration',
|
|
855
|
-
expires_in: 3600,
|
|
856
|
-
}, 200);
|
|
857
|
-
}
|
|
858
|
-
};
|
|
859
|
-
|
|
860
|
-
try {
|
|
861
|
-
const auth = await client.authenticateBackendPkceAndExchange({
|
|
862
|
-
ctx,
|
|
863
|
-
apiKey: 'api-key-001',
|
|
864
|
-
scopes: ['onboarding', 'family-registration'],
|
|
865
|
-
endpointId: 'wallet-backed-auth',
|
|
866
|
-
codeVerifier: 'verifier-001',
|
|
867
|
-
pollOptions: { timeoutMs: 5000, intervalMs: 1 },
|
|
868
|
-
});
|
|
869
|
-
|
|
870
|
-
assert.equal(auth.status, 'fetched');
|
|
871
|
-
assert.equal(auth.accessToken, 'access-token-001');
|
|
872
|
-
assert.equal(calls[0].url, 'http://localhost:3000/host/cds-ES/v1/health-care/acme/identity/auth/_dcr');
|
|
873
|
-
|
|
874
|
-
const dcrPayload = JSON.parse(calls[0].options.body);
|
|
875
|
-
assert.deepEqual(dcrPayload.meta.jws.protected.jwk, walletJwk);
|
|
876
|
-
} finally {
|
|
877
|
-
globalThis.fetch = originalFetch;
|
|
878
|
-
}
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
test('authenticateBackendSmartStandard signs a private_key_jwt assertion with wallet ES384 key', async () => {
|
|
882
|
-
const provider = new MemoryWalletProvider();
|
|
883
|
-
const walletContext = { tenantId: 'service-a', jurisdiction: 'ES', sector: 'health-care' };
|
|
884
|
-
const [walletJwk] = await provider.getPublicJwks(walletContext);
|
|
885
|
-
const client = new DataspaceNodeClient({
|
|
886
|
-
baseUrl: 'http://localhost:3000',
|
|
887
|
-
wallet: provider,
|
|
888
|
-
});
|
|
889
|
-
const calls = [];
|
|
890
|
-
const originalFetch = globalThis.fetch;
|
|
891
|
-
|
|
892
|
-
globalThis.fetch = async (url, options) => {
|
|
893
|
-
calls.push({ url: String(url), options });
|
|
894
|
-
return jsonResponse({
|
|
895
|
-
access_token: 'smart-token-001',
|
|
896
|
-
token_type: 'Bearer',
|
|
897
|
-
scope: 'system/*.read system/*.write',
|
|
898
|
-
expires_in: 3600,
|
|
899
|
-
}, 200);
|
|
900
|
-
};
|
|
901
|
-
|
|
902
|
-
try {
|
|
903
|
-
const result = await client.authenticateBackendSmartStandard({
|
|
904
|
-
clientId: 'service-a',
|
|
905
|
-
scopes: ['system/*.read', 'system/*.write'],
|
|
906
|
-
walletContext,
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
assert.equal(result.status, 'fetched');
|
|
910
|
-
assert.equal(result.profile, 'smart-backend.v1');
|
|
911
|
-
assert.equal(calls.length, 1);
|
|
912
|
-
assert.equal(calls[0].url, 'http://localhost:3000/token');
|
|
913
|
-
|
|
914
|
-
const tokenRequest = JSON.parse(calls[0].options.body);
|
|
915
|
-
assert.equal(tokenRequest.client_id, 'service-a');
|
|
916
|
-
assert.equal(tokenRequest.grant_type, 'client_credentials');
|
|
917
|
-
assert.ok(typeof tokenRequest.client_assertion === 'string');
|
|
918
|
-
|
|
919
|
-
const [encodedHeader, encodedClaims, encodedSignature] = tokenRequest.client_assertion.split('.');
|
|
920
|
-
const header = JSON.parse(Buffer.from(encodedHeader, 'base64url').toString('utf8'));
|
|
921
|
-
const claims = JSON.parse(Buffer.from(encodedClaims, 'base64url').toString('utf8'));
|
|
922
|
-
assert.equal(header.alg, 'ES384');
|
|
923
|
-
assert.equal(header.kid, walletJwk.kid);
|
|
924
|
-
assert.equal(claims.iss, 'service-a');
|
|
925
|
-
assert.equal(claims.sub, 'service-a');
|
|
926
|
-
assert.equal(claims.aud, 'http://localhost:3000/token');
|
|
927
|
-
assert.equal(await provider.verify(`${encodedHeader}.${encodedClaims}`, encodedSignature, walletJwk), true);
|
|
928
|
-
} finally {
|
|
929
|
-
globalThis.fetch = originalFetch;
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
test('submitAndPoll uses DIDComm plain submit and async poll until non-202', async () => {
|
|
934
|
-
const calls = [];
|
|
935
|
-
const originalFetch = globalThis.fetch;
|
|
936
|
-
|
|
937
|
-
globalThis.fetch = async (url, options) => {
|
|
938
|
-
calls.push({ url: String(url), options });
|
|
939
|
-
if (calls.length === 1) {
|
|
940
|
-
return jsonResponse({ accepted: true }, 202);
|
|
941
|
-
}
|
|
942
|
-
if (calls.length === 2) {
|
|
943
|
-
return jsonResponse({ thid: 'thid-001', status: 'PENDING' }, 202);
|
|
944
|
-
}
|
|
945
|
-
return jsonResponse({ thid: 'thid-001', status: 'COMPLETED', body: { ok: true } }, 200);
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
try {
|
|
949
|
-
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000', bearerToken: 'demo-token' });
|
|
950
|
-
const payload = createDidcommPlainMessage({
|
|
951
|
-
iss: 'issuer',
|
|
952
|
-
aud: 'audience',
|
|
953
|
-
thid: 'thid-001',
|
|
954
|
-
body: { data: [] },
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
const result = await client.submitAndPoll(
|
|
958
|
-
'/host/cds-ES/v1/test-network/registry/org.schema/Organization/_batch',
|
|
959
|
-
'/host/cds-ES/v1/test-network/registry/org.schema/Organization/_batch-response',
|
|
960
|
-
payload,
|
|
961
|
-
{ timeoutMs: 5000, intervalMs: 1 },
|
|
962
|
-
);
|
|
963
|
-
|
|
964
|
-
assert.equal(result.submit.status, 202);
|
|
965
|
-
assert.equal(result.poll.status, 200);
|
|
966
|
-
assert.equal(result.poll.attempts, 2);
|
|
967
|
-
|
|
968
|
-
assert.equal(calls[0].options.method, 'POST');
|
|
969
|
-
assert.equal(calls[0].options.headers['Content-Type'], 'application/didcomm-plaintext+json');
|
|
970
|
-
assert.equal(calls[0].options.headers.Authorization, 'Bearer demo-token');
|
|
971
|
-
|
|
972
|
-
assert.equal(calls[1].options.headers['Content-Type'], 'application/json');
|
|
973
|
-
assert.equal(calls[2].options.headers['Content-Type'], 'application/json');
|
|
974
|
-
} finally {
|
|
975
|
-
globalThis.fetch = originalFetch;
|
|
976
|
-
}
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
test('uploadConversionFile sends multipart with file and fields', async () => {
|
|
980
|
-
const calls = [];
|
|
981
|
-
const originalFetch = globalThis.fetch;
|
|
982
|
-
|
|
983
|
-
globalThis.fetch = async (url, options) => {
|
|
984
|
-
calls.push({ url: String(url), options });
|
|
985
|
-
return jsonResponse({ thid: 'upload-thid-001', status: 'queued' }, 202);
|
|
986
|
-
};
|
|
987
|
-
|
|
988
|
-
try {
|
|
989
|
-
const client = new DataspaceNodeClient({ baseUrl: 'http://localhost:3000' });
|
|
990
|
-
const submit = await client.uploadConversionFile({
|
|
991
|
-
path: '/acme/cds-ES/v1/animal-care/conversion/excel-adapter/xlsx/_upload',
|
|
992
|
-
fileName: 'input.xlsx',
|
|
993
|
-
fileContent: new Uint8Array([0x01, 0x02, 0x03]),
|
|
994
|
-
fields: { mode: 'didcomm-plain' },
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
assert.equal(submit.status, 202);
|
|
998
|
-
assert.equal(calls.length, 1);
|
|
999
|
-
assert.equal(calls[0].options.method, 'POST');
|
|
1000
|
-
|
|
1001
|
-
const body = calls[0].options.body;
|
|
1002
|
-
assert.ok(body instanceof FormData);
|
|
1003
|
-
|
|
1004
|
-
const modeValues = body.getAll('mode');
|
|
1005
|
-
assert.equal(modeValues.length, 1);
|
|
1006
|
-
assert.equal(modeValues[0], 'didcomm-plain');
|
|
1007
|
-
|
|
1008
|
-
const filePart = body.get('file');
|
|
1009
|
-
assert.ok(filePart instanceof Blob);
|
|
1010
|
-
} finally {
|
|
1011
|
-
globalThis.fetch = originalFetch;
|
|
1012
|
-
}
|
|
1013
|
-
});
|
|
1014
|
-
`);
|
|
1015
|
-
|
|
1016
|
-
console.log('updated');
|