@interop/was-client 0.5.0 → 0.7.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 +166 -0
- package/dist/Collection.d.ts +28 -3
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +74 -13
- package/dist/Collection.js.map +1 -1
- package/dist/Resource.d.ts +64 -9
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +110 -26
- package/dist/Resource.js.map +1 -1
- package/dist/Space.d.ts +4 -0
- package/dist/Space.d.ts.map +1 -1
- package/dist/Space.js +4 -0
- package/dist/Space.js.map +1 -1
- package/dist/WasClient.d.ts +10 -2
- package/dist/WasClient.d.ts.map +1 -1
- package/dist/WasClient.js +13 -6
- package/dist/WasClient.js.map +1 -1
- package/dist/codec.d.ts +127 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +2 -0
- package/dist/codec.js.map +1 -0
- package/dist/edv/EdvCodec.d.ts +148 -0
- package/dist/edv/EdvCodec.d.ts.map +1 -0
- package/dist/edv/EdvCodec.js +284 -0
- package/dist/edv/EdvCodec.js.map +1 -0
- package/dist/edv/WasTransport.d.ts +178 -0
- package/dist/edv/WasTransport.d.ts.map +1 -0
- package/dist/edv/WasTransport.js +284 -0
- package/dist/edv/WasTransport.js.map +1 -0
- package/dist/edv/index.d.ts +21 -0
- package/dist/edv/index.d.ts.map +1 -0
- package/dist/edv/index.js +21 -0
- package/dist/edv/index.js.map +1 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +29 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/codec.d.ts +27 -0
- package/dist/internal/codec.d.ts.map +1 -0
- package/dist/internal/codec.js +44 -0
- package/dist/internal/codec.js.map +1 -0
- package/dist/internal/conditional.d.ts +40 -0
- package/dist/internal/conditional.d.ts.map +1 -0
- package/dist/internal/conditional.js +35 -0
- package/dist/internal/conditional.js.map +1 -0
- package/dist/internal/content.d.ts +30 -1
- package/dist/internal/content.d.ts.map +1 -1
- package/dist/internal/content.js +68 -3
- package/dist/internal/content.js.map +1 -1
- package/dist/internal/request.d.ts +5 -1
- package/dist/internal/request.d.ts.map +1 -1
- package/dist/internal/request.js +2 -7
- package/dist/internal/request.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +13 -3
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The EDV (Encrypted Data Vault) resource codec and its `EncryptionProvider`
|
|
6
|
+
* factory -- the encrypting half of the codec seam. Bound to an
|
|
7
|
+
* encrypted collection, it encrypts a caller's value into an EDV envelope
|
|
8
|
+
* (`{ id, sequence, indexed, jwe }`) on write and decrypts it on read, so
|
|
9
|
+
* `collection.put(id, obj)` / `collection.get(id)` transparently round-trip
|
|
10
|
+
* ciphertext. Keys live in the wallet and are supplied per-collection by the
|
|
11
|
+
* app's `resolveKeys`; they never reach the server, which stores only opaque
|
|
12
|
+
* JWE envelopes.
|
|
13
|
+
*
|
|
14
|
+
* This reuses `EdvClientCore`'s encrypt/decrypt primitives (the same JWE
|
|
15
|
+
* machinery as the standalone `WasTransport`), but here the WAS Resource CRUD --
|
|
16
|
+
* the transport role -- is played by core was-client's `Collection`/`Resource`
|
|
17
|
+
* I/O, so the codec is a pure encode/decode transform and needs no transport of
|
|
18
|
+
* its own.
|
|
19
|
+
*
|
|
20
|
+
* Scope (documents-only):
|
|
21
|
+
*
|
|
22
|
+
* - **Restrict-mode ids.** `add()` mints a 128-bit multibase EDV id; the WAS
|
|
23
|
+
* resource id IS that EDV id. `put(id, ...)` accepts only an EDV-format id --
|
|
24
|
+
* a human-readable id is rejected (it would leak onto the URL). Carry a
|
|
25
|
+
* human-readable label inside the encrypted content instead. (Blind-derived
|
|
26
|
+
* ids are a deferred future item.)
|
|
27
|
+
* - **Small binary as a single JWE.** A `Blob`/`Uint8Array` under the size cap
|
|
28
|
+
* is wrapped and encrypted as one document; an oversized one is rejected
|
|
29
|
+
* (chunked encrypted blobs need the server's `chunked-streams` affordance).
|
|
30
|
+
* - **Enforced sequence (conditional writes).** The codec sets
|
|
31
|
+
* `conditionalWrites`, so the write path pre-reads the current envelope and
|
|
32
|
+
* hands it to `encode`: an update advances `sequence` from its prior value and
|
|
33
|
+
* pins the write to the server's current ETag via `If-Match`, while a fresh
|
|
34
|
+
* insert (`sequence: 0`) is guarded by `If-None-Match: *`. A stale write
|
|
35
|
+
* surfaces as a `PreconditionFailedError` (412) -- the lost-update guard --
|
|
36
|
+
* rather than the old advisory last-writer-wins. Against a backend that does
|
|
37
|
+
* not advertise `conditional-writes` (no ETag) it degrades to advisory.
|
|
38
|
+
* - **No server-visible metadata.** `allowsServerMetadata` is `false`, so
|
|
39
|
+
* `setName`/`setTags` throw on an encrypted collection (the core seam enforces
|
|
40
|
+
* this).
|
|
41
|
+
*/
|
|
42
|
+
import { EdvClientCore } from '@interop/edv-client';
|
|
43
|
+
import type { HttpResponse } from '@interop/http-client';
|
|
44
|
+
import type { IKeyAgreementKey, IKeyResolver } from '@interop/data-integrity-core';
|
|
45
|
+
import type { EncodedWrite, EncryptionProvider, ResourceCodec } from '../codec.js';
|
|
46
|
+
import type { Json } from '../types.js';
|
|
47
|
+
import { EDV_CONTENT_TYPE } from './WasTransport.js';
|
|
48
|
+
/**
|
|
49
|
+
* A {@link ResourceCodec} that encrypts on write and decrypts on read using an
|
|
50
|
+
* `EdvClientCore`'s JWE primitives. One instance is bound per encrypted
|
|
51
|
+
* collection handle.
|
|
52
|
+
*/
|
|
53
|
+
export declare class EdvCodec implements ResourceCodec {
|
|
54
|
+
readonly allowsServerMetadata = false;
|
|
55
|
+
readonly conditionalWrites = true;
|
|
56
|
+
private readonly _edv;
|
|
57
|
+
private readonly _keyAgreementKey;
|
|
58
|
+
private readonly _contentType;
|
|
59
|
+
private readonly _maxBlobBytes;
|
|
60
|
+
/**
|
|
61
|
+
* @param options {object}
|
|
62
|
+
* @param options.edv {EdvClientCore} holds the cipher + key resolver
|
|
63
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the recipient/decrypt key
|
|
64
|
+
* @param options.contentType {string} stored envelope content type
|
|
65
|
+
* @param options.maxBlobBytes {number} single-document binary cap
|
|
66
|
+
*/
|
|
67
|
+
constructor({ edv, keyAgreementKey, contentType, maxBlobBytes }: {
|
|
68
|
+
edv: EdvClientCore;
|
|
69
|
+
keyAgreementKey: IKeyAgreementKey;
|
|
70
|
+
contentType: string;
|
|
71
|
+
maxBlobBytes: number;
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* @inheritdoc
|
|
75
|
+
*/
|
|
76
|
+
encode({ id, data, contentType, current }: {
|
|
77
|
+
id?: string;
|
|
78
|
+
data: Json | Blob | Uint8Array;
|
|
79
|
+
contentType?: string;
|
|
80
|
+
current?: HttpResponse | null;
|
|
81
|
+
}): Promise<EncodedWrite>;
|
|
82
|
+
/**
|
|
83
|
+
* @inheritdoc
|
|
84
|
+
*/
|
|
85
|
+
decode(response: {
|
|
86
|
+
data?: unknown;
|
|
87
|
+
json(): Promise<unknown>;
|
|
88
|
+
}): Promise<Json | Blob>;
|
|
89
|
+
/**
|
|
90
|
+
* Resolves a caller value to the EDV document `content`: a JSON object/array
|
|
91
|
+
* passes through; a `Blob`/`Uint8Array` (under the cap) is wrapped as a
|
|
92
|
+
* base64 blob record; a bare primitive is rejected (mirroring the plaintext
|
|
93
|
+
* `prepareBody` contract).
|
|
94
|
+
*
|
|
95
|
+
* @param data {Json | Blob | Uint8Array}
|
|
96
|
+
* @param [contentType] {string}
|
|
97
|
+
* @returns {Promise<object>}
|
|
98
|
+
*/
|
|
99
|
+
private _toContent;
|
|
100
|
+
/**
|
|
101
|
+
* Wraps binary bytes as an EDV document content record, enforcing the
|
|
102
|
+
* single-document size cap.
|
|
103
|
+
*
|
|
104
|
+
* @param bytes {Uint8Array}
|
|
105
|
+
* @param contentType {string}
|
|
106
|
+
* @returns {object}
|
|
107
|
+
*/
|
|
108
|
+
private _blobContent;
|
|
109
|
+
/**
|
|
110
|
+
* Reconstructs a caller value from decrypted EDV document content: a `Blob`
|
|
111
|
+
* for a wrapped blob record, otherwise the JSON content verbatim.
|
|
112
|
+
*
|
|
113
|
+
* @param content {unknown}
|
|
114
|
+
* @returns {Json | Blob}
|
|
115
|
+
*/
|
|
116
|
+
private _fromContent;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Builds an {@link EncryptionProvider} that encrypts the collections the client
|
|
120
|
+
* holds keys for, wiring the per-collection keys (from `resolveKeys`) into an
|
|
121
|
+
* {@link EdvCodec}. Pass the result as `WasClient`'s `encryption` option; core
|
|
122
|
+
* resolves a codec for a collection exactly when `resolveKeys` returns keys for
|
|
123
|
+
* it (the switch is keys alone -- encryption is not a backend feature).
|
|
124
|
+
*
|
|
125
|
+
* @param options {object}
|
|
126
|
+
* @param options.resolveKeys {function} returns the collection's
|
|
127
|
+
* `{ keyAgreementKey, keyResolver }`, or `null` to read/write it as plaintext
|
|
128
|
+
* @param [options.contentType] {string} stored envelope content type;
|
|
129
|
+
* defaults to `application/json`. Pass `EDV_CONTENT_TYPE`
|
|
130
|
+
* (`application/edv+json`) against a server that registers an
|
|
131
|
+
* `application/*+json` parser.
|
|
132
|
+
* @param [options.maxBlobBytes] {number} single-document binary cap (default
|
|
133
|
+
* 1 MiB)
|
|
134
|
+
* @returns {EncryptionProvider}
|
|
135
|
+
*/
|
|
136
|
+
export declare function createEdvEncryption({ resolveKeys, contentType, maxBlobBytes }: {
|
|
137
|
+
resolveKeys: (ref: {
|
|
138
|
+
spaceId: string;
|
|
139
|
+
collectionId: string;
|
|
140
|
+
}) => Promise<{
|
|
141
|
+
keyAgreementKey: IKeyAgreementKey;
|
|
142
|
+
keyResolver: IKeyResolver;
|
|
143
|
+
} | null>;
|
|
144
|
+
contentType?: string;
|
|
145
|
+
maxBlobBytes?: number;
|
|
146
|
+
}): EncryptionProvider;
|
|
147
|
+
export { EDV_CONTENT_TYPE };
|
|
148
|
+
//# sourceMappingURL=EdvCodec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EdvCodec.d.ts","sourceRoot":"","sources":["../../src/edv/EdvCodec.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,KAAK,EAEV,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACd,MAAM,aAAa,CAAA;AAGpB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAsEpD;;;;GAIG;AACH,qBAAa,QAAS,YAAW,aAAa;IAC5C,QAAQ,CAAC,oBAAoB,SAAQ;IACrC,QAAQ,CAAC,iBAAiB,QAAO;IAEjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IAEtC;;;;;;OAMG;gBACS,EACV,GAAG,EACH,eAAe,EACf,WAAW,EACX,YAAY,EACb,EAAE;QACD,GAAG,EAAE,aAAa,CAAA;QAClB,eAAe,EAAE,gBAAgB,CAAA;QACjC,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;KACrB;IAOD;;OAEG;IACG,MAAM,CAAC,EACX,EAAE,EACF,IAAI,EACJ,WAAW,EACX,OAAO,EACR,EAAE;QACD,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,UAAU,CAAA;QAC9B,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAAA;KAC9B,GAAG,OAAO,CAAC,YAAY,CAAC;IA+CzB;;OAEG;IACG,MAAM,CAAC,QAAQ,EAAE;QACrB,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;KACzB,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAWxB;;;;;;;;;OASG;YACW,UAAU;IAuBxB;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;IAYpB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;CAarB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,WAAkC,EAClC,YAAqC,EACtC,EAAE;IACD,WAAW,EAAE,CAAC,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;QACvE,eAAe,EAAE,gBAAgB,CAAA;QACjC,WAAW,EAAE,YAAY,CAAA;KAC1B,GAAG,IAAI,CAAC,CAAA;IACT,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,GAAG,kBAAkB,CAmBrB;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The EDV (Encrypted Data Vault) resource codec and its `EncryptionProvider`
|
|
6
|
+
* factory -- the encrypting half of the codec seam. Bound to an
|
|
7
|
+
* encrypted collection, it encrypts a caller's value into an EDV envelope
|
|
8
|
+
* (`{ id, sequence, indexed, jwe }`) on write and decrypts it on read, so
|
|
9
|
+
* `collection.put(id, obj)` / `collection.get(id)` transparently round-trip
|
|
10
|
+
* ciphertext. Keys live in the wallet and are supplied per-collection by the
|
|
11
|
+
* app's `resolveKeys`; they never reach the server, which stores only opaque
|
|
12
|
+
* JWE envelopes.
|
|
13
|
+
*
|
|
14
|
+
* This reuses `EdvClientCore`'s encrypt/decrypt primitives (the same JWE
|
|
15
|
+
* machinery as the standalone `WasTransport`), but here the WAS Resource CRUD --
|
|
16
|
+
* the transport role -- is played by core was-client's `Collection`/`Resource`
|
|
17
|
+
* I/O, so the codec is a pure encode/decode transform and needs no transport of
|
|
18
|
+
* its own.
|
|
19
|
+
*
|
|
20
|
+
* Scope (documents-only):
|
|
21
|
+
*
|
|
22
|
+
* - **Restrict-mode ids.** `add()` mints a 128-bit multibase EDV id; the WAS
|
|
23
|
+
* resource id IS that EDV id. `put(id, ...)` accepts only an EDV-format id --
|
|
24
|
+
* a human-readable id is rejected (it would leak onto the URL). Carry a
|
|
25
|
+
* human-readable label inside the encrypted content instead. (Blind-derived
|
|
26
|
+
* ids are a deferred future item.)
|
|
27
|
+
* - **Small binary as a single JWE.** A `Blob`/`Uint8Array` under the size cap
|
|
28
|
+
* is wrapped and encrypted as one document; an oversized one is rejected
|
|
29
|
+
* (chunked encrypted blobs need the server's `chunked-streams` affordance).
|
|
30
|
+
* - **Enforced sequence (conditional writes).** The codec sets
|
|
31
|
+
* `conditionalWrites`, so the write path pre-reads the current envelope and
|
|
32
|
+
* hands it to `encode`: an update advances `sequence` from its prior value and
|
|
33
|
+
* pins the write to the server's current ETag via `If-Match`, while a fresh
|
|
34
|
+
* insert (`sequence: 0`) is guarded by `If-None-Match: *`. A stale write
|
|
35
|
+
* surfaces as a `PreconditionFailedError` (412) -- the lost-update guard --
|
|
36
|
+
* rather than the old advisory last-writer-wins. Against a backend that does
|
|
37
|
+
* not advertise `conditional-writes` (no ETag) it degrades to advisory.
|
|
38
|
+
* - **No server-visible metadata.** `allowsServerMetadata` is `false`, so
|
|
39
|
+
* `setName`/`setTags` throw on an encrypted collection (the core seam enforces
|
|
40
|
+
* this).
|
|
41
|
+
*/
|
|
42
|
+
import { EdvClientCore } from '@interop/edv-client';
|
|
43
|
+
import { ValidationError } from '../errors.js';
|
|
44
|
+
import { readJsonData } from '../internal/content.js';
|
|
45
|
+
import { EDV_CONTENT_TYPE } from './WasTransport.js';
|
|
46
|
+
/**
|
|
47
|
+
* Default ceiling for a single-document (unchunked) encrypted binary write.
|
|
48
|
+
* Past this a `Blob`/`Uint8Array` is rejected until chunked encrypted blobs are
|
|
49
|
+
* supported. 1 MiB before the ~33% base64 inflation.
|
|
50
|
+
*/
|
|
51
|
+
const DEFAULT_MAX_BLOB_BYTES = 1024 * 1024;
|
|
52
|
+
/**
|
|
53
|
+
* The default stored content type. Plain JSON keeps the codec portable across
|
|
54
|
+
* any document-capable server (the envelope is still self-identifying by its
|
|
55
|
+
* `jwe` field). Pass `contentType: EDV_CONTENT_TYPE`
|
|
56
|
+
* (`application/edv+json`) against a server that registers an
|
|
57
|
+
* `application/*+json` parser to mark envelopes distinctly in listings.
|
|
58
|
+
*/
|
|
59
|
+
const DEFAULT_CONTENT_TYPE = 'application/json';
|
|
60
|
+
/**
|
|
61
|
+
* Marker property tagging an encrypted document whose decrypted content is a
|
|
62
|
+
* binary blob (rather than a JSON value), so `decode` can reconstruct a `Blob`.
|
|
63
|
+
* Namespaced to avoid colliding with caller JSON.
|
|
64
|
+
*/
|
|
65
|
+
const BLOB_MARKER = '@interop/was-client:edvBlob';
|
|
66
|
+
/**
|
|
67
|
+
* Heuristic for an EDV document id: multibase base58btc (leading `z`) of a
|
|
68
|
+
* 128-bit value. Used to reject human-readable ids on `put()`. Deliberately a
|
|
69
|
+
* loose, dependency-free check (base58 charset, leading `z`, minimum length) --
|
|
70
|
+
* it rejects ids with human separators (`.`/`-`/`_`/spaces) and is not a full
|
|
71
|
+
* decode-and-length verification.
|
|
72
|
+
*/
|
|
73
|
+
const EDV_DOC_ID = /^z[1-9A-HJ-NP-Za-km-z]{21,}$/;
|
|
74
|
+
const ENCODER = new TextEncoder();
|
|
75
|
+
function isBlob(value) {
|
|
76
|
+
return typeof Blob !== 'undefined' && value instanceof Blob;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Encodes bytes to standard base64 using the platform `btoa` (present in modern
|
|
80
|
+
* Node and browsers), via a binary string.
|
|
81
|
+
*
|
|
82
|
+
* @param bytes {Uint8Array}
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
function bytesToBase64(bytes) {
|
|
86
|
+
let binary = '';
|
|
87
|
+
for (const byte of bytes) {
|
|
88
|
+
binary += String.fromCharCode(byte);
|
|
89
|
+
}
|
|
90
|
+
return btoa(binary);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Decodes standard base64 to bytes using the platform `atob`.
|
|
94
|
+
*
|
|
95
|
+
* @param base64 {string}
|
|
96
|
+
* @returns {Uint8Array}
|
|
97
|
+
*/
|
|
98
|
+
function base64ToBytes(base64) {
|
|
99
|
+
const binary = atob(base64);
|
|
100
|
+
const bytes = new Uint8Array(binary.length);
|
|
101
|
+
for (let index = 0; index < binary.length; index++) {
|
|
102
|
+
bytes[index] = binary.charCodeAt(index);
|
|
103
|
+
}
|
|
104
|
+
return bytes;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A {@link ResourceCodec} that encrypts on write and decrypts on read using an
|
|
108
|
+
* `EdvClientCore`'s JWE primitives. One instance is bound per encrypted
|
|
109
|
+
* collection handle.
|
|
110
|
+
*/
|
|
111
|
+
export class EdvCodec {
|
|
112
|
+
allowsServerMetadata = false;
|
|
113
|
+
conditionalWrites = true;
|
|
114
|
+
_edv;
|
|
115
|
+
_keyAgreementKey;
|
|
116
|
+
_contentType;
|
|
117
|
+
_maxBlobBytes;
|
|
118
|
+
/**
|
|
119
|
+
* @param options {object}
|
|
120
|
+
* @param options.edv {EdvClientCore} holds the cipher + key resolver
|
|
121
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the recipient/decrypt key
|
|
122
|
+
* @param options.contentType {string} stored envelope content type
|
|
123
|
+
* @param options.maxBlobBytes {number} single-document binary cap
|
|
124
|
+
*/
|
|
125
|
+
constructor({ edv, keyAgreementKey, contentType, maxBlobBytes }) {
|
|
126
|
+
this._edv = edv;
|
|
127
|
+
this._keyAgreementKey = keyAgreementKey;
|
|
128
|
+
this._contentType = contentType;
|
|
129
|
+
this._maxBlobBytes = maxBlobBytes;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* @inheritdoc
|
|
133
|
+
*/
|
|
134
|
+
async encode({ id, data, contentType, current }) {
|
|
135
|
+
if (id !== undefined && !EDV_DOC_ID.test(id)) {
|
|
136
|
+
throw new ValidationError(`Cannot write a human-readable id "${id}" to an encrypted collection ` +
|
|
137
|
+
'-- it would leak onto the URL. Use add() to mint an EDV document ' +
|
|
138
|
+
'id, or carry the human-readable label inside the encrypted content.');
|
|
139
|
+
}
|
|
140
|
+
const docId = id ?? (await this._edv.generateId());
|
|
141
|
+
const content = await this._toContent(data, contentType);
|
|
142
|
+
// When the write path pre-read a current envelope, advance `sequence` from
|
|
143
|
+
// its prior value (`_encrypt({ update: true })` increments it) and pin the
|
|
144
|
+
// write to the server's current ETag with `If-Match`. With no prior envelope
|
|
145
|
+
// this is a fresh insert (`sequence: 0`) guarded by `If-None-Match: *`
|
|
146
|
+
// (create-if-absent), so a concurrent first writer cannot be clobbered.
|
|
147
|
+
const priorDoc = current
|
|
148
|
+
? (await readJsonData(current))
|
|
149
|
+
: null;
|
|
150
|
+
const recipients = this._edv._createDefaultRecipients(this._keyAgreementKey);
|
|
151
|
+
const encrypted = await this._edv._encrypt({
|
|
152
|
+
doc: {
|
|
153
|
+
id: docId,
|
|
154
|
+
content,
|
|
155
|
+
...(priorDoc && { sequence: priorDoc.sequence })
|
|
156
|
+
},
|
|
157
|
+
recipients,
|
|
158
|
+
keyResolver: this._edv.keyResolver,
|
|
159
|
+
hmac: undefined,
|
|
160
|
+
update: priorDoc !== null
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
id: docId,
|
|
164
|
+
body: ENCODER.encode(JSON.stringify(encrypted)),
|
|
165
|
+
contentType: this._contentType,
|
|
166
|
+
// Pin an update to the server's current ETag; guard a fresh insert with
|
|
167
|
+
// create-if-absent. (An ETag is absent only against a backend without the
|
|
168
|
+
// conditional-writes feature, where this degrades to an advisory write.)
|
|
169
|
+
...(priorDoc
|
|
170
|
+
? { ifMatch: current.headers.get('etag') ?? undefined }
|
|
171
|
+
: { ifNoneMatch: true })
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* @inheritdoc
|
|
176
|
+
*/
|
|
177
|
+
async decode(response) {
|
|
178
|
+
const encryptedDoc = (await readJsonData(response));
|
|
179
|
+
const decrypted = await this._edv._decrypt({
|
|
180
|
+
encryptedDoc,
|
|
181
|
+
keyAgreementKey: this._keyAgreementKey
|
|
182
|
+
});
|
|
183
|
+
return this._fromContent(decrypted.content);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Resolves a caller value to the EDV document `content`: a JSON object/array
|
|
187
|
+
* passes through; a `Blob`/`Uint8Array` (under the cap) is wrapped as a
|
|
188
|
+
* base64 blob record; a bare primitive is rejected (mirroring the plaintext
|
|
189
|
+
* `prepareBody` contract).
|
|
190
|
+
*
|
|
191
|
+
* @param data {Json | Blob | Uint8Array}
|
|
192
|
+
* @param [contentType] {string}
|
|
193
|
+
* @returns {Promise<object>}
|
|
194
|
+
*/
|
|
195
|
+
async _toContent(data, contentType) {
|
|
196
|
+
if (isBlob(data)) {
|
|
197
|
+
const bytes = new Uint8Array(await data.arrayBuffer());
|
|
198
|
+
return this._blobContent(bytes, contentType || data.type || 'application/octet-stream');
|
|
199
|
+
}
|
|
200
|
+
if (data instanceof Uint8Array) {
|
|
201
|
+
return this._blobContent(data, contentType || 'application/octet-stream');
|
|
202
|
+
}
|
|
203
|
+
if (data !== null && typeof data === 'object') {
|
|
204
|
+
return data;
|
|
205
|
+
}
|
|
206
|
+
throw new ValidationError('Encrypted resource data must be a plain object/array (JSON) or a ' +
|
|
207
|
+
'Blob/Uint8Array (binary).');
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Wraps binary bytes as an EDV document content record, enforcing the
|
|
211
|
+
* single-document size cap.
|
|
212
|
+
*
|
|
213
|
+
* @param bytes {Uint8Array}
|
|
214
|
+
* @param contentType {string}
|
|
215
|
+
* @returns {object}
|
|
216
|
+
*/
|
|
217
|
+
_blobContent(bytes, contentType) {
|
|
218
|
+
if (bytes.length > this._maxBlobBytes) {
|
|
219
|
+
throw new ValidationError(`Encrypted binary write of ${bytes.length} bytes exceeds the ` +
|
|
220
|
+
`single-document limit of ${this._maxBlobBytes} bytes. Chunked ` +
|
|
221
|
+
"encrypted blobs need the server's chunked-streams affordance " +
|
|
222
|
+
'(not yet available).');
|
|
223
|
+
}
|
|
224
|
+
return { [BLOB_MARKER]: true, contentType, base64: bytesToBase64(bytes) };
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Reconstructs a caller value from decrypted EDV document content: a `Blob`
|
|
228
|
+
* for a wrapped blob record, otherwise the JSON content verbatim.
|
|
229
|
+
*
|
|
230
|
+
* @param content {unknown}
|
|
231
|
+
* @returns {Json | Blob}
|
|
232
|
+
*/
|
|
233
|
+
_fromContent(content) {
|
|
234
|
+
if (content !== null &&
|
|
235
|
+
typeof content === 'object' &&
|
|
236
|
+
content[BLOB_MARKER] === true) {
|
|
237
|
+
const record = content;
|
|
238
|
+
return new Blob([base64ToBytes(record.base64)], {
|
|
239
|
+
type: record.contentType
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return content;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Builds an {@link EncryptionProvider} that encrypts the collections the client
|
|
247
|
+
* holds keys for, wiring the per-collection keys (from `resolveKeys`) into an
|
|
248
|
+
* {@link EdvCodec}. Pass the result as `WasClient`'s `encryption` option; core
|
|
249
|
+
* resolves a codec for a collection exactly when `resolveKeys` returns keys for
|
|
250
|
+
* it (the switch is keys alone -- encryption is not a backend feature).
|
|
251
|
+
*
|
|
252
|
+
* @param options {object}
|
|
253
|
+
* @param options.resolveKeys {function} returns the collection's
|
|
254
|
+
* `{ keyAgreementKey, keyResolver }`, or `null` to read/write it as plaintext
|
|
255
|
+
* @param [options.contentType] {string} stored envelope content type;
|
|
256
|
+
* defaults to `application/json`. Pass `EDV_CONTENT_TYPE`
|
|
257
|
+
* (`application/edv+json`) against a server that registers an
|
|
258
|
+
* `application/*+json` parser.
|
|
259
|
+
* @param [options.maxBlobBytes] {number} single-document binary cap (default
|
|
260
|
+
* 1 MiB)
|
|
261
|
+
* @returns {EncryptionProvider}
|
|
262
|
+
*/
|
|
263
|
+
export function createEdvEncryption({ resolveKeys, contentType = DEFAULT_CONTENT_TYPE, maxBlobBytes = DEFAULT_MAX_BLOB_BYTES }) {
|
|
264
|
+
return {
|
|
265
|
+
async resolveCodec({ spaceId, collectionId }) {
|
|
266
|
+
const keys = await resolveKeys({ spaceId, collectionId });
|
|
267
|
+
if (!keys) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const edv = new EdvClientCore({
|
|
271
|
+
keyAgreementKey: keys.keyAgreementKey,
|
|
272
|
+
keyResolver: keys.keyResolver
|
|
273
|
+
});
|
|
274
|
+
return new EdvCodec({
|
|
275
|
+
edv,
|
|
276
|
+
keyAgreementKey: keys.keyAgreementKey,
|
|
277
|
+
contentType,
|
|
278
|
+
maxBlobBytes
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
export { EDV_CONTENT_TYPE };
|
|
284
|
+
//# sourceMappingURL=EdvCodec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EdvCodec.js","sourceRoot":"","sources":["../../src/edv/EdvCodec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* An `@interop/edv-client` `Transport` that maps Encrypted Data Vault (EDV)
|
|
6
|
+
* document operations onto ordinary WAS Resource CRUD -- the "EDV-over-WAS"
|
|
7
|
+
* layout profile (Layer 1). It is paired with `EdvClientCore`, which does all
|
|
8
|
+
* encryption, decryption, and index blinding client-side; this transport only
|
|
9
|
+
* moves opaque JWE documents to and from a WAS server, reusing the client's
|
|
10
|
+
* zcap-signed request layer (`WasClient.request()`). Keys never reach the
|
|
11
|
+
* server.
|
|
12
|
+
*
|
|
13
|
+
* Profile decisions encoded here:
|
|
14
|
+
*
|
|
15
|
+
* - **Vault per Collection.** The WAS Collection is the EDV vault; each
|
|
16
|
+
* encrypted document is one WAS Resource.
|
|
17
|
+
* - **Restrict-mode ids.** The WAS resource id IS the EDV document id (the
|
|
18
|
+
* 128-bit multibase value `EdvClientCore.generateId()` produces), which is
|
|
19
|
+
* URL-safe and never a reserved segment.
|
|
20
|
+
* - **Encrypted content type.** Documents are stored as `application/json` by
|
|
21
|
+
* default, so the profile works against an unmodified WAS server. The
|
|
22
|
+
* preferred marker is `application/edv+json` (exported as `EDV_CONTENT_TYPE`),
|
|
23
|
+
* which distinguishes EDV envelopes from plaintext application JSON in
|
|
24
|
+
* listings and metadata -- but the server must register an `application/*+json`
|
|
25
|
+
* content-type parser to accept it (the reference was-teaching-server does; a
|
|
26
|
+
* server that does not will reject it with 415). Pass `contentType:
|
|
27
|
+
* EDV_CONTENT_TYPE` to opt into it where the server supports it.
|
|
28
|
+
*
|
|
29
|
+
* Scope: documents only (`insert` / `update` / `get`). Blinded `find` / `count`
|
|
30
|
+
* / `updateIndex` and chunked streams (`storeChunk` / `getChunk`) require
|
|
31
|
+
* server-side EDV affordances (blinded `/query`, the `/{id}/chunks/{n}`
|
|
32
|
+
* sub-segment, conditional writes) that a plaintext WAS server does not yet
|
|
33
|
+
* provide, so they throw here. Because there are no conditional writes, the EDV
|
|
34
|
+
* `sequence` is advisory (last-writer-wins on `update`).
|
|
35
|
+
*/
|
|
36
|
+
import { Transport } from '@interop/edv-client';
|
|
37
|
+
import type { IEncryptedDocument } from '@interop/data-integrity-core';
|
|
38
|
+
import type { WasClient } from '../WasClient.js';
|
|
39
|
+
/**
|
|
40
|
+
* The preferred content type marking a stored EDV-encrypted document (a JSON
|
|
41
|
+
* envelope whose `jwe` property carries the ciphertext). Requires the server to
|
|
42
|
+
* register an `application/*+json` content-type parser; otherwise use the
|
|
43
|
+
* default `application/json` (see `WasTransport`'s `contentType` option).
|
|
44
|
+
*/
|
|
45
|
+
export declare const EDV_CONTENT_TYPE = "application/edv+json";
|
|
46
|
+
/**
|
|
47
|
+
* The subset of `WasClient` this transport depends on: the signed-request
|
|
48
|
+
* escape hatch. Declared structurally so tests can supply a lightweight stub.
|
|
49
|
+
*/
|
|
50
|
+
type WasRequester = Pick<WasClient, 'request'>;
|
|
51
|
+
export declare class WasTransport extends Transport {
|
|
52
|
+
readonly spaceId: string;
|
|
53
|
+
readonly collectionId: string;
|
|
54
|
+
readonly contentType: string;
|
|
55
|
+
private readonly _was;
|
|
56
|
+
/**
|
|
57
|
+
* @param options {object}
|
|
58
|
+
* @param options.was {WasClient} a WAS client holding the signer
|
|
59
|
+
* @param options.spaceId {string} the vault's Space id
|
|
60
|
+
* @param options.collectionId {string} the vault Collection id
|
|
61
|
+
* @param [options.contentType] {string} content type for stored envelopes;
|
|
62
|
+
* defaults to `application/json` (accepted by an unmodified server). Pass
|
|
63
|
+
* `EDV_CONTENT_TYPE` against a server that registers an `application/*+json`
|
|
64
|
+
* parser.
|
|
65
|
+
*/
|
|
66
|
+
constructor({ was, spaceId, collectionId, contentType }: {
|
|
67
|
+
was: WasRequester;
|
|
68
|
+
spaceId: string;
|
|
69
|
+
collectionId: string;
|
|
70
|
+
contentType?: string;
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* The WAS resource path for a document id, delegating to was-client's
|
|
74
|
+
* internal `resourcePath` builder so the percent-encoding and trailing-slash
|
|
75
|
+
* rules stay defined in one place (no trailing slash -- get/put/delete by
|
|
76
|
+
* id).
|
|
77
|
+
*
|
|
78
|
+
* @param id {string} the EDV document id (= WAS resource id)
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
private _resourcePath;
|
|
82
|
+
/**
|
|
83
|
+
* Writes an encrypted document to its WAS resource path as
|
|
84
|
+
* `application/edv+json` (the envelope serialized to bytes so the stored
|
|
85
|
+
* content type is exact).
|
|
86
|
+
*
|
|
87
|
+
* @param id {string}
|
|
88
|
+
* @param encrypted {IEncryptedDocument}
|
|
89
|
+
* @returns {Promise<HttpResponse>}
|
|
90
|
+
*/
|
|
91
|
+
private _put;
|
|
92
|
+
/**
|
|
93
|
+
* @inheritdoc
|
|
94
|
+
*
|
|
95
|
+
* Inserts a new encrypted document. WAS `PUT` is an upsert, so to preserve
|
|
96
|
+
* EDV insert semantics (`DuplicateError` if the id already exists) this first
|
|
97
|
+
* checks for an existing resource. The check + write is not atomic -- with no
|
|
98
|
+
* server-side conditional writes yet, this is an advisory, single-writer
|
|
99
|
+
* guard.
|
|
100
|
+
*
|
|
101
|
+
* @param options {object}
|
|
102
|
+
* @param options.encrypted {IEncryptedDocument} the document to insert
|
|
103
|
+
* @returns {Promise<void>}
|
|
104
|
+
*/
|
|
105
|
+
insert({ encrypted }?: {
|
|
106
|
+
encrypted?: IEncryptedDocument;
|
|
107
|
+
}): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* @inheritdoc
|
|
110
|
+
*
|
|
111
|
+
* Updates (upserts) an encrypted document. The EDV `sequence` is advisory
|
|
112
|
+
* here -- without server-side conditional writes, a stale write is not
|
|
113
|
+
* rejected (last-writer-wins).
|
|
114
|
+
*
|
|
115
|
+
* @param options {object}
|
|
116
|
+
* @param options.encrypted {IEncryptedDocument} the document to update
|
|
117
|
+
* @returns {Promise<void>}
|
|
118
|
+
*/
|
|
119
|
+
update({ encrypted }?: {
|
|
120
|
+
encrypted?: IEncryptedDocument;
|
|
121
|
+
}): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* @inheritdoc
|
|
124
|
+
*
|
|
125
|
+
* Reads an encrypted document by id. Throws a `NotFoundError` (the name
|
|
126
|
+
* `EdvClientCore` expects) when the resource is missing or not visible.
|
|
127
|
+
*
|
|
128
|
+
* @param options {object}
|
|
129
|
+
* @param options.id {string} the document id to read
|
|
130
|
+
* @returns {Promise<IEncryptedDocument>}
|
|
131
|
+
*/
|
|
132
|
+
get({ id }?: {
|
|
133
|
+
id?: string;
|
|
134
|
+
}): Promise<IEncryptedDocument>;
|
|
135
|
+
/**
|
|
136
|
+
* Resolves to `true` if a resource exists at the document's path. A 404
|
|
137
|
+
* resolves to `false`; any other error propagates.
|
|
138
|
+
*
|
|
139
|
+
* @param id {string}
|
|
140
|
+
* @returns {Promise<boolean>}
|
|
141
|
+
*/
|
|
142
|
+
private _exists;
|
|
143
|
+
/**
|
|
144
|
+
* @inheritdoc
|
|
145
|
+
*
|
|
146
|
+
* Blinded-index query is not part of the documents-only EDV-over-WAS profile;
|
|
147
|
+
* it needs the server's `/query` affordance.
|
|
148
|
+
*/
|
|
149
|
+
find(): Promise<never>;
|
|
150
|
+
/**
|
|
151
|
+
* @inheritdoc
|
|
152
|
+
*
|
|
153
|
+
* Index updates need the server's `/{id}/index` affordance.
|
|
154
|
+
*/
|
|
155
|
+
updateIndex(): Promise<never>;
|
|
156
|
+
/**
|
|
157
|
+
* @inheritdoc
|
|
158
|
+
*
|
|
159
|
+
* Chunked streams need the reserved `/{id}/chunks/{n}` sub-segment.
|
|
160
|
+
*/
|
|
161
|
+
storeChunk(): Promise<never>;
|
|
162
|
+
/**
|
|
163
|
+
* @inheritdoc
|
|
164
|
+
*
|
|
165
|
+
* Chunked streams need the reserved `/{id}/chunks/{n}` sub-segment.
|
|
166
|
+
*/
|
|
167
|
+
getChunk(): Promise<never>;
|
|
168
|
+
/**
|
|
169
|
+
* Throws a uniform "not supported in this profile" error for EDV operations
|
|
170
|
+
* that depend on server-side affordances absent from a plaintext WAS server.
|
|
171
|
+
*
|
|
172
|
+
* @param operation {string}
|
|
173
|
+
* @returns {never}
|
|
174
|
+
*/
|
|
175
|
+
private _unsupported;
|
|
176
|
+
}
|
|
177
|
+
export {};
|
|
178
|
+
//# sourceMappingURL=WasTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WasTransport.d.ts","sourceRoot":"","sources":["../../src/edv/WasTransport.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAKhD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,yBAAyB,CAAA;AAQtD;;;GAGG;AACH,KAAK,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;AAoC9C,qBAAa,YAAa,SAAQ,SAAS;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAE5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IAEnC;;;;;;;;;OASG;gBACS,EACV,GAAG,EACH,OAAO,EACP,YAAY,EACZ,WAAkC,EACnC,EAAE;QACD,GAAG,EAAE,YAAY,CAAA;QACjB,OAAO,EAAE,MAAM,CAAA;QACf,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB;IAQD;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAIrB;;;;;;;;OAQG;YACW,IAAI;IAalB;;;;;;;;;;;;OAYG;IACY,MAAM,CAAC,EACpB,SAAS,EACV,GAAE;QAAE,SAAS,CAAC,EAAE,kBAAkB,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1D;;;;;;;;;;OAUG;IACY,MAAM,CAAC,EACpB,SAAS,EACV,GAAE;QAAE,SAAS,CAAC,EAAE,kBAAkB,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB1D;;;;;;;;;OASG;IACY,GAAG,CAAC,EACjB,EAAE,EACH,GAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAuBrD;;;;;;OAMG;YACW,OAAO;IAYrB;;;;;OAKG;IACY,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;IAIrC;;;;OAIG;IACY,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;IAI5C;;;;OAIG;IACY,UAAU,IAAI,OAAO,CAAC,KAAK,CAAC;IAI3C;;;;OAIG;IACY,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC;IAIzC;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;CAQrB"}
|