@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
|
@@ -8,15 +8,16 @@ import './assert_dev_env.js';
|
|
|
8
8
|
* - Cross-privilege: verifies admin routes return 403 for non-admin users,
|
|
9
9
|
* and non-admin responses exclude admin-only fields
|
|
10
10
|
*
|
|
11
|
+
* Cadence: per-describe `setup_test()` call (see `round_trip.ts` module
|
|
12
|
+
* docstring). The runtime body manages its own request ordering (auth-free
|
|
13
|
+
* → cross-privilege → 2xx) to avoid session-invalidation contamination
|
|
14
|
+
* between assertions, so per-test fixture re-creation isn't needed.
|
|
15
|
+
*
|
|
11
16
|
* @module
|
|
12
17
|
*/
|
|
13
|
-
import { describe, test, beforeAll,
|
|
18
|
+
import { describe, test, beforeAll, assert } from 'vitest';
|
|
14
19
|
import { ROLE_ADMIN } from '../auth/role_schema.js';
|
|
15
|
-
import { create_test_app } from './app_server.js';
|
|
16
|
-
import { create_pglite_factory } from './db.js';
|
|
17
20
|
import { resolve_valid_path, generate_valid_body } from './schema_generators.js';
|
|
18
|
-
import { run_migrations } from '../db/migrate.js';
|
|
19
|
-
import { auth_migration_ns } from '../auth/migrations.js';
|
|
20
21
|
import { is_null_schema, is_strict_object_schema } from '../http/schema_helpers.js';
|
|
21
22
|
import { is_keeper_auth, is_public_auth } from '../http/auth_shape.js';
|
|
22
23
|
import { sensitive_field_blocklist, admin_only_field_blocklist, assert_no_sensitive_fields_in_json, pick_auth_headers, } from './integration_helpers.js';
|
|
@@ -92,9 +93,15 @@ export const assert_non_admin_schemas_no_admin_fields = (surface, admin_only_fie
|
|
|
92
93
|
* contain no sensitive fields
|
|
93
94
|
*/
|
|
94
95
|
export const describe_data_exposure_tests = (options) => {
|
|
95
|
-
|
|
96
|
+
if (options.surface_source.kind !== 'inline') {
|
|
97
|
+
throw new Error("describe_data_exposure_tests requires surface_source.kind === 'inline' — " +
|
|
98
|
+
'the cross-process snapshot variant lands with the spawned-backend transport');
|
|
99
|
+
}
|
|
100
|
+
const { surface, route_specs } = options.surface_source.spec;
|
|
101
|
+
const { sensitive_fields = sensitive_field_blocklist, admin_only_fields = admin_only_field_blocklist, } = options;
|
|
102
|
+
const skip_set = new Set(options.skip_routes);
|
|
103
|
+
void options.capabilities;
|
|
96
104
|
describe('data exposure — schema-level', () => {
|
|
97
|
-
const { surface } = build();
|
|
98
105
|
test('no sensitive fields in any output schema', () => {
|
|
99
106
|
assert_output_schemas_no_sensitive_fields(surface, sensitive_fields);
|
|
100
107
|
});
|
|
@@ -114,151 +121,125 @@ export const describe_data_exposure_tests = (options) => {
|
|
|
114
121
|
}
|
|
115
122
|
});
|
|
116
123
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
127
|
-
for (const factory of factories) {
|
|
128
|
-
const describe_fn = factory.skip ? describe.skip : describe;
|
|
129
|
-
describe_fn(`data exposure — runtime (${factory.name})`, () => {
|
|
130
|
-
let test_app;
|
|
131
|
-
let authed_account;
|
|
132
|
-
let admin_account;
|
|
133
|
-
let db;
|
|
134
|
-
beforeAll(async () => {
|
|
135
|
-
db = await factory.create();
|
|
136
|
-
test_app = await create_test_app({
|
|
137
|
-
session_options: options.session_options,
|
|
138
|
-
create_route_specs: options.create_route_specs,
|
|
139
|
-
db,
|
|
140
|
-
app_options: options.app_options,
|
|
141
|
-
});
|
|
142
|
-
authed_account = await test_app.create_account({
|
|
143
|
-
username: 'exposure_authed',
|
|
144
|
-
roles: [],
|
|
145
|
-
});
|
|
146
|
-
admin_account = await test_app.create_account({
|
|
147
|
-
username: 'exposure_admin',
|
|
148
|
-
roles: [ROLE_ADMIN],
|
|
149
|
-
});
|
|
124
|
+
describe('data exposure — runtime', () => {
|
|
125
|
+
let fixture;
|
|
126
|
+
let authed_account;
|
|
127
|
+
let admin_account;
|
|
128
|
+
beforeAll(async () => {
|
|
129
|
+
fixture = await options.setup_test();
|
|
130
|
+
authed_account = await fixture.create_account({
|
|
131
|
+
username: 'exposure_authed',
|
|
132
|
+
roles: [],
|
|
150
133
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
134
|
+
admin_account = await fixture.create_account({
|
|
135
|
+
username: 'exposure_admin',
|
|
136
|
+
roles: [ROLE_ADMIN],
|
|
154
137
|
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
let error_body;
|
|
173
|
-
try {
|
|
174
|
-
error_body = await res.clone().json();
|
|
175
|
-
}
|
|
176
|
-
catch {
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
assert_no_sensitive_fields_in_json(error_body, sensitive_fields, `unauthenticated ${route_key} (${res.status})`);
|
|
138
|
+
});
|
|
139
|
+
// Tests that don't fire authenticated requests run first — they don't
|
|
140
|
+
// invalidate sessions and are independent of test order.
|
|
141
|
+
test('unauthenticated error responses contain no sensitive fields', async () => {
|
|
142
|
+
const protected_specs = route_specs.filter((s) => !is_public_auth(s.auth));
|
|
143
|
+
for (const spec of protected_specs) {
|
|
144
|
+
const route_key = `${spec.method} ${spec.path}`;
|
|
145
|
+
if (skip_set.has(route_key))
|
|
146
|
+
continue;
|
|
147
|
+
const url = resolve_valid_path(spec.path, spec.params);
|
|
148
|
+
const res = await fixture.transport(url, {
|
|
149
|
+
method: spec.method,
|
|
150
|
+
headers: { host: 'localhost', origin: 'http://localhost:5173' },
|
|
151
|
+
});
|
|
152
|
+
if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
|
|
153
|
+
await res.body?.cancel();
|
|
154
|
+
continue;
|
|
180
155
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
test('admin routes return 403 for non-admin user', async () => {
|
|
185
|
-
const admin_specs = test_app.route_specs.filter((s) => s.auth.roles?.includes('admin') ?? false);
|
|
186
|
-
for (const spec of admin_specs) {
|
|
187
|
-
const route_key = `${spec.method} ${spec.path}`;
|
|
188
|
-
if (skip_set.has(route_key))
|
|
189
|
-
continue;
|
|
190
|
-
const url = resolve_valid_path(spec.path, spec.params);
|
|
191
|
-
const headers = authed_account.create_session_headers();
|
|
192
|
-
const res = await test_app.app.request(url, {
|
|
193
|
-
method: spec.method,
|
|
194
|
-
headers,
|
|
195
|
-
});
|
|
196
|
-
assert.strictEqual(res.status, 403, `${route_key} should return 403 for non-admin user`);
|
|
197
|
-
let error_body;
|
|
198
|
-
try {
|
|
199
|
-
error_body = await res.clone().json();
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
assert_no_sensitive_fields_in_json(error_body, sensitive_fields, `${route_key} 403`);
|
|
156
|
+
let error_body;
|
|
157
|
+
try {
|
|
158
|
+
error_body = await res.clone().json();
|
|
205
159
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
160
|
+
catch {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
assert_no_sensitive_fields_in_json(error_body, sensitive_fields, `unauthenticated ${route_key} (${res.status})`);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
// Cross-privilege test runs before 2xx tests — admin routes reject
|
|
167
|
+
// without calling handlers, so sessions stay intact.
|
|
168
|
+
test('admin routes return 403 for non-admin user', async () => {
|
|
169
|
+
const admin_specs = route_specs.filter((s) => s.auth.roles?.includes('admin') ?? false);
|
|
170
|
+
for (const spec of admin_specs) {
|
|
171
|
+
const route_key = `${spec.method} ${spec.path}`;
|
|
172
|
+
if (skip_set.has(route_key))
|
|
173
|
+
continue;
|
|
174
|
+
const url = resolve_valid_path(spec.path, spec.params);
|
|
175
|
+
const headers = authed_account.create_session_headers();
|
|
176
|
+
const res = await fixture.transport(url, {
|
|
177
|
+
method: spec.method,
|
|
178
|
+
headers,
|
|
219
179
|
});
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const body = generate_valid_body(spec.input);
|
|
228
|
-
const headers = pick_auth_headers(spec, test_app, authed_account, admin_account);
|
|
229
|
-
const request_init = {
|
|
230
|
-
method: spec.method,
|
|
231
|
-
headers: {
|
|
232
|
-
...headers,
|
|
233
|
-
...(body ? { 'content-type': 'application/json' } : {}),
|
|
234
|
-
},
|
|
235
|
-
...(body ? { body: JSON.stringify(body) } : {}),
|
|
236
|
-
};
|
|
237
|
-
const res = await test_app.app.request(url, request_init);
|
|
238
|
-
if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
|
|
239
|
-
await res.body?.cancel();
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (!res.ok)
|
|
243
|
-
continue;
|
|
244
|
-
let response_body;
|
|
245
|
-
try {
|
|
246
|
-
response_body = await res.clone().json();
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
assert_no_sensitive_fields_in_json(response_body, sensitive_fields, `${route_key} (${res.status})`);
|
|
252
|
-
// Admin-only field check applies to non-elevated routes with strict
|
|
253
|
-
// output schemas. Loose schemas (e.g. surface route returning JSON
|
|
254
|
-
// Schema representations) may contain admin field names as metadata.
|
|
255
|
-
if (!is_elevated &&
|
|
256
|
-
!is_null_schema(spec.output) &&
|
|
257
|
-
is_strict_object_schema(spec.output)) {
|
|
258
|
-
assert_no_sensitive_fields_in_json(response_body, admin_only_fields, `non-admin ${route_key} (${res.status})`);
|
|
259
|
-
}
|
|
180
|
+
assert.strictEqual(res.status, 403, `${route_key} should return 403 for non-admin user`);
|
|
181
|
+
let error_body;
|
|
182
|
+
try {
|
|
183
|
+
error_body = await res.clone().json();
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
continue;
|
|
260
187
|
}
|
|
188
|
+
assert_no_sensitive_fields_in_json(error_body, sensitive_fields, `${route_key} 403`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// 2xx tests run last — handlers like logout and session-revoke-all
|
|
192
|
+
// invalidate sessions as a side effect. Sort GET before POST so
|
|
193
|
+
// data-returning routes are checked before destructive routes fire.
|
|
194
|
+
test('all 2xx responses pass field blocklists', async () => {
|
|
195
|
+
// sort GET before mutations to check data-returning routes
|
|
196
|
+
// before destructive routes (logout, revoke-all) invalidate sessions
|
|
197
|
+
const sorted_specs = [...route_specs].sort((a, b) => {
|
|
198
|
+
if (a.method === 'GET' && b.method !== 'GET')
|
|
199
|
+
return -1;
|
|
200
|
+
if (a.method !== 'GET' && b.method === 'GET')
|
|
201
|
+
return 1;
|
|
202
|
+
return 0;
|
|
261
203
|
});
|
|
204
|
+
for (const spec of sorted_specs) {
|
|
205
|
+
const route_key = `${spec.method} ${spec.path}`;
|
|
206
|
+
if (skip_set.has(route_key))
|
|
207
|
+
continue;
|
|
208
|
+
// keeper auth (daemon token) is strictly more privileged than admin
|
|
209
|
+
const is_elevated = is_keeper_auth(spec.auth) || (spec.auth.roles?.includes('admin') ?? false);
|
|
210
|
+
const url = resolve_valid_path(spec.path, spec.params);
|
|
211
|
+
const body = generate_valid_body(spec.input);
|
|
212
|
+
const headers = pick_auth_headers(spec, fixture, authed_account, admin_account);
|
|
213
|
+
const request_init = {
|
|
214
|
+
method: spec.method,
|
|
215
|
+
headers: {
|
|
216
|
+
...headers,
|
|
217
|
+
...(body ? { 'content-type': 'application/json' } : {}),
|
|
218
|
+
},
|
|
219
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
220
|
+
};
|
|
221
|
+
const res = await fixture.transport(url, request_init);
|
|
222
|
+
if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
|
|
223
|
+
await res.body?.cancel();
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (!res.ok)
|
|
227
|
+
continue;
|
|
228
|
+
let response_body;
|
|
229
|
+
try {
|
|
230
|
+
response_body = await res.clone().json();
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
assert_no_sensitive_fields_in_json(response_body, sensitive_fields, `${route_key} (${res.status})`);
|
|
236
|
+
// Admin-only field check applies to non-elevated routes with strict
|
|
237
|
+
// output schemas. Loose schemas (e.g. surface route returning JSON
|
|
238
|
+
// Schema representations) may contain admin field names as metadata.
|
|
239
|
+
if (!is_elevated && !is_null_schema(spec.output) && is_strict_object_schema(spec.output)) {
|
|
240
|
+
assert_no_sensitive_fields_in_json(response_body, admin_only_fields, `non-admin ${route_key} (${res.status})`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
262
243
|
});
|
|
263
|
-
}
|
|
244
|
+
});
|
|
264
245
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
|
-
import type { Account, Actor } from '../auth/account_schema.js';
|
|
2
|
+
import type { Account, Actor, CreateRoleGrantInput, RoleGrant } from '../auth/account_schema.js';
|
|
3
3
|
import type { Db } from '../db/db.js';
|
|
4
4
|
/** The `{account, actor}` row pair returned by `create_test_account_with_actor`. */
|
|
5
5
|
export interface TestAccountWithActor {
|
|
@@ -19,4 +19,14 @@ export declare const create_test_account_with_actor: (db: Db, options: {
|
|
|
19
19
|
username: string;
|
|
20
20
|
password_hash?: string;
|
|
21
21
|
}) => Promise<TestAccountWithActor>;
|
|
22
|
+
/**
|
|
23
|
+
* Materialize a role_grant directly via `query_create_role_grant`, bypassing
|
|
24
|
+
* the production offer/accept consent flow. Use only when the test focuses on
|
|
25
|
+
* revoke or isolation semantics rather than the consent path itself — the
|
|
26
|
+
* schema permits null `source_offer_id` for exactly this case
|
|
27
|
+
* (`account_schema.ts`). For tests that exercise the production grant flow,
|
|
28
|
+
* drive `role_grant_offer_create_action_spec` + `role_grant_offer_accept_action_spec`
|
|
29
|
+
* over RPC instead.
|
|
30
|
+
*/
|
|
31
|
+
export declare const create_test_role_grant_direct: (db: Db, input: CreateRoleGrantInput) => Promise<RoleGrant>;
|
|
22
32
|
//# sourceMappingURL=db_entities.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db_entities.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db_entities.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"db_entities.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db_entities.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAC,MAAM,2BAA2B,CAAC;AAC/F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACb;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAC1C,IAAI,EAAE,EACN,SAAS;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAC,KACjD,OAAO,CAAC,oBAAoB,CAI7B,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,GACzC,IAAI,EAAE,EACN,OAAO,oBAAoB,KACzB,OAAO,CAAC,SAAS,CAAyC,CAAC"}
|
|
@@ -10,12 +10,14 @@ import './assert_dev_env.js';
|
|
|
10
10
|
* wrapper in every file.
|
|
11
11
|
*
|
|
12
12
|
* For full-fledged test accounts that also need an API token + signed
|
|
13
|
-
* session cookie + role_grants, use `
|
|
13
|
+
* session cookie + role_grants, use `bootstrap_test_keeper` (keeper) or
|
|
14
|
+
* `create_test_account_with_credentials` (additional accounts) from
|
|
14
15
|
* `app_server.ts` instead.
|
|
15
16
|
*
|
|
16
17
|
* @module
|
|
17
18
|
*/
|
|
18
19
|
import { query_create_account_with_actor } from '../auth/account_queries.js';
|
|
20
|
+
import { query_create_role_grant } from '../auth/role_grant_queries.js';
|
|
19
21
|
/**
|
|
20
22
|
* Create an `account` + `actor` row pair in the database for tests.
|
|
21
23
|
*
|
|
@@ -26,3 +28,13 @@ import { query_create_account_with_actor } from '../auth/account_queries.js';
|
|
|
26
28
|
* test suite.
|
|
27
29
|
*/
|
|
28
30
|
export const create_test_account_with_actor = async (db, options) => query_create_account_with_actor({ db }, { username: options.username, password_hash: options.password_hash ?? 'hash' });
|
|
31
|
+
/**
|
|
32
|
+
* Materialize a role_grant directly via `query_create_role_grant`, bypassing
|
|
33
|
+
* the production offer/accept consent flow. Use only when the test focuses on
|
|
34
|
+
* revoke or isolation semantics rather than the consent path itself — the
|
|
35
|
+
* schema permits null `source_offer_id` for exactly this case
|
|
36
|
+
* (`account_schema.ts`). For tests that exercise the production grant flow,
|
|
37
|
+
* drive `role_grant_offer_create_action_spec` + `role_grant_offer_accept_action_spec`
|
|
38
|
+
* over RPC instead.
|
|
39
|
+
*/
|
|
40
|
+
export const create_test_role_grant_direct = async (db, input) => query_create_role_grant({ db }, input);
|
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
2
|
import type { SessionOptions } from '../auth/session_cookie.js';
|
|
3
|
-
import type { AppServerContext } from '../server/app_server.js';
|
|
4
|
-
import type { RouteSpec } from '../http/route_spec.js';
|
|
5
|
-
import { type SuiteAppOptions } from './app_server.js';
|
|
6
|
-
import { type DbFactory } from './db.js';
|
|
7
3
|
import { type RpcEndpointsSuiteOption } from './rpc_helpers.js';
|
|
4
|
+
import type { BackendCapabilities } from './cross_backend/capabilities.js';
|
|
5
|
+
import type { SetupTest } from './cross_backend/setup.js';
|
|
6
|
+
import type { SurfaceSource } from './transports/surface_source.js';
|
|
8
7
|
/**
|
|
9
8
|
* Configuration for `describe_standard_integration_tests`.
|
|
10
9
|
*/
|
|
11
10
|
export interface StandardIntegrationTestOptions {
|
|
12
|
-
/** Session config for cookie-based auth. */
|
|
13
|
-
session_options: SessionOptions<string>;
|
|
14
|
-
/** Route spec factory — same one used in production. */
|
|
15
|
-
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
16
|
-
/** Optional overrides for `AppServerOptions`. */
|
|
17
|
-
app_options?: SuiteAppOptions;
|
|
18
11
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
12
|
+
* Per-test fixture-producing function. The integration suite calls
|
|
13
|
+
* this in every `test()` body — auth_integration_truncate_tables
|
|
14
|
+
* clears `account`, so each test re-bootstraps the keeper.
|
|
15
|
+
*/
|
|
16
|
+
setup_test: SetupTest;
|
|
17
|
+
/**
|
|
18
|
+
* Source of the app surface for route iteration and error-coverage
|
|
19
|
+
* scoping. Currently requires `kind: 'inline'` — the cross-process
|
|
20
|
+
* snapshot variant lands alongside the spawned-backend transport plumbing.
|
|
21
|
+
*/
|
|
22
|
+
surface_source: SurfaceSource;
|
|
23
|
+
/** Backend capability declarations — companion to `fixture.in_process` narrowing. */
|
|
24
|
+
capabilities: BackendCapabilities;
|
|
25
|
+
/**
|
|
26
|
+
* Session config — needed to resolve factory-form `rpc_endpoints`
|
|
27
|
+
* against a stub `AppServerContext` at setup time and to read
|
|
28
|
+
* `cookie_name` for manual cookie composition in the origin-verify
|
|
29
|
+
* cases.
|
|
21
30
|
*/
|
|
22
|
-
|
|
31
|
+
session_options: SessionOptions<string>;
|
|
23
32
|
/**
|
|
24
33
|
* RPC endpoint specs — required. This suite dispatches
|
|
25
34
|
* `account_verify`, `account_session_*`, and `account_token_*` via
|
|
@@ -28,16 +37,21 @@ export interface StandardIntegrationTestOptions {
|
|
|
28
37
|
* `require_rpc_endpoint_path` on setup so consumer projects see a
|
|
29
38
|
* clear setup error instead of confusing test failures.
|
|
30
39
|
*
|
|
31
|
-
* Accepts either an array (eager) or a factory
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `create_standard_rpc_actions(ctx.deps, {app_settings: ctx.app_settings})`
|
|
36
|
-
* pattern). The factory must return the same endpoint `path` regardless
|
|
37
|
-
* of ctx — it is invoked once at setup with a stub ctx for path lookup
|
|
38
|
-
* and again per-test by `create_app_server` for live dispatch.
|
|
40
|
+
* Accepts either an array (eager) or a factory — see `rpc_helpers.ts`
|
|
41
|
+
* for the union semantics. The factory must return the same endpoint
|
|
42
|
+
* `path` regardless of ctx — invoked once at setup with a stub ctx
|
|
43
|
+
* for path lookup; the running backend handles live dispatch.
|
|
39
44
|
*/
|
|
40
45
|
rpc_endpoints: RpcEndpointsSuiteOption;
|
|
46
|
+
/**
|
|
47
|
+
* Minimum error-coverage ratio to enforce on the scoped REST surface
|
|
48
|
+
* (login / logout / password / signup + the shared RPC endpoint).
|
|
49
|
+
* Default `DEFAULT_INTEGRATION_ERROR_COVERAGE` (0.2). Set to `0` to
|
|
50
|
+
* skip the assertion entirely — useful for consumers with minimal
|
|
51
|
+
* route sets whose declared error codes outpace the suite's
|
|
52
|
+
* denial-path drivers.
|
|
53
|
+
*/
|
|
54
|
+
error_coverage_min?: number;
|
|
41
55
|
}
|
|
42
56
|
/**
|
|
43
57
|
* Standard integration test suite for fuz_app auth routes.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAO9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,gCAAgC,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,aAAa,CAAC;IAC9B,qFAAqF;IACrF,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;;OAKG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IAy3CF,CAAC"}
|