cursedbelt 2.5.1 β 2.6.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/dist/server/private-media/unlock.d.ts.map +1 -1
- package/dist/server/private-media/unlock.js +6 -1
- package/dist/server/private-media/unlock.js.map +1 -1
- package/dist/server/storage/binaryServerStore.d.ts +2 -42
- package/dist/server/storage/binaryServerStore.d.ts.map +1 -1
- package/dist/server/storage/binaryServerStore.js +8 -0
- package/dist/server/storage/binaryServerStore.js.map +1 -1
- package/dist/server/storage/streamToken.d.ts.map +1 -1
- package/dist/server/storage/streamToken.js +8 -1
- package/dist/server/storage/streamToken.js.map +1 -1
- package/dist/server/storage/types.d.ts +41 -0
- package/dist/server/storage/types.d.ts.map +1 -1
- package/dist/server/storage/types.js +11 -0
- package/dist/server/storage/types.js.map +1 -1
- package/package.json +7 -1
- package/src/leafSubpathsImportNothing.spec.ts +302 -0
- package/src/server/private-media/unlock.ts +6 -1
- package/src/server/storage/binaryServerStore.ts +6 -45
- package/src/server/storage/streamToken.ts +8 -1
- package/src/server/storage/types.ts +61 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unlock.d.ts","sourceRoot":"","sources":["../../../src/server/private-media/unlock.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"unlock.d.ts","sourceRoot":"","sources":["../../../src/server/private-media/unlock.ts"],"names":[],"mappings":"AAOA,OAAO,EAA4C,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AAclF,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IACE,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,CACd,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EACzC,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB,CAAC;AAEN,MAAM,WAAW,aAAa;IAC5B;oEACgE;IAChE,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAC7E,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,OAAO,CAAC;KACf,CAAC,CAAC;IACH,0FAA0F;IAC1F,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACnD,4FAA4F;IAC5F,YAAY,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC;CACrF;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,aAAa,CAiDhB"}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { decryptSecret, deriveKey, encryptSecret } from 'cwip/node';
|
|
2
|
-
|
|
2
|
+
// π΄ `../auth/jwt`, NOT `../auth` β same reason as `storage/streamToken.ts`, and this is the
|
|
3
|
+
// SECOND edge of the pair: `cursedbelt/server/storage` reaches private-media, so the barrel
|
|
4
|
+
// import here put `otplib` (via `auth/index` β `totp.ts`) back into the storage graph after
|
|
5
|
+
// streamToken.ts had been fixed. Found by bundling the graph and reading the externals, not by
|
|
6
|
+
// grep β a two-hop edge is invisible to the obvious search.
|
|
7
|
+
import { signToken, verifyPurposeToken } from '../auth/jwt';
|
|
3
8
|
import { derivePasswordSetKey, deriveServerSetKey } from './crypto';
|
|
4
9
|
// ββ Unlock service ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
5
10
|
// Turns a private-mode unlock into a short-lived token that CARRIES the set key so later
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unlock.js","sourceRoot":"","sources":["../../../src/server/private-media/unlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"unlock.js","sourceRoot":"","sources":["../../../src/server/private-media/unlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACpE,6FAA6F;AAC7F,4FAA4F;AAC5F,4FAA4F;AAC5F,+FAA+F;AAC/F,4DAA4D;AAC5D,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAgB,MAAM,UAAU,CAAC;AAElF,mFAAmF;AACnF,yFAAyF;AACzF,0FAA0F;AAC1F,4FAA4F;AAC5F,gFAAgF;AAChF,mGAAmG;AACnG,gGAAgG;AAChG,6FAA6F;AAC7F,sFAAsF;AAEtF,MAAM,OAAO,GAAG,WAAW,CAAC;AAoC5B,MAAM,UAAU,mBAAmB,CAAC,IAOnC;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;IAE/D,MAAM,YAAY,GAAG,CAAC,EACpB,MAAM,EACN,QAAQ,EACR,QAAQ,GAKT,EAAU,EAAE,CACX,QAAQ;QACN,CAAC,CAAC,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAC1C,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAEpD,OAAO;QACL,YAAY;QACZ,KAAK,CAAC,MAAM,CAAC,IAAI;YACf,MAAM,IAAI,GAAY,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;YAC9D,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,KAAK,GAAG,MAAM,SAAS,CAC3B;gBACE,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,SAAS,EAAE,IAAI;gBACf,KAAK,EAAE,EAAE;gBACT,OAAO,EAAE,OAAO;gBAChB,EAAE,EAAE,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;gBACrD,CAAC,EAAE,IAAI;aACR,EACD,IAAI,CAAC,YAAY,EACjB,GAAG,CACJ,CAAC;YACF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACzB,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,KAAK;YAChB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,MAAM,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;gBACtE,MAAM,MAAM,GAAI,CAAsB,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,GAAI,CAAqB,CAAC,CAAC,CAAC;gBACtC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,UAAU,CAAC;oBAAE,OAAO,IAAI,CAAC;gBAC5F,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;gBACrE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YAC5C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,44 +1,4 @@
|
|
|
1
|
-
import type { MediaStore } from './types';
|
|
2
|
-
|
|
3
|
-
* Claims binary-server verifies (see binary-server/src/tokens.ts). This is the ONE
|
|
4
|
-
* canonical shape for a binary-server file token across the fleet: cursedbelt's
|
|
5
|
-
* injected-signer `createBinaryServerStore` (below) and `@satellites/satellite-kit`'s
|
|
6
|
-
* self-signing `createBinaryStore` both mint tokens against the SAME binary-server, so
|
|
7
|
-
* the claim set must never drift β satellite-kit imports this exact type rather than
|
|
8
|
-
* redeclaring it, so a token one side signs is always shaped for the other to read. The
|
|
9
|
-
* app's signer turns these into an RS256 JWT.
|
|
10
|
-
*/
|
|
11
|
-
export type FileTokenClaims = {
|
|
12
|
-
/** Exact authorized path, app-prefixed: `<app>/<key>`. */
|
|
13
|
-
k?: string;
|
|
14
|
-
/** Authorized path prefix, app-prefixed (container trees, e.g. an HLS tree). */
|
|
15
|
-
p?: string;
|
|
16
|
-
/** Acting user/app id β audit trail on binary-server's events, never authz. */
|
|
17
|
-
uid?: string;
|
|
18
|
-
/** Chunk-session id (must match across every chunk of one upload). */
|
|
19
|
-
sid?: string;
|
|
20
|
-
/** Chunk index (0-based). */
|
|
21
|
-
ci?: number;
|
|
22
|
-
/** Total chunk count. */
|
|
23
|
-
tc?: number;
|
|
24
|
-
/** Download filename β Content-Disposition. */
|
|
25
|
-
fn?: string;
|
|
26
|
-
dl?: 'inline' | 'attachment';
|
|
27
|
-
/** Per-op scope. A DELETE against binary-server requires a token carrying `op: 'delete'`
|
|
28
|
-
* (in addition to the internal secret) β a plain read/download token can never be replayed
|
|
29
|
-
* as a delete, and the token's issuer + `k`/`p` tenant-scope the delete. Owner ruling #10. */
|
|
30
|
-
op?: 'delete';
|
|
31
|
-
};
|
|
32
|
-
export type FileTokenSigner = (claims: FileTokenClaims) => Promise<string>;
|
|
33
|
-
export type BinaryServerConfig = {
|
|
34
|
-
/** e.g. 'https://binary-server.cursedalchemy.com'. */
|
|
35
|
-
baseUrl: string;
|
|
36
|
-
/** App namespace (path + token scope), e.g. 'cursedalchemy'. */
|
|
37
|
-
appId: string;
|
|
38
|
-
/** App-provided RS256 signer (app holds the private key). */
|
|
39
|
-
signFileToken: FileTokenSigner;
|
|
40
|
-
/** Shared secret for internal ops (delete). Sent as x-internal-secret. */
|
|
41
|
-
internalSecret?: string;
|
|
42
|
-
};
|
|
1
|
+
import type { BinaryServerConfig, MediaStore } from './types';
|
|
2
|
+
export type { BinaryServerConfig, FileTokenClaims, FileTokenSigner } from './types';
|
|
43
3
|
export declare function createBinaryServerStore(cfg: BinaryServerConfig): MediaStore;
|
|
44
4
|
//# sourceMappingURL=binaryServerStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binaryServerStore.d.ts","sourceRoot":"","sources":["../../../src/server/storage/binaryServerStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"binaryServerStore.d.ts","sourceRoot":"","sources":["../../../src/server/storage/binaryServerStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAK9D,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAWpF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,kBAAkB,GAAG,UAAU,CAqF3E"}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
// binary-server MediaStore adapter. binary-server (Bun/SQLite, RS256 token-gated, Range-capable)
|
|
2
|
+
// is a production storage path: a home/edge file service the app signs short-lived tokens for.
|
|
3
|
+
// User-facing downloads REDIRECT (303) to the signed edge URL rather than proxying bytes back
|
|
4
|
+
// through the app; server-side reads (transcode source, checks) stream locally via getRange.
|
|
5
|
+
//
|
|
6
|
+
// The RS256 signer is INJECTED β the app holds the private key (binary-server holds only the
|
|
7
|
+
// public key). cursedbelt carries no key material and no asymmetric-crypto specifics; it just
|
|
8
|
+
// calls `signFileToken` with the claims binary-server expects.
|
|
1
9
|
export function createBinaryServerStore(cfg) {
|
|
2
10
|
const appKey = (key) => `${cfg.appId}/${key}`;
|
|
3
11
|
const mediaUrl = async (key, extra) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binaryServerStore.js","sourceRoot":"","sources":["../../../src/server/storage/binaryServerStore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"binaryServerStore.js","sourceRoot":"","sources":["../../../src/server/storage/binaryServerStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamToken.d.ts","sourceRoot":"","sources":["../../../src/server/storage/streamToken.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"streamToken.d.ts","sourceRoot":"","sources":["../../../src/server/storage/streamToken.ts"],"names":[],"mappings":"AAyBA,+FAA+F;AAC/F,MAAM,MAAM,WAAW,GAAG;IACxB,uFAAuF;IACvF,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACpD;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GAAG,kBAAkB,CAiCrB"}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
// π΄ `../auth/jwt`, NOT `../auth`. The jose half is a REAL dependency β stream tokens are
|
|
2
|
+
// signed and verified here, and jose is a plain `dependencies` entry every consumer already
|
|
3
|
+
// installs. The BARREL was the accident: `../auth/index.ts` also reaches `totp.ts` β `otplib`,
|
|
4
|
+
// an OPTIONAL peer, so every app that touched anything in `cursedbelt/server/storage` was made
|
|
5
|
+
// to install a one-time-password library for code it never calls. Import the leaf module that
|
|
6
|
+
// has what you need; never the index that has everything. `src/leafSubpathsImportNothing.spec.ts`
|
|
7
|
+
// fails if this file (or anything else the storage barrel reaches) undoes it.
|
|
8
|
+
import { signToken, verifyPurposeToken } from '../auth/jwt';
|
|
2
9
|
// ββ Stream tokens βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
3
10
|
// Media bytes are loaded by <video>/hls.js/<img>, which cannot send the platform Bearer
|
|
4
11
|
// header an app's auth uses. So byte-serving routes live OUTSIDE the guarded API and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamToken.js","sourceRoot":"","sources":["../../../src/server/storage/streamToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"streamToken.js","sourceRoot":"","sources":["../../../src/server/storage/streamToken.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,4FAA4F;AAC5F,+FAA+F;AAC/F,+FAA+F;AAC/F,8FAA8F;AAC9F,kGAAkG;AAClG,8EAA8E;AAC9E,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE5D,mFAAmF;AACnF,wFAAwF;AACxF,qFAAqF;AACrF,0EAA0E;AAC1E,EAAE;AACF,2FAA2F;AAC3F,0FAA0F;AAC1F,+FAA+F;AAC/F,kEAAkE;AAClE,EAAE;AACF,2FAA2F;AAC3F,yFAAyF;AACzF,0EAA0E;AAE1E,MAAM,oBAAoB,GAAG,cAAc,CAAC;AAe5C,MAAM,UAAU,wBAAwB,CAAC,IAIxC;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,iBAAiB,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;IAClD,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG;YAChC,yFAAyF;YACzF,uFAAuF;YACvF,uEAAuE;YACvE,OAAO,SAAS,CACd;gBACE,MAAM,EAAE,KAAK,CAAC,KAAK;gBACnB,SAAS,EAAE,cAAc;gBACzB,KAAK,EAAE,EAAE;gBACT,OAAO,EAAE,oBAAoB;gBAC7B,GAAG,EAAE,KAAK,CAAC,QAAQ;gBACnB,GAAG,EAAE,KAAK,CAAC,OAAO;aACnB,EACD,IAAI,CAAC,MAAM,EACX,UAAU,CACX,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,KAAK;YAChB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,MAAM,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;gBAC7E,MAAM,GAAG,GAAI,CAAuB,CAAC,GAAG,CAAC;gBACzC,MAAM,GAAG,GAAI,CAAuB,CAAC,GAAG,CAAC;gBACzC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ;oBAAE,OAAO,IAAI,CAAC;gBACpE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;YAC1D,CAAC;YAAC,MAAM,CAAC;gBACP,oDAAoD;gBACpD,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -47,6 +47,47 @@ export interface MediaStore {
|
|
|
47
47
|
filename?: string;
|
|
48
48
|
}): Promise<string | null>;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Claims binary-server verifies (see binary-server/src/tokens.ts). This is the ONE
|
|
52
|
+
* canonical shape for a binary-server file token across the fleet: cursedbelt's
|
|
53
|
+
* injected-signer `createBinaryServerStore` and `@satellites/satellite-kit`'s
|
|
54
|
+
* self-signing `createBinaryStore` both mint tokens against the SAME binary-server, so
|
|
55
|
+
* the claim set must never drift β satellite-kit imports this exact type rather than
|
|
56
|
+
* redeclaring it, so a token one side signs is always shaped for the other to read. The
|
|
57
|
+
* app's signer turns these into an RS256 JWT.
|
|
58
|
+
*/
|
|
59
|
+
export type FileTokenClaims = {
|
|
60
|
+
/** Exact authorized path, app-prefixed: `<app>/<key>`. */
|
|
61
|
+
k?: string;
|
|
62
|
+
/** Authorized path prefix, app-prefixed (container trees, e.g. an HLS tree). */
|
|
63
|
+
p?: string;
|
|
64
|
+
/** Acting user/app id β audit trail on binary-server's events, never authz. */
|
|
65
|
+
uid?: string;
|
|
66
|
+
/** Chunk-session id (must match across every chunk of one upload). */
|
|
67
|
+
sid?: string;
|
|
68
|
+
/** Chunk index (0-based). */
|
|
69
|
+
ci?: number;
|
|
70
|
+
/** Total chunk count. */
|
|
71
|
+
tc?: number;
|
|
72
|
+
/** Download filename β Content-Disposition. */
|
|
73
|
+
fn?: string;
|
|
74
|
+
dl?: 'inline' | 'attachment';
|
|
75
|
+
/** Per-op scope. A DELETE against binary-server requires a token carrying `op: 'delete'`
|
|
76
|
+
* (in addition to the internal secret) β a plain read/download token can never be replayed
|
|
77
|
+
* as a delete, and the token's issuer + `k`/`p` tenant-scope the delete. Owner ruling #10. */
|
|
78
|
+
op?: 'delete';
|
|
79
|
+
};
|
|
80
|
+
export type FileTokenSigner = (claims: FileTokenClaims) => Promise<string>;
|
|
81
|
+
export type BinaryServerConfig = {
|
|
82
|
+
/** e.g. 'https://binary-server.cursedalchemy.com'. */
|
|
83
|
+
baseUrl: string;
|
|
84
|
+
/** App namespace (path + token scope), e.g. 'cursedalchemy'. */
|
|
85
|
+
appId: string;
|
|
86
|
+
/** App-provided RS256 signer (app holds the private key). */
|
|
87
|
+
signFileToken: FileTokenSigner;
|
|
88
|
+
/** Shared secret for internal ops (delete). Sent as x-internal-secret. */
|
|
89
|
+
internalSecret?: string;
|
|
90
|
+
};
|
|
50
91
|
/** A source of videos for a gallery/catalogue. Apps implement this differently (a disk folder,
|
|
51
92
|
* a binary-server app, ca's Files tree + private set). Consumers are source-agnostic. */
|
|
52
93
|
export interface MediaSource {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/server/storage/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/server/storage/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
// with no Bearer can still be authorized), a source registry, and the ingestβderivatives
|
|
6
6
|
// pipeline glue. Ported/generalised from ca's proven media stack; the app maps its own records
|
|
7
7
|
// to a MediaSource. Pairs with the `PlayableVideo` contract in `cursedbelt/media-core`.
|
|
8
|
+
//
|
|
9
|
+
// π΄ THIS FILE IS A PUBLISHED LEAF β `cursedbelt/server/storage/types` β AND IT MUST STAY
|
|
10
|
+
// IMPORT-FREE AT RUNTIME. `src/leafSubpathsImportNothing.spec.ts` imports it in a fixture with
|
|
11
|
+
// no packages resolvable at all and fails if it throws, so an `import` here that survives
|
|
12
|
+
// transpilation (anything that is not `import type`) turns that spec red rather than turning
|
|
13
|
+
// up in a consumer's install. The subpath exists because `./server/storage`'s own index is a
|
|
14
|
+
// barrel: it reaches `streamToken` β `auth/jwt` β jose, the whole SQLite file catalogue under
|
|
15
|
+
// `files/`, and a lazily-imported `sharp` β while an app that wants only the SHAPE of a file
|
|
16
|
+
// token (apps/collections wanted exactly `FileTokenClaims`, a type TypeScript erases) was made
|
|
17
|
+
// to install a one-time-password library to get it. Add types here freely; add a value import
|
|
18
|
+
// here never.
|
|
8
19
|
// ββ Keyspace helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
9
20
|
// One source of truth for every storage key. Sources + serve routes + the pipeline all
|
|
10
21
|
// address bytes through these, so a key layout is never hand-spelled twice.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/server/storage/types.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,4FAA4F;AAC5F,6FAA6F;AAC7F,8FAA8F;AAC9F,yFAAyF;AACzF,+FAA+F;AAC/F,wFAAwF;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/server/storage/types.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,4FAA4F;AAC5F,6FAA6F;AAC7F,8FAA8F;AAC9F,yFAAyF;AACzF,+FAA+F;AAC/F,wFAAwF;AACxF,EAAE;AACF,0FAA0F;AAC1F,+FAA+F;AAC/F,0FAA0F;AAC1F,6FAA6F;AAC7F,6FAA6F;AAC7F,8FAA8F;AAC9F,6FAA6F;AAC7F,+FAA+F;AAC/F,8FAA8F;AAC9F,cAAc;AA4Ld,qFAAqF;AACrF,uFAAuF;AACvF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,UAAU,KAAK,IAAI,EAAE,EAAE,CAAC;AAClF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE,MAAM,CAAC;AAC/E,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,EAAE,EAAE,CAAC;AAC/E,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,EAAE,cAAc,CAAC;AAC9F,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,GAAW,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC;AACpG,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE,aAAa,CAAC;AACzF;2EAC2E;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE,cAAc,CAAC;AAC3F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE,cAAc,CAAC;AAC1F,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE,cAAc,CAAC;AACvF;;qGAEqG;AACrG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,EAAE,EAAE,CAAC;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,EAAE,MAAM,CAAC;AACxF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAC/D,OAAO,KAAK,IAAI,EAAE,kBAAkB,CAAC;AACvC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,GAAW,EAAE,EAAE,CAC1E,OAAO,KAAK,IAAI,EAAE,QAAQ,GAAG,EAAE,CAAC;AAClC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,EAAE,aAAa,CAAC;AAC/F,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,EAAE,aAAa,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Shared Bun + React framework",
|
|
@@ -459,6 +459,12 @@
|
|
|
459
459
|
"source": "./src/server/storage/index.ts",
|
|
460
460
|
"import": "./dist/server/storage/index.js"
|
|
461
461
|
},
|
|
462
|
+
"./server/storage/types": {
|
|
463
|
+
"types": "./dist/server/storage/types.d.ts",
|
|
464
|
+
"bun": "./src/server/storage/types.ts",
|
|
465
|
+
"source": "./src/server/storage/types.ts",
|
|
466
|
+
"import": "./dist/server/storage/types.js"
|
|
467
|
+
},
|
|
462
468
|
"./server/master-lock": {
|
|
463
469
|
"types": "./dist/server/master-lock/index.d.ts",
|
|
464
470
|
"bun": "./src/server/master-lock/index.ts",
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import pkg from '../package.json';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A LEAF subpath's whole promise is what it does NOT pull in. Nothing in this repo
|
|
7
|
+
* measured that until now (2026-09-15), so the promise was kept by hand β and it was
|
|
8
|
+
* broken twice in two days, in the same shape, one level apart.
|
|
9
|
+
*
|
|
10
|
+
* ## The measurement this exists because of
|
|
11
|
+
*
|
|
12
|
+
* `apps/collections/src/kit/binaryStore.ts` needed exactly one thing from here:
|
|
13
|
+
*
|
|
14
|
+
* import type { FileTokenClaims } from "cursedbelt/server/storage";
|
|
15
|
+
*
|
|
16
|
+
* A TYPE β TypeScript erases it, so the app should have paid nothing at runtime. What it
|
|
17
|
+
* actually paid was:
|
|
18
|
+
*
|
|
19
|
+
* error: Cannot find package 'otplib' from
|
|
20
|
+
* β¦/node_modules/cursedbelt/src/server/auth/totp.ts
|
|
21
|
+
*
|
|
22
|
+
* β¦and four unrelated route suites died before anything was measured. `server/storage`'s
|
|
23
|
+
* index is a barrel; the barrel reached `streamToken.ts`, which imported the `../auth`
|
|
24
|
+
* INDEX for two jose functions, and that index reaches `totp.ts` β `otplib`, an optional
|
|
25
|
+
* peer. (`sessionStore.ts` takes its Kysely db as a TYPE, so the `kysely` half of the same
|
|
26
|
+
* trap arrives through the `./server` barrel's `db/kysely.ts` instead β measured, not
|
|
27
|
+
* assumed: bundling `./server/storage` externals before this change listed `otplib` and
|
|
28
|
+
* `hono/cookie`, never `kysely`.) The app declared `otplib` as a direct dependency to get
|
|
29
|
+
* its route suites back β a one-time-password library installed to satisfy a claim shape.
|
|
30
|
+
*
|
|
31
|
+
* That is the exact shape 2.5.1 shipped `./server/login-throttle` to fix, one level
|
|
32
|
+
* deeper, and the rule written down after it ("never import the `cursedbelt/server`
|
|
33
|
+
* barrel for one primitive") could not catch it, because `./server/storage` IS a leaf by
|
|
34
|
+
* that rule's standard and has a barrel of its own. A rule that only a human can apply
|
|
35
|
+
* gets re-broken by the next module that looks like a leaf. So: a check.
|
|
36
|
+
*
|
|
37
|
+
* ## What it does
|
|
38
|
+
*
|
|
39
|
+
* Assembles a throwaway consumer under `$TMPDIR` β a `node_modules/cursedbelt` holding
|
|
40
|
+
* this package's REAL package.json (so the real `exports` map is what resolves) and
|
|
41
|
+
* nothing else but the leaf source files themselves β then spawns bun in it and imports
|
|
42
|
+
* each leaf by its PUBLIC specifier. No peers are installed, no other file of this package
|
|
43
|
+
* is even on disk, so any import that survives transpilation fails the import and this
|
|
44
|
+
* spec with it. `import type` is erased and costs nothing, which is the whole point:
|
|
45
|
+
* `verbatimModuleSyntax` is on, so a type import that is not written `import type` is
|
|
46
|
+
* already a typecheck error, and source and `dist` therefore carry the same module graph.
|
|
47
|
+
*
|
|
48
|
+
* ## π΄ The trap that makes the naive version of this check a lie
|
|
49
|
+
*
|
|
50
|
+
* Bun AUTO-INSTALLS a missing bare import out of `~/.bun/install/cache` when the walk up
|
|
51
|
+
* from the importing file finds NO `node_modules` at all. Measured while building this, in
|
|
52
|
+
* a scratch directory holding two files and nothing else: `import { authenticator } from
|
|
53
|
+
* 'otplib'` resolved β from
|
|
54
|
+
* `~/.bun/install/cache/otplib@13.5.0@@@1/dist/index.js` β and the only error was a missing
|
|
55
|
+
* named export. An "empty fixture" therefore proves NOTHING about what a leaf drags: every
|
|
56
|
+
* peer in the cache answers.
|
|
57
|
+
*
|
|
58
|
+
* Two things hold the line, and the second is the one that matters:
|
|
59
|
+
*
|
|
60
|
+
* 1. `bunfig.toml` with `[install] auto = "disable"` in the fixture root. Note what this
|
|
61
|
+
* is and is not: with a `node_modules/` present (as here) bun does not auto-install
|
|
62
|
+
* anyway β removing the line and re-running left the negative control failing exactly
|
|
63
|
+
* as before. It is the second lock, for the day this fixture is refactored into one
|
|
64
|
+
* that has no `node_modules` and the trap silently comes back.
|
|
65
|
+
* 2. The NEGATIVE CONTROL: a file importing `otplib` and nothing else is copied in beside
|
|
66
|
+
* the leaves, and this spec fails if importing it SUCCEEDS. Whatever makes peers
|
|
67
|
+
* resolvable β auto-install, a `node_modules` above the scratch root, a future bun
|
|
68
|
+
* default β the control sees it and reds, instead of letting every assertion below
|
|
69
|
+
* pass vacuously.
|
|
70
|
+
*
|
|
71
|
+
* ## π΄ Verified failing, 2026-09-15, before it was trusted β every branch
|
|
72
|
+
*
|
|
73
|
+
* Β· `import 'otplib'` added to `src/server/storage/types.ts`
|
|
74
|
+
* β "cursedbelt/server/storage/types must import nothing at runtime β¦ Cannot find
|
|
75
|
+
* package 'otplib' imported from β¦/node_modules/cursedbelt/src/server/storage/types.ts"
|
|
76
|
+
* Β· the same, but `import { createStreamTokenService } from './streamToken'` β a
|
|
77
|
+
* relative import of a module inside this package
|
|
78
|
+
* β red too: "Cannot find module './streamToken'". A leaf may not reach sideways
|
|
79
|
+
* either, which is why ONLY the leaf files are copied into the fixture.
|
|
80
|
+
* Β· `./server/storage/types` re-pointed at `src/server/storage/index.ts`, the barrel it
|
|
81
|
+
* was split out of
|
|
82
|
+
* β red: "Cannot find module './videoDerivativeKeys'". A barrel cannot pass this.
|
|
83
|
+
* Β· the negative control re-pointed at `loginThrottle.ts`, which imports nothing, so the
|
|
84
|
+
* fixture stopped demonstrating that peers are unreachable
|
|
85
|
+
* β red on the control itself: "the fixture resolved 'otplib' β¦ Every assertion below
|
|
86
|
+
* is vacuous until that is fixed". The vacuity guard fires.
|
|
87
|
+
* Β· `src/server/private-media/unlock.ts` put back to `import β¦ from '../auth'` β the
|
|
88
|
+
* second, two-hop edge, which grep for `../auth` inside `src/server/storage` cannot see
|
|
89
|
+
* β red: "importing cursedbelt/server/storage statically pulls these OPTIONAL peers
|
|
90
|
+
* β¦ + otplib". This is the branch that found that edge in the first place.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
const REPO = fileURLToPath(new URL('..', import.meta.url));
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Every subpath that promises to cost a consumer NOTHING at runtime. A leaf earns its
|
|
97
|
+
* place here by being the answer to "I want one type / one primitive out of a module
|
|
98
|
+
* whose index is heavy" β the entry names the runtime symbol proving the module really
|
|
99
|
+
* evaluated, so an accidentally-empty module can never pass this vacuously.
|
|
100
|
+
*
|
|
101
|
+
* The file is read from `exports`, never hard-coded: re-target the subpath and the check
|
|
102
|
+
* follows it to the new file rather than going quietly green against the old one.
|
|
103
|
+
*/
|
|
104
|
+
const LEAVES = [
|
|
105
|
+
{
|
|
106
|
+
subpath: './server/storage/types',
|
|
107
|
+
/** Claim/config shapes + the keyspace helpers. apps/collections wants `FileTokenClaims`. */
|
|
108
|
+
evaluates: 'fsSourceKey',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
subpath: './server/login-throttle',
|
|
112
|
+
/** The failed-login backoff. Every app with a login door; shipped as 2.5.1. */
|
|
113
|
+
evaluates: 'createLoginThrottle',
|
|
114
|
+
},
|
|
115
|
+
] as const;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Imports `otplib` and nothing else, so its failure message is unambiguous. It is the
|
|
119
|
+
* proof that the fixture has no peers β not an assertion about `totp.ts` itself.
|
|
120
|
+
*/
|
|
121
|
+
const CONTROL = 'src/server/auth/totp.ts';
|
|
122
|
+
|
|
123
|
+
type Outcome = { ok: true; keys: string[] } | { ok: false; error: string };
|
|
124
|
+
|
|
125
|
+
const exportsMap = pkg.exports as Record<string, { bun?: string } | string>;
|
|
126
|
+
|
|
127
|
+
/** `./src/server/storage/types.ts` β `src/server/storage/types.ts`. */
|
|
128
|
+
const sourceOf = (subpath: string): string => {
|
|
129
|
+
const entry = exportsMap[subpath];
|
|
130
|
+
const bun = typeof entry === 'object' && entry !== null ? entry.bun : undefined;
|
|
131
|
+
if (typeof bun !== 'string') {
|
|
132
|
+
throw new Error(`package.json exports has no \`bun\` target for "${subpath}"`);
|
|
133
|
+
}
|
|
134
|
+
return bun.replace(/^\.\//, '');
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const run = async (): Promise<Record<string, Outcome>> => {
|
|
138
|
+
// Per-process TMPDIR (see scripts/testTmpRoot.ts), so parallel suites cannot collide and
|
|
139
|
+
// the preload's sweep is what cleans up after a killed run.
|
|
140
|
+
const root = `${process.env.TMPDIR ?? '/tmp'}/leaf-subpath-fixture`;
|
|
141
|
+
const pkgDir = `${root}/node_modules/cursedbelt`;
|
|
142
|
+
|
|
143
|
+
// The second lock against bun's auto-install (see the header). The negative control is the
|
|
144
|
+
// first one, and the one that would catch this line being wrong.
|
|
145
|
+
await Bun.write(`${root}/bunfig.toml`, '[install]\nauto = "disable"\n');
|
|
146
|
+
// The REAL manifest: the export map under test, resolved exactly as a consumer resolves it.
|
|
147
|
+
await Bun.write(`${pkgDir}/package.json`, Bun.file(`${REPO}/package.json`));
|
|
148
|
+
|
|
149
|
+
const targets: Array<{ name: string; specifier: string }> = [];
|
|
150
|
+
for (const leaf of LEAVES) {
|
|
151
|
+
const source = sourceOf(leaf.subpath);
|
|
152
|
+
// π΄ Bun.file/Bun.write, NOT node:fs β this repo's test preload virtualizes node:fs, so a
|
|
153
|
+
// copy made through it can land in a mock the spawned child cannot see. Same trap as
|
|
154
|
+
// publishShape.spec.ts.
|
|
155
|
+
await Bun.write(`${pkgDir}/${source}`, Bun.file(`${REPO}/${source}`));
|
|
156
|
+
targets.push({ name: leaf.subpath, specifier: `cursedbelt/${leaf.subpath.slice(2)}` });
|
|
157
|
+
}
|
|
158
|
+
await Bun.write(`${pkgDir}/${CONTROL}`, Bun.file(`${REPO}/${CONTROL}`));
|
|
159
|
+
// By PATH, not by subpath: the control is deliberately not exported, and an
|
|
160
|
+
// ERR_PACKAGE_PATH_NOT_EXPORTED would prove nothing about peer resolution.
|
|
161
|
+
targets.push({ name: 'control', specifier: `./node_modules/cursedbelt/${CONTROL}` });
|
|
162
|
+
|
|
163
|
+
await Bun.write(
|
|
164
|
+
`${root}/entry.ts`,
|
|
165
|
+
`const targets = ${JSON.stringify(targets)};
|
|
166
|
+
const out = {};
|
|
167
|
+
for (const t of targets) {
|
|
168
|
+
try {
|
|
169
|
+
const mod = await import(t.specifier);
|
|
170
|
+
out[t.name] = { ok: true, keys: Object.keys(mod) };
|
|
171
|
+
} catch (error) {
|
|
172
|
+
out[t.name] = { ok: false, error: String((error && error.message) || error) };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
console.log(JSON.stringify(out));
|
|
176
|
+
`,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const child = Bun.spawnSync(['bun', 'run', 'entry.ts'], {
|
|
180
|
+
cwd: root,
|
|
181
|
+
stdout: 'pipe',
|
|
182
|
+
stderr: 'pipe',
|
|
183
|
+
});
|
|
184
|
+
const stdout = child.stdout.toString().trim();
|
|
185
|
+
if (child.exitCode !== 0 || stdout === '') {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`the fixture could not run at all (exit ${child.exitCode}) β that is a broken check, not a passing one:\n${child.stderr.toString()}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return JSON.parse(stdout) as Record<string, Outcome>;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The other half, and the one that caught the SECOND edge. A leaf can be clean while the
|
|
195
|
+
* barrel it was split out of still drags an optional peer, and a barrel is what most apps
|
|
196
|
+
* import β `music`, `family` and `roms` all take `cursedbelt/server/storage` whole.
|
|
197
|
+
*
|
|
198
|
+
* Measured by bundling the subpath's relative graph with every bare import left external
|
|
199
|
+
* and reading what remains. The subpaths listed must reach NO optional peer with a STATIC
|
|
200
|
+
* import: a static one is paid at import time by every consumer, which is the crash the
|
|
201
|
+
* task above is about; a `await import('sharp')` inside the function that needs it is
|
|
202
|
+
* exactly the right shape and stays legal here.
|
|
203
|
+
*
|
|
204
|
+
* Limitation, stated rather than hidden: the externals are read out of the bundle's import
|
|
205
|
+
* statements textually, so an import bun emits across several lines would be missed. That
|
|
206
|
+
* makes this half a floor, not a ceiling β the runtime fixture above is the strict one.
|
|
207
|
+
*/
|
|
208
|
+
const NO_STATIC_OPTIONAL_PEERS = [
|
|
209
|
+
{
|
|
210
|
+
subpath: './server/storage',
|
|
211
|
+
/** Its `streamToken.ts` and (two hops out) `private-media/unlock.ts` both imported the
|
|
212
|
+
* `../auth` index for two jose functions, so `otplib` was static in here until 2.6.0. */
|
|
213
|
+
entry: 'src/server/storage/index.ts',
|
|
214
|
+
},
|
|
215
|
+
] as const;
|
|
216
|
+
|
|
217
|
+
const OPTIONAL_PEERS = new Set(
|
|
218
|
+
Object.entries(
|
|
219
|
+
(pkg as { peerDependenciesMeta?: Record<string, { optional?: boolean }> })
|
|
220
|
+
.peerDependenciesMeta ?? {},
|
|
221
|
+
)
|
|
222
|
+
.filter(([, meta]) => meta?.optional === true)
|
|
223
|
+
.map(([name]) => name),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
/** `import x from "pkg"`, `import "pkg"`, `export β¦ from "pkg"` β one line each, as bun emits. */
|
|
227
|
+
const STATIC_SPECIFIER = /^(?:import|export)\s[^\n]*?["']([^"'\n]+)["'];?$|^import\s*["']([^"'\n]+)["'];?$/gm;
|
|
228
|
+
|
|
229
|
+
const staticOptionalPeersOf = (entry: string): string[] => {
|
|
230
|
+
const build = Bun.spawnSync(
|
|
231
|
+
['bun', 'build', entry, '--target=bun', '--packages=external'],
|
|
232
|
+
{ cwd: REPO, stdout: 'pipe', stderr: 'pipe' },
|
|
233
|
+
);
|
|
234
|
+
const bundle = build.stdout.toString();
|
|
235
|
+
if (build.exitCode !== 0 || bundle.trim() === '') {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`could not bundle ${entry} (exit ${build.exitCode}) β a check that cannot measure is not a passing check:\n${build.stderr.toString()}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const found = new Set<string>();
|
|
241
|
+
for (const match of bundle.matchAll(STATIC_SPECIFIER)) {
|
|
242
|
+
const specifier = match[1] ?? match[2];
|
|
243
|
+
if (specifier === undefined || specifier.startsWith('.')) continue;
|
|
244
|
+
// `@scope/name/deep` β `@scope/name`, `pkg/deep` β `pkg`.
|
|
245
|
+
const parts = specifier.split('/');
|
|
246
|
+
const name = specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
247
|
+
if (name !== undefined && OPTIONAL_PEERS.has(name)) found.add(name);
|
|
248
|
+
}
|
|
249
|
+
return [...found].sort();
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const outcomes = await run();
|
|
253
|
+
|
|
254
|
+
describe('leaf subpaths import nothing at runtime', () => {
|
|
255
|
+
it('has a fixture with no peers installed (negative control)', () => {
|
|
256
|
+
const control = outcomes.control;
|
|
257
|
+
expect(control, 'the control never ran β the fixture is not measuring anything').toBeDefined();
|
|
258
|
+
expect(
|
|
259
|
+
control?.ok === false,
|
|
260
|
+
`the fixture resolved 'otplib' β bun auto-installed it out of ~/.bun/install/cache, or a node_modules above the fixture answered. Every assertion below is vacuous until that is fixed; do not delete this test to make it pass.`,
|
|
261
|
+
).toBe(true);
|
|
262
|
+
expect(
|
|
263
|
+
control?.ok === false ? control.error : '',
|
|
264
|
+
'the control failed for the wrong reason β it must fail on the missing PACKAGE',
|
|
265
|
+
).toContain("Cannot find package 'otplib'");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
for (const leaf of LEAVES) {
|
|
269
|
+
it(`cursedbelt/${leaf.subpath.slice(2)} imports clean`, () => {
|
|
270
|
+
const outcome = outcomes[leaf.subpath];
|
|
271
|
+
expect(outcome, `${leaf.subpath} was never imported by the fixture`).toBeDefined();
|
|
272
|
+
expect(
|
|
273
|
+
outcome?.ok === true ? '' : outcome?.ok === false ? outcome.error : 'missing',
|
|
274
|
+
`cursedbelt/${leaf.subpath.slice(2)} must import NOTHING at runtime β that is the entire reason the subpath exists. Move whatever it now reaches behind \`import type\`, or split the leaf again; do not add the dependency to a consumer.`,
|
|
275
|
+
).toBe('');
|
|
276
|
+
// Non-vacuity: a module that resolved but exported nothing would otherwise pass.
|
|
277
|
+
expect(
|
|
278
|
+
outcome?.ok === true ? outcome.keys : [],
|
|
279
|
+
`${leaf.subpath} resolved but does not export ${leaf.evaluates} β the check is pointing at the wrong file`,
|
|
280
|
+
).toContain(leaf.evaluates);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('barrels reach no optional peer statically', () => {
|
|
286
|
+
it('knows which peers are optional', () => {
|
|
287
|
+
// Guards the assertions below: an empty set makes every one of them pass vacuously.
|
|
288
|
+
expect(OPTIONAL_PEERS.size, 'peerDependenciesMeta lists no optional peer').toBeGreaterThan(10);
|
|
289
|
+
expect([...OPTIONAL_PEERS], 'otplib is the peer this whole spec was written for').toContain(
|
|
290
|
+
'otplib',
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
for (const target of NO_STATIC_OPTIONAL_PEERS) {
|
|
295
|
+
it(`cursedbelt/${target.subpath.slice(2)} costs a consumer no optional peer to import`, () => {
|
|
296
|
+
expect(
|
|
297
|
+
staticOptionalPeersOf(target.entry),
|
|
298
|
+
`importing cursedbelt/${target.subpath.slice(2)} statically pulls these OPTIONAL peers, so every consumer must install them for code it may never call. Either import the leaf module that has what you need instead of a sibling INDEX (that is how 'otplib' got in here twice), or make the dependency lazy: \`const { x } = await import('pkg')\` inside the function that needs it.`,
|
|
299
|
+
).toEqual([]);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { decryptSecret, deriveKey, encryptSecret } from 'cwip/node';
|
|
2
|
-
|
|
2
|
+
// π΄ `../auth/jwt`, NOT `../auth` β same reason as `storage/streamToken.ts`, and this is the
|
|
3
|
+
// SECOND edge of the pair: `cursedbelt/server/storage` reaches private-media, so the barrel
|
|
4
|
+
// import here put `otplib` (via `auth/index` β `totp.ts`) back into the storage graph after
|
|
5
|
+
// streamToken.ts had been fixed. Found by bundling the graph and reading the externals, not by
|
|
6
|
+
// grep β a two-hop edge is invisible to the obvious search.
|
|
7
|
+
import { signToken, verifyPurposeToken } from '../auth/jwt';
|
|
3
8
|
import { derivePasswordSetKey, deriveServerSetKey, type KeyMode } from './crypto';
|
|
4
9
|
|
|
5
10
|
// ββ Unlock service ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type { MediaStore } from './types';
|
|
1
|
+
import type { BinaryServerConfig, MediaStore } from './types';
|
|
2
|
+
|
|
3
|
+
// The token shapes moved to `./types` (published as `cursedbelt/server/storage/types`) so an
|
|
4
|
+
// app can import the CLAIM SHAPE without importing this adapter's module graph. Re-exported
|
|
5
|
+
// here so every existing `from './binaryServerStore'` and the `./index` barrel keep resolving.
|
|
6
|
+
export type { BinaryServerConfig, FileTokenClaims, FileTokenSigner } from './types';
|
|
2
7
|
|
|
3
8
|
// binary-server MediaStore adapter. binary-server (Bun/SQLite, RS256 token-gated, Range-capable)
|
|
4
9
|
// is a production storage path: a home/edge file service the app signs short-lived tokens for.
|
|
@@ -9,50 +14,6 @@ import type { MediaStore } from './types';
|
|
|
9
14
|
// public key). cursedbelt carries no key material and no asymmetric-crypto specifics; it just
|
|
10
15
|
// calls `signFileToken` with the claims binary-server expects.
|
|
11
16
|
|
|
12
|
-
/**
|
|
13
|
-
* Claims binary-server verifies (see binary-server/src/tokens.ts). This is the ONE
|
|
14
|
-
* canonical shape for a binary-server file token across the fleet: cursedbelt's
|
|
15
|
-
* injected-signer `createBinaryServerStore` (below) and `@satellites/satellite-kit`'s
|
|
16
|
-
* self-signing `createBinaryStore` both mint tokens against the SAME binary-server, so
|
|
17
|
-
* the claim set must never drift β satellite-kit imports this exact type rather than
|
|
18
|
-
* redeclaring it, so a token one side signs is always shaped for the other to read. The
|
|
19
|
-
* app's signer turns these into an RS256 JWT.
|
|
20
|
-
*/
|
|
21
|
-
export type FileTokenClaims = {
|
|
22
|
-
/** Exact authorized path, app-prefixed: `<app>/<key>`. */
|
|
23
|
-
k?: string;
|
|
24
|
-
/** Authorized path prefix, app-prefixed (container trees, e.g. an HLS tree). */
|
|
25
|
-
p?: string;
|
|
26
|
-
/** Acting user/app id β audit trail on binary-server's events, never authz. */
|
|
27
|
-
uid?: string;
|
|
28
|
-
/** Chunk-session id (must match across every chunk of one upload). */
|
|
29
|
-
sid?: string;
|
|
30
|
-
/** Chunk index (0-based). */
|
|
31
|
-
ci?: number;
|
|
32
|
-
/** Total chunk count. */
|
|
33
|
-
tc?: number;
|
|
34
|
-
/** Download filename β Content-Disposition. */
|
|
35
|
-
fn?: string;
|
|
36
|
-
dl?: 'inline' | 'attachment';
|
|
37
|
-
/** Per-op scope. A DELETE against binary-server requires a token carrying `op: 'delete'`
|
|
38
|
-
* (in addition to the internal secret) β a plain read/download token can never be replayed
|
|
39
|
-
* as a delete, and the token's issuer + `k`/`p` tenant-scope the delete. Owner ruling #10. */
|
|
40
|
-
op?: 'delete';
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export type FileTokenSigner = (claims: FileTokenClaims) => Promise<string>;
|
|
44
|
-
|
|
45
|
-
export type BinaryServerConfig = {
|
|
46
|
-
/** e.g. 'https://binary-server.cursedalchemy.com'. */
|
|
47
|
-
baseUrl: string;
|
|
48
|
-
/** App namespace (path + token scope), e.g. 'cursedalchemy'. */
|
|
49
|
-
appId: string;
|
|
50
|
-
/** App-provided RS256 signer (app holds the private key). */
|
|
51
|
-
signFileToken: FileTokenSigner;
|
|
52
|
-
/** Shared secret for internal ops (delete). Sent as x-internal-secret. */
|
|
53
|
-
internalSecret?: string;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
17
|
export function createBinaryServerStore(cfg: BinaryServerConfig): MediaStore {
|
|
57
18
|
const appKey = (key: string) => `${cfg.appId}/${key}`;
|
|
58
19
|
const mediaUrl = async (key: string, extra?: { fn?: string; dl?: 'inline' | 'attachment' }) => {
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
// π΄ `../auth/jwt`, NOT `../auth`. The jose half is a REAL dependency β stream tokens are
|
|
2
|
+
// signed and verified here, and jose is a plain `dependencies` entry every consumer already
|
|
3
|
+
// installs. The BARREL was the accident: `../auth/index.ts` also reaches `totp.ts` β `otplib`,
|
|
4
|
+
// an OPTIONAL peer, so every app that touched anything in `cursedbelt/server/storage` was made
|
|
5
|
+
// to install a one-time-password library for code it never calls. Import the leaf module that
|
|
6
|
+
// has what you need; never the index that has everything. `src/leafSubpathsImportNothing.spec.ts`
|
|
7
|
+
// fails if this file (or anything else the storage barrel reaches) undoes it.
|
|
8
|
+
import { signToken, verifyPurposeToken } from '../auth/jwt';
|
|
2
9
|
|
|
3
10
|
// ββ Stream tokens βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
4
11
|
// Media bytes are loaded by <video>/hls.js/<img>, which cannot send the platform Bearer
|
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
// with no Bearer can still be authorized), a source registry, and the ingestβderivatives
|
|
6
6
|
// pipeline glue. Ported/generalised from ca's proven media stack; the app maps its own records
|
|
7
7
|
// to a MediaSource. Pairs with the `PlayableVideo` contract in `cursedbelt/media-core`.
|
|
8
|
+
//
|
|
9
|
+
// π΄ THIS FILE IS A PUBLISHED LEAF β `cursedbelt/server/storage/types` β AND IT MUST STAY
|
|
10
|
+
// IMPORT-FREE AT RUNTIME. `src/leafSubpathsImportNothing.spec.ts` imports it in a fixture with
|
|
11
|
+
// no packages resolvable at all and fails if it throws, so an `import` here that survives
|
|
12
|
+
// transpilation (anything that is not `import type`) turns that spec red rather than turning
|
|
13
|
+
// up in a consumer's install. The subpath exists because `./server/storage`'s own index is a
|
|
14
|
+
// barrel: it reaches `streamToken` β `auth/jwt` β jose, the whole SQLite file catalogue under
|
|
15
|
+
// `files/`, and a lazily-imported `sharp` β while an app that wants only the SHAPE of a file
|
|
16
|
+
// token (apps/collections wanted exactly `FileTokenClaims`, a type TypeScript erases) was made
|
|
17
|
+
// to install a one-time-password library to get it. Add types here freely; add a value import
|
|
18
|
+
// here never.
|
|
8
19
|
|
|
9
20
|
import type { ProcessingStatus } from '../../core/media';
|
|
10
21
|
|
|
@@ -54,6 +65,56 @@ export interface MediaStore {
|
|
|
54
65
|
redirectUrl?(key: string, opts: { mime: string; filename?: string }): Promise<string | null>;
|
|
55
66
|
}
|
|
56
67
|
|
|
68
|
+
// ββ binary-server file tokens βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
69
|
+
// Declared HERE rather than beside the adapter that uses them (`binaryServerStore.ts`, which
|
|
70
|
+
// re-exports them for compatibility) because these three are the shapes a consumer needs
|
|
71
|
+
// WITHOUT the adapter: apps/collections' `binaryStore.ts` mints its own tokens and imports
|
|
72
|
+
// `FileTokenClaims` alone.
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Claims binary-server verifies (see binary-server/src/tokens.ts). This is the ONE
|
|
76
|
+
* canonical shape for a binary-server file token across the fleet: cursedbelt's
|
|
77
|
+
* injected-signer `createBinaryServerStore` and `@satellites/satellite-kit`'s
|
|
78
|
+
* self-signing `createBinaryStore` both mint tokens against the SAME binary-server, so
|
|
79
|
+
* the claim set must never drift β satellite-kit imports this exact type rather than
|
|
80
|
+
* redeclaring it, so a token one side signs is always shaped for the other to read. The
|
|
81
|
+
* app's signer turns these into an RS256 JWT.
|
|
82
|
+
*/
|
|
83
|
+
export type FileTokenClaims = {
|
|
84
|
+
/** Exact authorized path, app-prefixed: `<app>/<key>`. */
|
|
85
|
+
k?: string;
|
|
86
|
+
/** Authorized path prefix, app-prefixed (container trees, e.g. an HLS tree). */
|
|
87
|
+
p?: string;
|
|
88
|
+
/** Acting user/app id β audit trail on binary-server's events, never authz. */
|
|
89
|
+
uid?: string;
|
|
90
|
+
/** Chunk-session id (must match across every chunk of one upload). */
|
|
91
|
+
sid?: string;
|
|
92
|
+
/** Chunk index (0-based). */
|
|
93
|
+
ci?: number;
|
|
94
|
+
/** Total chunk count. */
|
|
95
|
+
tc?: number;
|
|
96
|
+
/** Download filename β Content-Disposition. */
|
|
97
|
+
fn?: string;
|
|
98
|
+
dl?: 'inline' | 'attachment';
|
|
99
|
+
/** Per-op scope. A DELETE against binary-server requires a token carrying `op: 'delete'`
|
|
100
|
+
* (in addition to the internal secret) β a plain read/download token can never be replayed
|
|
101
|
+
* as a delete, and the token's issuer + `k`/`p` tenant-scope the delete. Owner ruling #10. */
|
|
102
|
+
op?: 'delete';
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type FileTokenSigner = (claims: FileTokenClaims) => Promise<string>;
|
|
106
|
+
|
|
107
|
+
export type BinaryServerConfig = {
|
|
108
|
+
/** e.g. 'https://binary-server.cursedalchemy.com'. */
|
|
109
|
+
baseUrl: string;
|
|
110
|
+
/** App namespace (path + token scope), e.g. 'cursedalchemy'. */
|
|
111
|
+
appId: string;
|
|
112
|
+
/** App-provided RS256 signer (app holds the private key). */
|
|
113
|
+
signFileToken: FileTokenSigner;
|
|
114
|
+
/** Shared secret for internal ops (delete). Sent as x-internal-secret. */
|
|
115
|
+
internalSecret?: string;
|
|
116
|
+
};
|
|
117
|
+
|
|
57
118
|
/** A source of videos for a gallery/catalogue. Apps implement this differently (a disk folder,
|
|
58
119
|
* a binary-server app, ca's Files tree + private set). Consumers are source-agnostic. */
|
|
59
120
|
export interface MediaSource {
|