@fuzdev/fuz_app 0.108.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/README.md +4 -0
- package/dist/actions/CLAUDE.md +1 -1
- package/dist/auth/CLAUDE.md +7 -6
- 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/audit_emitter.d.ts +9 -5
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.d.ts +7 -1
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +16 -1
- 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 +26 -7
- 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/migrations.d.ts +7 -0
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +25 -8
- 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/db/CLAUDE.md +9 -5
- package/dist/db/cell_audit_queries.d.ts +4 -2
- package/dist/db/cell_audit_queries.d.ts.map +1 -1
- package/dist/db/cell_audit_queries.js +4 -2
- package/dist/db/pg_error.d.ts +12 -0
- package/dist/db/pg_error.d.ts.map +1 -1
- package/dist/db/pg_error.js +15 -1
- 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/http/CLAUDE.md +78 -18
- package/dist/http/db_routes.d.ts +96 -3
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +263 -46
- package/dist/http/error_schemas.d.ts +12 -1
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -1
- package/dist/http/pending_effects.d.ts +5 -5
- package/dist/http/pending_effects.d.ts.map +1 -1
- package/dist/http/pending_effects.js +1 -1
- 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 +36 -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 +10 -2
- 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/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +53 -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/query_shape.d.ts +10 -0
- package/dist/testing/cross_backend/query_shape.d.ts.map +1 -0
- package/dist/testing/cross_backend/query_shape.js +128 -0
- 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/dist/ui/CLAUDE.md +9 -4
- package/dist/ui/table_state.svelte.d.ts +18 -5
- package/dist/ui/table_state.svelte.d.ts.map +1 -1
- package/dist/ui/table_state.svelte.js +22 -7
- package/dist/ui/ui_format.d.ts.map +1 -1
- package/dist/ui/ui_format.js +4 -0
- package/package.json +1 -1
|
@@ -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)}`);
|
|
@@ -561,7 +565,11 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
561
565
|
'role_grant_offer_decline',
|
|
562
566
|
'role_grant_offer_retract',
|
|
563
567
|
'role_grant_offer_expire',
|
|
564
|
-
'role_grant_offer_supersede'
|
|
568
|
+
'role_grant_offer_supersede',
|
|
569
|
+
// `db_admin_row_delete` fires from the generic db-admin browser
|
|
570
|
+
// (`http/db_routes.ts`), which the standard test app doesn't mount —
|
|
571
|
+
// covered in `db_routes.db.test.ts`.
|
|
572
|
+
'db_admin_row_delete'
|
|
565
573
|
]);
|
|
566
574
|
test('all audit event types are covered or explicitly excluded', () => {
|
|
567
575
|
const all_covered = new Set([...COVERED_EVENT_TYPES, ...EXCLUDED_EVENT_TYPES]);
|
|
@@ -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":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4F9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAsF3D,eAAO,MAAM,mCAAmC,GAAI,SAAS,wBAAwB,KAAG,IAi2BvF,CAAC"}
|
|
@@ -27,6 +27,12 @@ import '../assert_dev_env.js';
|
|
|
27
27
|
* - **clone** — shallow copies item / field *edges* (shared `child_id` /
|
|
28
28
|
* `target_id`); deep clones each viewable child into a fresh cell at the
|
|
29
29
|
* same position. Both null `path` and stamp the caller as owner.
|
|
30
|
+
* - **`shared_with: 'me'`** — the grant-driven `cell_list` branch: a grantee
|
|
31
|
+
* lists exactly the cells that admit them via `cell_grant`, their own cells
|
|
32
|
+
* excluded, with the `cell_grants` enrichment keyed by cell id. Plus the two
|
|
33
|
+
* guards — null-auth is `cell_list_shared_with_requires_auth`, and any value
|
|
34
|
+
* other than `'me'` is `invalid_params` on both spines. The cross twin of
|
|
35
|
+
* the in-process `auth/cell_actions.list.db.test.ts` §"shared_with: me".
|
|
30
36
|
* - **audit** — `cell_audit_list` is manage-tier: the owner reads the cell's
|
|
31
37
|
* timeline; a viewer-grant holder who can `cell_get` the cell still gets the
|
|
32
38
|
* IDOR 404 on the timeline (D14).
|
|
@@ -53,7 +59,7 @@ import '../assert_dev_env.js';
|
|
|
53
59
|
*/
|
|
54
60
|
import { describe, assert } from 'vitest';
|
|
55
61
|
import { fractional_index_between } from '@fuzdev/fuz_util/fractional_index.ts';
|
|
56
|
-
import { CellCreateOutput, CellUpdateOutput, CellCloneOutput, CellGetOutput } from '../../auth/cell_action_specs.js';
|
|
62
|
+
import { CellCreateOutput, CellUpdateOutput, CellCloneOutput, CellGetOutput, CellListOutput } from '../../auth/cell_action_specs.js';
|
|
57
63
|
import { AuditLogListOutput } from '../../auth/admin_action_specs.js';
|
|
58
64
|
import { CellGrantCreateOutput, CellGrantListOutput, CellGrantRevokeOutput } from '../../auth/cell_grant_action_specs.js';
|
|
59
65
|
import { CellFieldDeleteOutput, CellFieldListOutput, CellFieldSetOutput } from '../../auth/cell_field_action_specs.js';
|
|
@@ -131,6 +137,52 @@ export const describe_cell_relations_cross_tests = (options) => {
|
|
|
131
137
|
assert.ok(!post.ok, 'revoked editor still edited');
|
|
132
138
|
assert.strictEqual(error_reason(post), 'cell_not_found');
|
|
133
139
|
});
|
|
140
|
+
test_if(capabilities.cell_relations, "cell_list shared_with: 'me' lists grant-admitted cells, excludes owned, enriches cell_grants", async () => {
|
|
141
|
+
const fixture = await setup_test();
|
|
142
|
+
const owner = await fixture.create_account({ username: 'cell_shared_owner' });
|
|
143
|
+
const viewer = await fixture.create_account({ username: 'cell_shared_viewer' });
|
|
144
|
+
const t = fixture.fresh_transport();
|
|
145
|
+
const owner_h = owner.create_session_headers();
|
|
146
|
+
const viewer_h = viewer.create_session_headers();
|
|
147
|
+
const shared = await create_cell(t, rpc_path, owner_h, { kind: 'note', data: {} });
|
|
148
|
+
// A cell the viewer owns — owner ≠ sharee, so it must NOT appear.
|
|
149
|
+
await create_cell(t, rpc_path, viewer_h, { kind: 'note', data: {} });
|
|
150
|
+
// The grant-admit control: owner-owned but NOT granted to the viewer.
|
|
151
|
+
// Without it the semi-join is untested — the shared-with SQL's two
|
|
152
|
+
// conjuncts (created_by IS DISTINCT FROM caller AND id IN grant-set)
|
|
153
|
+
// would select the same set, so dropping the grant clause changes
|
|
154
|
+
// nothing. This ungranted cell is exactly what dropping it would leak.
|
|
155
|
+
await create_cell(t, rpc_path, owner_h, { kind: 'note', data: {} });
|
|
156
|
+
const grant = expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_create', {
|
|
157
|
+
cell_id: shared,
|
|
158
|
+
level: 'viewer',
|
|
159
|
+
principal: { kind: 'actor', actor_id: viewer.actor.id }
|
|
160
|
+
}, owner_h), CellGrantCreateOutput).grant;
|
|
161
|
+
const listed = expect_output(await cross_rpc_call(t, rpc_path, 'cell_list', { shared_with: 'me' }, viewer_h), CellListOutput);
|
|
162
|
+
assert.deepStrictEqual(listed.cells.map((c) => c.id), [shared], 'shared_with list is not exactly the grant-admitted cell');
|
|
163
|
+
// The enrichment: the caller's own admit grants, keyed by cell id.
|
|
164
|
+
assert.ok(listed.cell_grants, 'shared_with list omitted the cell_grants enrichment');
|
|
165
|
+
const grants = listed.cell_grants[shared];
|
|
166
|
+
assert.ok(grants, 'cell_grants missing the shared cell key');
|
|
167
|
+
assert.strictEqual(grants.length, 1);
|
|
168
|
+
assert.strictEqual(grants[0].id, grant.id);
|
|
169
|
+
});
|
|
170
|
+
test_if(capabilities.cell_relations, 'null-auth cell_list shared_with → cell_list_shared_with_requires_auth', async () => {
|
|
171
|
+
const fixture = await setup_test();
|
|
172
|
+
const anon = fixture.fresh_transport({ origin: null });
|
|
173
|
+
const bad = await cross_rpc_call(anon, rpc_path, 'cell_list', { shared_with: 'me' }, {});
|
|
174
|
+
assert.ok(!bad.ok, 'anon shared_with filter accepted');
|
|
175
|
+
assert.strictEqual(error_reason(bad), 'cell_list_shared_with_requires_auth');
|
|
176
|
+
});
|
|
177
|
+
test_if(capabilities.cell_relations, "cell_list shared_with other than 'me' → invalid_params", async () => {
|
|
178
|
+
const fixture = await setup_test();
|
|
179
|
+
// TS rejects at the schema (`z.literal('me')`), Rust in the handler
|
|
180
|
+
// guard — both must land on -32602, so a caller can't widen the
|
|
181
|
+
// grant-driven branch to another actor on either spine.
|
|
182
|
+
const bad = await cross_rpc_call(fixture.fresh_transport(), rpc_path, 'cell_list', { shared_with: 'not-me' }, fixture.create_session_headers());
|
|
183
|
+
assert.ok(!bad.ok, "cell_list accepted a shared_with other than 'me'");
|
|
184
|
+
assert.strictEqual(bad.error?.code, -32602);
|
|
185
|
+
});
|
|
134
186
|
test_if(capabilities.cell_relations, 'editor-grant holder cannot flip visibility → cell_visibility_manage_only', async () => {
|
|
135
187
|
const fixture = await setup_test();
|
|
136
188
|
const owner = await fixture.create_account({ username: 'cell_vis_owner' });
|
|
@@ -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
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the query-shape parity suite. The standard RPC-dispatched
|
|
5
|
+
* cross-suite shape (`setup_test` / `capabilities` / `rpc_path`); aliases the
|
|
6
|
+
* shared `RpcPathCrossSuiteOptions` rather than minting a duplicate.
|
|
7
|
+
*/
|
|
8
|
+
export type QueryShapeCrossTestOptions = RpcPathCrossSuiteOptions;
|
|
9
|
+
export declare const describe_query_shape_cross_tests: (options: QueryShapeCrossTestOptions) => void;
|
|
10
|
+
//# sourceMappingURL=query_shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/query_shape.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0C9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;GAIG;AACH,MAAM,MAAM,0BAA0B,GAAG,wBAAwB,CAAC;AAQlE,eAAO,MAAM,gCAAgC,GAAI,SAAS,0BAA0B,KAAG,IAwHtF,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-backend query-string shape parity suite.
|
|
4
|
+
*
|
|
5
|
+
* The TS pipeline reads queries through Hono's `c.req.query()` — a duplicated
|
|
6
|
+
* key resolves to its **first** occurrence — and only routes that declare a
|
|
7
|
+
* `z.strictObject` query schema 400 unknown keys, in the pipeline's
|
|
8
|
+
* params → query → 401 → authz → 403 phase order. A derived-serde axum
|
|
9
|
+
* extractor diverges on every axis (a plain-text pre-handler "duplicate
|
|
10
|
+
* field" 400 observable anonymously, silently ignored unknown keys, query
|
|
11
|
+
* rejection ahead of the 401), so each spine surface that reads a query is
|
|
12
|
+
* pinned here on both backends:
|
|
13
|
+
*
|
|
14
|
+
* - **`GET /api/account/status`** — NO query schema: a duplicated `acting`
|
|
15
|
+
* reads first-wins (the persona resolves), unknown keys are ignored, and an
|
|
16
|
+
* anonymous caller with junk query hears 401, never a query 400.
|
|
17
|
+
* - **the JSON-RPC GET endpoint** — `method` / `id` / `params` read
|
|
18
|
+
* first-wins; a duplicated `method` must dispatch the first one on every
|
|
19
|
+
* backend (a last-wins reader dispatches a different method per backend on
|
|
20
|
+
* the same request).
|
|
21
|
+
* - **`GET /api/facts/:hash`** (gated on `capabilities.fact_serving`) —
|
|
22
|
+
* strict query schema: unknown keys and a malformed `acting` are 400
|
|
23
|
+
* `invalid_query_params` even for an anonymous caller (query precedes the
|
|
24
|
+
* 401 guard), while a duplicated `acting` takes the first occurrence.
|
|
25
|
+
*
|
|
26
|
+
* The per-surface in-process TS pins live beside each module's own tests
|
|
27
|
+
* (`account_status.test.ts`, `action_rpc.test.ts`,
|
|
28
|
+
* `serve_fact_route.db.test.ts`); this suite is the cross-impl gate, so it is
|
|
29
|
+
* cross-process only — like `login_security.ts` and `testing_backdoor.ts`.
|
|
30
|
+
*
|
|
31
|
+
* `$lib`-free by contract (relative specifiers only), like the sibling
|
|
32
|
+
* cross-backend suites.
|
|
33
|
+
*
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
import { describe, test, assert } from 'vitest';
|
|
37
|
+
import { account_verify_action_spec } from '../../auth/account_action_specs.js';
|
|
38
|
+
import { ERROR_INVALID_QUERY_PARAMS } from '../../http/error_schemas.js';
|
|
39
|
+
import { test_if } from './capabilities.js';
|
|
40
|
+
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
41
|
+
/** The status route every spine serves (bundled into the account family). */
|
|
42
|
+
const STATUS_PATH = '/api/account/status';
|
|
43
|
+
/** A well-formed blake3 hash that references no stored fact. */
|
|
44
|
+
const MISSING_HASH = `blake3:${'a'.repeat(64)}`;
|
|
45
|
+
export const describe_query_shape_cross_tests = (options) => {
|
|
46
|
+
const { setup_test, capabilities } = options;
|
|
47
|
+
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
48
|
+
describe('query-shape parity (duplicate keys, unknown keys, phase order)', () => {
|
|
49
|
+
test_if(capabilities.account_status, 'status: duplicated acting takes the first occurrence (persona resolves)', async () => {
|
|
50
|
+
const fixture = await setup_test();
|
|
51
|
+
// First occurrence is the keeper's real actor; the second is a
|
|
52
|
+
// mismatching-but-well-formed uuid. First-wins resolves the real
|
|
53
|
+
// persona; last-wins would fall through to `actor: null`, and a
|
|
54
|
+
// derived-serde extractor would 400 before the handler ran.
|
|
55
|
+
const nil = '00000000-0000-0000-0000-000000000000';
|
|
56
|
+
const res = await fixture.transport(`${STATUS_PATH}?acting=${fixture.actor.id}&acting=${nil}`, { method: 'GET', headers: fixture.create_session_headers() });
|
|
57
|
+
assert.strictEqual(res.status, 200);
|
|
58
|
+
const body = (await res.json());
|
|
59
|
+
assert.strictEqual(body.actor?.id, fixture.actor.id, 'first acting occurrence must win');
|
|
60
|
+
});
|
|
61
|
+
test_if(capabilities.account_status, 'status: anonymous with duplicated/unknown query keys → 401, never a query 400', async () => {
|
|
62
|
+
const fixture = await setup_test();
|
|
63
|
+
const anon = fixture.fresh_transport();
|
|
64
|
+
// The status route has no query schema — an anonymous prober with a
|
|
65
|
+
// junk query must hear the same 401 as a clean one (a pre-handler
|
|
66
|
+
// query reject here is a backend fingerprint).
|
|
67
|
+
const dup = await anon(`${STATUS_PATH}?acting=a&acting=b`, { method: 'GET' });
|
|
68
|
+
assert.strictEqual(dup.status, 401);
|
|
69
|
+
const unknown = await anon(`${STATUS_PATH}?foo=1&bar=2`, { method: 'GET' });
|
|
70
|
+
assert.strictEqual(unknown.status, 401);
|
|
71
|
+
});
|
|
72
|
+
test_if(capabilities.account_status, 'status: unknown query keys are ignored for an authed caller (no strict schema)', async () => {
|
|
73
|
+
const fixture = await setup_test();
|
|
74
|
+
const res = await fixture.transport(`${STATUS_PATH}?foo=1`, {
|
|
75
|
+
method: 'GET',
|
|
76
|
+
headers: fixture.create_session_headers()
|
|
77
|
+
});
|
|
78
|
+
assert.strictEqual(res.status, 200);
|
|
79
|
+
});
|
|
80
|
+
test('rpc GET: duplicated method and id keys take the first occurrence', async () => {
|
|
81
|
+
const fixture = await setup_test();
|
|
82
|
+
const method = account_verify_action_spec.method;
|
|
83
|
+
// First-wins dispatches `account_verify` with id 7; a last-wins
|
|
84
|
+
// reader dispatches the missing method (method_not_found) with id 8.
|
|
85
|
+
const res = await fixture.transport(`${rpc_path}?method=${method}&method=definitely_missing_method&id=7&id=8`, { method: 'GET', headers: fixture.create_session_headers() });
|
|
86
|
+
assert.strictEqual(res.status, 200);
|
|
87
|
+
const body = (await res.json());
|
|
88
|
+
assert.strictEqual(body.id, 7, 'first id occurrence must win');
|
|
89
|
+
assert.ok(body.result !== undefined, 'first method occurrence must dispatch');
|
|
90
|
+
});
|
|
91
|
+
test_if(capabilities.fact_serving, 'bare-hash facts: unknown query key → 400 invalid_query_params, even anonymous', async () => {
|
|
92
|
+
const fixture = await setup_test();
|
|
93
|
+
// Authed admin (the keeper): the strict query schema refuses the
|
|
94
|
+
// unknown key before the handler can 404 the missing fact.
|
|
95
|
+
const as_admin = await fixture.transport(`/api/facts/${MISSING_HASH}?foo=1`, {
|
|
96
|
+
method: 'GET',
|
|
97
|
+
headers: fixture.create_session_headers()
|
|
98
|
+
});
|
|
99
|
+
assert.strictEqual(as_admin.status, 400);
|
|
100
|
+
const body = (await as_admin.json());
|
|
101
|
+
assert.strictEqual(body.error, ERROR_INVALID_QUERY_PARAMS);
|
|
102
|
+
// Anonymous: query validation precedes the 401 guard (phase
|
|
103
|
+
// order), with the clean anonymous request as the 401 control.
|
|
104
|
+
const anon = fixture.fresh_transport();
|
|
105
|
+
const anon_bad = await anon(`/api/facts/${MISSING_HASH}?foo=1`, { method: 'GET' });
|
|
106
|
+
assert.strictEqual(anon_bad.status, 400);
|
|
107
|
+
const anon_clean = await anon(`/api/facts/${MISSING_HASH}`, { method: 'GET' });
|
|
108
|
+
assert.strictEqual(anon_clean.status, 401);
|
|
109
|
+
});
|
|
110
|
+
test_if(capabilities.fact_serving, 'bare-hash facts: duplicated acting takes the first occurrence; malformed acting → 400', async () => {
|
|
111
|
+
const fixture = await setup_test();
|
|
112
|
+
// First occurrence is the keeper's real actor; the second is
|
|
113
|
+
// garbage. First-wins passes the query phase and reaches the
|
|
114
|
+
// handler's masked 404 (the hash references nothing) — a
|
|
115
|
+
// derived-serde extractor would 400 the duplication instead.
|
|
116
|
+
const dup = await fixture.transport(`/api/facts/${MISSING_HASH}?acting=${fixture.actor.id}&acting=not-a-uuid`, { method: 'GET', headers: fixture.create_session_headers() });
|
|
117
|
+
assert.strictEqual(dup.status, 404, 'duplicated acting must resolve first-wins');
|
|
118
|
+
// A malformed single acting is refused at the query phase.
|
|
119
|
+
const bad = await fixture.transport(`/api/facts/${MISSING_HASH}?acting=not-a-uuid`, {
|
|
120
|
+
method: 'GET',
|
|
121
|
+
headers: fixture.create_session_headers()
|
|
122
|
+
});
|
|
123
|
+
assert.strictEqual(bad.status, 400);
|
|
124
|
+
const body = (await bad.json());
|
|
125
|
+
assert.strictEqual(body.error, ERROR_INVALID_QUERY_PARAMS);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
};
|
|
@@ -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');
|