@drakkar.software/starfish-client 3.0.0-alpha.40 → 3.0.0-alpha.43
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/dist/blob-seal.d.ts +123 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +32 -6
- package/dist/index.js.map +4 -4
- package/package.json +2 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic client-side blob sealing helpers.
|
|
3
|
+
*
|
|
4
|
+
* Provides a structural {@link ByteSealer} interface and two convenience
|
|
5
|
+
* functions — {@link sealAndPushBlob} / {@link pullAndOpenBlob} — that wire a
|
|
6
|
+
* sealer (e.g. a `KeyringEncryptor` from `@drakkar.software/starfish-keyring`)
|
|
7
|
+
* into the binary blob transport (`StarfishClient.pushBlob` / `pullBlob`).
|
|
8
|
+
*
|
|
9
|
+
* The server collection must use `encryption: "none"` — all sealing and
|
|
10
|
+
* unsealing happens client-side. The server stores and returns opaque
|
|
11
|
+
* ciphertext.
|
|
12
|
+
*
|
|
13
|
+
* **AAD (Additional Authenticated Data):** callers MUST pass a stable, unique
|
|
14
|
+
* `aad` string (typically the storage path, e.g. the value returned by a
|
|
15
|
+
* `*Name(spaceId, blobId)` helper). The AAD is bound into the AES-GCM tag
|
|
16
|
+
* and prevents ciphertext relocation: a blob sealed with AAD `"path/a"` cannot
|
|
17
|
+
* be opened as `"path/b"`. When `aad` is omitted, the document key (path with
|
|
18
|
+
* the `/push/` or `/pull/` action prefix stripped) is used as a fallback —
|
|
19
|
+
* the same key is derived on both the seal and open side so round-trips work
|
|
20
|
+
* without an explicit `aad`. **Callers with existing sealed data MUST pass the
|
|
21
|
+
* same explicit AAD they used when sealing** to preserve back-compat.
|
|
22
|
+
*
|
|
23
|
+
* @module blob-seal
|
|
24
|
+
*/
|
|
25
|
+
import type { StarfishClient } from "./client.js";
|
|
26
|
+
import type { BlobPushResult } from "./client.js";
|
|
27
|
+
/**
|
|
28
|
+
* Structural interface satisfied by a `KeyringEncryptor` from
|
|
29
|
+
* `@drakkar.software/starfish-keyring` (and any compatible cipher adapter).
|
|
30
|
+
*
|
|
31
|
+
* Implementations must use AES-256-GCM and bind the `aad` into the ciphertext
|
|
32
|
+
* tag so that relocation is detected on open.
|
|
33
|
+
*/
|
|
34
|
+
export interface ByteSealer {
|
|
35
|
+
/**
|
|
36
|
+
* Encrypt `bytes` and return the sealed ciphertext.
|
|
37
|
+
* @param bytes - Plaintext bytes to seal.
|
|
38
|
+
* @param aad - Additional authenticated data bound into the ciphertext tag.
|
|
39
|
+
* Must be the same string passed to {@link openBytes}.
|
|
40
|
+
*/
|
|
41
|
+
sealBytes(bytes: Uint8Array, aad?: string): Promise<Uint8Array>;
|
|
42
|
+
/**
|
|
43
|
+
* Decrypt a sealed blob back to plaintext bytes.
|
|
44
|
+
* @param blob - Ciphertext produced by {@link sealBytes}.
|
|
45
|
+
* @param aad - Same AAD string that was used during sealing.
|
|
46
|
+
* @throws if the ciphertext has been tampered with or the AAD does not match.
|
|
47
|
+
*/
|
|
48
|
+
openBytes(blob: Uint8Array, aad?: string): Promise<Uint8Array>;
|
|
49
|
+
}
|
|
50
|
+
/** Options for {@link sealAndPushBlob}. */
|
|
51
|
+
export interface SealAndPushBlobOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Additional authenticated data bound into the AES-GCM ciphertext tag.
|
|
54
|
+
*
|
|
55
|
+
* Callers with existing sealed data MUST pass the **same explicit AAD** they
|
|
56
|
+
* used when originally sealing so that already-stored blobs can still be
|
|
57
|
+
* opened. When omitted, the document key (push path with the `/push/`
|
|
58
|
+
* prefix stripped) is used as a fallback.
|
|
59
|
+
*/
|
|
60
|
+
aad?: string;
|
|
61
|
+
/**
|
|
62
|
+
* When set, throws a `RangeError` before sealing if `bytes.length` exceeds
|
|
63
|
+
* this limit. Checked against the **plaintext** size (before sealing adds
|
|
64
|
+
* overhead).
|
|
65
|
+
*
|
|
66
|
+
* Note: AES-256-GCM sealing adds ~28 bytes of overhead (12-byte nonce +
|
|
67
|
+
* 16-byte tag). If you are mirroring the server's `maxBodyBytes` limit,
|
|
68
|
+
* subtract at least 28 to ensure the sealed ciphertext also fits.
|
|
69
|
+
*/
|
|
70
|
+
maxBytes?: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Seal `bytes` with `sealer` (AAD bound to the storage path) and push the
|
|
74
|
+
* resulting ciphertext to the server via `client.pushBlob`.
|
|
75
|
+
*
|
|
76
|
+
* Sealed bytes are always pushed with `Content-Type: application/octet-stream`,
|
|
77
|
+
* matching the `allowedMimeTypes` of a {@link createSealedParquetCollection} preset.
|
|
78
|
+
*
|
|
79
|
+
* @param client - A connected `StarfishClient`.
|
|
80
|
+
* @param sealer - A `ByteSealer` (e.g. `KeyringEncryptor` from `starfish-keyring`).
|
|
81
|
+
* @param path - Push path (starts with `/push/…`).
|
|
82
|
+
* @param bytes - Plaintext bytes to seal and upload.
|
|
83
|
+
* @param opts - See {@link SealAndPushBlobOptions}.
|
|
84
|
+
* @returns The server's push result (hash).
|
|
85
|
+
*
|
|
86
|
+
* @throws {RangeError} if `opts.maxBytes` is set and `bytes.length` exceeds it.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const result = await sealAndPushBlob(client, enc, objectBlobPush(spaceId, blobId), bytes, {
|
|
91
|
+
* aad: objectBlobName(spaceId, blobId),
|
|
92
|
+
* })
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function sealAndPushBlob(client: StarfishClient, sealer: ByteSealer, path: string, bytes: Uint8Array, opts?: SealAndPushBlobOptions): Promise<BlobPushResult>;
|
|
96
|
+
/** Options for {@link pullAndOpenBlob}. */
|
|
97
|
+
export interface PullAndOpenBlobOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Additional authenticated data — must match the AAD used when sealing.
|
|
100
|
+
* Defaults to the document key (pull path with the `/pull/` prefix stripped),
|
|
101
|
+
* which matches the default AAD used by {@link sealAndPushBlob}.
|
|
102
|
+
*/
|
|
103
|
+
aad?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Pull a sealed blob from the server and unseal it with `sealer`.
|
|
107
|
+
*
|
|
108
|
+
* @param client - A connected `StarfishClient`.
|
|
109
|
+
* @param sealer - A `ByteSealer` that can open the sealed bytes.
|
|
110
|
+
* @param path - Pull path (starts with `/pull/…` or is a bare document key).
|
|
111
|
+
* @param opts - See {@link PullAndOpenBlobOptions}.
|
|
112
|
+
* @returns The original plaintext bytes.
|
|
113
|
+
*
|
|
114
|
+
* @throws if the ciphertext is invalid, tampered with, or the AAD does not match.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* const bytes = await pullAndOpenBlob(client, enc, objectBlobPull(spaceId, blobId), {
|
|
119
|
+
* aad: objectBlobName(spaceId, blobId),
|
|
120
|
+
* })
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export declare function pullAndOpenBlob(client: StarfishClient, sealer: ByteSealer, path: string, opts?: PullAndOpenBlobOptions): Promise<Uint8Array>;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,10 @@ export { stableStringify, computeHash } from "@drakkar.software/starfish-protoco
|
|
|
4
4
|
export { buildRevocationList, revocationListCanonicalSigningInput } from "@drakkar.software/starfish-protocol";
|
|
5
5
|
export type { RevocationList, RevocationEntry, RevokedSubject, BuildRevocationListOpts, } from "@drakkar.software/starfish-protocol";
|
|
6
6
|
export type { PullResult, PushSuccess, PullKeyringProjection } from "@drakkar.software/starfish-protocol";
|
|
7
|
-
export { StarfishClient, pullWasFromCache } from "./client.js";
|
|
7
|
+
export { StarfishClient, pullWasFromCache, stripPushPrefix } from "./client.js";
|
|
8
8
|
export type { BlobPullResult, BlobPushResult, AppendPullOptions, PullOptions, BatchPullOptions, BatchPullResult, BatchPullEntry, } from "./client.js";
|
|
9
|
+
export { sealAndPushBlob, pullAndOpenBlob } from "./blob-seal.js";
|
|
10
|
+
export type { ByteSealer, SealAndPushBlobOptions, PullAndOpenBlobOptions, } from "./blob-seal.js";
|
|
9
11
|
export { PARQUET_MIME_TYPE, PARQUET_MIME_TYPES } from "@drakkar.software/starfish-protocol";
|
|
10
12
|
export { SyncManager, AbortError } from "./sync.js";
|
|
11
13
|
export type { SyncManagerOptions, SyncSigner } from "./sync.js";
|
package/dist/index.js
CHANGED
|
@@ -610,9 +610,9 @@ var StarfishClient = class {
|
|
|
610
610
|
if (capCtx) {
|
|
611
611
|
const authorKey = this.appendAuthorKey(capCtx);
|
|
612
612
|
if (authorKey) {
|
|
613
|
-
const
|
|
613
|
+
const documentKey2 = stripPushPrefix(path);
|
|
614
614
|
const { authorPubkey, authorSignature } = signAppendAuthor(
|
|
615
|
-
|
|
615
|
+
documentKey2,
|
|
616
616
|
data,
|
|
617
617
|
authorKey.authorPubHex,
|
|
618
618
|
capCtx.devEdPrivHex
|
|
@@ -665,9 +665,9 @@ var StarfishClient = class {
|
|
|
665
665
|
*/
|
|
666
666
|
async appendAnonymous(path, element, signer) {
|
|
667
667
|
const sendPath = this.applyNamespace(path);
|
|
668
|
-
const
|
|
668
|
+
const documentKey2 = stripPushPrefix(path);
|
|
669
669
|
const { authorPubkey, authorSignature } = signAppendAuthor(
|
|
670
|
-
|
|
670
|
+
documentKey2,
|
|
671
671
|
element,
|
|
672
672
|
signer.edPubHex,
|
|
673
673
|
signer.edPrivHex
|
|
@@ -775,6 +775,29 @@ var StarfishClient = class {
|
|
|
775
775
|
}
|
|
776
776
|
};
|
|
777
777
|
|
|
778
|
+
// src/blob-seal.ts
|
|
779
|
+
function documentKey(path) {
|
|
780
|
+
if (path.startsWith("/push/")) return path.slice("/push/".length);
|
|
781
|
+
if (path.startsWith("/pull/")) return path.slice("/pull/".length);
|
|
782
|
+
return path;
|
|
783
|
+
}
|
|
784
|
+
async function sealAndPushBlob(client, sealer, path, bytes, opts = {}) {
|
|
785
|
+
const { aad = documentKey(path), maxBytes } = opts;
|
|
786
|
+
if (maxBytes !== void 0 && bytes.length > maxBytes) {
|
|
787
|
+
throw new RangeError(
|
|
788
|
+
`sealAndPushBlob: payload is ${bytes.length} bytes \u2014 maximum allowed is ${maxBytes} bytes`
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
const sealed = await sealer.sealBytes(bytes, aad);
|
|
792
|
+
return client.pushBlob(path, sealed, "application/octet-stream");
|
|
793
|
+
}
|
|
794
|
+
async function pullAndOpenBlob(client, sealer, path, opts = {}) {
|
|
795
|
+
const { aad = documentKey(path) } = opts;
|
|
796
|
+
const result = await client.pullBlob(path);
|
|
797
|
+
const stored = new Uint8Array(result.data);
|
|
798
|
+
return sealer.openBytes(stored, aad);
|
|
799
|
+
}
|
|
800
|
+
|
|
778
801
|
// src/index.ts
|
|
779
802
|
import { PARQUET_MIME_TYPE, PARQUET_MIME_TYPES } from "@drakkar.software/starfish-protocol";
|
|
780
803
|
|
|
@@ -1019,8 +1042,8 @@ var SyncManager = class {
|
|
|
1019
1042
|
if (this.signer) {
|
|
1020
1043
|
const { devEdPubHex, sign } = await this.signer.getSigner();
|
|
1021
1044
|
if (this.aborted) throw new AbortError();
|
|
1022
|
-
const
|
|
1023
|
-
const canonical = docAuthorCanonicalInput(
|
|
1045
|
+
const documentKey2 = stripPushPrefix(this.pushPath);
|
|
1046
|
+
const canonical = docAuthorCanonicalInput(documentKey2, sealed);
|
|
1024
1047
|
const sigBytes = await sign(new TextEncoder().encode(canonical));
|
|
1025
1048
|
if (this.aborted) throw new AbortError();
|
|
1026
1049
|
author = {
|
|
@@ -2225,13 +2248,16 @@ export {
|
|
|
2225
2248
|
noopSyncLogger,
|
|
2226
2249
|
parseRetryAfterMs,
|
|
2227
2250
|
pruneTombstones,
|
|
2251
|
+
pullAndOpenBlob,
|
|
2228
2252
|
pullWasFromCache,
|
|
2229
2253
|
registerBackgroundSync,
|
|
2230
2254
|
registerServiceWorker,
|
|
2231
2255
|
revocationListCanonicalSigningInput,
|
|
2256
|
+
sealAndPushBlob,
|
|
2232
2257
|
stableStringify2 as stableStringify,
|
|
2233
2258
|
startAdaptivePolling,
|
|
2234
2259
|
startPolling,
|
|
2260
|
+
stripPushPrefix,
|
|
2235
2261
|
timestampWinner,
|
|
2236
2262
|
unregisterServiceWorkers,
|
|
2237
2263
|
withConflictMeta
|