@fuzdev/fuz_app 0.63.0 → 0.65.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/actions/CLAUDE.md +525 -827
- package/dist/actions/broadcast_api.d.ts +1 -1
- package/dist/actions/broadcast_api.js +1 -1
- package/dist/actions/cancel.d.ts +2 -2
- package/dist/actions/cancel.js +3 -3
- package/dist/actions/connection_closer.d.ts +65 -0
- package/dist/actions/connection_closer.d.ts.map +1 -0
- package/dist/actions/connection_closer.js +38 -0
- package/dist/actions/register_action_ws.d.ts +2 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +23 -2
- package/dist/actions/register_ws_endpoint.d.ts +12 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +5 -5
- package/dist/actions/transports_ws_auth_guard.d.ts +25 -10
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +24 -9
- package/dist/actions/ws_endpoint_spec.d.ts +119 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
- package/dist/actions/ws_endpoint_spec.js +13 -0
- package/dist/auth/CLAUDE.md +592 -1808
- package/dist/auth/account_action_specs.d.ts +1 -1
- package/dist/auth/account_actions.d.ts +13 -0
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +31 -1
- package/dist/auth/account_routes.d.ts +12 -2
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +55 -8
- package/dist/auth/account_schema.d.ts +4 -4
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.d.ts +8 -8
- package/dist/auth/admin_actions.d.ts +11 -0
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +25 -0
- package/dist/auth/api_token_queries.js +1 -1
- package/dist/auth/audit_emitter.d.ts +56 -12
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +38 -12
- package/dist/auth/audit_log_ddl.d.ts +1 -1
- package/dist/auth/audit_log_ddl.d.ts.map +1 -1
- package/dist/auth/audit_log_ddl.js +1 -1
- package/dist/auth/audit_log_schema.d.ts +5 -3
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +5 -3
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +1 -5
- package/dist/auth/bootstrap_routes.d.ts +8 -2
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +15 -11
- package/dist/auth/invite_schema.d.ts +2 -2
- package/dist/auth/keyring.d.ts +6 -6
- package/dist/auth/keyring.js +8 -8
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +4 -2
- package/dist/auth/signup_routes.d.ts +1 -1
- package/dist/auth/standard_rpc_actions.d.ts +1 -0
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +1 -0
- package/dist/db/create_db.d.ts.map +1 -1
- package/dist/db/create_db.js +13 -0
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.js +3 -3
- package/dist/http/CLAUDE.md +225 -483
- package/dist/http/error_schemas.d.ts +0 -4
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +0 -4
- package/dist/http/ip_canonical.d.ts +100 -0
- package/dist/http/ip_canonical.d.ts.map +1 -0
- package/dist/http/ip_canonical.js +195 -0
- package/dist/http/origin.d.ts +14 -6
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +14 -32
- package/dist/http/pending_effects.d.ts +1 -1
- package/dist/http/pending_effects.js +1 -1
- package/dist/http/proxy.d.ts +13 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +15 -23
- package/dist/http/surface.d.ts +50 -0
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +27 -1
- package/dist/primitive_schemas.d.ts +20 -4
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +25 -4
- package/dist/realtime/sse_auth_guard.d.ts +16 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +15 -3
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_backend.d.ts +66 -19
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +57 -34
- package/dist/server/app_server.d.ts +101 -10
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +105 -6
- package/dist/server/env.d.ts +7 -7
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +14 -14
- package/dist/server/startup.d.ts.map +1 -1
- package/dist/server/startup.js +12 -0
- package/dist/server/static.d.ts +4 -4
- package/dist/server/static.js +7 -7
- package/dist/testing/CLAUDE.md +269 -59
- package/dist/testing/admin_integration.d.ts +18 -23
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +159 -202
- package/dist/testing/adversarial_headers.d.ts +6 -0
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -5
- package/dist/testing/app_server.d.ts +148 -60
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +143 -54
- package/dist/testing/attack_surface.d.ts +8 -7
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +12 -8
- package/dist/testing/audit_completeness.d.ts +23 -22
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +199 -158
- package/dist/testing/audit_drift_guard.d.ts +116 -0
- package/dist/testing/audit_drift_guard.d.ts.map +1 -0
- package/dist/testing/audit_drift_guard.js +134 -0
- package/dist/testing/bootstrap_success.d.ts +28 -0
- package/dist/testing/bootstrap_success.d.ts.map +1 -0
- package/dist/testing/bootstrap_success.js +144 -0
- package/dist/testing/connection_closer_helpers.d.ts +44 -0
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
- package/dist/testing/connection_closer_helpers.js +48 -0
- package/dist/testing/cross_backend/capabilities.d.ts +64 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
- package/dist/testing/cross_backend/capabilities.js +47 -0
- package/dist/testing/cross_backend/setup.d.ts +215 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/setup.js +101 -0
- package/dist/testing/data_exposure.d.ts +14 -15
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +127 -146
- package/dist/testing/db_entities.d.ts +11 -1
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +13 -1
- package/dist/testing/integration.d.ts +35 -21
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +231 -293
- package/dist/testing/integration_helpers.d.ts +16 -6
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +7 -7
- package/dist/testing/mock_fs.d.ts.map +1 -1
- package/dist/testing/mock_fs.js +0 -2
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +13 -4
- package/dist/testing/role_grant_helpers.d.ts +31 -0
- package/dist/testing/role_grant_helpers.d.ts.map +1 -0
- package/dist/testing/role_grant_helpers.js +46 -0
- package/dist/testing/round_trip.d.ts +21 -16
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +65 -86
- package/dist/testing/rpc_helpers.d.ts +2 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.d.ts +24 -21
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +91 -106
- package/dist/testing/schema_introspect.d.ts +106 -0
- package/dist/testing/schema_introspect.d.ts.map +1 -0
- package/dist/testing/schema_introspect.js +123 -0
- package/dist/testing/schema_parity.d.ts +144 -0
- package/dist/testing/schema_parity.d.ts.map +1 -0
- package/dist/testing/schema_parity.js +233 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +12 -6
- package/dist/testing/standard.d.ts +57 -25
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +62 -5
- package/dist/testing/stubs.d.ts +22 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +28 -21
- package/dist/testing/surface_invariants.d.ts +66 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +103 -1
- package/dist/testing/transports/surface_source.d.ts +51 -0
- package/dist/testing/transports/surface_source.d.ts.map +1 -0
- package/dist/testing/transports/surface_source.js +19 -0
- package/dist/ui/SurfaceExplorer.svelte +161 -2
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -28,18 +28,15 @@ import './assert_dev_env.js';
|
|
|
28
28
|
import { describe, test, assert, afterAll } from 'vitest';
|
|
29
29
|
import { ROLE_KEEPER, ROLE_ADMIN } from '../auth/role_schema.js';
|
|
30
30
|
import { GRANT_PATH_ADMIN } from '../auth/grant_path_schema.js';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
31
|
+
import {} from './app_server.js';
|
|
32
|
+
import { create_test_role_grant_direct } from './db_entities.js';
|
|
33
|
+
import { role_grant_offer_and_accept } from './role_grant_helpers.js';
|
|
34
34
|
import { find_auth_route } from './integration_helpers.js';
|
|
35
|
-
import { run_migrations } from '../db/migrate.js';
|
|
36
35
|
import { ErrorCoverageCollector, assert_error_coverage, DEFAULT_INTEGRATION_ERROR_COVERAGE, } from './error_coverage.js';
|
|
37
36
|
import { rpc_call_for_spec, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
|
|
38
37
|
import { role_grant_offer_create_action_spec, role_grant_revoke_action_spec, } from '../auth/role_grant_offer_action_specs.js';
|
|
39
38
|
import { admin_account_list_action_spec, admin_session_list_action_spec, admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, audit_log_list_action_spec, audit_log_role_grant_history_action_spec, } from '../auth/admin_action_specs.js';
|
|
40
39
|
import { account_token_create_action_spec, account_verify_action_spec, } from '../auth/account_action_specs.js';
|
|
41
|
-
import { query_create_role_grant } from '../auth/role_grant_queries.js';
|
|
42
|
-
import { query_accept_offer } from '../auth/role_grant_offer_queries.js';
|
|
43
40
|
/**
|
|
44
41
|
* Pick a role for admin-grant testing, preferring a non-admin app-defined
|
|
45
42
|
* role whose `RoleSpec.grant_paths` includes `'admin'` (the
|
|
@@ -53,19 +50,6 @@ const pick_grantable_role = (role_specs) => {
|
|
|
53
50
|
}
|
|
54
51
|
return ROLE_ADMIN; // fallback
|
|
55
52
|
};
|
|
56
|
-
/**
|
|
57
|
-
* Build `CreateTestAppOptions` from admin test options plus a database and roles.
|
|
58
|
-
*/
|
|
59
|
-
const build_admin_test_app_options = (options, db, roles) => ({
|
|
60
|
-
session_options: options.session_options,
|
|
61
|
-
create_route_specs: options.create_route_specs,
|
|
62
|
-
db,
|
|
63
|
-
roles: roles ?? [ROLE_KEEPER, ROLE_ADMIN],
|
|
64
|
-
app_options: {
|
|
65
|
-
...options.app_options,
|
|
66
|
-
rpc_endpoints: options.rpc_endpoints,
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
53
|
/**
|
|
70
54
|
* Standard admin integration test suite for fuz_app admin routes.
|
|
71
55
|
*
|
|
@@ -81,55 +65,44 @@ const build_admin_test_app_options = (options, db, roles) => ({
|
|
|
81
65
|
* see a clear setup error rather than `method not found` mid-suite.
|
|
82
66
|
*/
|
|
83
67
|
export const describe_standard_admin_integration_tests = (options) => {
|
|
68
|
+
if (options.surface_source.kind !== 'inline') {
|
|
69
|
+
throw new Error("describe_standard_admin_integration_tests requires surface_source.kind === 'inline' — " +
|
|
70
|
+
'the cross-process snapshot variant lands with the spawned-backend transport');
|
|
71
|
+
}
|
|
72
|
+
const route_specs = options.surface_source.spec.route_specs;
|
|
84
73
|
// Hard-fail early so consumers see a clear setup error instead of a
|
|
85
|
-
// confusing test failure when `rpc_endpoints` is missing.
|
|
86
|
-
// callers are resolved with a stub ctx purely to extract the endpoint
|
|
87
|
-
// path; real handlers run per-test via `app_options.rpc_endpoints`.
|
|
74
|
+
// confusing test failure when `rpc_endpoints` is missing.
|
|
88
75
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
89
76
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
};
|
|
93
|
-
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
94
|
-
const describe_db = create_describe_db(factories, auth_integration_truncate_tables);
|
|
95
|
-
describe_db('standard_admin_integration', (get_db) => {
|
|
77
|
+
void options.capabilities;
|
|
78
|
+
describe('standard_admin_integration', () => {
|
|
96
79
|
const { cookie_name } = options.session_options;
|
|
97
80
|
const { role_specs } = options.roles;
|
|
98
81
|
const grantable_role = pick_grantable_role(role_specs);
|
|
99
82
|
// Error coverage tracking across test groups
|
|
100
83
|
const error_collector = new ErrorCoverageCollector();
|
|
101
|
-
let captured_route_specs = null;
|
|
102
84
|
afterAll(() => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// of asserting.
|
|
121
|
-
// The admin RPC surface is covered by
|
|
122
|
-
// `describe_rpc_round_trip_tests`, not this collector.
|
|
123
|
-
if (admin_routes.length <= 1) {
|
|
124
|
-
console.log(`[error coverage] skipped admin REST coverage assertion — ` +
|
|
125
|
-
`scoped surface has ${admin_routes.length} route(s); ` +
|
|
126
|
-
`admin RPC surface is covered by describe_rpc_round_trip_tests`);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
assert_error_coverage(error_collector, admin_routes, {
|
|
130
|
-
min_coverage: DEFAULT_INTEGRATION_ERROR_COVERAGE,
|
|
131
|
-
});
|
|
85
|
+
// Scope coverage to admin auth-related routes. Account listing,
|
|
86
|
+
// session/token revoke-all, audit-log reads, and invite CRUD all
|
|
87
|
+
// live on the RPC surface; the only admin REST route remaining
|
|
88
|
+
// is the optional `GET /audit/stream` SSE (admin RPC methods
|
|
89
|
+
// live behind spec-level role auth on the shared endpoint path).
|
|
90
|
+
const admin_routes = route_specs.filter((s) => s.path.endsWith('/audit/stream') && (s.auth.roles?.includes('admin') ?? false));
|
|
91
|
+
// Adaptive threshold: when the scoped admin REST surface is
|
|
92
|
+
// effectively empty (0–1 routes — typical for the RPC-first
|
|
93
|
+
// admin surface), the 20% baseline is meaningless — a single
|
|
94
|
+
// SSE route that can't be exercised against an error schema
|
|
95
|
+
// drops the ratio to 0.0%. Log an informational skip instead
|
|
96
|
+
// of asserting.
|
|
97
|
+
if (admin_routes.length <= 1) {
|
|
98
|
+
console.log(`[error coverage] skipped admin REST coverage assertion — ` +
|
|
99
|
+
`scoped surface has ${admin_routes.length} route(s); ` +
|
|
100
|
+
`admin RPC surface is covered by describe_rpc_round_trip_tests`);
|
|
101
|
+
return;
|
|
132
102
|
}
|
|
103
|
+
assert_error_coverage(error_collector, admin_routes, {
|
|
104
|
+
min_coverage: DEFAULT_INTEGRATION_ERROR_COVERAGE,
|
|
105
|
+
});
|
|
133
106
|
});
|
|
134
107
|
/** Make request headers for a given session cookie. */
|
|
135
108
|
const create_headers = (session_cookie, extra) => ({
|
|
@@ -139,41 +112,22 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
139
112
|
...extra,
|
|
140
113
|
});
|
|
141
114
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* side; exercising the recipient's UI-wired accept path is covered by
|
|
146
|
-
* `describe_rpc_round_trip_tests` + fuz_app's own action suite.
|
|
115
|
+
* Suite-local wrapper around `role_grant_offer_and_accept` that closes
|
|
116
|
+
* over `rpc_path` so call sites stay terse. See the shared helper for
|
|
117
|
+
* the cross-suite contract.
|
|
147
118
|
*/
|
|
148
|
-
const offer_and_accept =
|
|
149
|
-
const res = await rpc_call_for_spec({
|
|
150
|
-
app: args.app,
|
|
151
|
-
path: rpc_path,
|
|
152
|
-
spec: role_grant_offer_create_action_spec,
|
|
153
|
-
params: { to_account_id: args.to_account_id, role: args.role },
|
|
154
|
-
headers: args.admin_headers,
|
|
155
|
-
});
|
|
156
|
-
assert.ok(res.ok, `role_grant_offer_create failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
157
|
-
const { offer } = res.result;
|
|
158
|
-
const accept_result = await get_db().transaction(async (tx) => query_accept_offer({ db: tx }, {
|
|
159
|
-
offer_id: offer.id,
|
|
160
|
-
to_account_id: args.to_account_id,
|
|
161
|
-
actor_id: args.to_actor_id,
|
|
162
|
-
ip: null,
|
|
163
|
-
}));
|
|
164
|
-
return { offer_id: offer.id, role_grant_id: accept_result.role_grant.id };
|
|
165
|
-
};
|
|
119
|
+
const offer_and_accept = (args) => role_grant_offer_and_accept({ ...args, rpc_path });
|
|
166
120
|
// --- 1. Admin account listing (RPC) ---
|
|
167
121
|
describe('admin account listing', () => {
|
|
168
122
|
test('admin can list all accounts', async () => {
|
|
169
|
-
const
|
|
170
|
-
const user_two = await
|
|
123
|
+
const fixture = await options.setup_test();
|
|
124
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
171
125
|
const res = await rpc_call_for_spec({
|
|
172
|
-
app:
|
|
126
|
+
app: { request: fixture.transport },
|
|
173
127
|
path: rpc_path,
|
|
174
128
|
spec: admin_account_list_action_spec,
|
|
175
129
|
params: {},
|
|
176
|
-
headers:
|
|
130
|
+
headers: fixture.create_session_headers(),
|
|
177
131
|
});
|
|
178
132
|
assert.ok(res.ok, `admin_account_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
179
133
|
assert.ok(Array.isArray(res.result.accounts), 'Expected accounts array');
|
|
@@ -184,14 +138,19 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
184
138
|
assert.ok(found, 'Expected user_two in accounts listing');
|
|
185
139
|
});
|
|
186
140
|
test('non-admin cannot list accounts', async () => {
|
|
187
|
-
const
|
|
188
|
-
|
|
141
|
+
const fixture = await options.setup_test();
|
|
142
|
+
// Default keeper has both ROLE_KEEPER + ROLE_ADMIN; mint a fresh
|
|
143
|
+
// account with only ROLE_KEEPER (no admin) to probe the 403 path.
|
|
144
|
+
const keeper_only = await fixture.create_account({
|
|
145
|
+
username: 'keeper_only',
|
|
146
|
+
roles: [ROLE_KEEPER],
|
|
147
|
+
});
|
|
189
148
|
const res = await rpc_call_for_spec({
|
|
190
|
-
app:
|
|
149
|
+
app: { request: fixture.transport },
|
|
191
150
|
path: rpc_path,
|
|
192
151
|
spec: admin_account_list_action_spec,
|
|
193
152
|
params: {},
|
|
194
|
-
headers:
|
|
153
|
+
headers: keeper_only.create_session_headers(),
|
|
195
154
|
});
|
|
196
155
|
assert.ok(!res.ok, 'Expected admin_account_list to fail for non-admin');
|
|
197
156
|
assert.strictEqual(res.status, 403);
|
|
@@ -208,25 +167,25 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
208
167
|
// --- 3. Admin session management ---
|
|
209
168
|
describe('admin session management', () => {
|
|
210
169
|
test('admin can list all active sessions', async () => {
|
|
211
|
-
const
|
|
212
|
-
await
|
|
170
|
+
const fixture = await options.setup_test();
|
|
171
|
+
await fixture.create_account({ username: 'user_two' });
|
|
213
172
|
const res = await rpc_call_for_spec({
|
|
214
|
-
app:
|
|
173
|
+
app: { request: fixture.transport },
|
|
215
174
|
path: rpc_path,
|
|
216
175
|
spec: admin_session_list_action_spec,
|
|
217
176
|
params: {},
|
|
218
|
-
headers:
|
|
177
|
+
headers: fixture.create_session_headers(),
|
|
219
178
|
});
|
|
220
179
|
assert.ok(res.ok, `admin_session_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
221
180
|
assert.ok(Array.isArray(res.result.sessions), 'Expected sessions array');
|
|
222
181
|
assert.ok(res.result.sessions.length >= 2, 'Expected sessions from multiple accounts');
|
|
223
182
|
});
|
|
224
183
|
test('admin can revoke all sessions for another account', async () => {
|
|
225
|
-
const
|
|
226
|
-
const user_two = await
|
|
184
|
+
const fixture = await options.setup_test();
|
|
185
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
227
186
|
// Verify user_two's session works via `account_verify` RPC
|
|
228
187
|
const before = await rpc_call_for_spec({
|
|
229
|
-
app:
|
|
188
|
+
app: { request: fixture.transport },
|
|
230
189
|
path: rpc_path,
|
|
231
190
|
spec: account_verify_action_spec,
|
|
232
191
|
params: undefined,
|
|
@@ -235,18 +194,18 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
235
194
|
assert.strictEqual(before.status, 200);
|
|
236
195
|
// Admin revokes all sessions for user_two via RPC
|
|
237
196
|
const res = await rpc_call_for_spec({
|
|
238
|
-
app:
|
|
197
|
+
app: { request: fixture.transport },
|
|
239
198
|
path: rpc_path,
|
|
240
199
|
spec: admin_session_revoke_all_action_spec,
|
|
241
200
|
params: { account_id: user_two.account.id },
|
|
242
|
-
headers:
|
|
201
|
+
headers: fixture.create_session_headers(),
|
|
243
202
|
});
|
|
244
203
|
assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
245
204
|
assert.strictEqual(res.result.ok, true);
|
|
246
205
|
assert.ok(res.result.count >= 1, 'Expected at least 1 revoked session');
|
|
247
206
|
// Verify user_two's session no longer works
|
|
248
207
|
const after = await rpc_call_for_spec({
|
|
249
|
-
app:
|
|
208
|
+
app: { request: fixture.transport },
|
|
250
209
|
path: rpc_path,
|
|
251
210
|
spec: account_verify_action_spec,
|
|
252
211
|
params: undefined,
|
|
@@ -255,25 +214,25 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
255
214
|
assert.strictEqual(after.status, 401);
|
|
256
215
|
});
|
|
257
216
|
test('admin revoking own sessions invalidates own session', async () => {
|
|
258
|
-
const
|
|
217
|
+
const fixture = await options.setup_test();
|
|
259
218
|
// Admin revokes own sessions via RPC
|
|
260
219
|
const res = await rpc_call_for_spec({
|
|
261
|
-
app:
|
|
220
|
+
app: { request: fixture.transport },
|
|
262
221
|
path: rpc_path,
|
|
263
222
|
spec: admin_session_revoke_all_action_spec,
|
|
264
|
-
params: { account_id:
|
|
265
|
-
headers:
|
|
223
|
+
params: { account_id: fixture.account.id },
|
|
224
|
+
headers: fixture.create_session_headers(),
|
|
266
225
|
});
|
|
267
226
|
assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
268
227
|
assert.strictEqual(res.result.ok, true);
|
|
269
228
|
assert.ok(res.result.count >= 1, 'Expected at least 1 revoked session');
|
|
270
229
|
// Admin's own session should no longer work
|
|
271
230
|
const after = await rpc_call_for_spec({
|
|
272
|
-
app:
|
|
231
|
+
app: { request: fixture.transport },
|
|
273
232
|
path: rpc_path,
|
|
274
233
|
spec: account_verify_action_spec,
|
|
275
234
|
params: undefined,
|
|
276
|
-
headers:
|
|
235
|
+
headers: fixture.create_session_headers(),
|
|
277
236
|
});
|
|
278
237
|
assert.strictEqual(after.status, 401);
|
|
279
238
|
});
|
|
@@ -281,11 +240,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
281
240
|
// --- 4. Admin token management ---
|
|
282
241
|
describe('admin token management', () => {
|
|
283
242
|
test('admin can revoke all tokens for another account', async () => {
|
|
284
|
-
const
|
|
285
|
-
const user_two = await
|
|
243
|
+
const fixture = await options.setup_test();
|
|
244
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
286
245
|
// Verify user_two's bearer token works via `account_verify` RPC
|
|
287
246
|
const before = await rpc_call_for_spec({
|
|
288
|
-
app:
|
|
247
|
+
app: { request: fixture.transport },
|
|
289
248
|
path: rpc_path,
|
|
290
249
|
spec: account_verify_action_spec,
|
|
291
250
|
params: undefined,
|
|
@@ -295,18 +254,18 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
295
254
|
assert.strictEqual(before.status, 200);
|
|
296
255
|
// Admin revokes all tokens for user_two via RPC
|
|
297
256
|
const res = await rpc_call_for_spec({
|
|
298
|
-
app:
|
|
257
|
+
app: { request: fixture.transport },
|
|
299
258
|
path: rpc_path,
|
|
300
259
|
spec: admin_token_revoke_all_action_spec,
|
|
301
260
|
params: { account_id: user_two.account.id },
|
|
302
|
-
headers:
|
|
261
|
+
headers: fixture.create_session_headers(),
|
|
303
262
|
});
|
|
304
263
|
assert.ok(res.ok, `admin_token_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
305
264
|
assert.strictEqual(res.result.ok, true);
|
|
306
265
|
assert.ok(res.result.count >= 1, 'Expected at least 1 revoked token');
|
|
307
266
|
// Verify user_two's bearer token no longer works
|
|
308
267
|
const after = await rpc_call_for_spec({
|
|
309
|
-
app:
|
|
268
|
+
app: { request: fixture.transport },
|
|
310
269
|
path: rpc_path,
|
|
311
270
|
spec: account_verify_action_spec,
|
|
312
271
|
params: undefined,
|
|
@@ -319,36 +278,36 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
319
278
|
// --- 5. Audit log RPC reads ---
|
|
320
279
|
describe('audit log RPC reads', () => {
|
|
321
280
|
test('admin can list audit log events', async () => {
|
|
322
|
-
const
|
|
281
|
+
const fixture = await options.setup_test();
|
|
323
282
|
const res = await rpc_call_for_spec({
|
|
324
|
-
app:
|
|
283
|
+
app: { request: fixture.transport },
|
|
325
284
|
path: rpc_path,
|
|
326
285
|
spec: audit_log_list_action_spec,
|
|
327
286
|
params: {},
|
|
328
|
-
headers:
|
|
287
|
+
headers: fixture.create_session_headers(),
|
|
329
288
|
});
|
|
330
289
|
assert.ok(res.ok, `audit_log_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
331
290
|
assert.ok(Array.isArray(res.result.events), 'Expected events array');
|
|
332
291
|
});
|
|
333
292
|
test('audit log supports event_type filter', async () => {
|
|
334
|
-
const
|
|
293
|
+
const fixture = await options.setup_test();
|
|
335
294
|
// Admin offer emits `role_grant_offer_create`. The downstream
|
|
336
295
|
// `role_grant_create` only fires on accept — out of scope for this test.
|
|
337
|
-
const user_two = await
|
|
296
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
338
297
|
const offer_res = await rpc_call_for_spec({
|
|
339
|
-
app:
|
|
298
|
+
app: { request: fixture.transport },
|
|
340
299
|
path: rpc_path,
|
|
341
300
|
spec: role_grant_offer_create_action_spec,
|
|
342
301
|
params: { to_account_id: user_two.account.id, role: grantable_role },
|
|
343
|
-
headers:
|
|
302
|
+
headers: fixture.create_session_headers(),
|
|
344
303
|
});
|
|
345
304
|
assert.ok(offer_res.ok, 'role_grant_offer_create should succeed');
|
|
346
305
|
const res = await rpc_call_for_spec({
|
|
347
|
-
app:
|
|
306
|
+
app: { request: fixture.transport },
|
|
348
307
|
path: rpc_path,
|
|
349
308
|
spec: audit_log_list_action_spec,
|
|
350
309
|
params: { event_type: 'role_grant_offer_create' },
|
|
351
|
-
headers:
|
|
310
|
+
headers: fixture.create_session_headers(),
|
|
352
311
|
});
|
|
353
312
|
assert.ok(res.ok, `audit_log_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
354
313
|
assert.ok(res.result.events.length >= 1, 'Expected at least 1 role_grant_offer_create event');
|
|
@@ -357,23 +316,22 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
357
316
|
}
|
|
358
317
|
});
|
|
359
318
|
test('admin can view role_grant history', async () => {
|
|
360
|
-
const
|
|
319
|
+
const fixture = await options.setup_test();
|
|
361
320
|
// Drive the full consent flow so `role_grant_create` lands in the audit log
|
|
362
321
|
// — `query_audit_log_list_role_grant_history` filters to (role_grant_create, role_grant_revoke).
|
|
363
|
-
const user_two = await
|
|
322
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
364
323
|
await offer_and_accept({
|
|
365
|
-
app:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
to_actor_id: user_two.actor.id,
|
|
324
|
+
app: { request: fixture.transport },
|
|
325
|
+
grantor: fixture,
|
|
326
|
+
recipient: user_two,
|
|
369
327
|
role: grantable_role,
|
|
370
328
|
});
|
|
371
329
|
const res = await rpc_call_for_spec({
|
|
372
|
-
app:
|
|
330
|
+
app: { request: fixture.transport },
|
|
373
331
|
path: rpc_path,
|
|
374
332
|
spec: audit_log_role_grant_history_action_spec,
|
|
375
333
|
params: {},
|
|
376
|
-
headers:
|
|
334
|
+
headers: fixture.create_session_headers(),
|
|
377
335
|
});
|
|
378
336
|
assert.ok(res.ok, `audit_log_role_grant_history failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
379
337
|
assert.ok(res.result.events.length >= 1, 'Expected at least 1 role_grant history event');
|
|
@@ -382,93 +340,94 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
382
340
|
// --- 6. Admin audit trail ---
|
|
383
341
|
describe('admin audit trail', () => {
|
|
384
342
|
test('role_grant revoke creates audit event', async () => {
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
const
|
|
343
|
+
const fixture = await options.setup_test();
|
|
344
|
+
assert(fixture.in_process, 'direct role_grant seed requires in-process db');
|
|
345
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
346
|
+
const role_grant = await create_test_role_grant_direct(fixture.backend_internals.deps.db, {
|
|
388
347
|
actor_id: user_two.actor.id,
|
|
389
348
|
role: grantable_role,
|
|
390
|
-
granted_by:
|
|
349
|
+
granted_by: fixture.actor.id,
|
|
391
350
|
});
|
|
392
351
|
// Revoke via RPC
|
|
393
352
|
const revoke_res = await rpc_call_for_spec({
|
|
394
|
-
app:
|
|
353
|
+
app: { request: fixture.transport },
|
|
395
354
|
path: rpc_path,
|
|
396
355
|
spec: role_grant_revoke_action_spec,
|
|
397
356
|
params: { actor_id: user_two.actor.id, role_grant_id: role_grant.id },
|
|
398
|
-
headers:
|
|
357
|
+
headers: fixture.create_session_headers(),
|
|
399
358
|
});
|
|
400
359
|
assert.ok(revoke_res.ok, `role_grant_revoke failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
401
360
|
// Check audit log for role_grant_revoke event
|
|
402
361
|
const audit_res = await rpc_call_for_spec({
|
|
403
|
-
app:
|
|
362
|
+
app: { request: fixture.transport },
|
|
404
363
|
path: rpc_path,
|
|
405
364
|
spec: audit_log_list_action_spec,
|
|
406
365
|
params: { event_type: 'role_grant_revoke' },
|
|
407
|
-
headers:
|
|
366
|
+
headers: fixture.create_session_headers(),
|
|
408
367
|
});
|
|
409
368
|
assert.ok(audit_res.ok, `audit_log_list failed: ${audit_res.ok ? '' : JSON.stringify(audit_res.error)}`);
|
|
410
369
|
assert.ok(audit_res.result.events.length >= 1, 'Expected role_grant_revoke audit event');
|
|
411
370
|
assert.strictEqual(audit_res.result.events[0].event_type, 'role_grant_revoke');
|
|
412
371
|
});
|
|
413
372
|
test('admin session revoke-all creates audit event', async () => {
|
|
414
|
-
const
|
|
415
|
-
const user_two = await
|
|
373
|
+
const fixture = await options.setup_test();
|
|
374
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
416
375
|
// Revoke all sessions for user_two via RPC
|
|
417
376
|
const revoke_res = await rpc_call_for_spec({
|
|
418
|
-
app:
|
|
377
|
+
app: { request: fixture.transport },
|
|
419
378
|
path: rpc_path,
|
|
420
379
|
spec: admin_session_revoke_all_action_spec,
|
|
421
380
|
params: { account_id: user_two.account.id },
|
|
422
|
-
headers:
|
|
381
|
+
headers: fixture.create_session_headers(),
|
|
423
382
|
});
|
|
424
383
|
assert.ok(revoke_res.ok, `admin_session_revoke_all failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
425
384
|
// Check audit log
|
|
426
385
|
const audit_res = await rpc_call_for_spec({
|
|
427
|
-
app:
|
|
386
|
+
app: { request: fixture.transport },
|
|
428
387
|
path: rpc_path,
|
|
429
388
|
spec: audit_log_list_action_spec,
|
|
430
389
|
params: { event_type: 'session_revoke_all' },
|
|
431
|
-
headers:
|
|
390
|
+
headers: fixture.create_session_headers(),
|
|
432
391
|
});
|
|
433
392
|
assert.ok(audit_res.ok, 'audit_log_list should succeed');
|
|
434
393
|
assert.ok(audit_res.result.events.length >= 1, 'Expected session_revoke_all audit event');
|
|
435
394
|
assert.strictEqual(audit_res.result.events[0].event_type, 'session_revoke_all');
|
|
436
395
|
});
|
|
437
396
|
test('admin token revoke-all creates audit event', async () => {
|
|
438
|
-
const
|
|
439
|
-
const user_two = await
|
|
397
|
+
const fixture = await options.setup_test();
|
|
398
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
440
399
|
// Revoke all tokens for user_two via RPC
|
|
441
400
|
const revoke_res = await rpc_call_for_spec({
|
|
442
|
-
app:
|
|
401
|
+
app: { request: fixture.transport },
|
|
443
402
|
path: rpc_path,
|
|
444
403
|
spec: admin_token_revoke_all_action_spec,
|
|
445
404
|
params: { account_id: user_two.account.id },
|
|
446
|
-
headers:
|
|
405
|
+
headers: fixture.create_session_headers(),
|
|
447
406
|
});
|
|
448
407
|
assert.ok(revoke_res.ok, `admin_token_revoke_all failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
449
408
|
// Check audit log
|
|
450
409
|
const audit_res = await rpc_call_for_spec({
|
|
451
|
-
app:
|
|
410
|
+
app: { request: fixture.transport },
|
|
452
411
|
path: rpc_path,
|
|
453
412
|
spec: audit_log_list_action_spec,
|
|
454
413
|
params: { event_type: 'token_revoke_all' },
|
|
455
|
-
headers:
|
|
414
|
+
headers: fixture.create_session_headers(),
|
|
456
415
|
});
|
|
457
416
|
assert.ok(audit_res.ok, 'audit_log_list should succeed');
|
|
458
417
|
assert.ok(audit_res.result.events.length >= 1, 'Expected token_revoke_all audit event');
|
|
459
418
|
assert.strictEqual(audit_res.result.events[0].event_type, 'token_revoke_all');
|
|
460
419
|
});
|
|
461
420
|
test('admin session revoke-all 404 emits failure audit', async () => {
|
|
462
|
-
const
|
|
421
|
+
const fixture = await options.setup_test();
|
|
463
422
|
// `Uuid = z.uuid()` is v4-strict; use a valid v4 shape so we hit the
|
|
464
423
|
// handler's account lookup rather than failing at param validation.
|
|
465
424
|
const missing_id = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaa01';
|
|
466
425
|
const res = await rpc_call_for_spec({
|
|
467
|
-
app:
|
|
426
|
+
app: { request: fixture.transport },
|
|
468
427
|
path: rpc_path,
|
|
469
428
|
spec: admin_session_revoke_all_action_spec,
|
|
470
429
|
params: { account_id: missing_id },
|
|
471
|
-
headers:
|
|
430
|
+
headers: fixture.create_session_headers(),
|
|
472
431
|
});
|
|
473
432
|
assert.ok(!res.ok, 'Expected 404 for missing account');
|
|
474
433
|
assert.strictEqual(res.status, 404);
|
|
@@ -477,11 +436,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
477
436
|
// `target_account_id` is null (FK prevents referencing a missing id)
|
|
478
437
|
// — the probed id is preserved under `metadata.attempted_account_id`.
|
|
479
438
|
const audit_res = await rpc_call_for_spec({
|
|
480
|
-
app:
|
|
439
|
+
app: { request: fixture.transport },
|
|
481
440
|
path: rpc_path,
|
|
482
441
|
spec: audit_log_list_action_spec,
|
|
483
442
|
params: { event_type: 'session_revoke_all' },
|
|
484
|
-
headers:
|
|
443
|
+
headers: fixture.create_session_headers(),
|
|
485
444
|
});
|
|
486
445
|
assert.ok(audit_res.ok, 'audit_log_list should succeed');
|
|
487
446
|
const failure = audit_res.result.events.find((e) => e.outcome === 'failure');
|
|
@@ -492,24 +451,24 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
492
451
|
assert.strictEqual(failure_meta.attempted_account_id, missing_id);
|
|
493
452
|
});
|
|
494
453
|
test('admin token revoke-all 404 emits failure audit', async () => {
|
|
495
|
-
const
|
|
454
|
+
const fixture = await options.setup_test();
|
|
496
455
|
const missing_id = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaa02';
|
|
497
456
|
const res = await rpc_call_for_spec({
|
|
498
|
-
app:
|
|
457
|
+
app: { request: fixture.transport },
|
|
499
458
|
path: rpc_path,
|
|
500
459
|
spec: admin_token_revoke_all_action_spec,
|
|
501
460
|
params: { account_id: missing_id },
|
|
502
|
-
headers:
|
|
461
|
+
headers: fixture.create_session_headers(),
|
|
503
462
|
});
|
|
504
463
|
assert.ok(!res.ok, 'Expected 404 for missing account');
|
|
505
464
|
assert.strictEqual(res.status, 404);
|
|
506
465
|
assert.strictEqual(res.error.data.reason, 'account_not_found');
|
|
507
466
|
const audit_res = await rpc_call_for_spec({
|
|
508
|
-
app:
|
|
467
|
+
app: { request: fixture.transport },
|
|
509
468
|
path: rpc_path,
|
|
510
469
|
spec: audit_log_list_action_spec,
|
|
511
470
|
params: { event_type: 'token_revoke_all' },
|
|
512
|
-
headers:
|
|
471
|
+
headers: fixture.create_session_headers(),
|
|
513
472
|
});
|
|
514
473
|
assert.ok(audit_res.ok, 'audit_log_list should succeed');
|
|
515
474
|
const failure = audit_res.result.events.find((e) => e.outcome === 'failure');
|
|
@@ -522,19 +481,19 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
522
481
|
});
|
|
523
482
|
// --- 7. Audit log completeness ---
|
|
524
483
|
describe('audit log completeness', () => {
|
|
525
|
-
test('auth mutations each produce
|
|
526
|
-
const
|
|
527
|
-
const login_route = find_auth_route(
|
|
528
|
-
const logout_route = find_auth_route(
|
|
529
|
-
const password_route = find_auth_route(
|
|
484
|
+
test('auth mutations each produce at least one audit event', async () => {
|
|
485
|
+
const fixture = await options.setup_test();
|
|
486
|
+
const login_route = find_auth_route(route_specs, '/login', 'POST');
|
|
487
|
+
const logout_route = find_auth_route(route_specs, '/logout', 'POST');
|
|
488
|
+
const password_route = find_auth_route(route_specs, '/password', 'POST');
|
|
530
489
|
// skip if required routes are missing (consumer may not wire all routes).
|
|
531
490
|
// Token creation goes through `account_token_create` RPC — always wired
|
|
532
491
|
// because `rpc_endpoints` is required at the suite level.
|
|
533
492
|
if (!login_route || !logout_route || !password_route)
|
|
534
493
|
return;
|
|
535
|
-
const user_two = await
|
|
494
|
+
const user_two = await fixture.create_account({ username: 'audit_user' });
|
|
536
495
|
// 1. login (user_two logs in)
|
|
537
|
-
const login_res = await
|
|
496
|
+
const login_res = await fixture.transport(login_route.path, {
|
|
538
497
|
method: 'POST',
|
|
539
498
|
headers: {
|
|
540
499
|
host: 'localhost',
|
|
@@ -550,7 +509,7 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
550
509
|
const user_two_cookie = cookie_match?.[1];
|
|
551
510
|
// 2. logout (user_two logs out)
|
|
552
511
|
if (user_two_cookie) {
|
|
553
|
-
await
|
|
512
|
+
await fixture.transport(logout_route.path, {
|
|
554
513
|
method: 'POST',
|
|
555
514
|
headers: {
|
|
556
515
|
host: 'localhost',
|
|
@@ -563,34 +522,33 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
563
522
|
// consentful flow: offer + accept so both `role_grant_offer_create` and
|
|
564
523
|
// `role_grant_create` audit events land.
|
|
565
524
|
const { role_grant_id } = await offer_and_accept({
|
|
566
|
-
app:
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
to_actor_id: user_two.actor.id,
|
|
525
|
+
app: { request: fixture.transport },
|
|
526
|
+
grantor: fixture,
|
|
527
|
+
recipient: user_two,
|
|
570
528
|
role: grantable_role,
|
|
571
529
|
});
|
|
572
530
|
// 4. revoke role_grant (RPC)
|
|
573
531
|
const revoke_res = await rpc_call_for_spec({
|
|
574
|
-
app:
|
|
532
|
+
app: { request: fixture.transport },
|
|
575
533
|
path: rpc_path,
|
|
576
534
|
spec: role_grant_revoke_action_spec,
|
|
577
535
|
params: { actor_id: user_two.actor.id, role_grant_id },
|
|
578
|
-
headers:
|
|
536
|
+
headers: fixture.create_session_headers(),
|
|
579
537
|
});
|
|
580
538
|
assert.ok(revoke_res.ok, `role_grant_revoke failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
581
539
|
// 5. create token (RPC)
|
|
582
540
|
const token_res = await rpc_call_for_spec({
|
|
583
|
-
app:
|
|
541
|
+
app: { request: fixture.transport },
|
|
584
542
|
path: rpc_path,
|
|
585
543
|
spec: account_token_create_action_spec,
|
|
586
544
|
params: { name: 'audit-test-token' },
|
|
587
|
-
headers:
|
|
545
|
+
headers: fixture.create_session_headers(),
|
|
588
546
|
});
|
|
589
547
|
assert.ok(token_res.ok, `account_token_create failed: ${token_res.ok ? '' : JSON.stringify(token_res.error)}`);
|
|
590
548
|
// 6. password change
|
|
591
|
-
await
|
|
549
|
+
await fixture.transport(password_route.path, {
|
|
592
550
|
method: 'POST',
|
|
593
|
-
headers:
|
|
551
|
+
headers: fixture.create_session_headers({
|
|
594
552
|
'content-type': 'application/json',
|
|
595
553
|
}),
|
|
596
554
|
body: JSON.stringify({
|
|
@@ -600,7 +558,7 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
600
558
|
});
|
|
601
559
|
// query audit log and verify events
|
|
602
560
|
// re-login as admin since password change revoked sessions
|
|
603
|
-
const relogin_res = await
|
|
561
|
+
const relogin_res = await fixture.transport(login_route.path, {
|
|
604
562
|
method: 'POST',
|
|
605
563
|
headers: {
|
|
606
564
|
host: 'localhost',
|
|
@@ -608,7 +566,7 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
608
566
|
'content-type': 'application/json',
|
|
609
567
|
},
|
|
610
568
|
body: JSON.stringify({
|
|
611
|
-
username:
|
|
569
|
+
username: fixture.account.username,
|
|
612
570
|
password: 'new-audit-password-789',
|
|
613
571
|
}),
|
|
614
572
|
});
|
|
@@ -622,7 +580,7 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
622
580
|
cookie: `${cookie_name}=${relogin_match[1]}`,
|
|
623
581
|
};
|
|
624
582
|
const audit_res = await rpc_call_for_spec({
|
|
625
|
-
app:
|
|
583
|
+
app: { request: fixture.transport },
|
|
626
584
|
path: rpc_path,
|
|
627
585
|
spec: audit_log_list_action_spec,
|
|
628
586
|
params: {},
|
|
@@ -653,23 +611,23 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
653
611
|
// --- 8. Admin-to-admin isolation ---
|
|
654
612
|
describe('admin-to-admin isolation', () => {
|
|
655
613
|
test('admin B revoking own role_grant via RPC succeeds', async () => {
|
|
656
|
-
const
|
|
657
|
-
captured_route_specs ??= test_app.route_specs;
|
|
614
|
+
const fixture = await options.setup_test();
|
|
658
615
|
// Bootstrap user is admin A. Create admin B.
|
|
659
|
-
const admin_b = await
|
|
616
|
+
const admin_b = await fixture.create_account({
|
|
660
617
|
username: 'admin_b_iso',
|
|
661
618
|
roles: ['admin'],
|
|
662
619
|
});
|
|
663
620
|
// Seed an active role_grant directly — the revoke IDOR check is the
|
|
664
621
|
// subject of this test, not the grant→accept cycle.
|
|
665
|
-
|
|
622
|
+
assert(fixture.in_process, 'direct role_grant seed requires in-process db');
|
|
623
|
+
const role_grant = await create_test_role_grant_direct(fixture.backend_internals.deps.db, {
|
|
666
624
|
actor_id: admin_b.actor.id,
|
|
667
625
|
role: grantable_role,
|
|
668
|
-
granted_by:
|
|
626
|
+
granted_by: fixture.actor.id,
|
|
669
627
|
});
|
|
670
628
|
// Admin B revokes their own role_grant via RPC — should succeed
|
|
671
629
|
const revoke_res = await rpc_call_for_spec({
|
|
672
|
-
app:
|
|
630
|
+
app: { request: fixture.transport },
|
|
673
631
|
path: rpc_path,
|
|
674
632
|
spec: role_grant_revoke_action_spec,
|
|
675
633
|
params: { actor_id: admin_b.actor.id, role_grant_id: role_grant.id },
|
|
@@ -679,32 +637,32 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
679
637
|
assert.strictEqual(revoke_res.result.revoked, true);
|
|
680
638
|
});
|
|
681
639
|
test('admin revoke-all sessions for another admin works', async () => {
|
|
682
|
-
const
|
|
683
|
-
const admin_b = await
|
|
640
|
+
const fixture = await options.setup_test();
|
|
641
|
+
const admin_b = await fixture.create_account({
|
|
684
642
|
username: 'admin_b_sess',
|
|
685
643
|
roles: ['admin'],
|
|
686
644
|
});
|
|
687
645
|
// Admin A revokes all of admin B's sessions via RPC
|
|
688
646
|
const res = await rpc_call_for_spec({
|
|
689
|
-
app:
|
|
647
|
+
app: { request: fixture.transport },
|
|
690
648
|
path: rpc_path,
|
|
691
649
|
spec: admin_session_revoke_all_action_spec,
|
|
692
650
|
params: { account_id: admin_b.account.id },
|
|
693
|
-
headers:
|
|
651
|
+
headers: fixture.create_session_headers(),
|
|
694
652
|
});
|
|
695
653
|
assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
696
654
|
assert.ok(typeof res.result.count === 'number', 'Expected count field in response');
|
|
697
655
|
assert.ok(res.result.count >= 1, 'Expected at least 1 session revoked');
|
|
698
656
|
});
|
|
699
657
|
test('admin revoke-all tokens for another admin works', async () => {
|
|
700
|
-
const
|
|
701
|
-
const admin_b = await
|
|
658
|
+
const fixture = await options.setup_test();
|
|
659
|
+
const admin_b = await fixture.create_account({
|
|
702
660
|
username: 'admin_b_tok',
|
|
703
661
|
roles: ['admin'],
|
|
704
662
|
});
|
|
705
663
|
// Admin B creates an API token via RPC
|
|
706
664
|
const token_res = await rpc_call_for_spec({
|
|
707
|
-
app:
|
|
665
|
+
app: { request: fixture.transport },
|
|
708
666
|
path: rpc_path,
|
|
709
667
|
spec: account_token_create_action_spec,
|
|
710
668
|
params: { name: 'admin-b-token' },
|
|
@@ -713,11 +671,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
713
671
|
assert.ok(token_res.ok, `account_token_create failed: ${token_res.ok ? '' : JSON.stringify(token_res.error)}`);
|
|
714
672
|
// Admin A revokes all of admin B's tokens via RPC
|
|
715
673
|
const res = await rpc_call_for_spec({
|
|
716
|
-
app:
|
|
674
|
+
app: { request: fixture.transport },
|
|
717
675
|
path: rpc_path,
|
|
718
676
|
spec: admin_token_revoke_all_action_spec,
|
|
719
677
|
params: { account_id: admin_b.account.id },
|
|
720
|
-
headers:
|
|
678
|
+
headers: fixture.create_session_headers(),
|
|
721
679
|
});
|
|
722
680
|
assert.ok(res.ok, `admin_token_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
723
681
|
assert.ok(typeof res.result.count === 'number', 'Expected count field in response');
|
|
@@ -725,11 +683,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
725
683
|
assert.ok(res.result.count >= 1, 'Expected at least 1 token revoked');
|
|
726
684
|
});
|
|
727
685
|
test('non-admin cannot access admin routes for another account', async () => {
|
|
728
|
-
const
|
|
729
|
-
const regular_user = await
|
|
686
|
+
const fixture = await options.setup_test();
|
|
687
|
+
const regular_user = await fixture.create_account({ username: 'regular_user_iso' });
|
|
730
688
|
// Regular user tries to list accounts via the admin RPC — should 403
|
|
731
689
|
const res = await rpc_call_for_spec({
|
|
732
|
-
app:
|
|
690
|
+
app: { request: fixture.transport },
|
|
733
691
|
path: rpc_path,
|
|
734
692
|
spec: admin_account_list_action_spec,
|
|
735
693
|
params: {},
|
|
@@ -742,23 +700,22 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
742
700
|
// --- 8a. Error coverage: unauthenticated access to admin routes ---
|
|
743
701
|
describe('error coverage breadth', () => {
|
|
744
702
|
test('exercises 401/403 on admin routes for error coverage', async () => {
|
|
745
|
-
const
|
|
746
|
-
captured_route_specs ??= test_app.route_specs;
|
|
703
|
+
const fixture = await options.setup_test();
|
|
747
704
|
// `/api/admin` is nearly empty — admin reads and mutations live
|
|
748
705
|
// on the RPC endpoint behind spec-level role auth. The path-prefix carve is still the right scope here
|
|
749
706
|
// because error coverage is tracked against REST `RouteSpec`s,
|
|
750
707
|
// not RPC method specs (`describe_rpc_round_trip_tests` covers
|
|
751
708
|
// the admin RPC surface separately).
|
|
752
709
|
const prefix = options.admin_prefix ?? '/api/admin';
|
|
753
|
-
const admin_routes =
|
|
710
|
+
const admin_routes = route_specs.filter((s) => s.path.startsWith(prefix) && (s.auth.roles?.includes('admin') ?? false));
|
|
754
711
|
// Hit admin routes without auth to exercise 401 error schemas.
|
|
755
712
|
for (const route of admin_routes) {
|
|
756
|
-
const res = await
|
|
713
|
+
const res = await fixture.transport(route.path, {
|
|
757
714
|
method: route.method,
|
|
758
715
|
headers: { host: 'localhost' },
|
|
759
716
|
});
|
|
760
717
|
if (res.status === 401 || res.status === 403) {
|
|
761
|
-
error_collector.record(
|
|
718
|
+
error_collector.record(route_specs, route.method, route.path, res.status);
|
|
762
719
|
}
|
|
763
720
|
}
|
|
764
721
|
});
|