@fuzdev/fuz_app 0.109.0 → 0.110.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/auth/CLAUDE.md +6 -5
- package/dist/auth/account_action_specs.d.ts +18 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +14 -1
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +6 -2
- package/dist/auth/bootstrap_account.d.ts +7 -2
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +9 -4
- package/dist/auth/bootstrap_routes.d.ts +12 -4
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +15 -6
- package/dist/auth/daemon_token_middleware.d.ts +7 -69
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
- package/dist/auth/daemon_token_middleware.js +8 -104
- package/dist/auth/deps.d.ts +9 -5
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/middleware.js +1 -1
- package/dist/auth/request_context.d.ts +1 -3
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +2 -5
- package/dist/auth/session_cookie.d.ts +18 -36
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +16 -26
- package/dist/auth/session_middleware.d.ts +1 -1
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +1 -4
- package/dist/auth/session_queries.d.ts +14 -30
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +15 -45
- package/dist/auth/token_lifetime.d.ts +51 -0
- package/dist/auth/token_lifetime.d.ts.map +1 -0
- package/dist/auth/token_lifetime.js +54 -0
- package/dist/dev/setup.d.ts +5 -9
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +18 -21
- package/dist/runtime/deno.d.ts.map +1 -1
- package/dist/runtime/deno.js +27 -1
- package/dist/runtime/deps.d.ts +38 -4
- package/dist/runtime/deps.d.ts.map +1 -1
- package/dist/runtime/fs.d.ts +24 -5
- package/dist/runtime/fs.d.ts.map +1 -1
- package/dist/runtime/fs.js +38 -7
- package/dist/runtime/mock.d.ts +7 -0
- package/dist/runtime/mock.d.ts.map +1 -1
- package/dist/runtime/mock.js +41 -1
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +7 -1
- package/dist/runtime/secure_file.d.ts +71 -0
- package/dist/runtime/secure_file.d.ts.map +1 -0
- package/dist/runtime/secure_file.js +125 -0
- package/dist/server/app_backend.d.ts +6 -5
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +2 -3
- package/dist/testing/CLAUDE.md +15 -1
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +10 -2
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +13 -8
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +5 -1
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_grant_role.js +34 -1
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +2 -1
- package/dist/testing/cross_backend/setup.js +1 -1
- package/dist/testing/cross_backend/token_lifetime.d.ts +15 -0
- package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -0
- package/dist/testing/cross_backend/token_lifetime.js +99 -0
- package/dist/testing/cross_backend/token_scope_surface.js +1 -1
- package/dist/testing/daemon_token_rotation.d.ts +83 -0
- package/dist/testing/daemon_token_rotation.d.ts.map +1 -0
- package/dist/testing/daemon_token_rotation.js +99 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +15 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +4 -4
- package/dist/ui/AccountSessions.svelte +0 -5
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +7 -4
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +0 -5
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardened secret-file read — the TS twin of the Rust spine's
|
|
3
|
+
* `fuz_sys::secure_file::load_secure_file`.
|
|
4
|
+
*
|
|
5
|
+
* The file this guards is typically the highest-value credential on the host
|
|
6
|
+
* (the bootstrap token mints the keeper account), so the read fails loud
|
|
7
|
+
* rather than degrading: a symlink, a group/other-accessible mode, or an
|
|
8
|
+
* oversized file is refused, never returned. The deploy recipe places the
|
|
9
|
+
* token at `0600`, so the check ratifies the shape zap produces; a
|
|
10
|
+
* hand-placed `0644` file fails at boot instead of being read.
|
|
11
|
+
*
|
|
12
|
+
* The Node implementation lives here (`load_secure_file_node`, wired as
|
|
13
|
+
* `create_node_runtime().read_secure_file`); the Deno runtime implements the
|
|
14
|
+
* same contract over `Deno.open` in `runtime/deno.ts`, and the mock honors
|
|
15
|
+
* the mode/size checks over its in-memory map. All three refuse through the
|
|
16
|
+
* shared `assert_secure_mode` / `assert_secure_size` / `read_secure_bounded`
|
|
17
|
+
* helpers so the checks — and their operator-facing messages — can't drift
|
|
18
|
+
* between runtimes.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { constants } from 'node:fs';
|
|
23
|
+
import { open } from 'node:fs/promises';
|
|
24
|
+
import process from 'node:process';
|
|
25
|
+
/**
|
|
26
|
+
* Maximum size for secure files. Prevents DoS from unexpectedly large files;
|
|
27
|
+
* 4 KiB is generous for token/key material. Matches the Rust twin's
|
|
28
|
+
* `MAX_SECURE_FILE_SIZE`.
|
|
29
|
+
*/
|
|
30
|
+
export const MAX_SECURE_FILE_SIZE = 4096;
|
|
31
|
+
/**
|
|
32
|
+
* Whether POSIX file modes are meaningful on this platform. On Windows Node
|
|
33
|
+
* synthesizes modes (always group/other-readable), so enforcing the check
|
|
34
|
+
* would refuse every file; the Rust twin is Unix-only and never faces this.
|
|
35
|
+
* The mock runtime checks unconditionally — its modes are simulated.
|
|
36
|
+
*/
|
|
37
|
+
const modes_apply = process.platform !== 'win32';
|
|
38
|
+
/**
|
|
39
|
+
* Refuse any group/other-accessible mode (only `0600`/`0400` pass).
|
|
40
|
+
*
|
|
41
|
+
* Callers own the platform gating: the real runtimes skip the check where
|
|
42
|
+
* modes aren't meaningful (Node on Windows, a `null` Deno mode); the mock
|
|
43
|
+
* checks its simulated modes unconditionally.
|
|
44
|
+
*
|
|
45
|
+
* @throws Error naming the path, the offending mode, and the `chmod` fix
|
|
46
|
+
*/
|
|
47
|
+
export const assert_secure_mode = (path, mode) => {
|
|
48
|
+
if ((mode & 0o077) !== 0) {
|
|
49
|
+
throw new Error(`insecure permissions on ${path}: ${(mode & 0o777).toString(8)} (expected 0600) — fix with: chmod 600 ${path}`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Refuse a byte count over `MAX_SECURE_FILE_SIZE`.
|
|
54
|
+
*
|
|
55
|
+
* @throws Error naming the path, the size, and the cap
|
|
56
|
+
*/
|
|
57
|
+
export const assert_secure_size = (path, size) => {
|
|
58
|
+
if (size > MAX_SECURE_FILE_SIZE) {
|
|
59
|
+
throw new Error(`secure file too large: ${path} (${size} bytes, max ${MAX_SECURE_FILE_SIZE})`);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Drain `read_chunk` into a cap-bounded buffer.
|
|
64
|
+
*
|
|
65
|
+
* Reads up to cap + 1 bytes so a file growing between stat and read is still
|
|
66
|
+
* caught (the stat-time size check alone races). `read_chunk` fills the given
|
|
67
|
+
* target from the file's current position and returns the bytes read
|
|
68
|
+
* (`null`/`0` = EOF) — the seam that lets the Node and Deno handle APIs share
|
|
69
|
+
* one loop.
|
|
70
|
+
*
|
|
71
|
+
* @throws Error when more than `MAX_SECURE_FILE_SIZE` bytes arrive
|
|
72
|
+
*/
|
|
73
|
+
export const read_secure_bounded = async (path, read_chunk) => {
|
|
74
|
+
const buffer = new Uint8Array(MAX_SECURE_FILE_SIZE + 1);
|
|
75
|
+
let total = 0;
|
|
76
|
+
while (total < buffer.length) {
|
|
77
|
+
const n = await read_chunk(buffer.subarray(total));
|
|
78
|
+
if (n === null || n === 0)
|
|
79
|
+
break;
|
|
80
|
+
total += n;
|
|
81
|
+
}
|
|
82
|
+
assert_secure_size(path, total);
|
|
83
|
+
return buffer.subarray(0, total);
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Read a secret file with fail-loud checks (Node implementation).
|
|
87
|
+
*
|
|
88
|
+
* - `O_NOFOLLOW` atomically rejects symlinks during open (no TOCTOU window)
|
|
89
|
+
* - the permission check runs on the open descriptor, not the path, so the
|
|
90
|
+
* file can't be swapped between check and read — any group/other access
|
|
91
|
+
* (not `0600`/`0400`) is refused
|
|
92
|
+
* - a size cap bounds the read; the read itself is limited to cap + 1 so a
|
|
93
|
+
* file growing between stat and read is still caught
|
|
94
|
+
*
|
|
95
|
+
* @param path - path to the secret file
|
|
96
|
+
* @returns the file's bytes
|
|
97
|
+
* @throws Error on a missing file, symlink, permissive mode, oversized file, or I/O failure
|
|
98
|
+
*/
|
|
99
|
+
export const load_secure_file_node = async (path) => {
|
|
100
|
+
let handle;
|
|
101
|
+
try {
|
|
102
|
+
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
// ELOOP means the path was a symlink (O_NOFOLLOW refused it).
|
|
106
|
+
if (err.code === 'ELOOP') {
|
|
107
|
+
throw new Error(`secure file is a symlink: ${path}`);
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const s = await handle.stat();
|
|
113
|
+
if (modes_apply)
|
|
114
|
+
assert_secure_mode(path, s.mode);
|
|
115
|
+
assert_secure_size(path, s.size);
|
|
116
|
+
// `position: null` reads sequentially from the handle's own cursor.
|
|
117
|
+
return await read_secure_bounded(path, async (target) => {
|
|
118
|
+
const { bytesRead } = await handle.read(target, 0, target.length, null);
|
|
119
|
+
return bytesRead;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await handle.close();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
@@ -16,7 +16,6 @@ import { type AuditEmitter } from '../auth/audit_emitter.js';
|
|
|
16
16
|
import type { DbType, Db } from '../db/db.js';
|
|
17
17
|
import type { Keyring } from '../auth/keyring.js';
|
|
18
18
|
import type { PasswordHashDeps } from '../auth/password.js';
|
|
19
|
-
import type { StatResult } from '../runtime/deps.js';
|
|
20
19
|
import { type MigrationNamespace, type MigrationResult } from '../db/migrate.js';
|
|
21
20
|
/**
|
|
22
21
|
* Result of `create_app_backend()` — database metadata + deps bundle.
|
|
@@ -83,10 +82,12 @@ export declare const default_audit_factory: AuditFactory;
|
|
|
83
82
|
* (e.g., tx uses `runtime.exit(1)` on invalid keys).
|
|
84
83
|
*/
|
|
85
84
|
export interface CreateAppBackendOptions {
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Hardened secret-file read for the bootstrap token — pass the runtime's
|
|
87
|
+
* `read_secure_file` (see `FsSecureReadDeps`). Rejects symlinks,
|
|
88
|
+
* group/other-accessible modes, and oversized files.
|
|
89
|
+
*/
|
|
90
|
+
read_secure_file: (path: string) => Promise<Uint8Array>;
|
|
90
91
|
/** Delete a file. */
|
|
91
92
|
delete_file: (path: string) => Promise<void>;
|
|
92
93
|
/** Database connection URL (`postgres://`, `file://`, or `memory://`). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,
|
|
1
|
+
{"version":3,"file":"app_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAkB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIjG;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,oIAAoI;IACpI,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC3D,iEAAiE;IACjE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE;IAAE,EAAE,EAAE,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,KAAK,YAAY,CAAC;AAE7E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,EAAE,YACF,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACvC;;;;OAIG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,EAAE,YAAY,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACzD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAU,SAAS,uBAAuB,KAAG,OAAO,CAAC,UAAU,CAiD7F,CAAC"}
|
|
@@ -39,7 +39,7 @@ export const default_audit_factory = ({ db, log }) => create_audit_emitter({ db,
|
|
|
39
39
|
* @throws Error if `migration_namespaces` contains a namespace in `reserved_migration_namespaces`
|
|
40
40
|
*/
|
|
41
41
|
export const create_app_backend = async (options) => {
|
|
42
|
-
const { database_url, keyring, password,
|
|
42
|
+
const { database_url, keyring, password, read_secure_file, delete_file, audit_factory } = options;
|
|
43
43
|
const log = options.log ?? new Logger('server');
|
|
44
44
|
const { db, close, db_type, db_name } = await create_db(database_url);
|
|
45
45
|
// Everything after `create_db` can throw — reserved-namespace check,
|
|
@@ -69,8 +69,7 @@ export const create_app_backend = async (options) => {
|
|
|
69
69
|
keyring,
|
|
70
70
|
password,
|
|
71
71
|
db,
|
|
72
|
-
|
|
73
|
-
read_text_file,
|
|
72
|
+
read_secure_file,
|
|
74
73
|
delete_file,
|
|
75
74
|
log,
|
|
76
75
|
audit
|
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -32,7 +32,7 @@ time (never runtime), where a throwing guard would break `vite build`.
|
|
|
32
32
|
- `stub_handler()` — fresh `Response('stub')`.
|
|
33
33
|
- `stub_mw` — pass-through middleware (`async (_c, next) => next()`).
|
|
34
34
|
- `stub_app_deps` — frozen `AppDeps`, every capability throwing, `audit` a no-op `AuditEmitter` from `create_test_audit_emitter`.
|
|
35
|
-
- `create_stub_app_deps()` — factory: fresh `AppDeps` with no-op
|
|
35
|
+
- `create_stub_app_deps()` — factory: fresh `AppDeps` with no-op keyring/password/`delete_file`, a `read_secure_file` that throws ENOENT (the no-token-file state), a `create_noop_stub` DB, silent `Logger`, no-op `audit`.
|
|
36
36
|
- `create_test_audit_emitter()` — no-op `AuditEmitter`; `emit` / `emit_role_grant_target` no-op, `emit_pool` resolves immediately, `notify` no-op, `add_listener` throws (use `create_recording_audit_emitter` for a listener-accepting emitter), `listener_count` returns 0.
|
|
37
37
|
- `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `subscribe` returns a no-op cleanup; `on_audit_event` no-op; `registry` is a fresh `SubscriberRegistry` (live `.size` / `.close_*` for registry-state tests, isolated per call). For real SSE plumbing build via `create_audit_log_sse` against `create_test_app`.
|
|
38
38
|
- `create_stub_api_middleware({include_daemon_token?})` — stub `MiddlewareSpec[]` matching `create_auth_middleware_specs`'s output (origin/session/request_context/bearer_auth, optional daemon_token) for surface generation without booting real auth. See `auth/CLAUDE.md` §Middleware for the real stack.
|
|
@@ -1418,6 +1418,20 @@ module's own tests (`account_status.test.ts`, `action_rpc.test.ts`,
|
|
|
1418
1418
|
`serve_fact_route.db.test.ts`); fuz_app's own wiring is
|
|
1419
1419
|
`cross_backend/query_shape.cross.test.ts`.
|
|
1420
1420
|
|
|
1421
|
+
### API-token lifetime parity — `cross_backend/token_lifetime.ts`
|
|
1422
|
+
|
|
1423
|
+
`describe_token_lifetime_cross_tests({setup_test, rpc_path?})` — the
|
|
1424
|
+
imperative round-trip on `account_token_create`'s required `lifetime` union:
|
|
1425
|
+
a `{kind:'ttl', days}` mint returns and stores a bounded `expires_at`
|
|
1426
|
+
(create output + `account_token_list` read-back), `{kind:'eternal'}` stores
|
|
1427
|
+
NULL, and an omitted or out-of-range lifetime 400s identically on both
|
|
1428
|
+
spines. Exists because the action-manifest parity gate captures no param
|
|
1429
|
+
schemas while the Rust spine hand-parses params — a spine that silently
|
|
1430
|
+
dropped the field would answer the same mint with a 200 and an eternal
|
|
1431
|
+
token; only the stored-state read-back catches it. Ungated (mint + list are
|
|
1432
|
+
on every spine's standard surface). Runs both legs
|
|
1433
|
+
(`token_lifetime_parity.db.test.ts` + `token_lifetime.cross.test.ts`).
|
|
1434
|
+
|
|
1421
1435
|
### Readiness probe parity — `cross_backend/ready.ts`
|
|
1422
1436
|
|
|
1423
1437
|
`describe_ready_cross_tests({setup_test, capabilities, ready_path?})` — the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAW3E,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAiBD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,
|
|
1
|
+
{"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAW3E,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAiBD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IA23BF,CAAC"}
|
|
@@ -594,7 +594,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
594
594
|
app: { request: fixture.transport },
|
|
595
595
|
path: rpc_path,
|
|
596
596
|
spec: account_token_create_action_spec,
|
|
597
|
-
params: {
|
|
597
|
+
params: {
|
|
598
|
+
name: 'audit-test-token',
|
|
599
|
+
scope: { kind: 'full' },
|
|
600
|
+
lifetime: { kind: 'eternal' }
|
|
601
|
+
},
|
|
598
602
|
headers: fixture.create_session_headers()
|
|
599
603
|
});
|
|
600
604
|
assert.ok(token_res.ok, `account_token_create failed: ${token_res.ok ? '' : JSON.stringify(token_res.error)}`);
|
|
@@ -721,7 +725,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
721
725
|
app: { request: fixture.transport },
|
|
722
726
|
path: rpc_path,
|
|
723
727
|
spec: account_token_create_action_spec,
|
|
724
|
-
params: {
|
|
728
|
+
params: {
|
|
729
|
+
name: 'admin-b-token',
|
|
730
|
+
scope: { kind: 'full' },
|
|
731
|
+
lifetime: { kind: 'eternal' }
|
|
732
|
+
},
|
|
725
733
|
headers: create_headers(admin_b.session_cookie)
|
|
726
734
|
});
|
|
727
735
|
assert.ok(token_res.ok, `account_token_create failed: ${token_res.ok ? '' : JSON.stringify(token_res.error)}`);
|
|
@@ -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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"}
|
|
@@ -157,8 +157,9 @@ export const bootstrap_test_keeper = async (options) => {
|
|
|
157
157
|
/** Silent logger for tests — suppresses all output. */
|
|
158
158
|
const test_log = new Logger('test', { level: 'off' });
|
|
159
159
|
const default_test_fs_stubs = {
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
read_secure_file: async (path) => {
|
|
161
|
+
throw new Error(`ENOENT: no such file or directory: ${path}`);
|
|
162
|
+
},
|
|
162
163
|
delete_file: async () => { }
|
|
163
164
|
};
|
|
164
165
|
/**
|
|
@@ -375,14 +376,18 @@ export const create_test_app_for_bootstrap = async (options) => {
|
|
|
375
376
|
if (options.db) {
|
|
376
377
|
await options.db.query('UPDATE bootstrap_lock SET bootstrapped = false WHERE bootstrapped = true');
|
|
377
378
|
}
|
|
378
|
-
// Token-aware fs stubs: the bootstrap route's
|
|
379
|
-
//
|
|
379
|
+
// Token-aware fs stubs: the bootstrap route's secure read resolves against
|
|
380
|
+
// the configured token_path (both the boot-time availability probe and the
|
|
381
|
+
// request-time read go through it); a deleted or unknown path throws like
|
|
382
|
+
// the real runtimes' ENOENT.
|
|
380
383
|
let token_file_deleted = false;
|
|
381
384
|
const fs_stubs = {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
read_secure_file: async (path) => {
|
|
386
|
+
if (path === bootstrap.token_path && !token_file_deleted) {
|
|
387
|
+
return new TextEncoder().encode(bootstrap_token);
|
|
388
|
+
}
|
|
389
|
+
throw new Error(`ENOENT: no such file or directory: ${path}`);
|
|
390
|
+
},
|
|
386
391
|
delete_file: async (path) => {
|
|
387
392
|
if (path === bootstrap.token_path)
|
|
388
393
|
token_file_deleted = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AAgGD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,
|
|
1
|
+
{"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AAgGD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAinBzF,CAAC"}
|
|
@@ -169,7 +169,11 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
169
169
|
app: { request: fixture.transport },
|
|
170
170
|
path: rpc_path,
|
|
171
171
|
spec: account_token_create_action_spec,
|
|
172
|
-
params: {
|
|
172
|
+
params: {
|
|
173
|
+
name: 'audit-test',
|
|
174
|
+
scope: { kind: 'full' },
|
|
175
|
+
lifetime: { kind: 'eternal' }
|
|
176
|
+
},
|
|
173
177
|
headers: fixture.create_session_headers()
|
|
174
178
|
});
|
|
175
179
|
assert.ok(res.ok, `account_token_create failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_grant_role.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_grant_role.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_grant_role.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_grant_role.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmD9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,gBAAyB,CAAC;AAEvD,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAK5D,eAAO,MAAM,oCAAoC,GAAI,SAAS,wBAAwB,KAAG,IAsOxF,CAAC"}
|
|
@@ -34,7 +34,7 @@ import '../assert_dev_env.js';
|
|
|
34
34
|
* @module
|
|
35
35
|
*/
|
|
36
36
|
import { describe, assert } from 'vitest';
|
|
37
|
-
import { CellCreateOutput, CellGetOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
|
|
37
|
+
import { CellCreateOutput, CellGetOutput, CellListOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
|
|
38
38
|
import { CellGrantCreateOutput, ERROR_CELL_GRANT_UNKNOWN_ROLE } from '../../auth/cell_grant_action_specs.js';
|
|
39
39
|
import { test_if } from './capabilities.js';
|
|
40
40
|
import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
|
|
@@ -71,6 +71,39 @@ export const describe_cell_grant_role_cross_tests = (options) => {
|
|
|
71
71
|
assert.ok(!stranger_view.ok, 'non-holder must not see the cell');
|
|
72
72
|
assert.strictEqual(error_reason(stranger_view), 'cell_not_found');
|
|
73
73
|
});
|
|
74
|
+
test_if(capabilities.cell_relations, "cell_list shared_with:'me' admits through the role branch of the grant predicate", async () => {
|
|
75
|
+
// The role-admit arm of the shared-with predicate (the
|
|
76
|
+
// `caller_role_grants` branch of the CTE in the Rust
|
|
77
|
+
// `query_cell_list` / the TS list query) — pinned nowhere else:
|
|
78
|
+
// the actor-shaped `shared_with:'me'` case in `cell_relations.ts`
|
|
79
|
+
// runs the CTE but its role branch never matches, and the
|
|
80
|
+
// storage-half copy in `fuz_cell/tests` is a textually separate
|
|
81
|
+
// EXISTS the CTE can silently drift from. An ungranted control
|
|
82
|
+
// cell keeps the semi-join non-vacuous (dropping it must change
|
|
83
|
+
// the result), and a non-holder proves the branch keys on the
|
|
84
|
+
// role, not on authentication.
|
|
85
|
+
const fixture = await setup_test();
|
|
86
|
+
const t = fixture.transport;
|
|
87
|
+
const owner = await fixture.create_account({ username: 'cell_role_list_owner' });
|
|
88
|
+
const owner_h = owner.create_session_headers();
|
|
89
|
+
const holder = fixture.extra_accounts[CELL_ROLE_HOLDER_USERNAME];
|
|
90
|
+
assert.ok(holder, `fixture must seed the ${CELL_ROLE_HOLDER_USERNAME} extra account`);
|
|
91
|
+
const stranger = await fixture.create_account({ username: 'cell_role_list_stranger' });
|
|
92
|
+
const granted = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: { label: 'role-shared' } }, owner_h), CellCreateOutput);
|
|
93
|
+
// control: same owner, same visibility, no grant — must stay out
|
|
94
|
+
const ungranted = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: { label: 'unshared control' } }, owner_h), CellCreateOutput);
|
|
95
|
+
expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_create', {
|
|
96
|
+
cell_id: granted.cell.id,
|
|
97
|
+
level: 'viewer',
|
|
98
|
+
principal: { kind: 'role', role: CELL_EDITOR_ROLE }
|
|
99
|
+
}, owner_h), CellGrantCreateOutput);
|
|
100
|
+
const holder_list = expect_output(await cross_rpc_call(t, rpc_path, 'cell_list', { shared_with: 'me' }, holder.create_session_headers()), CellListOutput);
|
|
101
|
+
const holder_ids = holder_list.cells.map((c) => c.id);
|
|
102
|
+
assert.ok(holder_ids.includes(granted.cell.id), 'the role-granted cell must reach a holder of the role via shared_with:me');
|
|
103
|
+
assert.ok(!holder_ids.includes(ungranted.cell.id), 'the ungranted control cell must not — the semi-join is load-bearing');
|
|
104
|
+
const stranger_list = expect_output(await cross_rpc_call(t, rpc_path, 'cell_list', { shared_with: 'me' }, stranger.create_session_headers()), CellListOutput);
|
|
105
|
+
assert.ok(!stranger_list.cells.some((c) => c.id === granted.cell.id), 'a non-holder must not be admitted through the role branch');
|
|
106
|
+
});
|
|
74
107
|
test_if(capabilities.cell_relations, 'role-shaped grant for an unregistered role is rejected at create', async () => {
|
|
75
108
|
const fixture = await setup_test();
|
|
76
109
|
const t = fixture.transport;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8B9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAOnE,OAAO,EAMN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAEN,KAAK,eAAe,EAEpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAGzD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;
|
|
1
|
+
{"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8B9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAOnE,OAAO,EAMN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAEN,KAAK,eAAe,EAEpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAGzD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAqPD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC,MAAM,CAIrD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,MAAM,KACX,IAQF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EACvC,OAAO,MAAM,KACX,IAYF,CAAC;AAwKF;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,uBAAuB,KAAG,IAkEnF,CAAC"}
|
|
@@ -71,7 +71,8 @@ const mint_scoped_token = async (fixture, resolved_rpc_endpoints) => {
|
|
|
71
71
|
spec: account_token_create_action_spec,
|
|
72
72
|
params: {
|
|
73
73
|
name: 'conformance scoped token',
|
|
74
|
-
scope: { kind: 'methods', methods: [SCOPED_TOKEN_ADMITTED_METHOD] }
|
|
74
|
+
scope: { kind: 'methods', methods: [SCOPED_TOKEN_ADMITTED_METHOD] },
|
|
75
|
+
lifetime: { kind: 'eternal' }
|
|
75
76
|
},
|
|
76
77
|
headers: fixture.create_session_headers()
|
|
77
78
|
});
|
|
@@ -375,7 +375,7 @@ const mint_account = async (handle, options) => {
|
|
|
375
375
|
// point of the default-deny design. A minted-account fixture stands in for an
|
|
376
376
|
// ordinary account credential, so it takes full account authority; the
|
|
377
377
|
// narrowed credential is the conformance table's `scoped_token` principal.
|
|
378
|
-
const token_result = await rpc_via_transport(transport, handle.config.rpc_path, 'account_token_create', { scope: { kind: 'full' } }, handle.config.name);
|
|
378
|
+
const token_result = await rpc_via_transport(transport, handle.config.rpc_path, 'account_token_create', { scope: { kind: 'full' }, lifetime: { kind: 'eternal' } }, handle.config.name);
|
|
379
379
|
const token_parsed = TokenCreateResponseShape.safeParse(token_result);
|
|
380
380
|
if (!token_parsed.success) {
|
|
381
381
|
throw new Error(`account_token_create(${handle.config.name}) returned unexpected result: ${JSON.stringify(token_result)}`);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { SetupTest } from './setup.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the token-lifetime parity suite. Ungated — the mint + list pair
|
|
5
|
+
* is on every spine's standard surface, so there is no capability flag (the
|
|
6
|
+
* sibling of `identity_parity.ts` rather than the cell suites).
|
|
7
|
+
*/
|
|
8
|
+
export interface TokenLifetimeCrossTestOptions {
|
|
9
|
+
/** Per-test fixture-producing function (fresh keeper + db per call). */
|
|
10
|
+
readonly setup_test: SetupTest;
|
|
11
|
+
/** RPC endpoint path the methods are mounted on. Default `/api/rpc`. */
|
|
12
|
+
readonly rpc_path?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const describe_token_lifetime_cross_tests: (options: TokenLifetimeCrossTestOptions) => void;
|
|
15
|
+
//# sourceMappingURL=token_lifetime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token_lifetime.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/token_lifetime.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA+C9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC7C,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAaD,eAAO,MAAM,mCAAmC,GAC/C,SAAS,6BAA6B,KACpC,IAiHF,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-backend parity suite for the **API token lifetime** axis of
|
|
4
|
+
* `account_token_create` — the round-trip that keeps a one-sided landing
|
|
5
|
+
* impossible.
|
|
6
|
+
*
|
|
7
|
+
* ## Why this suite exists
|
|
8
|
+
*
|
|
9
|
+
* The action-manifest parity gate captures `{method, side_effects, auth}` and
|
|
10
|
+
* **no param schemas**, while the TS spine validates params against a Zod
|
|
11
|
+
* `strictObject` and the Rust spine hand-parses with `params.get(...)`. So a
|
|
12
|
+
* spine that silently ignored the `lifetime` field would answer the same mint
|
|
13
|
+
* request with a **200 and an eternal token** — same method, same status,
|
|
14
|
+
* nothing red anywhere. The forcing function has to observe the *stored*
|
|
15
|
+
* state: mint with a TTL, then read `expires_at` back off the wire.
|
|
16
|
+
*
|
|
17
|
+
* ## What it pins
|
|
18
|
+
*
|
|
19
|
+
* - **A TTL mint stores a bounded expiry** — `account_token_create` with
|
|
20
|
+
* `{kind:'ttl', days}` returns a non-null `expires_at` (create output *and*
|
|
21
|
+
* `account_token_list`), landing within a generous window around
|
|
22
|
+
* `now + days`. A spine that dropped the field returns `null` and fails.
|
|
23
|
+
* - **An eternal mint stores NULL** — the control: `{kind:'eternal'}` yields
|
|
24
|
+
* `expires_at: null` on both reads.
|
|
25
|
+
* - **An omitted lifetime is refused** — `lifetime` is required; a mint
|
|
26
|
+
* without it must 400 `invalid_params` on both spines. This is the direct
|
|
27
|
+
* pin on the silently-ignoring-unknown-shapes hazard.
|
|
28
|
+
* - **Out-of-range days are refused** — `days: 0` 400s identically, so the
|
|
29
|
+
* two hand-rolled bounds checks can't drift.
|
|
30
|
+
*
|
|
31
|
+
* `$lib`-free by contract (relative specifiers only), like the sibling
|
|
32
|
+
* cross-backend suites. Runs both legs — in-process (`gro test`) and
|
|
33
|
+
* cross-process — through the shared `{setup_test}` protocol.
|
|
34
|
+
*
|
|
35
|
+
* @module
|
|
36
|
+
*/
|
|
37
|
+
import { describe, test, assert } from 'vitest';
|
|
38
|
+
import { account_token_create_action_spec, account_token_list_action_spec, TokenCreateOutput, TokenListOutput } from '../../auth/account_action_specs.js';
|
|
39
|
+
import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
|
|
40
|
+
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
41
|
+
/** TTL used by the bounded-mint case. */
|
|
42
|
+
const TTL_DAYS = 30;
|
|
43
|
+
/**
|
|
44
|
+
* Window slack around `now + days` for the expiry assertion — generous (1h)
|
|
45
|
+
* because the TS spine computes the expiry from the handler clock while the
|
|
46
|
+
* Rust spine binds a `SystemTime` computed at dispatch; only gross divergence
|
|
47
|
+
* (wrong unit, wrong sign, silently-dropped field) should fail.
|
|
48
|
+
*/
|
|
49
|
+
const EXPIRY_SLACK_MS = 60 * 60 * 1000;
|
|
50
|
+
export const describe_token_lifetime_cross_tests = (options) => {
|
|
51
|
+
const { setup_test } = options;
|
|
52
|
+
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
53
|
+
describe('api token lifetime parity', () => {
|
|
54
|
+
test('a ttl mint stores a bounded expiry (create output + list round-trip)', async () => {
|
|
55
|
+
const fixture = await setup_test();
|
|
56
|
+
const before = Date.now();
|
|
57
|
+
const create_res = await cross_rpc_call(fixture.transport, rpc_path, account_token_create_action_spec.method, {
|
|
58
|
+
name: 'ttl-token',
|
|
59
|
+
scope: { kind: 'full' },
|
|
60
|
+
lifetime: { kind: 'ttl', days: TTL_DAYS }
|
|
61
|
+
}, fixture.create_session_headers());
|
|
62
|
+
const created = expect_output(create_res, TokenCreateOutput);
|
|
63
|
+
assert.ok(created.expires_at !== null, 'a ttl mint must return a non-null expires_at — null means the spine dropped the field');
|
|
64
|
+
const expires_ms = Date.parse(created.expires_at);
|
|
65
|
+
assert.ok(!Number.isNaN(expires_ms), `expires_at must parse as a date: ${created.expires_at}`);
|
|
66
|
+
const expected = before + TTL_DAYS * 86_400_000;
|
|
67
|
+
assert.ok(Math.abs(expires_ms - expected) < EXPIRY_SLACK_MS, `expires_at must land near now + ${TTL_DAYS}d (got ${created.expires_at})`);
|
|
68
|
+
// The read-back — the half a silently-ignoring spine can't fake.
|
|
69
|
+
const list_res = await cross_rpc_call(fixture.transport, rpc_path, account_token_list_action_spec.method, undefined, fixture.create_session_headers());
|
|
70
|
+
const listed = expect_output(list_res, TokenListOutput);
|
|
71
|
+
const row = listed.tokens.find((t) => t.id === created.id);
|
|
72
|
+
assert.ok(row, 'the minted token must appear in account_token_list');
|
|
73
|
+
assert.ok(row.expires_at !== null, 'account_token_list must report the stored expiry, not null');
|
|
74
|
+
});
|
|
75
|
+
test('an eternal mint stores NULL expiry', async () => {
|
|
76
|
+
const fixture = await setup_test();
|
|
77
|
+
const create_res = await cross_rpc_call(fixture.transport, rpc_path, account_token_create_action_spec.method, { name: 'eternal-token', scope: { kind: 'full' }, lifetime: { kind: 'eternal' } }, fixture.create_session_headers());
|
|
78
|
+
const created = expect_output(create_res, TokenCreateOutput);
|
|
79
|
+
assert.strictEqual(created.expires_at, null, 'an eternal mint must return expires_at: null');
|
|
80
|
+
const list_res = await cross_rpc_call(fixture.transport, rpc_path, account_token_list_action_spec.method, undefined, fixture.create_session_headers());
|
|
81
|
+
const listed = expect_output(list_res, TokenListOutput);
|
|
82
|
+
const row = listed.tokens.find((t) => t.id === created.id);
|
|
83
|
+
assert.ok(row, 'the minted token must appear in account_token_list');
|
|
84
|
+
assert.strictEqual(row.expires_at, null, 'the stored token must be eternal');
|
|
85
|
+
});
|
|
86
|
+
test('an omitted lifetime is refused with invalid_params on every spine', async () => {
|
|
87
|
+
const fixture = await setup_test();
|
|
88
|
+
const res = await cross_rpc_call(fixture.transport, rpc_path, account_token_create_action_spec.method, { name: 'no-lifetime', scope: { kind: 'full' } }, fixture.create_session_headers());
|
|
89
|
+
assert.ok(!res.ok, 'a mint without lifetime must be refused, never defaulted');
|
|
90
|
+
assert.strictEqual(res.error?.code, -32602, `expected invalid_params, got ${JSON.stringify(res.error)} (reason: ${String(error_reason(res))})`);
|
|
91
|
+
});
|
|
92
|
+
test('out-of-range ttl days are refused identically', async () => {
|
|
93
|
+
const fixture = await setup_test();
|
|
94
|
+
const res = await cross_rpc_call(fixture.transport, rpc_path, account_token_create_action_spec.method, { name: 'zero-days', scope: { kind: 'full' }, lifetime: { kind: 'ttl', days: 0 } }, fixture.create_session_headers());
|
|
95
|
+
assert.ok(!res.ok, 'days: 0 must be refused');
|
|
96
|
+
assert.strictEqual(res.error?.code, -32602, `expected invalid_params: ${JSON.stringify(res.error)}`);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
};
|
|
@@ -87,7 +87,7 @@ const surface_denial_body = (surface) => ({
|
|
|
87
87
|
* narrowed token on an account that lacks the gating role.
|
|
88
88
|
*/
|
|
89
89
|
const mint_narrowed_token = async (transport, rpc_path, session_headers) => {
|
|
90
|
-
const res = await cross_rpc_call(transport, rpc_path, account_token_create_action_spec.method, { scope: { kind: 'methods', methods: [ADMITTED_METHOD] } }, session_headers);
|
|
90
|
+
const res = await cross_rpc_call(transport, rpc_path, account_token_create_action_spec.method, { scope: { kind: 'methods', methods: [ADMITTED_METHOD] }, lifetime: { kind: 'eternal' } }, session_headers);
|
|
91
91
|
assert.ok(res.ok, `minting a narrowed token failed: ${JSON.stringify(res.error)}`);
|
|
92
92
|
const token = res.result?.token;
|
|
93
93
|
assert.ok(typeof token === 'string' && token.length > 0, 'mint returned no token');
|