@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.
@@ -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 documentKey = stripPushPrefix(path);
613
+ const documentKey2 = stripPushPrefix(path);
614
614
  const { authorPubkey, authorSignature } = signAppendAuthor(
615
- documentKey,
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 documentKey = stripPushPrefix(path);
668
+ const documentKey2 = stripPushPrefix(path);
669
669
  const { authorPubkey, authorSignature } = signAppendAuthor(
670
- documentKey,
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 documentKey = stripPushPrefix(this.pushPath);
1023
- const canonical = docAuthorCanonicalInput(documentKey, sealed);
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