@fuzdev/fuz_app 0.146.1 → 0.147.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.
Files changed (48) hide show
  1. package/dist/auth/CLAUDE.md +1 -1
  2. package/dist/auth/bootstrap_account.d.ts +2 -1
  3. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  4. package/dist/auth/bootstrap_account.js +17 -6
  5. package/dist/auth/keyring.d.ts +35 -38
  6. package/dist/auth/keyring.d.ts.map +1 -1
  7. package/dist/auth/keyring.js +43 -50
  8. package/dist/auth/password_argon2.d.ts.map +1 -1
  9. package/dist/auth/password_argon2.js +11 -8
  10. package/dist/auth/session_cookie.d.ts.map +1 -1
  11. package/dist/auth/session_cookie.js +5 -2
  12. package/dist/db/CLAUDE.md +18 -16
  13. package/dist/db/cell_queries.d.ts +3 -2
  14. package/dist/db/cell_queries.d.ts.map +1 -1
  15. package/dist/db/cell_queries.js +3 -2
  16. package/dist/db/fact_disk_storage.d.ts +15 -15
  17. package/dist/db/fact_disk_storage.d.ts.map +1 -1
  18. package/dist/db/fact_disk_storage.js +22 -28
  19. package/dist/db/fact_queries.d.ts +2 -3
  20. package/dist/db/fact_queries.d.ts.map +1 -1
  21. package/dist/db/fact_queries.js +2 -3
  22. package/dist/db/fact_store.d.ts +20 -18
  23. package/dist/db/fact_store.d.ts.map +1 -1
  24. package/dist/db/fact_store.js +30 -44
  25. package/dist/db/file_fact_url.d.ts +19 -4
  26. package/dist/db/file_fact_url.d.ts.map +1 -1
  27. package/dist/db/file_fact_url.js +19 -4
  28. package/dist/server/env.d.ts.map +1 -1
  29. package/dist/server/env.js +2 -2
  30. package/dist/server/serve_fact_route.d.ts +9 -5
  31. package/dist/server/serve_fact_route.d.ts.map +1 -1
  32. package/dist/server/serve_fact_route.js +15 -7
  33. package/dist/testing/CLAUDE.md +19 -2
  34. package/dist/testing/app_server.d.ts.map +1 -1
  35. package/dist/testing/app_server.js +2 -2
  36. package/dist/testing/cross_backend/bootstrap_backend.d.ts +11 -1
  37. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -1
  38. package/dist/testing/cross_backend/bootstrap_backend.js +2 -1
  39. package/dist/testing/cross_backend/fact_serving.d.ts +5 -0
  40. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  41. package/dist/testing/cross_backend/fact_serving.js +25 -1
  42. package/dist/testing/cross_backend/session_cookie_codec.d.ts +19 -0
  43. package/dist/testing/cross_backend/session_cookie_codec.d.ts.map +1 -0
  44. package/dist/testing/cross_backend/session_cookie_codec.js +128 -0
  45. package/dist/testing/cross_backend/testing_reset_actions.d.ts +7 -3
  46. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  47. package/dist/testing/cross_backend/testing_reset_actions.js +13 -8
  48. package/package.json +6 -6
@@ -154,7 +154,7 @@ Key module-scope values:
154
154
 
155
155
  - `stub_password_deps` — `PasswordHashDeps` hashing via `stub_hash_${password}` and verifying by equality. Deterministic, no Argon2 cost — use for every test not specifically exercising password hashing.
156
156
  - `create_loopback_app_server_options()` — the loopback `create_app_server` options an in-process test server takes (`localhost` origins, the loopback proxy trusted, an empty env schema), fresh per call; `create_test_app` and `create_test_app_for_bootstrap` spread it under their own defaults (`rate_limiters: 'disabled_for_testing'`, `await_pending_effects: true`).
157
- - `TEST_COOKIE_SECRET` — 64-hex-char deterministic cookie secret. Produces a valid `Keyring` via `create_validated_keyring`. Never used in production — the stub guard plus fixed value is the contract.
157
+ - `TEST_COOKIE_SECRET` — 64-hex-char deterministic cookie secret. Produces a valid `Keyring` via `create_keyring`. Never used in production — the stub guard plus fixed value is the contract.
158
158
  - `fallback_db_factory` — module-level auth-only default factory (`create_default_db_factory`) `create_test_app_server` uses when no `db` is passed, reusing its driver's WASM cache. When `migration_namespaces` is supplied, a memoized sibling factory (keyed by namespace set) migrating `[auth_migration_ns, ...migration_namespaces]` is used instead — same shared WASM, extra tables.
159
159
 
160
160
  Two helpers share the "insert account + actor + roles + API token + session +
@@ -970,7 +970,9 @@ source of truth for wire-shape conformance.
970
970
  hardening headers and bodyless masked 404s); like cells it stays off the
971
971
  declared surface and is `true` on every spine that mounts the serve routes +
972
972
  the `_testing_put_fact` seeder (whose optional `holder` records a
973
- `fact_holder` row, so a non-admin can reference what it seeds) — it also
973
+ `fact_holder` row, so a non-admin can reference what it seeds, and whose
974
+ optional `external_url` writes a bare external row naming that URL, so the
975
+ suite can pin that a URL naming another hash's file is refused) — it also
974
976
  gates `describe_cell_ref_admission_cross_tests`. `ready` gates `describe_ready_cross_tests`
975
977
  (anonymous `GET /ready` → `200 {ready: true}` on a clean spine bootstrap);
976
978
  like cells/sse the `/ready` deploy gate stays off the declared surface, `true`
@@ -1917,6 +1919,21 @@ spine bootstrap (auth + cell + fact) and is regenerated +
1917
1919
  drift-guarded by `src/test/cross_backend/spine_expected_schema.db.test.ts`
1918
1920
  (`UPDATE_SCHEMA_READY=1`).
1919
1921
 
1922
+ ### Session cookie codec parity — `cross_backend/session_cookie_codec.ts`
1923
+
1924
+ `describe_session_cookie_codec_cross_tests({setup_test, cookie_name,
1925
+ cookie_keys, rpc_path?})` — both spines accept exactly one spelling of a signed
1926
+ session cookie. The suite holds the backend's `SECRET_FUZ_COOKIE_KEYS`
1927
+ (`cookie_keys`), signs `token:expires_at` itself, and probes `account_verify`
1928
+ with: a signature whose final character carries non-zero trailing bits (the
1929
+ same MAC under `atob`), an unpadded signature, and correctly signed
1930
+ `expires_at` values `+N`, ` N`, `N.0` and one past `u64::MAX` — each refused
1931
+ with `authentication_required`. Controls: re-signing the keeper cookie's
1932
+ payload reproduces it byte for byte and it authenticates, and a leading-zero
1933
+ `0N` authenticates (the `u64` bound is on the value, not the digit count).
1934
+ Ungated; cross-process only. fuz_app's own wiring is
1935
+ `src/test/cross_backend/session_cookie_codec.cross.test.ts`.
1936
+
1920
1937
  ### Testing-backdoor credential gate — `cross_backend/testing_backdoor.ts`
1921
1938
 
1922
1939
  `describe_testing_backdoor_cross_tests({setup_test, rpc_path?, ws?})` —
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  import { Logger } from '@fuzdev/fuz_util/log.ts';
4
4
  import { DEFAULT_TEST_PASSWORD } from './test_credentials.js';
5
5
  import { ROLE_KEEPER } from '../auth/role_schema.js';
6
- import { create_validated_keyring } from '../auth/keyring.js';
6
+ import { create_keyring } from '../auth/keyring.js';
7
7
  import { generate_api_token } from '../auth/api_token.js';
8
8
  import { query_create_account_with_actor } from '../auth/account_queries.js';
9
9
  import { query_create_role_grant } from '../auth/role_grant_queries.js';
@@ -182,7 +182,7 @@ const _build_test_backend = async (options) => {
182
182
  throw new Error('test app setup: pass either `db` (caller owns migrations) or `migration_namespaces` ' +
183
183
  '(harness migrates), not both');
184
184
  }
185
- const keyring_result = create_validated_keyring(TEST_COOKIE_SECRET);
185
+ const keyring_result = create_keyring(TEST_COOKIE_SECRET);
186
186
  if (!keyring_result.ok) {
187
187
  throw new Error(`Test keyring failed: ${keyring_result.errors.join(', ')}`);
188
188
  }
@@ -25,6 +25,16 @@ import '../assert_dev_env.js';
25
25
  */
26
26
  import type { BackendConfig } from './backend_config.js';
27
27
  import type { BootstrappedBackendHandle } from './setup.js';
28
+ /** Options for `bootstrap_backend`. */
29
+ export interface BootstrapBackendOptions {
30
+ /**
31
+ * Runs after the binary is up and before the keeper bootstrap — the one
32
+ * window in a backend's life where `POST {bootstrap_path}` still answers on
33
+ * the token, so probes of the bootstrap route itself go here. A throw tears
34
+ * the backend down like a failed bootstrap.
35
+ */
36
+ readonly before_bootstrap?: (config: BackendConfig) => Promise<void>;
37
+ }
28
38
  /**
29
39
  * Spawn the test binary described by `config`, bootstrap a keeper, and
30
40
  * return the enriched handle.
@@ -37,5 +47,5 @@ import type { BootstrappedBackendHandle } from './setup.js';
37
47
  * Mirrors the composition `default_cross_process_setup`'s caller would
38
48
  * otherwise hand-roll in every consumer's `globalSetup`.
39
49
  */
40
- export declare const bootstrap_backend: (config: BackendConfig) => Promise<BootstrappedBackendHandle>;
50
+ export declare const bootstrap_backend: (config: BackendConfig, options?: BootstrapBackendOptions) => Promise<BootstrappedBackendHandle>;
41
51
  //# sourceMappingURL=bootstrap_backend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/bootstrap_backend.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,aAAa,KACnB,OAAO,CAAC,yBAAyB,CAwBnC,CAAC"}
1
+ {"version":3,"file":"bootstrap_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/bootstrap_backend.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D,uCAAuC;AACvC,MAAM,WAAW,uBAAuB;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,aAAa,EACrB,UAAU,uBAAuB,KAC/B,OAAO,CAAC,yBAAyB,CAyBnC,CAAC"}
@@ -14,9 +14,10 @@ import { create_fetch_transport } from '../transports/fetch_transport.js';
14
14
  * Mirrors the composition `default_cross_process_setup`'s caller would
15
15
  * otherwise hand-roll in every consumer's `globalSetup`.
16
16
  */
17
- export const bootstrap_backend = async (config) => {
17
+ export const bootstrap_backend = async (config, options) => {
18
18
  const handle = await spawn_backend(config);
19
19
  try {
20
+ await options?.before_bootstrap?.(config);
20
21
  const keeper_transport = create_fetch_transport({ base_url: config.base_url });
21
22
  const keeper = await bootstrap({ transport: keeper_transport, config });
22
23
  return {
@@ -15,6 +15,11 @@ export interface CrossPutFactInput {
15
15
  readonly content_type?: string;
16
16
  /** The actor the put is on behalf of — recorded as a holder. Omit for a system put. */
17
17
  readonly holder?: string;
18
+ /**
19
+ * Store an external row naming this URL, verbatim and with no bytes, instead
20
+ * of an embedded one.
21
+ */
22
+ readonly external_url?: string;
18
23
  }
19
24
  /**
20
25
  * Seed an embedded fact over the daemon channel via `_testing_put_fact`;
@@ -1 +1 @@
1
- {"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8D9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElG;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,uCAAuC;IAC3F,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AAgBD,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,SAAS,IAAI,CAAC,WAAW,EAAE,iBAAiB,GAAG,6BAA6B,CAAC,EAC7E,UAAU,MAAM,EAChB,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAaV,CAAC;AAgDR,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAkRxF,CAAC"}
1
+ {"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmE9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElG;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,uCAAuC;IAC3F,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AAgBD,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,SAAS,IAAI,CAAC,WAAW,EAAE,iBAAiB,GAAG,6BAA6B,CAAC,EAC7E,UAAU,MAAM,EAChB,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAaV,CAAC;AAgDR,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAsTxF,CAAC"}
@@ -21,6 +21,10 @@ import '../assert_dev_env.js';
21
21
  * private, max-age=300`, whatever its content type;
22
22
  * - **bare-hash admin-only** — `GET /api/facts/:hash` is admin (keeper) only:
23
23
  * non-admin → 403, anonymous → 401;
24
+ * - **URL bound to hash** — a row whose `external_url` is a well-formed
25
+ * `file:` URL naming another hash's file is a masked 404 on both routes,
26
+ * refused before any disk read (each spine's unit suite proves the bytes
27
+ * behind such a URL never serve);
24
28
  * - **multi-actor fallthrough** — a multi-actor caller resolves to a null
25
29
  * (anonymous) context on the (`acting`-less) cell-scoped route, so it can't
26
30
  * read its own *private* fact there (admitted only by public cells). Opt-in
@@ -32,7 +36,8 @@ import '../assert_dev_env.js';
32
36
  * exactly as for an anonymous caller, while a public cell still serves.
33
37
  *
34
38
  * Facts are seeded **embedded** via `_testing_put_fact` (the cross-process
35
- * driver has no DB handle); the referencing cell via the `cell_create` RPC
39
+ * driver has no DB handle), or as a bare external row through its
40
+ * `external_url`; the referencing cell via the `cell_create` RPC
36
41
  * (the hash, a whole string value in `data`, is derived into `cell.refs`). A
37
42
  * non-admin owner's cell may reference only a fact it holds, so a fact a
38
43
  * non-admin references is put with that owner as its `holder` (the keeper is
@@ -193,6 +198,25 @@ export const describe_fact_serving_cross_tests = (options) => {
193
198
  const edge_no_bytes = await fact_get(t, cell_fact_path(unrelated_cell, UNRELATED_HASH), admin_headers);
194
199
  assert_masked_404(edge_no_bytes, 'edge to an absent fact');
195
200
  });
201
+ test_if(capabilities.fact_serving, "an external_url naming another fact's file is a masked 404 on both routes", async () => {
202
+ const fixture = await setup_test();
203
+ const owner = await fixture.create_account({ username: 'fact_url_owner' });
204
+ // a row written past the store: well-formed `file:` URL, but the
205
+ // address of another hash, so serving it would hand out that fact's
206
+ // bytes under this one's name
207
+ const other = 'ab' + 'cd'.repeat(31);
208
+ const hash = await cross_put_fact(fixture, rpc_path, {
209
+ content: 'row-with-a-foreign-url',
210
+ content_type: 'text/plain',
211
+ holder: owner.actor.id,
212
+ external_url: `file:${other.slice(0, 2)}/${other.slice(2)}`
213
+ });
214
+ const t = fixture.fresh_transport();
215
+ const cell_id = await create_cell_with_ref(t, owner.create_session_headers(), hash, 'public');
216
+ const anon = fixture.fresh_transport({ origin: null });
217
+ assert_masked_404(await fact_get(anon, cell_fact_path(cell_id, hash), {}), 'foreign-url fact via a public cell');
218
+ assert_masked_404(await fact_get(t, `/api/facts/${hash}`, fixture.create_session_headers()), 'foreign-url fact by bare hash, as admin');
219
+ });
196
220
  test_if(capabilities.fact_serving, 'bare-hash route is admin-only', async () => {
197
221
  const fixture = await setup_test();
198
222
  const non_admin = await fixture.create_account({ username: 'fact_non_admin' });
@@ -0,0 +1,19 @@
1
+ import '../assert_dev_env.js';
2
+ import type { SetupTest } from './setup.js';
3
+ /** Options for the session cookie codec parity suite. */
4
+ export interface SessionCookieCodecCrossTestOptions {
5
+ /** Per-test fixture producer (cross-process). */
6
+ readonly setup_test: SetupTest;
7
+ /** The spine's session cookie name (`handle.config.cookie_name`). */
8
+ readonly cookie_name: string;
9
+ /**
10
+ * The backend's `SECRET_FUZ_COOKIE_KEYS` value
11
+ * (`handle.config.env.SECRET_FUZ_COOKIE_KEYS`) — the suite signs with its
12
+ * primary key.
13
+ */
14
+ readonly cookie_keys: string;
15
+ /** JSON-RPC endpoint path. Default `SPINE_RPC_PATH`. */
16
+ readonly rpc_path?: string;
17
+ }
18
+ export declare const describe_session_cookie_codec_cross_tests: (options: SessionCookieCodecCrossTestOptions) => void;
19
+ //# sourceMappingURL=session_cookie_codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session_cookie_codec.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/session_cookie_codec.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAGzD,yDAAyD;AACzD,MAAM,WAAW,kCAAkC;IAClD,iDAAiD;IACjD,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AA2BD,eAAO,MAAM,yCAAyC,GACrD,SAAS,kCAAkC,KACzC,IAsGF,CAAC"}
@@ -0,0 +1,128 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend parity suite for the **signed session cookie codec** over real
4
+ * HTTP: which spellings of a correctly signed cookie each spine accepts.
5
+ *
6
+ * The cookie is `${token}:${expires_at}.${signature}`, the signature the padded
7
+ * standard base64 of an HMAC-SHA256 MAC. Both spines must accept exactly one
8
+ * spelling of a given signed payload, or a cookie that one spine refuses would
9
+ * authenticate on the other. The suite holds the test keyring, so it signs
10
+ * payloads itself and sends variants a lenient decoder would take:
11
+ *
12
+ * - **signature spelling** — the last data character bumped so the trailing
13
+ * bits are non-zero (`atob` decodes the same MAC), and the `=` padding
14
+ * dropped. Both must be refused.
15
+ * - **`expires_at` spelling** — `+N`, ` N`, `N.0`, and an all-digit value past
16
+ * `u64::MAX` (the Rust spine parses it as `u64`), each signed with the real
17
+ * key. All must be refused. A leading-zero `0N` reads as `N` on both, so it
18
+ * still authenticates — the bound is on the value, not the digit count.
19
+ *
20
+ * The positive controls run first: re-signing the keeper cookie's payload
21
+ * reproduces the cookie byte for byte (the suite's keyring is the backend's),
22
+ * and that cookie authenticates. Authentication is probed with the
23
+ * `account_verify` RPC action. The per-impl unit tests cover each codec
24
+ * directly; this suite is what catches the two drifting apart.
25
+ *
26
+ * @module
27
+ */
28
+ import { describe, test, assert } from 'vitest';
29
+ import { create_keyring } from '../../auth/keyring.js';
30
+ import { ERROR_AUTHENTICATION_REQUIRED } from '../../http/error_schemas.js';
31
+ import { cross_rpc_call, error_reason } from './cell_cross_helpers.js';
32
+ import { SPINE_RPC_PATH } from './spine_surface_constants.js';
33
+ /** The base64 alphabet, in index order. */
34
+ const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
35
+ /** `u64::MAX + 1`, the smallest all-digit `expires_at` the Rust spine cannot parse. */
36
+ const U64_MAX_PLUS_ONE = '18446744073709551616';
37
+ /** Split a signed cookie value on its last `.` into payload and signature. */
38
+ const split_signed = (signed) => {
39
+ const dot = signed.lastIndexOf('.');
40
+ assert.ok(dot !== -1, `signed cookie value has no '.' separator: ${signed}`);
41
+ return { payload: signed.slice(0, dot), signature: signed.slice(dot + 1) };
42
+ };
43
+ /**
44
+ * Bump the signature's last data character (index 42, which carries the MAC's
45
+ * final 4 bits and 2 zero padding bits) to the next base64 character. That sets
46
+ * a padding bit, which `atob` ignores, so the decoded MAC is unchanged.
47
+ */
48
+ const bump_trailing_bits = (signature) => {
49
+ const last_data = signature[42];
50
+ const index = BASE64_ALPHABET.indexOf(last_data);
51
+ assert.ok(index !== -1 && index % 4 === 0, `unexpected final signature character: ${last_data}`);
52
+ return signature.slice(0, 42) + BASE64_ALPHABET[index + 1] + signature.slice(43);
53
+ };
54
+ export const describe_session_cookie_codec_cross_tests = (options) => {
55
+ const { setup_test, cookie_name, cookie_keys } = options;
56
+ const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
57
+ const keyring_result = create_keyring(cookie_keys);
58
+ if (!keyring_result.ok) {
59
+ throw new Error(`cookie_keys is not a valid keyring: ${keyring_result.errors.join('; ')}`);
60
+ }
61
+ const keyring = keyring_result.keyring;
62
+ /** The keeper's signed cookie value, with its verified `token` and `expires_at`. */
63
+ const keeper_cookie = async (fixture) => {
64
+ const header = fixture.create_session_headers().cookie;
65
+ assert.ok(header, 'the keeper session headers carry a cookie');
66
+ const prefix = `${cookie_name}=`;
67
+ assert.ok(header.startsWith(prefix), `the keeper cookie is named '${cookie_name}'`);
68
+ const signed = header.slice(prefix.length);
69
+ const verified = await keyring.verify(signed);
70
+ assert.ok(verified, 'the keeper cookie verifies under cookie_keys');
71
+ const sep = verified.value.lastIndexOf(':');
72
+ assert.ok(sep !== -1, 'the keeper cookie payload is `token:expires_at`');
73
+ return {
74
+ signed,
75
+ token: verified.value.slice(0, sep),
76
+ expires_at: verified.value.slice(sep + 1)
77
+ };
78
+ };
79
+ const verify_with = (fixture, signed) => cross_rpc_call(fixture.fresh_transport(), rpc_path, 'account_verify', undefined, {
80
+ cookie: `${cookie_name}=${signed}`
81
+ });
82
+ const assert_unauthenticated = async (fixture, signed, label) => {
83
+ const result = await verify_with(fixture, signed);
84
+ assert.strictEqual(result.ok, false, `${label}: must not authenticate`);
85
+ assert.strictEqual(error_reason(result), ERROR_AUTHENTICATION_REQUIRED, label);
86
+ };
87
+ describe('session cookie codec parity', () => {
88
+ test('controls: re-signing the keeper payload reproduces its cookie, which authenticates', async () => {
89
+ const fixture = await setup_test();
90
+ const { signed, token, expires_at } = await keeper_cookie(fixture);
91
+ assert.strictEqual(await keyring.sign(`${token}:${expires_at}`), signed, 'the suite keyring signs as the backend does');
92
+ const result = await verify_with(fixture, signed);
93
+ assert.ok(result.ok, `the keeper cookie authenticates: ${JSON.stringify(result.error)}`);
94
+ });
95
+ test('a signature with non-zero trailing bits is refused', async () => {
96
+ const fixture = await setup_test();
97
+ const { payload, signature } = split_signed((await keeper_cookie(fixture)).signed);
98
+ await assert_unauthenticated(fixture, `${payload}.${bump_trailing_bits(signature)}`, 'non-zero trailing bits');
99
+ });
100
+ test('an unpadded signature is refused', async () => {
101
+ const fixture = await setup_test();
102
+ const { payload, signature } = split_signed((await keeper_cookie(fixture)).signed);
103
+ assert.ok(signature.endsWith('='), 'the canonical signature is padded');
104
+ await assert_unauthenticated(fixture, `${payload}.${signature.slice(0, -1)}`, 'unpadded');
105
+ });
106
+ test('a non-canonical expires_at is refused even when signed', async () => {
107
+ const fixture = await setup_test();
108
+ const { token, expires_at } = await keeper_cookie(fixture);
109
+ const variants = [
110
+ ['plus sign', `+${expires_at}`],
111
+ ['leading space', ` ${expires_at}`],
112
+ ['decimal', `${expires_at}.0`],
113
+ ['past u64::MAX', U64_MAX_PLUS_ONE]
114
+ ];
115
+ for (const [label, value] of variants) {
116
+ const signed = await keyring.sign(`${token}:${value}`);
117
+ await assert_unauthenticated(fixture, signed, `expires_at ${label} (${JSON.stringify(value)})`);
118
+ }
119
+ });
120
+ test('a leading-zero expires_at reads as its value and authenticates', async () => {
121
+ const fixture = await setup_test();
122
+ const { token, expires_at } = await keeper_cookie(fixture);
123
+ const signed = await keyring.sign(`${token}:0${expires_at}`);
124
+ const result = await verify_with(fixture, signed);
125
+ assert.ok(result.ok, `a leading zero authenticates: ${JSON.stringify(result.error)}`);
126
+ });
127
+ });
128
+ };
@@ -265,9 +265,12 @@ export declare const create_testing_drain_effects_action: (audit: AuditEmitter)
265
265
  * the put is on behalf of — recorded in `fact_holder` (deduplicated puts
266
266
  * included), as a real upload records its uploader, so the suites can drive
267
267
  * cell ref admission; absent, the put is a system put with no holder. The
268
- * referencing cell is seeded separately via the `cell_create` RPC. Embedded-only is enough for the
269
- * authz assertions (cell-scoped admit, cross-owner-no-leak, 404-mask, bare-hash
270
- * admin-only); external / X-Accel parity stays covered by the forge's own gate.
268
+ * referencing cell is seeded separately via the `cell_create` RPC. Embedded is
269
+ * enough for the authz assertions (cell-scoped admit, cross-owner-no-leak,
270
+ * 404-mask, bare-hash admin-only); external / X-Accel parity stays covered by
271
+ * the forge's own gate. `external_url`, when given, writes the row the way a
272
+ * writer past the store could — no bytes, that URL verbatim, unvalidated — so
273
+ * the suite can prove a serve refuses a URL that is not its hash's own address.
271
274
  *
272
275
  * `auth` gates on the daemon-token credential, matching `_testing_reset` — the
273
276
  * action does a direct `fact` insert the production wire never exposes. The
@@ -287,6 +290,7 @@ export declare const testing_put_fact_action_spec: {
287
290
  content: z.ZodString;
288
291
  content_type: z.ZodOptional<z.ZodString>;
289
292
  holder: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
293
+ external_url: z.ZodOptional<z.ZodString>;
290
294
  }, z.core.$strict>;
291
295
  readonly output: z.ZodObject<{
292
296
  hash: z.ZodString & z.ZodType<import("@fuzdev/fuz_util/hash_schemas.js").FactHash, string, z.core.$ZodTypeInternals<import("@fuzdev/fuz_util/hash_schemas.js").FactHash, string>>;
@@ -1 +1 @@
1
- {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"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"}
@@ -267,9 +267,12 @@ export const create_testing_drain_effects_action = (audit) => rpc_action(testing
267
267
  * the put is on behalf of — recorded in `fact_holder` (deduplicated puts
268
268
  * included), as a real upload records its uploader, so the suites can drive
269
269
  * cell ref admission; absent, the put is a system put with no holder. The
270
- * referencing cell is seeded separately via the `cell_create` RPC. Embedded-only is enough for the
271
- * authz assertions (cell-scoped admit, cross-owner-no-leak, 404-mask, bare-hash
272
- * admin-only); external / X-Accel parity stays covered by the forge's own gate.
270
+ * referencing cell is seeded separately via the `cell_create` RPC. Embedded is
271
+ * enough for the authz assertions (cell-scoped admit, cross-owner-no-leak,
272
+ * 404-mask, bare-hash admin-only); external / X-Accel parity stays covered by
273
+ * the forge's own gate. `external_url`, when given, writes the row the way a
274
+ * writer past the store could — no bytes, that URL verbatim, unvalidated — so
275
+ * the suite can prove a serve refuses a URL that is not its hash's own address.
273
276
  *
274
277
  * `auth` gates on the daemon-token credential, matching `_testing_reset` — the
275
278
  * action does a direct `fact` insert the production wire never exposes. The
@@ -284,12 +287,14 @@ export const testing_put_fact_action_spec = {
284
287
  input: z.strictObject({
285
288
  content: z.string(),
286
289
  content_type: z.string().optional(),
287
- holder: Uuid.optional()
290
+ holder: Uuid.optional(),
291
+ external_url: z.string().optional()
288
292
  }),
289
293
  output: z.strictObject({ hash: FactHashSchema }),
290
294
  async: true,
291
- description: 'Test-binary only — seed an embedded fact (blake3 of the UTF-8 content) and return its hash, ' +
292
- 'so the cross-process fact-serving suite can store bytes without a DB handle.'
295
+ description: 'Test-binary only — seed a fact row for the blake3 of the UTF-8 content (embedded, or with ' +
296
+ 'external_url an external row naming that URL) and return its hash, so the cross-process ' +
297
+ 'fact-serving suite can store facts without a DB handle.'
293
298
  };
294
299
  /** The wire method of each notification `_testing_flood_notifications` queues. */
295
300
  export const TESTING_FLOOD_NOTIFICATION_METHOD = '_testing_flood';
@@ -719,8 +724,8 @@ export const create_testing_actions = (deps, options) => {
719
724
  const hash = fact_hash_bytes(bytes);
720
725
  await query_put_fact({ db: ctx.db }, {
721
726
  hash,
722
- bytes,
723
- external_url: null,
727
+ bytes: input.external_url === undefined ? bytes : null,
728
+ external_url: input.external_url ?? null,
724
729
  content_type: input.content_type ?? null,
725
730
  size: bytes.length
726
731
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.146.1",
3
+ "version": "0.147.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -35,7 +35,7 @@
35
35
  "@fuzdev/blake3-wasm": ">=0.2.0",
36
36
  "@fuzdev/fuz_css": ">=0.70.0",
37
37
  "@fuzdev/fuz_schema": "^0.5.0",
38
- "@fuzdev/fuz_util": ">=0.77.0",
38
+ "@fuzdev/fuz_util": ">=0.78.0",
39
39
  "@fuzdev/pglet-wasm": ">=0.1.2",
40
40
  "@hono/bun": ">=1",
41
41
  "@hono/deno": ">=1",
@@ -92,10 +92,10 @@
92
92
  "@electric-sql/pglite": "^0.5.8",
93
93
  "@fuzdev/blake3-wasm": "^0.2.0",
94
94
  "@fuzdev/fuz_code": "^0.54.0",
95
- "@fuzdev/fuz_css": "^0.70.0",
95
+ "@fuzdev/fuz_css": "^0.71.0",
96
96
  "@fuzdev/fuz_schema": "^0.5.0",
97
- "@fuzdev/fuz_ui": "^0.217.0",
98
- "@fuzdev/fuz_util": "^0.77.0",
97
+ "@fuzdev/fuz_ui": "^0.220.0",
98
+ "@fuzdev/fuz_util": "^0.78.0",
99
99
  "@fuzdev/gro": "^0.220.0",
100
100
  "@fuzdev/mdz": "^0.6.0",
101
101
  "@fuzdev/pglet-wasm": "^0.1.2",
@@ -123,7 +123,7 @@
123
123
  "pg": "^8.20.0",
124
124
  "svelte": "^5.57.2",
125
125
  "svelte-check": "^4.7.6",
126
- "svelte-docinfo": "^0.13.0",
126
+ "svelte-docinfo": "^0.16.0",
127
127
  "svelte2tsx": "^0.7.52",
128
128
  "tslib": "^2.8.1",
129
129
  "typescript": "^6.0.3",