@fuzdev/fuz_app 0.59.0 → 0.61.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 +5 -5
- package/dist/actions/action_codegen.d.ts +1 -1
- package/dist/actions/action_codegen.js +2 -2
- package/dist/actions/action_event_helpers.d.ts +3 -3
- package/dist/actions/action_event_helpers.js +8 -8
- package/dist/actions/action_event_types.d.ts +3 -3
- package/dist/actions/action_event_types.js +3 -3
- package/dist/actions/transports_ws_auth_guard.d.ts +2 -2
- package/dist/actions/transports_ws_auth_guard.js +3 -3
- package/dist/auth/CLAUDE.md +157 -15
- package/dist/auth/actor_lookup_action_specs.d.ts +127 -0
- package/dist/auth/actor_lookup_action_specs.d.ts.map +1 -0
- package/dist/auth/actor_lookup_action_specs.js +93 -0
- package/dist/auth/actor_lookup_actions.d.ts +19 -0
- package/dist/auth/actor_lookup_actions.d.ts.map +1 -0
- package/dist/auth/actor_lookup_actions.js +32 -0
- package/dist/auth/actor_lookup_queries.d.ts +44 -0
- package/dist/auth/actor_lookup_queries.d.ts.map +1 -0
- package/dist/auth/actor_lookup_queries.js +42 -0
- package/dist/auth/actor_search_action_specs.d.ts +166 -0
- package/dist/auth/actor_search_action_specs.d.ts.map +1 -0
- package/dist/auth/actor_search_action_specs.js +139 -0
- package/dist/auth/actor_search_actions.d.ts +31 -0
- package/dist/auth/actor_search_actions.d.ts.map +1 -0
- package/dist/auth/actor_search_actions.js +61 -0
- package/dist/auth/actor_search_queries.d.ts +75 -0
- package/dist/auth/actor_search_queries.d.ts.map +1 -0
- package/dist/auth/actor_search_queries.js +91 -0
- package/dist/auth/admin_actions.js +2 -2
- package/dist/auth/all_action_spec_registries.d.ts +55 -0
- package/dist/auth/all_action_spec_registries.d.ts.map +1 -0
- package/dist/auth/all_action_spec_registries.js +59 -0
- package/dist/auth/audit_emitter.d.ts +1 -1
- package/dist/auth/audit_emitter.js +2 -2
- package/dist/auth/audit_log_queries.d.ts +1 -1
- package/dist/auth/audit_log_queries.js +3 -3
- package/dist/auth/audit_log_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.js +1 -1
- package/dist/auth/audit_log_schema.d.ts +5 -5
- package/dist/auth/audit_log_schema.js +7 -7
- package/dist/auth/auth_ddl.d.ts +7 -0
- package/dist/auth/auth_ddl.d.ts.map +1 -1
- package/dist/auth/auth_ddl.js +8 -0
- package/dist/auth/credential_type_schema.d.ts +1 -1
- package/dist/auth/credential_type_schema.js +3 -3
- package/dist/auth/grant_path_schema.d.ts +1 -1
- package/dist/auth/grant_path_schema.js +3 -3
- package/dist/auth/migrations.d.ts +4 -4
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +7 -6
- package/dist/auth/role_grant_offer_actions.js +2 -2
- package/dist/auth/role_grant_offer_notifications.d.ts +2 -2
- package/dist/auth/role_grant_offer_notifications.js +2 -2
- package/dist/auth/role_grant_queries.d.ts +21 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +31 -0
- package/dist/auth/role_schema.d.ts +2 -2
- package/dist/auth/role_schema.js +3 -3
- package/dist/auth/self_service_role_actions.d.ts +1 -1
- package/dist/auth/self_service_role_actions.js +2 -2
- package/dist/auth/session_cookie.d.ts +1 -1
- package/dist/auth/session_cookie.js +1 -1
- package/dist/auth/session_middleware.d.ts +1 -1
- package/dist/auth/session_middleware.js +5 -5
- package/dist/rate_limiter.d.ts +5 -5
- package/dist/rate_limiter.js +6 -6
- package/dist/realtime/sse_auth_guard.d.ts +3 -3
- package/dist/realtime/sse_auth_guard.js +4 -4
- package/dist/server/app_backend.d.ts +3 -3
- package/dist/server/app_backend.js +4 -4
- package/dist/server/app_server.d.ts +1 -1
- package/dist/server/app_server.js +10 -10
- package/dist/testing/CLAUDE.md +22 -12
- package/dist/testing/admin_integration.js +4 -4
- package/dist/testing/app_server.d.ts +1 -1
- package/dist/testing/app_server.js +2 -2
- package/dist/testing/attack_surface.d.ts +4 -4
- package/dist/testing/attack_surface.js +6 -6
- package/dist/testing/audit_completeness.js +4 -4
- package/dist/testing/data_exposure.d.ts +2 -2
- package/dist/testing/data_exposure.js +7 -7
- package/dist/testing/db.d.ts +8 -8
- package/dist/testing/db.js +11 -11
- package/dist/testing/integration.js +4 -4
- package/dist/testing/integration_helpers.d.ts +6 -6
- package/dist/testing/integration_helpers.js +7 -7
- package/dist/testing/rate_limiting.js +4 -4
- package/dist/testing/round_trip.js +2 -2
- package/dist/testing/rpc_round_trip.js +2 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +23 -2
- package/dist/testing/sse_round_trip.js +2 -2
- package/dist/testing/surface_invariants.d.ts +4 -4
- package/dist/testing/surface_invariants.js +5 -5
- package/dist/ui/AccountSessions.svelte +21 -6
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +32 -25
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -3
- package/dist/ui/AdminInvites.svelte +20 -15
- package/dist/ui/AdminOverview.svelte +19 -21
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminRoleGrantHistory.svelte +3 -3
- package/dist/ui/AdminSessions.svelte +19 -21
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSettings.svelte +1 -3
- package/dist/ui/AdminSettings.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +123 -69
- package/dist/ui/ConfirmButton.svelte +82 -24
- package/dist/ui/ConfirmButton.svelte.d.ts +8 -34
- package/dist/ui/ConfirmButton.svelte.d.ts.map +1 -1
- package/dist/ui/OpenSignupToggle.svelte +6 -4
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/RoleGrantOfferForm.svelte +4 -4
- package/dist/ui/RoleGrantOfferHistory.svelte +3 -3
- package/dist/ui/RoleGrantOfferInbox.svelte +10 -6
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +17 -7
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +32 -33
- package/dist/ui/admin_accounts_state.svelte.d.ts +48 -17
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +58 -76
- package/dist/ui/admin_invites_state.svelte.d.ts +14 -7
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +32 -48
- package/dist/ui/admin_sessions_state.svelte.d.ts +15 -8
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +30 -47
- package/dist/ui/app_settings_state.svelte.d.ts +8 -3
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +19 -27
- package/dist/ui/async_slot.svelte.d.ts +173 -0
- package/dist/ui/async_slot.svelte.d.ts.map +1 -0
- package/dist/ui/async_slot.svelte.js +241 -0
- package/dist/ui/audit_log_state.svelte.d.ts +8 -2
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +19 -18
- package/dist/ui/keyed_async_slot.svelte.d.ts +139 -0
- package/dist/ui/keyed_async_slot.svelte.d.ts.map +1 -0
- package/dist/ui/keyed_async_slot.svelte.js +177 -0
- package/dist/ui/role_grant_offers_state.svelte.d.ts +39 -7
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
- package/dist/ui/role_grant_offers_state.svelte.js +34 -15
- package/dist/ui/table_state.svelte.d.ts +10 -7
- package/dist/ui/table_state.svelte.d.ts.map +1 -1
- package/dist/ui/table_state.svelte.js +11 -8
- package/package.json +1 -1
- package/dist/ui/loadable.svelte.d.ts +0 -60
- package/dist/ui/loadable.svelte.d.ts.map +0 -1
- package/dist/ui/loadable.svelte.js +0 -80
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -91,7 +91,7 @@ from `app_server.ts` instead.
|
|
|
91
91
|
## Database — `db.ts`
|
|
92
92
|
|
|
93
93
|
Factory builders for parameterized DB tests. Consumer projects pass their
|
|
94
|
-
`init_schema` callback (which calls `run_migrations(db, [
|
|
94
|
+
`init_schema` callback (which calls `run_migrations(db, [auth_migration_ns, ...app_migrations])`);
|
|
95
95
|
factories accept any migration namespace set.
|
|
96
96
|
|
|
97
97
|
| Helper | Role |
|
|
@@ -101,10 +101,10 @@ factories accept any migration namespace set.
|
|
|
101
101
|
| `reset_pglite(db)` | `DROP SCHEMA public CASCADE` + recreate. Reuses a live PGlite instance. |
|
|
102
102
|
| `create_pglite_factory(init_schema)` | In-memory; no external deps; `skip: false`. See WASM caching below. |
|
|
103
103
|
| `create_pg_factory(init_schema, test_url?)` | PostgreSQL; `skip: true` when `test_url` is missing; drops `schema_version` before `init_schema` so migrations re-evaluate against actual tables (prevents stale tracker rows from skipping migrations when DDL changes between test sessions); pool is reused + cleaned up across `create()` calls. |
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `drop_auth_schema(db)` | `DROP TABLE IF EXISTS <table> CASCADE` for every entry in `
|
|
104
|
+
| `auth_truncate_tables` | `['invite', 'api_token', 'auth_session', 'role_grant', 'role_grant_offer', 'actor', 'account']` in FK-safe order. Excludes `audit_log` — unit DB tests don't need to truncate it. |
|
|
105
|
+
| `auth_integration_truncate_tables` | `auth_truncate_tables + ['audit_log']` — for integration suites that exercise the audit path. |
|
|
106
|
+
| `auth_drop_tables` | Full set from `auth_migrations` in drop order; call `drop_auth_schema(db)` at the top of `init_schema` on persistent pg databases that may hold stale DDL from previous fuz_app versions. |
|
|
107
|
+
| `drop_auth_schema(db)` | `DROP TABLE IF EXISTS <table> CASCADE` for every entry in `auth_drop_tables` plus `schema_version`. Safe on fresh DBs. |
|
|
108
108
|
| `create_describe_db(factories, truncate_tables)` | Returns `describe_db(name, fn)` that runs `fn(get_db)` once per factory, inside a `describe` block with shared `beforeAll(create)` + `beforeEach(TRUNCATE)` + `afterAll(close)`. Skipped factories use `describe.skip`. |
|
|
109
109
|
| `log_db_factory_status(factories)` | Console summary of enabled / skipped factories. |
|
|
110
110
|
|
|
@@ -234,13 +234,13 @@ Tightness audit:
|
|
|
234
234
|
classifies every route × status combination as `'literal' | 'enum' | 'generic'`.
|
|
235
235
|
- `assert_error_schema_tightness(surface, options?)` — fails routes below a
|
|
236
236
|
threshold (`min_specificity`, default `'enum'`) with `allowlist` + `ignore_statuses` escape hatches.
|
|
237
|
-
- `
|
|
237
|
+
- `fuz_app_stock_route_tightness_allowlist` — currently empty. Every
|
|
238
238
|
fuz_app-shipped route (account login/password/bootstrap/signup, db
|
|
239
239
|
health/tables/:name/tables/:name/rows/:id) has been tightened in place to
|
|
240
240
|
`z.enum([...])` / `z.literal(...)` against every emit-site code. Kept as a
|
|
241
241
|
forward-compatibility hook for future stock routes that need an interim
|
|
242
242
|
exemption; paths assume the standard `/api/account` + `/api/db` prefixes.
|
|
243
|
-
- `
|
|
243
|
+
- `default_error_schema_tightness` — `{ignore_statuses: [401, 403, 429], allowlist: fuz_app_stock_route_tightness_allowlist}`.
|
|
244
244
|
Applied by `describe_standard_attack_surface_tests` when
|
|
245
245
|
`error_schema_tightness` is omitted; pass an override config or `null` to
|
|
246
246
|
opt out.
|
|
@@ -323,8 +323,8 @@ Walks Zod schemas to generate valid values for adversarial/round-trip tests.
|
|
|
323
323
|
| `check_error_response_fields(body)` | Returns the list of fields outside `KNOWN_SAFE_ERROR_FIELDS` (`error`, `issues`, `required_roles`, `required_credential_types`, `retry_after`, `has_references`, `ok`). |
|
|
324
324
|
| `assert_no_error_info_leakage(body, context)` | Rejects field-name patterns (`stack`, `trace`, `sql`, …) + value patterns (`node_modules`, stack-like `at …`, `.ts:NN`). |
|
|
325
325
|
| `assert_rate_limit_retry_after_header(response, body)` | `Retry-After` numeric header equals `Math.ceil(body.retry_after)`. |
|
|
326
|
-
| `
|
|
327
|
-
| `
|
|
326
|
+
| `sensitive_field_blocklist` | `['password_hash', 'token_hash']` — never in any response body. |
|
|
327
|
+
| `admin_only_field_blocklist` | `['updated_by', 'created_by']` — never in non-admin response bodies. |
|
|
328
328
|
| `collect_json_keys_recursive(value)` | Deep walk; returns `Set<string>` of every key at every nesting depth. |
|
|
329
329
|
| `assert_no_sensitive_fields_in_json(body, blocklist, context)` | Rejects any key in the blocklist at any depth. |
|
|
330
330
|
| `pick_auth_headers(spec, test_app, authed_account, admin_account)` | `RouteAuth` → appropriate test credentials; role `admin` uses `admin_account`, other roles use bootstrapped keeper, `keeper` uses daemon token. |
|
|
@@ -337,7 +337,7 @@ Single-call bundle of 5 top-level groups (10 named tests + every
|
|
|
337
337
|
adversarial case per route):
|
|
338
338
|
|
|
339
339
|
1. **attack surface snapshot** — `matches committed snapshot`, `is deterministic`.
|
|
340
|
-
2. **attack surface structure** — `only expected public routes`, `full middleware stack on API routes`, `surface invariants`, `security policy`, `error schema tightness` (logs counts and asserts against `
|
|
340
|
+
2. **attack surface structure** — `only expected public routes`, `full middleware stack on API routes`, `surface invariants`, `security policy`, `error schema tightness` (logs counts and asserts against `default_error_schema_tightness` by default; pass an override config or `null` via `error_schema_tightness`).
|
|
341
341
|
3. **adversarial HTTP auth enforcement** — `unauthenticated → 401`, `wrong role → 403` × roles, `authenticated without role → 403`, `keeper routes reject session credential → 403`, `correct auth passes guard`.
|
|
342
342
|
4. **adversarial input validation** — delegated to `describe_adversarial_input`.
|
|
343
343
|
5. **adversarial 404 response validation** — delegated to `describe_adversarial_404`.
|
|
@@ -511,8 +511,8 @@ new arrivals (default 1000ms); drops the waiter on timeout so the
|
|
|
511
511
|
Six tests in two top-level groups:
|
|
512
512
|
|
|
513
513
|
1. **schema-level** (3 tests, no DB) — walks JSON Schema representations:
|
|
514
|
-
- `no sensitive fields in any output schema` — `
|
|
515
|
-
- `no admin-only fields in non-admin output schemas` — `
|
|
514
|
+
- `no sensitive fields in any output schema` — `sensitive_field_blocklist`
|
|
515
|
+
- `no admin-only fields in non-admin output schemas` — `admin_only_field_blocklist`
|
|
516
516
|
- `no sensitive fields in any error schema`
|
|
517
517
|
2. **runtime** (3 tests, DB-backed via `create_test_app`):
|
|
518
518
|
- `unauthenticated error responses contain no sensitive fields`
|
|
@@ -699,6 +699,16 @@ deps — no DB needed.
|
|
|
699
699
|
|
|
700
700
|
Options: `{build: () => AppSurfaceSpec, roles: Array<string>}`.
|
|
701
701
|
|
|
702
|
+
**Opt-in bundles need their own per-bundle suite file.** Action bundles
|
|
703
|
+
not folded into `create_standard_rpc_actions` (today `self_service_role_actions`
|
|
704
|
+
and `actor_lookup_actions`) get zero adversarial / round-trip coverage
|
|
705
|
+
from `describe_rpc_attack_surface_tests` + `describe_rpc_round_trip_tests`
|
|
706
|
+
unless the consumer ships a `<module>.rpc_suites.db.test.ts` mounting the
|
|
707
|
+
opt-in factory on the RPC endpoint and calling both suites. See
|
|
708
|
+
`../../test/CLAUDE.md` §Composable Test Suites for the obligation note
|
|
709
|
+
and `actor_lookup_actions.rpc_suites.db.test.ts` /
|
|
710
|
+
`role_grant_offer_actions.rpc_suites.db.test.ts` as templates.
|
|
711
|
+
|
|
702
712
|
## Cross-cutting conventions
|
|
703
713
|
|
|
704
714
|
- **`assert` from vitest, not `expect`.** Project-wide convention
|
|
@@ -28,9 +28,9 @@ 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 {
|
|
31
|
+
import { auth_migration_ns } from '../auth/migrations.js';
|
|
32
32
|
import { create_test_app } from './app_server.js';
|
|
33
|
-
import { create_pglite_factory, create_describe_db,
|
|
33
|
+
import { create_pglite_factory, create_describe_db, auth_integration_truncate_tables, } from './db.js';
|
|
34
34
|
import { find_auth_route } from './integration_helpers.js';
|
|
35
35
|
import { run_migrations } from '../db/migrate.js';
|
|
36
36
|
import { ErrorCoverageCollector, assert_error_coverage, DEFAULT_INTEGRATION_ERROR_COVERAGE, } from './error_coverage.js';
|
|
@@ -88,10 +88,10 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
88
88
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
89
89
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
90
90
|
const init_schema = async (db) => {
|
|
91
|
-
await run_migrations(db, [
|
|
91
|
+
await run_migrations(db, [auth_migration_ns]);
|
|
92
92
|
};
|
|
93
93
|
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
94
|
-
const describe_db = create_describe_db(factories,
|
|
94
|
+
const describe_db = create_describe_db(factories, auth_integration_truncate_tables);
|
|
95
95
|
describe_db('standard_admin_integration', (get_db) => {
|
|
96
96
|
const { cookie_name } = options.session_options;
|
|
97
97
|
const { role_specs } = options.roles;
|
|
@@ -120,7 +120,7 @@ export interface TestAppServerOptions {
|
|
|
120
120
|
*
|
|
121
121
|
* Use when the consumer registers extra event types via
|
|
122
122
|
* `create_audit_log_config({extra_events})` — without this, emits for
|
|
123
|
-
* those events fall back to `
|
|
123
|
+
* those events fall back to `builtin_audit_log_config` and log
|
|
124
124
|
* "unknown event_type" warnings.
|
|
125
125
|
*/
|
|
126
126
|
audit_log_config?: AuditLogConfig;
|
|
@@ -10,7 +10,7 @@ import { generate_session_token, hash_session_token, AUTH_SESSION_LIFETIME_MS, q
|
|
|
10
10
|
import { query_create_api_token } from '../auth/api_token_queries.js';
|
|
11
11
|
import { create_session_cookie_value } from '../auth/session_cookie.js';
|
|
12
12
|
import { run_migrations } from '../db/migrate.js';
|
|
13
|
-
import {
|
|
13
|
+
import { auth_migration_ns } from '../auth/migrations.js';
|
|
14
14
|
import { create_audit_emitter } from '../auth/audit_emitter.js';
|
|
15
15
|
import { create_app_server, } from '../server/app_server.js';
|
|
16
16
|
import { generate_daemon_token, DAEMON_TOKEN_HEADER, } from '../auth/daemon_token.js';
|
|
@@ -33,7 +33,7 @@ export const TEST_COOKIE_SECRET = 'a'.repeat(64);
|
|
|
33
33
|
// Shares the WASM instance cache from test_db.ts, avoiding redundant cold starts
|
|
34
34
|
// within the same vitest worker thread. Schema is reset on each create() call.
|
|
35
35
|
const fallback_pglite_factory = create_pglite_factory(async (db) => {
|
|
36
|
-
await run_migrations(db, [
|
|
36
|
+
await run_migrations(db, [auth_migration_ns]);
|
|
37
37
|
});
|
|
38
38
|
/**
|
|
39
39
|
* Bootstrap a test account with credentials.
|
|
@@ -20,7 +20,7 @@ export interface AdversarialTestOptions {
|
|
|
20
20
|
export declare const describe_adversarial_auth: (options: AdversarialTestOptions) => void;
|
|
21
21
|
/**
|
|
22
22
|
* Merge a consumer's `error_schema_tightness` option with
|
|
23
|
-
* `
|
|
23
|
+
* `default_error_schema_tightness` so `allowlist` and `ignore_statuses` are
|
|
24
24
|
* additive rather than replacing.
|
|
25
25
|
*
|
|
26
26
|
* - `undefined` → return the default as-is.
|
|
@@ -52,9 +52,9 @@ export interface StandardAttackSurfaceOptions {
|
|
|
52
52
|
security_policy?: SurfaceSecurityPolicyOptions;
|
|
53
53
|
/**
|
|
54
54
|
* Error schema tightness assertion config. Defaults to
|
|
55
|
-
* `
|
|
55
|
+
* `default_error_schema_tightness` (ignores 401/403/429,
|
|
56
56
|
* `min_specificity: 'enum'`, allowlist seeded with
|
|
57
|
-
* `
|
|
57
|
+
* `fuz_app_stock_route_tightness_allowlist`).
|
|
58
58
|
*
|
|
59
59
|
* Consumer-supplied `allowlist` and `ignore_statuses` are **additive** —
|
|
60
60
|
* the suite merges them underneath the stock defaults, so project-specific
|
|
@@ -74,7 +74,7 @@ export interface StandardAttackSurfaceOptions {
|
|
|
74
74
|
* 4. Middleware stack — every API route has the full middleware chain
|
|
75
75
|
* 5. Surface invariants — structural assertions (error schemas, descriptions, duplicates, consistency)
|
|
76
76
|
* 6. Security policy — rate limiting on sensitive routes, no unexpected public mutations, method conventions
|
|
77
|
-
* 7. Error schema tightness — informational log of generic vs specific error schemas, plus assertion against `
|
|
77
|
+
* 7. Error schema tightness — informational log of generic vs specific error schemas, plus assertion against `default_error_schema_tightness` by default (opt out with `error_schema_tightness: null`)
|
|
78
78
|
* 8. Adversarial auth — unauthenticated/wrong-role/correct-auth enforcement
|
|
79
79
|
* 9. Adversarial input — input body and params validation
|
|
80
80
|
* 10. Adversarial 404 — stub 404 handlers, validate response bodies against declared schemas
|
|
@@ -15,7 +15,7 @@ import './assert_dev_env.js';
|
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
17
17
|
import { test, assert, describe } from 'vitest';
|
|
18
|
-
import { assert_surface_invariants, assert_surface_security_policy, audit_error_schema_tightness, assert_error_schema_tightness,
|
|
18
|
+
import { assert_surface_invariants, assert_surface_security_policy, audit_error_schema_tightness, assert_error_schema_tightness, default_error_schema_tightness, fuz_app_stock_route_tightness_allowlist, } from './surface_invariants.js';
|
|
19
19
|
import { describe_adversarial_input } from './adversarial_input.js';
|
|
20
20
|
import { describe_adversarial_404 } from './adversarial_404.js';
|
|
21
21
|
import { create_test_app_from_specs, create_test_request_context, create_auth_test_apps, select_auth_app, resolve_test_path, } from './auth_apps.js';
|
|
@@ -164,7 +164,7 @@ export const describe_adversarial_auth = (options) => {
|
|
|
164
164
|
// --- Standard attack surface test suite ---
|
|
165
165
|
/**
|
|
166
166
|
* Merge a consumer's `error_schema_tightness` option with
|
|
167
|
-
* `
|
|
167
|
+
* `default_error_schema_tightness` so `allowlist` and `ignore_statuses` are
|
|
168
168
|
* additive rather than replacing.
|
|
169
169
|
*
|
|
170
170
|
* - `undefined` → return the default as-is.
|
|
@@ -181,11 +181,11 @@ export const resolve_standard_error_schema_tightness = (consumer) => {
|
|
|
181
181
|
if (consumer === null)
|
|
182
182
|
return null;
|
|
183
183
|
return {
|
|
184
|
-
...
|
|
184
|
+
...default_error_schema_tightness,
|
|
185
185
|
...consumer,
|
|
186
|
-
allowlist: [...
|
|
186
|
+
allowlist: [...fuz_app_stock_route_tightness_allowlist, ...(consumer?.allowlist ?? [])],
|
|
187
187
|
ignore_statuses: [
|
|
188
|
-
...(
|
|
188
|
+
...(default_error_schema_tightness.ignore_statuses ?? []),
|
|
189
189
|
...(consumer?.ignore_statuses ?? []),
|
|
190
190
|
],
|
|
191
191
|
};
|
|
@@ -200,7 +200,7 @@ export const resolve_standard_error_schema_tightness = (consumer) => {
|
|
|
200
200
|
* 4. Middleware stack — every API route has the full middleware chain
|
|
201
201
|
* 5. Surface invariants — structural assertions (error schemas, descriptions, duplicates, consistency)
|
|
202
202
|
* 6. Security policy — rate limiting on sensitive routes, no unexpected public mutations, method conventions
|
|
203
|
-
* 7. Error schema tightness — informational log of generic vs specific error schemas, plus assertion against `
|
|
203
|
+
* 7. Error schema tightness — informational log of generic vs specific error schemas, plus assertion against `default_error_schema_tightness` by default (opt out with `error_schema_tightness: null`)
|
|
204
204
|
* 8. Adversarial auth — unauthenticated/wrong-role/correct-auth enforcement
|
|
205
205
|
* 9. Adversarial input — input body and params validation
|
|
206
206
|
* 10. Adversarial 404 — stub 404 handlers, validate response bodies against declared schemas
|
|
@@ -15,9 +15,9 @@ import './assert_dev_env.js';
|
|
|
15
15
|
import { describe, test, assert } from 'vitest';
|
|
16
16
|
import { ROLE_KEEPER, ROLE_ADMIN } from '../auth/role_schema.js';
|
|
17
17
|
import { AUDIT_EVENT_TYPES } from '../auth/audit_log_schema.js';
|
|
18
|
-
import {
|
|
18
|
+
import { auth_migration_ns } from '../auth/migrations.js';
|
|
19
19
|
import { create_test_app, } from './app_server.js';
|
|
20
|
-
import { create_pglite_factory, create_describe_db,
|
|
20
|
+
import { create_pglite_factory, create_describe_db, auth_integration_truncate_tables, } from './db.js';
|
|
21
21
|
import { find_auth_route } from './integration_helpers.js';
|
|
22
22
|
import { run_migrations } from '../db/migrate.js';
|
|
23
23
|
import { query_accept_offer } from '../auth/role_grant_offer_queries.js';
|
|
@@ -75,10 +75,10 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
75
75
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
76
76
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
77
77
|
const init_schema = async (db) => {
|
|
78
|
-
await run_migrations(db, [
|
|
78
|
+
await run_migrations(db, [auth_migration_ns]);
|
|
79
79
|
};
|
|
80
80
|
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
81
|
-
const describe_db = create_describe_db(factories,
|
|
81
|
+
const describe_db = create_describe_db(factories, auth_integration_truncate_tables);
|
|
82
82
|
describe_db('audit_log_completeness', (get_db) => {
|
|
83
83
|
// --- Account routes ---
|
|
84
84
|
describe('account mutation audit events', () => {
|
|
@@ -27,9 +27,9 @@ export interface DataExposureTestOptions {
|
|
|
27
27
|
session_options: SessionOptions<string>;
|
|
28
28
|
/** Route spec factory for runtime tests. */
|
|
29
29
|
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
30
|
-
/** Fields that must never appear in any response. Default: `
|
|
30
|
+
/** Fields that must never appear in any response. Default: `sensitive_field_blocklist`. */
|
|
31
31
|
sensitive_fields?: ReadonlyArray<string>;
|
|
32
|
-
/** Fields that must not appear in non-admin responses. Default: `
|
|
32
|
+
/** Fields that must not appear in non-admin responses. Default: `admin_only_field_blocklist`. */
|
|
33
33
|
admin_only_fields?: ReadonlyArray<string>;
|
|
34
34
|
/** Optional overrides for `AppServerOptions`. */
|
|
35
35
|
app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
|
|
@@ -16,10 +16,10 @@ import { create_test_app } from './app_server.js';
|
|
|
16
16
|
import { create_pglite_factory } from './db.js';
|
|
17
17
|
import { resolve_valid_path, generate_valid_body } from './schema_generators.js';
|
|
18
18
|
import { run_migrations } from '../db/migrate.js';
|
|
19
|
-
import {
|
|
19
|
+
import { auth_migration_ns } from '../auth/migrations.js';
|
|
20
20
|
import { is_null_schema, is_strict_object_schema } from '../http/schema_helpers.js';
|
|
21
21
|
import { is_keeper_auth, is_public_auth } from '../http/auth_shape.js';
|
|
22
|
-
import {
|
|
22
|
+
import { sensitive_field_blocklist, admin_only_field_blocklist, assert_no_sensitive_fields_in_json, pick_auth_headers, } from './integration_helpers.js';
|
|
23
23
|
// --- Schema introspection ---
|
|
24
24
|
/**
|
|
25
25
|
* Recursively collect all property names from a JSON Schema.
|
|
@@ -58,7 +58,7 @@ export const collect_json_schema_property_names = (schema) => {
|
|
|
58
58
|
/**
|
|
59
59
|
* Assert that no output schema in the surface contains sensitive field names.
|
|
60
60
|
*/
|
|
61
|
-
export const assert_output_schemas_no_sensitive_fields = (surface, sensitive_fields =
|
|
61
|
+
export const assert_output_schemas_no_sensitive_fields = (surface, sensitive_fields = sensitive_field_blocklist) => {
|
|
62
62
|
for (const route of surface.routes) {
|
|
63
63
|
if (route.output_schema === null)
|
|
64
64
|
continue;
|
|
@@ -71,7 +71,7 @@ export const assert_output_schemas_no_sensitive_fields = (surface, sensitive_fie
|
|
|
71
71
|
/**
|
|
72
72
|
* Assert that non-admin route output schemas don't contain admin-only fields.
|
|
73
73
|
*/
|
|
74
|
-
export const assert_non_admin_schemas_no_admin_fields = (surface, admin_only_fields =
|
|
74
|
+
export const assert_non_admin_schemas_no_admin_fields = (surface, admin_only_fields = admin_only_field_blocklist) => {
|
|
75
75
|
const non_admin = surface.routes.filter((r) => !is_keeper_auth(r.auth) && !(r.auth.roles?.includes('admin') ?? false));
|
|
76
76
|
for (const route of non_admin) {
|
|
77
77
|
if (route.output_schema === null)
|
|
@@ -92,7 +92,7 @@ export const assert_non_admin_schemas_no_admin_fields = (surface, admin_only_fie
|
|
|
92
92
|
* contain no sensitive fields
|
|
93
93
|
*/
|
|
94
94
|
export const describe_data_exposure_tests = (options) => {
|
|
95
|
-
const { build, sensitive_fields =
|
|
95
|
+
const { build, sensitive_fields = sensitive_field_blocklist, admin_only_fields = admin_only_field_blocklist, } = options;
|
|
96
96
|
describe('data exposure — schema-level', () => {
|
|
97
97
|
const { surface } = build();
|
|
98
98
|
test('no sensitive fields in any output schema', () => {
|
|
@@ -118,10 +118,10 @@ export const describe_data_exposure_tests = (options) => {
|
|
|
118
118
|
};
|
|
119
119
|
// --- Runtime tests ---
|
|
120
120
|
const describe_data_exposure_runtime_tests = (options) => {
|
|
121
|
-
const { sensitive_fields =
|
|
121
|
+
const { sensitive_fields = sensitive_field_blocklist, admin_only_fields = admin_only_field_blocklist, } = options;
|
|
122
122
|
const skip_set = new Set(options.skip_routes);
|
|
123
123
|
const init_schema = async (db) => {
|
|
124
|
-
await run_migrations(db, [
|
|
124
|
+
await run_migrations(db, [auth_migration_ns]);
|
|
125
125
|
};
|
|
126
126
|
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
127
127
|
for (const factory of factories) {
|
package/dist/testing/db.d.ts
CHANGED
|
@@ -61,25 +61,25 @@ export declare const create_pg_factory: (init_schema: (db: Db) => Promise<void>,
|
|
|
61
61
|
*
|
|
62
62
|
* Consumer projects can spread this into their own list and append app-specific tables.
|
|
63
63
|
*/
|
|
64
|
-
export declare const
|
|
64
|
+
export declare const auth_truncate_tables: string[];
|
|
65
65
|
/**
|
|
66
66
|
* Auth tables including `audit_log` — for integration tests that exercise
|
|
67
67
|
* the full middleware stack (login, admin, rate limiting).
|
|
68
68
|
*
|
|
69
|
-
* Separate from `
|
|
69
|
+
* Separate from `auth_truncate_tables` because unit-level DB tests that don't
|
|
70
70
|
* touch audit logging don't need to truncate it.
|
|
71
71
|
*/
|
|
72
|
-
export declare const
|
|
72
|
+
export declare const auth_integration_truncate_tables: string[];
|
|
73
73
|
/**
|
|
74
74
|
* All auth tables in drop order (children first for FK safety).
|
|
75
75
|
*
|
|
76
|
-
* The full set created by `
|
|
77
|
-
* test DB initialization. `
|
|
76
|
+
* The full set created by `auth_migrations` — use for clean-slate
|
|
77
|
+
* test DB initialization. `auth_truncate_tables` is the subset for
|
|
78
78
|
* between-test data cleanup (excludes `audit_log`).
|
|
79
79
|
*
|
|
80
|
-
* When adding tables to `
|
|
80
|
+
* When adding tables to `auth_migrations`, add them here too.
|
|
81
81
|
*/
|
|
82
|
-
export declare const
|
|
82
|
+
export declare const auth_drop_tables: readonly ["app_settings", "invite", "audit_log", "api_token", "auth_session", "role_grant", "role_grant_offer", "actor", "account", "bootstrap_lock"];
|
|
83
83
|
/**
|
|
84
84
|
* Drop all auth tables and schema version tracking for a clean slate.
|
|
85
85
|
*
|
|
@@ -89,7 +89,7 @@ export declare const AUTH_DROP_TABLES: readonly ["app_settings", "invite", "audi
|
|
|
89
89
|
* Safe on fresh databases (`IF EXISTS` on all statements). No-op effect for
|
|
90
90
|
* PGlite (already fresh), but harmless to call unconditionally.
|
|
91
91
|
*
|
|
92
|
-
* @mutates db - drops every table in `
|
|
92
|
+
* @mutates db - drops every table in `auth_drop_tables` plus `schema_version`.
|
|
93
93
|
*/
|
|
94
94
|
export declare const drop_auth_schema: (db: Db) => Promise<void>;
|
|
95
95
|
/**
|
package/dist/testing/db.js
CHANGED
|
@@ -10,10 +10,10 @@ import './assert_dev_env.js';
|
|
|
10
10
|
* ```ts
|
|
11
11
|
* import {create_pglite_factory, create_pg_factory} from '@fuzdev/fuz_app/testing/db.js';
|
|
12
12
|
* import {run_migrations} from '@fuzdev/fuz_app/db/migrate.js';
|
|
13
|
-
* import {
|
|
13
|
+
* import {auth_migration_ns} from '@fuzdev/fuz_app/auth/migrations.js';
|
|
14
14
|
*
|
|
15
15
|
* const init_schema = async (db) => {
|
|
16
|
-
* await run_migrations(db, [
|
|
16
|
+
* await run_migrations(db, [auth_migration_ns]);
|
|
17
17
|
* };
|
|
18
18
|
* const pglite_factory = create_pglite_factory(init_schema);
|
|
19
19
|
* const pg_factory = create_pg_factory(init_schema, process.env.TEST_DATABASE_URL);
|
|
@@ -160,7 +160,7 @@ export const create_pg_factory = (init_schema, test_url) => {
|
|
|
160
160
|
*
|
|
161
161
|
* Consumer projects can spread this into their own list and append app-specific tables.
|
|
162
162
|
*/
|
|
163
|
-
export const
|
|
163
|
+
export const auth_truncate_tables = [
|
|
164
164
|
'invite',
|
|
165
165
|
'api_token',
|
|
166
166
|
'auth_session',
|
|
@@ -173,20 +173,20 @@ export const AUTH_TRUNCATE_TABLES = [
|
|
|
173
173
|
* Auth tables including `audit_log` — for integration tests that exercise
|
|
174
174
|
* the full middleware stack (login, admin, rate limiting).
|
|
175
175
|
*
|
|
176
|
-
* Separate from `
|
|
176
|
+
* Separate from `auth_truncate_tables` because unit-level DB tests that don't
|
|
177
177
|
* touch audit logging don't need to truncate it.
|
|
178
178
|
*/
|
|
179
|
-
export const
|
|
179
|
+
export const auth_integration_truncate_tables = [...auth_truncate_tables, 'audit_log'];
|
|
180
180
|
/**
|
|
181
181
|
* All auth tables in drop order (children first for FK safety).
|
|
182
182
|
*
|
|
183
|
-
* The full set created by `
|
|
184
|
-
* test DB initialization. `
|
|
183
|
+
* The full set created by `auth_migrations` — use for clean-slate
|
|
184
|
+
* test DB initialization. `auth_truncate_tables` is the subset for
|
|
185
185
|
* between-test data cleanup (excludes `audit_log`).
|
|
186
186
|
*
|
|
187
|
-
* When adding tables to `
|
|
187
|
+
* When adding tables to `auth_migrations`, add them here too.
|
|
188
188
|
*/
|
|
189
|
-
export const
|
|
189
|
+
export const auth_drop_tables = [
|
|
190
190
|
'app_settings',
|
|
191
191
|
'invite',
|
|
192
192
|
'audit_log',
|
|
@@ -207,10 +207,10 @@ export const AUTH_DROP_TABLES = [
|
|
|
207
207
|
* Safe on fresh databases (`IF EXISTS` on all statements). No-op effect for
|
|
208
208
|
* PGlite (already fresh), but harmless to call unconditionally.
|
|
209
209
|
*
|
|
210
|
-
* @mutates db - drops every table in `
|
|
210
|
+
* @mutates db - drops every table in `auth_drop_tables` plus `schema_version`.
|
|
211
211
|
*/
|
|
212
212
|
export const drop_auth_schema = async (db) => {
|
|
213
|
-
for (const table of
|
|
213
|
+
for (const table of auth_drop_tables) {
|
|
214
214
|
await db.query(`DROP TABLE IF EXISTS ${assert_valid_sql_identifier(table)} CASCADE`);
|
|
215
215
|
}
|
|
216
216
|
await db.query('DROP TABLE IF EXISTS schema_version CASCADE');
|
|
@@ -17,9 +17,9 @@ import './assert_dev_env.js';
|
|
|
17
17
|
* @module
|
|
18
18
|
*/
|
|
19
19
|
import { describe, test, assert, beforeAll, afterAll } from 'vitest';
|
|
20
|
-
import {
|
|
20
|
+
import { auth_migration_ns } from '../auth/migrations.js';
|
|
21
21
|
import { create_test_app } from './app_server.js';
|
|
22
|
-
import { create_pglite_factory, create_describe_db,
|
|
22
|
+
import { create_pglite_factory, create_describe_db, auth_integration_truncate_tables, } from './db.js';
|
|
23
23
|
import { find_auth_route, assert_response_matches_spec, create_expired_test_cookie, assert_no_error_info_leakage, } from './integration_helpers.js';
|
|
24
24
|
import { find_rpc_action, rpc_call_for_spec, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
|
|
25
25
|
import { RateLimiter } from '../rate_limiter.js';
|
|
@@ -70,10 +70,10 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
70
70
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
71
71
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
72
72
|
const init_schema = async (db) => {
|
|
73
|
-
await run_migrations(db, [
|
|
73
|
+
await run_migrations(db, [auth_migration_ns]);
|
|
74
74
|
};
|
|
75
75
|
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
76
|
-
const describe_db = create_describe_db(factories,
|
|
76
|
+
const describe_db = create_describe_db(factories, auth_integration_truncate_tables);
|
|
77
77
|
describe_db('standard_integration', (get_db) => {
|
|
78
78
|
const { cookie_name } = options.session_options;
|
|
79
79
|
// Error coverage tracking across test groups
|
|
@@ -17,18 +17,18 @@ export declare const find_route_spec: (specs: Array<RouteSpec>, method: string,
|
|
|
17
17
|
* session/token CRUD, admin operations, and role_grant flows live on the RPC
|
|
18
18
|
* surface and should be reached via `rpc_call`.
|
|
19
19
|
*/
|
|
20
|
-
export declare const
|
|
21
|
-
export type RestAuthRouteSuffix = (typeof
|
|
20
|
+
export declare const rest_auth_route_suffixes: readonly ["/login", "/logout", "/password", "/verify", "/signup", "/bootstrap"];
|
|
21
|
+
export type RestAuthRouteSuffix = (typeof rest_auth_route_suffixes)[number];
|
|
22
22
|
/**
|
|
23
23
|
* Find a REST auth route by suffix and method.
|
|
24
24
|
*
|
|
25
25
|
* Decouples tests from consumer route prefix (`/api/account/login`,
|
|
26
26
|
* `/api/auth/login`, etc.). `suffix` must be one of
|
|
27
|
-
* `
|
|
27
|
+
* `rest_auth_route_suffixes` — throws otherwise so a post-migration RPC
|
|
28
28
|
* method name (e.g. `/sessions/revoke-all`) fails loudly at the call site
|
|
29
29
|
* instead of silently returning `undefined`.
|
|
30
30
|
*
|
|
31
|
-
* @throws Error if `suffix` is not in `
|
|
31
|
+
* @throws Error if `suffix` is not in `rest_auth_route_suffixes`.
|
|
32
32
|
*/
|
|
33
33
|
export declare const find_auth_route: (specs: Array<RouteSpec>, suffix: RestAuthRouteSuffix, method: RouteMethod) => RouteSpec | undefined;
|
|
34
34
|
/**
|
|
@@ -74,9 +74,9 @@ export declare const assert_rate_limit_retry_after_header: (response: Response,
|
|
|
74
74
|
retry_after: number;
|
|
75
75
|
}) => void;
|
|
76
76
|
/** Field names that must never appear in any HTTP response body. */
|
|
77
|
-
export declare const
|
|
77
|
+
export declare const sensitive_field_blocklist: ReadonlyArray<string>;
|
|
78
78
|
/** Field names that must not appear in non-admin HTTP response bodies. */
|
|
79
|
-
export declare const
|
|
79
|
+
export declare const admin_only_field_blocklist: ReadonlyArray<string>;
|
|
80
80
|
/**
|
|
81
81
|
* Recursively collect all key names from a parsed JSON value.
|
|
82
82
|
*
|
|
@@ -38,7 +38,7 @@ export const find_route_spec = (specs, method, path) => {
|
|
|
38
38
|
* session/token CRUD, admin operations, and role_grant flows live on the RPC
|
|
39
39
|
* surface and should be reached via `rpc_call`.
|
|
40
40
|
*/
|
|
41
|
-
export const
|
|
41
|
+
export const rest_auth_route_suffixes = [
|
|
42
42
|
'/login',
|
|
43
43
|
'/logout',
|
|
44
44
|
'/password',
|
|
@@ -51,15 +51,15 @@ export const REST_AUTH_ROUTE_SUFFIXES = [
|
|
|
51
51
|
*
|
|
52
52
|
* Decouples tests from consumer route prefix (`/api/account/login`,
|
|
53
53
|
* `/api/auth/login`, etc.). `suffix` must be one of
|
|
54
|
-
* `
|
|
54
|
+
* `rest_auth_route_suffixes` — throws otherwise so a post-migration RPC
|
|
55
55
|
* method name (e.g. `/sessions/revoke-all`) fails loudly at the call site
|
|
56
56
|
* instead of silently returning `undefined`.
|
|
57
57
|
*
|
|
58
|
-
* @throws Error if `suffix` is not in `
|
|
58
|
+
* @throws Error if `suffix` is not in `rest_auth_route_suffixes`.
|
|
59
59
|
*/
|
|
60
60
|
export const find_auth_route = (specs, suffix, method) => {
|
|
61
|
-
if (!
|
|
62
|
-
throw new Error(`find_auth_route: unknown suffix ${JSON.stringify(suffix)} — expected one of ${
|
|
61
|
+
if (!rest_auth_route_suffixes.includes(suffix)) {
|
|
62
|
+
throw new Error(`find_auth_route: unknown suffix ${JSON.stringify(suffix)} — expected one of ${rest_auth_route_suffixes.join(', ')}. Use rpc_call for RPC methods.`);
|
|
63
63
|
}
|
|
64
64
|
return specs.find((s) => s.method === method && s.path.endsWith(suffix));
|
|
65
65
|
};
|
|
@@ -207,9 +207,9 @@ export const assert_rate_limit_retry_after_header = (response, body) => {
|
|
|
207
207
|
};
|
|
208
208
|
// --- Data exposure helpers ---
|
|
209
209
|
/** Field names that must never appear in any HTTP response body. */
|
|
210
|
-
export const
|
|
210
|
+
export const sensitive_field_blocklist = ['password_hash', 'token_hash'];
|
|
211
211
|
/** Field names that must not appear in non-admin HTTP response bodies. */
|
|
212
|
-
export const
|
|
212
|
+
export const admin_only_field_blocklist = ['updated_by', 'created_by'];
|
|
213
213
|
/**
|
|
214
214
|
* Recursively collect all key names from a parsed JSON value.
|
|
215
215
|
*
|
|
@@ -14,9 +14,9 @@ import './assert_dev_env.js';
|
|
|
14
14
|
import { describe, test, assert } from 'vitest';
|
|
15
15
|
import { RateLimiter } from '../rate_limiter.js';
|
|
16
16
|
import { RateLimitError } from '../http/error_schemas.js';
|
|
17
|
-
import {
|
|
17
|
+
import { auth_migration_ns } from '../auth/migrations.js';
|
|
18
18
|
import { create_test_app } from './app_server.js';
|
|
19
|
-
import { create_pglite_factory, create_describe_db,
|
|
19
|
+
import { create_pglite_factory, create_describe_db, auth_integration_truncate_tables, } from './db.js';
|
|
20
20
|
import { find_auth_route, assert_rate_limit_retry_after_header } from './integration_helpers.js';
|
|
21
21
|
import { rpc_call_non_browser, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
|
|
22
22
|
import { run_migrations } from '../db/migrate.js';
|
|
@@ -48,10 +48,10 @@ export const describe_rate_limiting_tests = (options) => {
|
|
|
48
48
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
49
49
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
50
50
|
const init_schema = async (db) => {
|
|
51
|
-
await run_migrations(db, [
|
|
51
|
+
await run_migrations(db, [auth_migration_ns]);
|
|
52
52
|
};
|
|
53
53
|
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
54
|
-
const describe_db = create_describe_db(factories,
|
|
54
|
+
const describe_db = create_describe_db(factories, auth_integration_truncate_tables);
|
|
55
55
|
/** Create a tight rate limiter for testing — low attempt count, long window. */
|
|
56
56
|
const create_test_rate_limiter = () => new RateLimiter({ max_attempts, window_ms: 60_000, cleanup_interval_ms: 0 });
|
|
57
57
|
describe_db('rate_limiting', (get_db) => {
|
|
@@ -15,7 +15,7 @@ import { create_pglite_factory } from './db.js';
|
|
|
15
15
|
import { assert_response_matches_spec, pick_auth_headers } from './integration_helpers.js';
|
|
16
16
|
import { resolve_valid_path, generate_valid_body } from './schema_generators.js';
|
|
17
17
|
import { run_migrations } from '../db/migrate.js';
|
|
18
|
-
import {
|
|
18
|
+
import { auth_migration_ns } from '../auth/migrations.js';
|
|
19
19
|
import { create_stub_app_server_context } from './stubs.js';
|
|
20
20
|
/**
|
|
21
21
|
* Run schema-driven round-trip validation tests.
|
|
@@ -33,7 +33,7 @@ import { create_stub_app_server_context } from './stubs.js';
|
|
|
33
33
|
export const describe_round_trip_validation = (options) => {
|
|
34
34
|
const skip_set = new Set(options.skip_routes);
|
|
35
35
|
const init_schema = async (db) => {
|
|
36
|
-
await run_migrations(db, [
|
|
36
|
+
await run_migrations(db, [auth_migration_ns]);
|
|
37
37
|
};
|
|
38
38
|
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
39
39
|
// Pre-compute route specs at describe time so test.each can register one
|
|
@@ -15,7 +15,7 @@ import { create_test_app, } from './app_server.js';
|
|
|
15
15
|
import { create_pglite_factory } from './db.js';
|
|
16
16
|
import { generate_valid_body } from './schema_generators.js';
|
|
17
17
|
import { run_migrations } from '../db/migrate.js';
|
|
18
|
-
import {
|
|
18
|
+
import { auth_migration_ns } from '../auth/migrations.js';
|
|
19
19
|
import { is_public_auth } from '../http/auth_shape.js';
|
|
20
20
|
import { create_rpc_post_init, create_rpc_get_url, assert_jsonrpc_error_response, assert_jsonrpc_success_response, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
|
|
21
21
|
/**
|
|
@@ -60,7 +60,7 @@ export const describe_rpc_round_trip_tests = (options) => {
|
|
|
60
60
|
// what the live dispatcher serves.
|
|
61
61
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
62
62
|
const init_schema = async (db) => {
|
|
63
|
-
await run_migrations(db, [
|
|
63
|
+
await run_migrations(db, [auth_migration_ns]);
|
|
64
64
|
};
|
|
65
65
|
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
66
66
|
for (const factory of factories) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema_generators.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_generators.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAKN,KAAK,YAAY,EACjB,MAAM,yBAAyB,CAAC;AAIjC;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,cAAc,CAAC,CAAC,OAAO,KAAG,MAAM,GAAG,IAkBhE,CAAC;AA+FF,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,GAAI,OAAO,YAAY,EAAE,cAAc,CAAC,CAAC,OAAO,KAAG,
|
|
1
|
+
{"version":3,"file":"schema_generators.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_generators.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAKN,KAAK,YAAY,EACjB,MAAM,yBAAyB,CAAC;AAIjC;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,cAAc,CAAC,CAAC,OAAO,KAAG,MAAM,GAAG,IAkBhE,CAAC;AA+FF,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,GAAI,OAAO,YAAY,EAAE,cAAc,CAAC,CAAC,OAAO,KAAG,OAgGnF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,EAAE,gBAAgB,CAAC,CAAC,SAAS,KAAG,MAa9E,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC/B,cAAc,CAAC,CAAC,OAAO,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SA6B5B,CAAC"}
|