dataspace-client-sdk-node 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ {
2
+ "iss": "did:web:controller.example.org",
3
+ "sub": "did:web:controller.example.org",
4
+ "aud": "did:web:host.example.com",
5
+ "vp": {
6
+ "@context": [
7
+ "https://www.w3.org/ns/credentials/v2"
8
+ ],
9
+ "type": [
10
+ "VerifiablePresentation"
11
+ ],
12
+ "holder": "did:web:controller.example.org",
13
+ "verifiableCredential": [
14
+ {
15
+ "@context": [
16
+ "https://www.w3.org/ns/credentials/v2",
17
+ "https://schema.org"
18
+ ],
19
+ "type": [
20
+ "VerifiableCredential",
21
+ "OrganizationCredential"
22
+ ],
23
+ "issuer": "did:web:localhost%3A3310",
24
+ "credentialSubject": {
25
+ "id": "did:web:globaldatacare.es:onehealth:organization:taxid:VATES-B00000000",
26
+ "@type": "Organization",
27
+ "legalName": "Example Data Provider SL",
28
+ "taxID": "VATES-B00000000",
29
+ "sameAs": "did:web:provider.example.org",
30
+ "url": "provider.example.org",
31
+ "alternateName": "example-provider",
32
+ "identifier": "did:web:globaldatacare.es:onehealth:organization:taxid:VATES-B00000000",
33
+ "identifierType": "TAX",
34
+ "identifierValue": "VATES-B00000000",
35
+ "address": {
36
+ "@type": "PostalAddress",
37
+ "addressCountry": "ES"
38
+ }
39
+ }
40
+ },
41
+ {
42
+ "@context": [
43
+ "https://www.w3.org/ns/credentials/v2",
44
+ "https://schema.org"
45
+ ],
46
+ "type": [
47
+ "VerifiableCredential",
48
+ "PersonCredential",
49
+ "LegalRepresentativeCredential"
50
+ ],
51
+ "issuer": "did:web:localhost%3A3310",
52
+ "credentialSubject": {
53
+ "id": "urn:person:identifier:IDCES-99999999R",
54
+ "@type": "Person",
55
+ "name": "Alex Example",
56
+ "identifier": "IDCES-99999999R",
57
+ "sameAs": "did:web:controller.example.org",
58
+ "memberOf": {
59
+ "@type": "Organization",
60
+ "taxID": "VATES-B00000000"
61
+ }
62
+ }
63
+ }
64
+ ]
65
+ }
66
+ }
67
+
@@ -0,0 +1,23 @@
1
+ import fs from 'node:fs';
2
+
3
+ function b64url(input) {
4
+ return Buffer.from(input).toString('base64url');
5
+ }
6
+
7
+ export function buildUnsignedVpJwt(payload) {
8
+ const now = Math.floor(Date.now() / 1000);
9
+ const header = { alg: 'none', typ: 'JWT' };
10
+ const claims = {
11
+ iat: now,
12
+ exp: now + 600,
13
+ nonce: `nonce-${now}`,
14
+ ...payload,
15
+ };
16
+ return `${b64url(JSON.stringify(header))}.${b64url(JSON.stringify(claims))}.`;
17
+ }
18
+
19
+ export function loadVpPayloadFixture(path) {
20
+ const raw = fs.readFileSync(path, 'utf8');
21
+ return JSON.parse(raw);
22
+ }
23
+
@@ -0,0 +1,108 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { DataspaceNodeClient } from '../dist/index.js';
6
+ import { buildUnsignedVpJwt, loadVpPayloadFixture } from './helpers/vp-token-fixture.mjs';
7
+
8
+ function env(name, fallback = '') {
9
+ const value = String(process.env[name] ?? fallback).trim();
10
+ return value;
11
+ }
12
+
13
+ const RUN = env('RUN_LIVE_GW_E2E', '0') === '1';
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+
16
+ test('LIVE UC5 chain on local GW (legal -> individual -> consent -> professional token)', { skip: !RUN }, async () => {
17
+ const baseUrl = env('BASE_URL', 'http://127.0.0.1:3000');
18
+ const vpTokenEnv = env('VP_TOKEN');
19
+ const vpTokenFile = env('VP_TOKEN_FILE', path.join(__dirname, 'fixtures', 'ica-vp-minimal.json'));
20
+ const tenantId = env('TENANT_ID', 'VATES-B00000000');
21
+ const jurisdiction = env('JURISDICTION', 'ES');
22
+ const sector = env('SECTOR', 'health-care');
23
+ const hostSector = env('HOST_REGISTRY_SECTOR', 'test');
24
+ const professionalIdToken = env(
25
+ 'PROFESSIONAL_ID_TOKEN',
26
+ 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJwcm9mZXNzaW9uYWwifQ.demo',
27
+ );
28
+
29
+ const vpToken = vpTokenEnv || buildUnsignedVpJwt(loadVpPayloadFixture(vpTokenFile));
30
+ assert.ok(vpToken, 'VP_TOKEN or VP_TOKEN_FILE fixture is required for live activation test.');
31
+
32
+ const hostCtx = { jurisdiction, sector: hostSector };
33
+ const ctx = { tenantId, jurisdiction, sector };
34
+ const pollOptions = { timeoutMs: 120000, intervalMs: 1500 };
35
+
36
+ const ping = await fetch(`${baseUrl}/host/.well-known/ping`);
37
+ assert.equal(ping.status, 200, 'GW ping must be healthy before running live UC5 chain.');
38
+
39
+ const legalClient = new DataspaceNodeClient({ baseUrl, ctx });
40
+
41
+ const activation = await legalClient.activateOrganizationInGatewayFromIcaProof(
42
+ hostCtx,
43
+ { vpToken },
44
+ pollOptions,
45
+ );
46
+ assert.equal(activation.poll.status, 200, 'Legal organization activation must complete (200).');
47
+
48
+ const individual = await legalClient.bootstrapSubjectOrganizationIndex(ctx, {
49
+ registrationPayload: {
50
+ body: {
51
+ data: [
52
+ {
53
+ type: 'Family-registration-form-v1.0',
54
+ meta: {
55
+ claims: {
56
+ '@context': 'org.schema',
57
+ 'org.schema.Organization.alternateName': env('INDIVIDUAL_ALTERNATE_NAME', `family-${Date.now()}`),
58
+ 'org.schema.Person.telephone': env('INDIVIDUAL_CONTROLLER_PHONE', 'tel:+34600111222'),
59
+ },
60
+ },
61
+ },
62
+ ],
63
+ },
64
+ },
65
+ pollOptions,
66
+ });
67
+ assert.equal(
68
+ individual.registration.poll.status,
69
+ 200,
70
+ `Individual organization registration must complete (tenantId='${tenantId}' must exist and be resolvable in GW).`,
71
+ );
72
+
73
+ const consent = await legalClient.grantProfessionalAccessSimple(ctx, {
74
+ subjectPhone: env('SUBJECT_PHONE', '+34600111222'),
75
+ subjectGivenName: env('SUBJECT_GIVEN_NAME', 'Ana'),
76
+ actor: { identifier: env('PROFESSIONAL_DID', 'did:web:professional.example.com') },
77
+ actorRole: env('PROFESSIONAL_ROLE', 'Practitioner'),
78
+ purpose: env('CONSENT_PURPOSE', 'TREAT'),
79
+ actions: ['organization/Composition.rs'],
80
+ pollOptions,
81
+ });
82
+ assert.equal(consent.poll.status, 200, 'Consent grant must complete.');
83
+
84
+ const smart = await legalClient.requestSmartTokenSimple({
85
+ tenantId,
86
+ jurisdiction,
87
+ sector,
88
+ idToken: professionalIdToken,
89
+ targetEndpoint: legalClient.getEndpointId(
90
+ {
91
+ section: 'organization',
92
+ format: 'org.hl7.fhir.r4',
93
+ resourceType: 'Composition',
94
+ action: '_search',
95
+ },
96
+ env('PROFESSIONAL_DID', 'did:web:professional.example.com'),
97
+ ),
98
+ scopes: ['organization/Composition.rs'],
99
+ timeoutSeconds: 60,
100
+ intervalSeconds: 2,
101
+ });
102
+
103
+ assert.ok(smart.accessToken, 'Professional SMART token must be issued.');
104
+ assert.ok(
105
+ Array.isArray(smart.scopes) && smart.scopes.includes('organization/Composition.rs'),
106
+ 'SMART token must include organization/Composition.rs scope.',
107
+ );
108
+ });
@@ -1,185 +0,0 @@
1
- # TODO / Prompt - Next Steps (dataspace-client-sdk-node)
2
-
3
- Objetivo de este backlog:
4
- - Mantener `dataspace-client-sdk-node` como SDK backend oficial para Node.
5
- - Añadir una capa wallet/KMS reutilizable para clientes backend Node (paridad conceptual con Python).
6
- - Evitar que servicios consumidores implementen crypto/auth ad-hoc fuera del SDK.
7
-
8
- Importante (decisión de arquitectura actual):
9
- - El wallet/KMS se implementa temporalmente dentro de este repo en:
10
- - `src/sdk/dataspace-wallet-sdk-node/`
11
- - Más adelante se extraerá a repo/npm independiente (`dataspace-wallet-sdk-node`) sin romper API.
12
-
13
- ## Repositorio fuente y referencias obligatorias
14
-
15
- Estas son las fuentes que el agente debe tomar como referencia técnica antes de implementar:
16
-
17
- 1. Frontend / cliente app:
18
- - `gdc-sdk-client-ts`
19
- - Papel: SDK frontend/wallet UX-side (no backend custodial KMS).
20
-
21
- 2. Backend gateway template:
22
- - `gwtemplate-node-ts`
23
- - Ruta clave: `src/gdc-backend-utils-node`
24
- - Papel: utilidades backend de crypto/KMS y contratos base.
25
-
26
- 3. Backend UNID (fork operativo):
27
- - `gdc-unid-node-ts`
28
- - Papel: integración real de GW + managers + KMS runtime + flujos de negocio.
29
-
30
- 4. SDK Python de referencia de paridad:
31
- - `connector-sdk-py`
32
- - Papel: referencia 1:1 de casos de uso backend.
33
-
34
- 5. Orquestador de canal (consumidor actual):
35
- - `uhc-unid-chat-node`
36
- - Papel: usa SDK de alto nivel; no debe contener crypto/KMS de backend.
37
-
38
- Regla: si hay contradicción entre implementación Node actual y Python, documentar la divergencia y dejar TODO de convergencia.
39
-
40
- ## Contexto que el agente debe respetar
41
-
42
- 1. `uhc-unid-chat-node` y otros consumidores deben seguir usando métodos de alto nivel del SDK.
43
- 2. Los cambios internos de path builders de identidad son breaking solo para consumidores que montan rutas a mano.
44
- 3. Si el consumidor usa `authenticateBackendPkceAndExchange(...)`, no debe cambiar nada en su código por la migración de rutas identity.
45
-
46
- ## Funcionamiento actual de backend tools + KMS (baseline)
47
-
48
- Resumen operativo esperado por el ecosistema backend:
49
-
50
- 1. Capa SDK cliente (`dataspace-client-sdk-node`):
51
- - construye paths canónicos,
52
- - hace submit/poll,
53
- - orquesta auth backend (`_dcr -> _code -> _token -> _exchange`).
54
-
55
- 2. Capa backend utils / KMS (hoy en GW/template repos):
56
- - `KmsService` / `DemoKmsService`,
57
- - interfaces de crypto (`IKmsService`, tipos JWK/JWS/JWE),
58
- - utilidades KEK/envelope para protección de claves y datos.
59
-
60
- 3. Capa GW backend (`gdc-unid-node-ts` / `gwtemplate-node-ts`):
61
- - runtime multitenant,
62
- - provisión y resolución de claves por tenant,
63
- - operaciones de firma/verificación/cifrado en contexto servidor.
64
-
65
- Límite de responsabilidad:
66
- - `dataspace-client-sdk-node` no debe replicar managers de GW.
67
- - Sí debe poder exponer wallet/KMS cliente reutilizable para backends no-GW.
68
-
69
- ## Definición de Hecho (DoD)
70
-
71
- 1. Existe módulo wallet interno funcional con interfaz pública estable.
72
- 2. El flujo `identity-exchange.v1` está soportado por métodos de alto nivel en Node (sin construir rutas manuales fuera del SDK).
73
- 3. Se documenta paridad Node/Python por capability (no solo por endpoint).
74
- 4. Hay tests que fallen si se rompe la paridad mínima acordada.
75
- 5. README y ejemplos permiten a otro equipo integrar sin leer código interno.
76
-
77
- ## Alcance técnico a implementar
78
-
79
- ### A) Wallet/KMS interno (nuevo)
80
-
81
- Crear módulo en `src/sdk/dataspace-wallet-sdk-node/`:
82
-
83
- - `types.ts`
84
- - `WalletContext` (`tenantId`, `jurisdiction`, `sector`, opcional `walletId`)
85
- - `PublicJwk`, `PrivateKeyRef`, `SignOptions`, `EncryptOptions`, `DecryptOptions`
86
- - `WalletProviderKind = 'mem' | 'seed' | 'external'`
87
-
88
- - `provider.ts`
89
- - `WalletProvider` interface:
90
- - `init(...)`
91
- - `getPublicJwks(context)`
92
- - `sign(payload, context, options)`
93
- - `verify(payload, signature, jwk)`
94
- - `encrypt(plaintext, recipientJwk, options)`
95
- - `decrypt(ciphertext, context, options)`
96
-
97
- - `providers/memory-provider.ts`
98
- - provider mínimo productivo para pruebas.
99
-
100
- - `providers/seed-provider.ts`
101
- - solo para `demo/dev` (determinístico).
102
- - prohibido recomendarlo para staging/prod.
103
-
104
- - `WalletClient.ts`
105
- - fachada single-tenant.
106
-
107
- - `MultiWalletClient.ts`
108
- - resolución por `tenantId/jurisdiction/sector`.
109
-
110
- ### B) Integración en `DataspaceNodeClient`
111
-
112
- - Aceptar opcionalmente `wallet` en constructor/options.
113
- - Mantener compatibilidad de los métodos actuales de negocio/auth.
114
- - No mover lógica de dominios al wallet: wallet solo crypto/key operations.
115
-
116
- ### C) Auth de alto nivel (paridad Python)
117
-
118
- Asegurar métodos de alto nivel para:
119
- - `authenticateBackendPkceAndExchange(...)`
120
- - base para `authenticateBackendSmartStandard(...)` (si el contrato backend ya está listo)
121
-
122
- Notas:
123
- - Internamente usar path builders canónicos nuevos.
124
- - Evitar exponer helpers de rutas como API pública “recomendada” para auth.
125
-
126
- ## Plan de ejecución (orden obligatorio)
127
-
128
- 1. Fase 1 - Contratos y estructura
129
- - Crear árbol `src/sdk/dataspace-wallet-sdk-node/*`.
130
- - Definir interfaces y tipos.
131
- - Añadir tests unitarios de contrato (sin integración GW todavía).
132
-
133
- 2. Fase 2 - Provider mem/seed
134
- - Implementar `memory-provider` y `seed-provider`.
135
- - Añadir tests de firma/verificación y cifrado/descifrado.
136
-
137
- 3. Fase 3 - Integración client
138
- - Conectar wallet opcional en `src/client.ts`.
139
- - Mantener sin breaking en usos actuales.
140
-
141
- 4. Fase 4 - Paridad y documentación
142
- - Actualizar `SDK_PARITY_MAP.md` con estado real.
143
- - Actualizar `README.md` con sección “Wallet/KMS for backend clients”.
144
- - Añadir ejemplo runnable en `examples/`.
145
-
146
- 5. Fase 5 - Puerta de extracción futura
147
- - Definir imports internos de forma que luego sea fácil reemplazar:
148
- - `src/sdk/dataspace-wallet-sdk-node/*` -> paquete npm externo.
149
-
150
- ## Tests mínimos obligatorios
151
-
152
- 1. Unit tests wallet:
153
- - sign/verify roundtrip
154
- - encrypt/decrypt roundtrip
155
- - error handling de context faltante
156
-
157
- 2. Unit tests auth orchestration:
158
- - secuencia `_dcr -> _code -> _token -> _exchange`
159
- - gestión de token cacheado vs refresh
160
-
161
- 3. Regression tests identity routes:
162
- - verificar que internamente se usa patrón canónico
163
- - `/{prefix}/cds-{j}/v1/{sector}/{tenantId}/identity/auth/{action}`
164
-
165
- 4. Public API smoke:
166
- - import del SDK no rompe para consumidores actuales.
167
-
168
- ## Criterios de seguridad
169
-
170
- - Ninguna private key en logs.
171
- - `seed-provider` marcado explícitamente como no apto para staging/prod.
172
- - Errores crypto sin volcar material sensible.
173
-
174
- ## Entregables esperados del agente
175
-
176
- 1. PR con código + tests + docs.
177
- 2. Resumen de decisiones (tradeoffs) en la descripción.
178
- 3. Tabla de estado actualizada en `SDK_PARITY_MAP.md`.
179
- 4. Lista de pendientes para extracción a repo independiente.
180
-
181
- ## Plantilla de commit sugerida
182
-
183
- - `feat(wallet): add internal dataspace-wallet-sdk-node module (mem/seed providers)`
184
- - `feat(auth): keep pkce high-level orchestration aligned with canonical identity routes`
185
- - `docs(parity): update SDK parity map and wallet roadmap`