@fuzdev/fuz_app 0.1.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/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/actions/action_bridge.d.ts +65 -0
- package/dist/actions/action_bridge.d.ts.map +1 -0
- package/dist/actions/action_bridge.js +76 -0
- package/dist/actions/action_codegen.d.ts +97 -0
- package/dist/actions/action_codegen.d.ts.map +1 -0
- package/dist/actions/action_codegen.js +280 -0
- package/dist/actions/action_registry.d.ts +35 -0
- package/dist/actions/action_registry.d.ts.map +1 -0
- package/dist/actions/action_registry.js +83 -0
- package/dist/actions/action_spec.d.ts +169 -0
- package/dist/actions/action_spec.d.ts.map +1 -0
- package/dist/actions/action_spec.js +76 -0
- package/dist/auth/account_queries.d.ts +96 -0
- package/dist/auth/account_queries.d.ts.map +1 -0
- package/dist/auth/account_queries.js +172 -0
- package/dist/auth/account_routes.d.ts +86 -0
- package/dist/auth/account_routes.d.ts.map +1 -0
- package/dist/auth/account_routes.js +406 -0
- package/dist/auth/account_schema.d.ts +192 -0
- package/dist/auth/account_schema.d.ts.map +1 -0
- package/dist/auth/account_schema.js +105 -0
- package/dist/auth/admin_routes.d.ts +29 -0
- package/dist/auth/admin_routes.d.ts.map +1 -0
- package/dist/auth/admin_routes.js +193 -0
- package/dist/auth/api_token.d.ts +33 -0
- package/dist/auth/api_token.d.ts.map +1 -0
- package/dist/auth/api_token.js +36 -0
- package/dist/auth/api_token_queries.d.ts +80 -0
- package/dist/auth/api_token_queries.d.ts.map +1 -0
- package/dist/auth/api_token_queries.js +116 -0
- package/dist/auth/app_settings_queries.d.ts +33 -0
- package/dist/auth/app_settings_queries.d.ts.map +1 -0
- package/dist/auth/app_settings_queries.js +51 -0
- package/dist/auth/app_settings_routes.d.ts +27 -0
- package/dist/auth/app_settings_routes.d.ts.map +1 -0
- package/dist/auth/app_settings_routes.js +66 -0
- package/dist/auth/app_settings_schema.d.ts +35 -0
- package/dist/auth/app_settings_schema.d.ts.map +1 -0
- package/dist/auth/app_settings_schema.js +22 -0
- package/dist/auth/audit_log_queries.d.ts +90 -0
- package/dist/auth/audit_log_queries.d.ts.map +1 -0
- package/dist/auth/audit_log_queries.js +205 -0
- package/dist/auth/audit_log_routes.d.ts +33 -0
- package/dist/auth/audit_log_routes.d.ts.map +1 -0
- package/dist/auth/audit_log_routes.js +106 -0
- package/dist/auth/audit_log_schema.d.ts +259 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -0
- package/dist/auth/audit_log_schema.js +123 -0
- package/dist/auth/bearer_auth.d.ts +32 -0
- package/dist/auth/bearer_auth.d.ts.map +1 -0
- package/dist/auth/bearer_auth.js +90 -0
- package/dist/auth/bootstrap_account.d.ts +82 -0
- package/dist/auth/bootstrap_account.d.ts.map +1 -0
- package/dist/auth/bootstrap_account.js +97 -0
- package/dist/auth/bootstrap_routes.d.ts +74 -0
- package/dist/auth/bootstrap_routes.d.ts.map +1 -0
- package/dist/auth/bootstrap_routes.js +154 -0
- package/dist/auth/daemon_token.d.ts +49 -0
- package/dist/auth/daemon_token.d.ts.map +1 -0
- package/dist/auth/daemon_token.js +49 -0
- package/dist/auth/daemon_token_middleware.d.ts +93 -0
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -0
- package/dist/auth/daemon_token_middleware.js +167 -0
- package/dist/auth/ddl.d.ts +27 -0
- package/dist/auth/ddl.d.ts.map +1 -0
- package/dist/auth/ddl.js +111 -0
- package/dist/auth/deps.d.ts +52 -0
- package/dist/auth/deps.d.ts.map +1 -0
- package/dist/auth/deps.js +10 -0
- package/dist/auth/invite_queries.d.ts +68 -0
- package/dist/auth/invite_queries.d.ts.map +1 -0
- package/dist/auth/invite_queries.js +105 -0
- package/dist/auth/invite_routes.d.ts +18 -0
- package/dist/auth/invite_routes.d.ts.map +1 -0
- package/dist/auth/invite_routes.js +129 -0
- package/dist/auth/invite_schema.d.ts +51 -0
- package/dist/auth/invite_schema.d.ts.map +1 -0
- package/dist/auth/invite_schema.js +25 -0
- package/dist/auth/keyring.d.ts +87 -0
- package/dist/auth/keyring.d.ts.map +1 -0
- package/dist/auth/keyring.js +142 -0
- package/dist/auth/middleware.d.ts +40 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +64 -0
- package/dist/auth/migrations.d.ts +42 -0
- package/dist/auth/migrations.d.ts.map +1 -0
- package/dist/auth/migrations.js +79 -0
- package/dist/auth/password.d.ts +39 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/password.js +25 -0
- package/dist/auth/password_argon2.d.ts +43 -0
- package/dist/auth/password_argon2.d.ts.map +1 -0
- package/dist/auth/password_argon2.js +76 -0
- package/dist/auth/permit_queries.d.ts +72 -0
- package/dist/auth/permit_queries.d.ts.map +1 -0
- package/dist/auth/permit_queries.js +116 -0
- package/dist/auth/request_context.d.ts +114 -0
- package/dist/auth/request_context.d.ts.map +1 -0
- package/dist/auth/request_context.js +176 -0
- package/dist/auth/require_keeper.d.ts +20 -0
- package/dist/auth/require_keeper.d.ts.map +1 -0
- package/dist/auth/require_keeper.js +35 -0
- package/dist/auth/role_schema.d.ts +69 -0
- package/dist/auth/role_schema.d.ts.map +1 -0
- package/dist/auth/role_schema.js +70 -0
- package/dist/auth/route_guards.d.ts +21 -0
- package/dist/auth/route_guards.d.ts.map +1 -0
- package/dist/auth/route_guards.js +32 -0
- package/dist/auth/session_cookie.d.ts +158 -0
- package/dist/auth/session_cookie.d.ts.map +1 -0
- package/dist/auth/session_cookie.js +135 -0
- package/dist/auth/session_lifecycle.d.ts +35 -0
- package/dist/auth/session_lifecycle.d.ts.map +1 -0
- package/dist/auth/session_lifecycle.js +27 -0
- package/dist/auth/session_middleware.d.ts +33 -0
- package/dist/auth/session_middleware.d.ts.map +1 -0
- package/dist/auth/session_middleware.js +62 -0
- package/dist/auth/session_queries.d.ts +135 -0
- package/dist/auth/session_queries.d.ts.map +1 -0
- package/dist/auth/session_queries.js +186 -0
- package/dist/auth/signup_routes.d.ts +32 -0
- package/dist/auth/signup_routes.d.ts.map +1 -0
- package/dist/auth/signup_routes.js +150 -0
- package/dist/cli/args.d.ts +48 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +76 -0
- package/dist/cli/config.d.ts +48 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +77 -0
- package/dist/cli/daemon.d.ts +82 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +149 -0
- package/dist/cli/help.d.ts +85 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +138 -0
- package/dist/cli/logger.d.ts +46 -0
- package/dist/cli/logger.d.ts.map +1 -0
- package/dist/cli/logger.js +48 -0
- package/dist/cli/util.d.ts +36 -0
- package/dist/cli/util.d.ts.map +1 -0
- package/dist/cli/util.js +50 -0
- package/dist/crypto.d.ts +13 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +19 -0
- package/dist/db/assert_row.d.ts +18 -0
- package/dist/db/assert_row.d.ts.map +1 -0
- package/dist/db/assert_row.js +24 -0
- package/dist/db/create_db.d.ts +38 -0
- package/dist/db/create_db.d.ts.map +1 -0
- package/dist/db/create_db.js +57 -0
- package/dist/db/db.d.ts +97 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +76 -0
- package/dist/db/db_pg.d.ts +21 -0
- package/dist/db/db_pg.d.ts.map +1 -0
- package/dist/db/db_pg.js +45 -0
- package/dist/db/db_pglite.d.ts +21 -0
- package/dist/db/db_pglite.d.ts.map +1 -0
- package/dist/db/db_pglite.js +28 -0
- package/dist/db/migrate.d.ts +67 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +118 -0
- package/dist/db/pg_error.d.ts +16 -0
- package/dist/db/pg_error.d.ts.map +1 -0
- package/dist/db/pg_error.js +15 -0
- package/dist/db/query_deps.d.ts +14 -0
- package/dist/db/query_deps.d.ts.map +1 -0
- package/dist/db/query_deps.js +9 -0
- package/dist/db/sql_identifier.d.ts +27 -0
- package/dist/db/sql_identifier.d.ts.map +1 -0
- package/dist/db/sql_identifier.js +31 -0
- package/dist/db/status.d.ts +62 -0
- package/dist/db/status.d.ts.map +1 -0
- package/dist/db/status.js +116 -0
- package/dist/dev/setup.d.ts +159 -0
- package/dist/dev/setup.d.ts.map +1 -0
- package/dist/dev/setup.js +265 -0
- package/dist/env/dotenv.d.ts +25 -0
- package/dist/env/dotenv.d.ts.map +1 -0
- package/dist/env/dotenv.js +52 -0
- package/dist/env/load.d.ts +52 -0
- package/dist/env/load.d.ts.map +1 -0
- package/dist/env/load.js +79 -0
- package/dist/env/mask.d.ts +19 -0
- package/dist/env/mask.d.ts.map +1 -0
- package/dist/env/mask.js +26 -0
- package/dist/env/resolve.d.ts +126 -0
- package/dist/env/resolve.d.ts.map +1 -0
- package/dist/env/resolve.js +200 -0
- package/dist/hono_context.d.ts +48 -0
- package/dist/hono_context.d.ts.map +1 -0
- package/dist/hono_context.js +22 -0
- package/dist/http/common_routes.d.ts +52 -0
- package/dist/http/common_routes.d.ts.map +1 -0
- package/dist/http/common_routes.js +65 -0
- package/dist/http/db_routes.d.ts +57 -0
- package/dist/http/db_routes.d.ts.map +1 -0
- package/dist/http/db_routes.js +176 -0
- package/dist/http/error_schemas.d.ts +169 -0
- package/dist/http/error_schemas.d.ts.map +1 -0
- package/dist/http/error_schemas.js +178 -0
- package/dist/http/middleware_spec.d.ts +19 -0
- package/dist/http/middleware_spec.d.ts.map +1 -0
- package/dist/http/middleware_spec.js +9 -0
- package/dist/http/origin.d.ts +57 -0
- package/dist/http/origin.d.ts.map +1 -0
- package/dist/http/origin.js +207 -0
- package/dist/http/proxy.d.ts +112 -0
- package/dist/http/proxy.d.ts.map +1 -0
- package/dist/http/proxy.js +240 -0
- package/dist/http/route_spec.d.ts +197 -0
- package/dist/http/route_spec.d.ts.map +1 -0
- package/dist/http/route_spec.js +243 -0
- package/dist/http/schema_helpers.d.ts +64 -0
- package/dist/http/schema_helpers.d.ts.map +1 -0
- package/dist/http/schema_helpers.js +90 -0
- package/dist/http/surface.d.ts +132 -0
- package/dist/http/surface.d.ts.map +1 -0
- package/dist/http/surface.js +156 -0
- package/dist/http/surface_query.d.ts +77 -0
- package/dist/http/surface_query.d.ts.map +1 -0
- package/dist/http/surface_query.js +86 -0
- package/dist/rate_limiter.d.ts +94 -0
- package/dist/rate_limiter.d.ts.map +1 -0
- package/dist/rate_limiter.js +156 -0
- package/dist/realtime/sse.d.ts +80 -0
- package/dist/realtime/sse.d.ts.map +1 -0
- package/dist/realtime/sse.js +109 -0
- package/dist/realtime/sse_auth_guard.d.ts +93 -0
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -0
- package/dist/realtime/sse_auth_guard.js +111 -0
- package/dist/realtime/subscriber_registry.d.ts +85 -0
- package/dist/realtime/subscriber_registry.d.ts.map +1 -0
- package/dist/realtime/subscriber_registry.js +108 -0
- package/dist/runtime/deno.d.ts +21 -0
- package/dist/runtime/deno.d.ts.map +1 -0
- package/dist/runtime/deno.js +83 -0
- package/dist/runtime/deps.d.ts +113 -0
- package/dist/runtime/deps.d.ts.map +1 -0
- package/dist/runtime/deps.js +10 -0
- package/dist/runtime/fs.d.ts +15 -0
- package/dist/runtime/fs.d.ts.map +1 -0
- package/dist/runtime/fs.js +17 -0
- package/dist/runtime/mock.d.ts +81 -0
- package/dist/runtime/mock.d.ts.map +1 -0
- package/dist/runtime/mock.js +195 -0
- package/dist/runtime/node.d.ts +17 -0
- package/dist/runtime/node.d.ts.map +1 -0
- package/dist/runtime/node.js +117 -0
- package/dist/schema_meta.d.ts +16 -0
- package/dist/schema_meta.d.ts.map +1 -0
- package/dist/schema_meta.js +9 -0
- package/dist/sensitivity.d.ts +15 -0
- package/dist/sensitivity.d.ts.map +1 -0
- package/dist/sensitivity.js +9 -0
- package/dist/server/app_backend.d.ts +74 -0
- package/dist/server/app_backend.d.ts.map +1 -0
- package/dist/server/app_backend.js +39 -0
- package/dist/server/app_server.d.ts +201 -0
- package/dist/server/app_server.d.ts.map +1 -0
- package/dist/server/app_server.js +266 -0
- package/dist/server/env.d.ts +68 -0
- package/dist/server/env.d.ts.map +1 -0
- package/dist/server/env.js +95 -0
- package/dist/server/startup.d.ts +22 -0
- package/dist/server/startup.d.ts.map +1 -0
- package/dist/server/startup.js +48 -0
- package/dist/server/static.d.ts +39 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +38 -0
- package/dist/server/validate_nginx.d.ts +34 -0
- package/dist/server/validate_nginx.d.ts.map +1 -0
- package/dist/server/validate_nginx.js +118 -0
- package/dist/testing/CLAUDE.md +3 -0
- package/dist/testing/admin_integration.d.ts +45 -0
- package/dist/testing/admin_integration.d.ts.map +1 -0
- package/dist/testing/admin_integration.js +840 -0
- package/dist/testing/adversarial_404.d.ts +15 -0
- package/dist/testing/adversarial_404.d.ts.map +1 -0
- package/dist/testing/adversarial_404.js +118 -0
- package/dist/testing/adversarial_headers.d.ts +36 -0
- package/dist/testing/adversarial_headers.d.ts.map +1 -0
- package/dist/testing/adversarial_headers.js +128 -0
- package/dist/testing/adversarial_input.d.ts +56 -0
- package/dist/testing/adversarial_input.d.ts.map +1 -0
- package/dist/testing/adversarial_input.js +494 -0
- package/dist/testing/app_server.d.ts +169 -0
- package/dist/testing/app_server.d.ts.map +1 -0
- package/dist/testing/app_server.js +240 -0
- package/dist/testing/assert_dev_env.d.ts +10 -0
- package/dist/testing/assert_dev_env.d.ts.map +1 -0
- package/dist/testing/assert_dev_env.js +13 -0
- package/dist/testing/assertions.d.ts +61 -0
- package/dist/testing/assertions.d.ts.map +1 -0
- package/dist/testing/assertions.js +96 -0
- package/dist/testing/attack_surface.d.ts +63 -0
- package/dist/testing/attack_surface.d.ts.map +1 -0
- package/dist/testing/attack_surface.js +224 -0
- package/dist/testing/audit_completeness.d.ts +29 -0
- package/dist/testing/audit_completeness.d.ts.map +1 -0
- package/dist/testing/audit_completeness.js +410 -0
- package/dist/testing/auth_apps.d.ts +55 -0
- package/dist/testing/auth_apps.d.ts.map +1 -0
- package/dist/testing/auth_apps.js +122 -0
- package/dist/testing/data_exposure.d.ts +62 -0
- package/dist/testing/data_exposure.d.ts.map +1 -0
- package/dist/testing/data_exposure.js +297 -0
- package/dist/testing/db.d.ts +111 -0
- package/dist/testing/db.d.ts.map +1 -0
- package/dist/testing/db.js +258 -0
- package/dist/testing/entities.d.ts +21 -0
- package/dist/testing/entities.d.ts.map +1 -0
- package/dist/testing/entities.js +42 -0
- package/dist/testing/error_coverage.d.ts +78 -0
- package/dist/testing/error_coverage.d.ts.map +1 -0
- package/dist/testing/error_coverage.js +135 -0
- package/dist/testing/integration.d.ts +37 -0
- package/dist/testing/integration.d.ts.map +1 -0
- package/dist/testing/integration.js +1139 -0
- package/dist/testing/integration_helpers.d.ts +107 -0
- package/dist/testing/integration_helpers.d.ts.map +1 -0
- package/dist/testing/integration_helpers.js +246 -0
- package/dist/testing/middleware.d.ts +125 -0
- package/dist/testing/middleware.d.ts.map +1 -0
- package/dist/testing/middleware.js +210 -0
- package/dist/testing/rate_limiting.d.ts +43 -0
- package/dist/testing/rate_limiting.d.ts.map +1 -0
- package/dist/testing/rate_limiting.js +216 -0
- package/dist/testing/round_trip.d.ts +37 -0
- package/dist/testing/round_trip.d.ts.map +1 -0
- package/dist/testing/round_trip.js +128 -0
- package/dist/testing/schema_generators.d.ts +33 -0
- package/dist/testing/schema_generators.d.ts.map +1 -0
- package/dist/testing/schema_generators.js +137 -0
- package/dist/testing/standard.d.ts +49 -0
- package/dist/testing/standard.d.ts.map +1 -0
- package/dist/testing/standard.js +16 -0
- package/dist/testing/stubs.d.ts +96 -0
- package/dist/testing/stubs.d.ts.map +1 -0
- package/dist/testing/stubs.js +192 -0
- package/dist/testing/surface_invariants.d.ts +189 -0
- package/dist/testing/surface_invariants.d.ts.map +1 -0
- package/dist/testing/surface_invariants.js +450 -0
- package/dist/ui/AccountSessions.svelte +75 -0
- package/dist/ui/AccountSessions.svelte.d.ts +19 -0
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -0
- package/dist/ui/AdminAccounts.svelte +107 -0
- package/dist/ui/AdminAccounts.svelte.d.ts +19 -0
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -0
- package/dist/ui/AdminAuditLog.svelte +144 -0
- package/dist/ui/AdminAuditLog.svelte.d.ts +4 -0
- package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -0
- package/dist/ui/AdminInvites.svelte +142 -0
- package/dist/ui/AdminInvites.svelte.d.ts +4 -0
- package/dist/ui/AdminInvites.svelte.d.ts.map +1 -0
- package/dist/ui/AdminOverview.svelte +337 -0
- package/dist/ui/AdminOverview.svelte.d.ts +4 -0
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -0
- package/dist/ui/AdminPermitHistory.svelte +61 -0
- package/dist/ui/AdminPermitHistory.svelte.d.ts +19 -0
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -0
- package/dist/ui/AdminSessions.svelte +85 -0
- package/dist/ui/AdminSessions.svelte.d.ts +19 -0
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -0
- package/dist/ui/AdminSettings.svelte +32 -0
- package/dist/ui/AdminSettings.svelte.d.ts +19 -0
- package/dist/ui/AdminSettings.svelte.d.ts.map +1 -0
- package/dist/ui/AdminSurface.svelte +42 -0
- package/dist/ui/AdminSurface.svelte.d.ts +4 -0
- package/dist/ui/AdminSurface.svelte.d.ts.map +1 -0
- package/dist/ui/AppShell.svelte +93 -0
- package/dist/ui/AppShell.svelte.d.ts +20 -0
- package/dist/ui/AppShell.svelte.d.ts.map +1 -0
- package/dist/ui/BootstrapForm.svelte +105 -0
- package/dist/ui/BootstrapForm.svelte.d.ts +4 -0
- package/dist/ui/BootstrapForm.svelte.d.ts.map +1 -0
- package/dist/ui/ColumnLayout.svelte +46 -0
- package/dist/ui/ColumnLayout.svelte.d.ts +11 -0
- package/dist/ui/ColumnLayout.svelte.d.ts.map +1 -0
- package/dist/ui/ConfirmButton.svelte +125 -0
- package/dist/ui/ConfirmButton.svelte.d.ts +54 -0
- package/dist/ui/ConfirmButton.svelte.d.ts.map +1 -0
- package/dist/ui/Datatable.svelte +185 -0
- package/dist/ui/Datatable.svelte.d.ts +35 -0
- package/dist/ui/Datatable.svelte.d.ts.map +1 -0
- package/dist/ui/LoginForm.svelte +82 -0
- package/dist/ui/LoginForm.svelte.d.ts +8 -0
- package/dist/ui/LoginForm.svelte.d.ts.map +1 -0
- package/dist/ui/LogoutButton.svelte +36 -0
- package/dist/ui/LogoutButton.svelte.d.ts +10 -0
- package/dist/ui/LogoutButton.svelte.d.ts.map +1 -0
- package/dist/ui/MenuLink.svelte +35 -0
- package/dist/ui/MenuLink.svelte.d.ts +12 -0
- package/dist/ui/MenuLink.svelte.d.ts.map +1 -0
- package/dist/ui/OpenSignupToggle.svelte +36 -0
- package/dist/ui/OpenSignupToggle.svelte.d.ts +19 -0
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -0
- package/dist/ui/PopoverButton.svelte +136 -0
- package/dist/ui/PopoverButton.svelte.d.ts +63 -0
- package/dist/ui/PopoverButton.svelte.d.ts.map +1 -0
- package/dist/ui/SignupForm.svelte +117 -0
- package/dist/ui/SignupForm.svelte.d.ts +7 -0
- package/dist/ui/SignupForm.svelte.d.ts.map +1 -0
- package/dist/ui/SurfaceExplorer.svelte +287 -0
- package/dist/ui/SurfaceExplorer.svelte.d.ts +8 -0
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -0
- package/dist/ui/account_sessions_state.svelte.d.ts +15 -0
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -0
- package/dist/ui/account_sessions_state.svelte.js +45 -0
- package/dist/ui/admin_accounts_state.svelte.d.ts +19 -0
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -0
- package/dist/ui/admin_accounts_state.svelte.js +65 -0
- package/dist/ui/admin_invites_state.svelte.d.ts +19 -0
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -0
- package/dist/ui/admin_invites_state.svelte.js +71 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts +18 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -0
- package/dist/ui/admin_sessions_state.svelte.js +62 -0
- package/dist/ui/app_settings_state.svelte.d.ts +14 -0
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -0
- package/dist/ui/app_settings_state.svelte.js +44 -0
- package/dist/ui/audit_log_state.svelte.d.ts +40 -0
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -0
- package/dist/ui/audit_log_state.svelte.js +153 -0
- package/dist/ui/auth_state.svelte.d.ts +85 -0
- package/dist/ui/auth_state.svelte.d.ts.map +1 -0
- package/dist/ui/auth_state.svelte.js +238 -0
- package/dist/ui/datatable.d.ts +25 -0
- package/dist/ui/datatable.d.ts.map +1 -0
- package/dist/ui/datatable.js +9 -0
- package/dist/ui/enter_advance.d.ts +13 -0
- package/dist/ui/enter_advance.d.ts.map +1 -0
- package/dist/ui/enter_advance.js +30 -0
- package/dist/ui/loadable.svelte.d.ts +55 -0
- package/dist/ui/loadable.svelte.d.ts.map +1 -0
- package/dist/ui/loadable.svelte.js +75 -0
- package/dist/ui/popover.svelte.d.ts +137 -0
- package/dist/ui/popover.svelte.d.ts.map +1 -0
- package/dist/ui/popover.svelte.js +288 -0
- package/dist/ui/position_helpers.d.ts +27 -0
- package/dist/ui/position_helpers.d.ts.map +1 -0
- package/dist/ui/position_helpers.js +81 -0
- package/dist/ui/sidebar_state.svelte.d.ts +30 -0
- package/dist/ui/sidebar_state.svelte.d.ts.map +1 -0
- package/dist/ui/sidebar_state.svelte.js +39 -0
- package/dist/ui/table_state.svelte.d.ts +63 -0
- package/dist/ui/table_state.svelte.d.ts.map +1 -0
- package/dist/ui/table_state.svelte.js +117 -0
- package/dist/ui/ui_fetch.d.ts +29 -0
- package/dist/ui/ui_fetch.d.ts.map +1 -0
- package/dist/ui/ui_fetch.js +37 -0
- package/dist/ui/ui_format.d.ts +63 -0
- package/dist/ui/ui_format.d.ts.map +1 -0
- package/dist/ui/ui_format.js +196 -0
- package/package.json +121 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Table-driven middleware test helpers.
|
|
4
|
+
*
|
|
5
|
+
* Provides mock builders for bearer auth middleware dependencies,
|
|
6
|
+
* a generic test runner that iterates case tables, and a reusable
|
|
7
|
+
* middleware stack factory for integration testing.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { vi, test, assert, describe } from 'vitest';
|
|
12
|
+
import { Hono } from 'hono';
|
|
13
|
+
import { Logger } from '@fuzdev/fuz_util/log.js';
|
|
14
|
+
import { create_bearer_auth_middleware } from '../auth/bearer_auth.js';
|
|
15
|
+
import { query_validate_api_token } from '../auth/api_token_queries.js';
|
|
16
|
+
import { query_account_by_id, query_actor_by_account } from '../auth/account_queries.js';
|
|
17
|
+
import { query_permit_find_active_for_actor } from '../auth/permit_queries.js';
|
|
18
|
+
import { create_proxy_middleware, get_client_ip } from '../http/proxy.js';
|
|
19
|
+
import { verify_request_source, parse_allowed_origins } from '../http/origin.js';
|
|
20
|
+
import { REQUEST_CONTEXT_KEY } from '../auth/request_context.js';
|
|
21
|
+
import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
|
|
22
|
+
import { ApiError } from '../http/error_schemas.js';
|
|
23
|
+
// Mock the query modules so test cases can control return values.
|
|
24
|
+
// vi.mock() is hoisted by vitest, so these run before any imports resolve.
|
|
25
|
+
vi.mock('../auth/api_token_queries.js', () => ({
|
|
26
|
+
query_validate_api_token: vi.fn(),
|
|
27
|
+
}));
|
|
28
|
+
vi.mock('../auth/account_queries.js', () => ({
|
|
29
|
+
query_account_by_id: vi.fn(),
|
|
30
|
+
query_actor_by_account: vi.fn(),
|
|
31
|
+
}));
|
|
32
|
+
vi.mock('../auth/permit_queries.js', () => ({
|
|
33
|
+
query_permit_find_active_for_actor: vi.fn(),
|
|
34
|
+
}));
|
|
35
|
+
/** Stub `QueryDeps` for bearer auth tests (no real DB needed). */
|
|
36
|
+
const STUB_DEPS = { db: {} };
|
|
37
|
+
/**
|
|
38
|
+
* Create mock dependencies for `create_bearer_auth_middleware`, configured per test case.
|
|
39
|
+
*
|
|
40
|
+
* Configures the module-level mocks for `query_validate_api_token`,
|
|
41
|
+
* `query_account_by_id`, `query_actor_by_account`, and `query_permit_find_active_for_actor`
|
|
42
|
+
* so each test case controls return values independently.
|
|
43
|
+
*
|
|
44
|
+
* @param tc - the test config providing mock return values
|
|
45
|
+
* @returns mocks bundle with spy references
|
|
46
|
+
*/
|
|
47
|
+
export const create_bearer_auth_mocks = (tc) => {
|
|
48
|
+
const mock_validate = vi.mocked(query_validate_api_token);
|
|
49
|
+
const mock_find_by_id = vi.mocked(query_account_by_id);
|
|
50
|
+
const mock_find_by_account = vi.mocked(query_actor_by_account);
|
|
51
|
+
const mock_find_active_for_actor = vi.mocked(query_permit_find_active_for_actor);
|
|
52
|
+
mock_validate
|
|
53
|
+
.mockReset()
|
|
54
|
+
.mockImplementation(() => Promise.resolve(tc.mock_validate_result));
|
|
55
|
+
mock_find_by_id
|
|
56
|
+
.mockReset()
|
|
57
|
+
.mockImplementation(() => Promise.resolve(tc.mock_find_by_id_result));
|
|
58
|
+
mock_find_by_account
|
|
59
|
+
.mockReset()
|
|
60
|
+
.mockImplementation(() => Promise.resolve(tc.mock_find_by_account_result));
|
|
61
|
+
mock_find_active_for_actor
|
|
62
|
+
.mockReset()
|
|
63
|
+
.mockImplementation(() => Promise.resolve(tc.mock_permits_result ?? []));
|
|
64
|
+
return { mock_validate, mock_find_by_id, mock_find_by_account, mock_find_active_for_actor };
|
|
65
|
+
};
|
|
66
|
+
/** Default client IP set by the proxy stub in test apps. */
|
|
67
|
+
export const TEST_CLIENT_IP = '127.0.0.1';
|
|
68
|
+
/**
|
|
69
|
+
* Create a Hono app wired with `create_bearer_auth_middleware` using mocked deps.
|
|
70
|
+
*
|
|
71
|
+
* The route handler at `/api/test` returns the resolved context in the response body,
|
|
72
|
+
* enabling assertions on `REQUEST_CONTEXT_KEY` and `CREDENTIAL_TYPE_KEY`.
|
|
73
|
+
*
|
|
74
|
+
* @param tc - the test config providing mock behavior
|
|
75
|
+
* @param ip_rate_limiter - optional rate limiter (null to disable)
|
|
76
|
+
* @returns the app and mocks bundle
|
|
77
|
+
*/
|
|
78
|
+
export const create_bearer_auth_test_app = (tc, ip_rate_limiter = null) => {
|
|
79
|
+
const mocks = create_bearer_auth_mocks(tc);
|
|
80
|
+
const bearer_middleware = create_bearer_auth_middleware(STUB_DEPS, ip_rate_limiter, new Logger('test', { level: 'off' }));
|
|
81
|
+
const app = new Hono();
|
|
82
|
+
// inject pre-existing request context if the test case specifies one
|
|
83
|
+
if (tc.pre_context) {
|
|
84
|
+
app.use('*', async (c, next) => {
|
|
85
|
+
c.set(REQUEST_CONTEXT_KEY, tc.pre_context);
|
|
86
|
+
c.set(CREDENTIAL_TYPE_KEY, 'session');
|
|
87
|
+
await next();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// proxy middleware stub — sets a known client_ip
|
|
91
|
+
app.use('*', async (c, next) => {
|
|
92
|
+
c.set('client_ip', TEST_CLIENT_IP);
|
|
93
|
+
await next();
|
|
94
|
+
});
|
|
95
|
+
app.use('/api/*', bearer_middleware);
|
|
96
|
+
// route handler echoes full context state for assertions
|
|
97
|
+
app.get('/api/test', (c) => {
|
|
98
|
+
const ctx = c.get(REQUEST_CONTEXT_KEY);
|
|
99
|
+
const cred = c.get(CREDENTIAL_TYPE_KEY);
|
|
100
|
+
return c.json({
|
|
101
|
+
ok: true,
|
|
102
|
+
has_context: ctx != null,
|
|
103
|
+
credential_type: cred ?? null,
|
|
104
|
+
account_id: ctx?.account.id ?? null,
|
|
105
|
+
actor_id: ctx?.actor.id ?? null,
|
|
106
|
+
permit_count: ctx?.permits.length ?? 0,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
return { app, mocks };
|
|
110
|
+
};
|
|
111
|
+
// --- Table-driven test runner ---
|
|
112
|
+
/**
|
|
113
|
+
* Run a table of bearer auth middleware test cases.
|
|
114
|
+
*
|
|
115
|
+
* Generates one `test()` per case inside a `describe()` block.
|
|
116
|
+
*
|
|
117
|
+
* @param suite_name - the describe block name
|
|
118
|
+
* @param cases - the test case table
|
|
119
|
+
* @param ip_rate_limiter - optional rate limiter shared across cases
|
|
120
|
+
*/
|
|
121
|
+
export const describe_bearer_auth_cases = (suite_name, cases, ip_rate_limiter = null) => {
|
|
122
|
+
describe(suite_name, () => {
|
|
123
|
+
for (const tc of cases) {
|
|
124
|
+
test(tc.name, async () => {
|
|
125
|
+
const { app, mocks } = create_bearer_auth_test_app(tc, ip_rate_limiter);
|
|
126
|
+
const res = await app.request('/api/test', {
|
|
127
|
+
method: 'GET',
|
|
128
|
+
headers: tc.headers,
|
|
129
|
+
});
|
|
130
|
+
const body = await res.json();
|
|
131
|
+
if (tc.expected_status === 'next') {
|
|
132
|
+
assert.strictEqual(res.status, 200, `expected next() but got ${res.status}`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
assert.strictEqual(res.status, tc.expected_status);
|
|
136
|
+
if (tc.expected_error) {
|
|
137
|
+
assert.strictEqual(body.error, tc.expected_error);
|
|
138
|
+
const error_schema = tc.expected_error_schema ?? ApiError;
|
|
139
|
+
error_schema.parse(body);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (tc.validate_expectation === 'not_called') {
|
|
143
|
+
assert.strictEqual(mocks.mock_validate.mock.calls.length, 0, 'validate should not have been called');
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
assert.ok(mocks.mock_validate.mock.calls.length > 0, 'validate should have been called');
|
|
147
|
+
}
|
|
148
|
+
if (tc.assert_context_set) {
|
|
149
|
+
assert.strictEqual(body.has_context, true, 'REQUEST_CONTEXT_KEY should be set');
|
|
150
|
+
assert.strictEqual(body.credential_type, 'api_token', 'CREDENTIAL_TYPE_KEY should be api_token');
|
|
151
|
+
}
|
|
152
|
+
if (tc.assert_context_preserved) {
|
|
153
|
+
assert.strictEqual(body.has_context, true, 'original context should be preserved');
|
|
154
|
+
assert.strictEqual(body.credential_type, 'session', 'credential type should remain session');
|
|
155
|
+
}
|
|
156
|
+
if (tc.assert_mocks) {
|
|
157
|
+
tc.assert_mocks(mocks);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
// --- Middleware stack test factory ---
|
|
164
|
+
/** Path used by the echo route in `create_test_middleware_stack_app`. */
|
|
165
|
+
export const TEST_MIDDLEWARE_PATH = '/api/test';
|
|
166
|
+
/**
|
|
167
|
+
* Create a Hono app with real proxy + origin + bearer middleware for integration testing.
|
|
168
|
+
*
|
|
169
|
+
* All DB queries return undefined (no real database needed).
|
|
170
|
+
* The echo route at `TEST_MIDDLEWARE_PATH` returns `{ok, client_ip, has_context}`.
|
|
171
|
+
*
|
|
172
|
+
* @param options - middleware stack configuration
|
|
173
|
+
* @returns the app and mock spies (reconfigure via `mockImplementation` for valid-token paths)
|
|
174
|
+
*/
|
|
175
|
+
export const create_test_middleware_stack_app = (options) => {
|
|
176
|
+
const trusted_proxies = options?.trusted_proxies ?? ['10.0.0.1'];
|
|
177
|
+
const allowed_origins_str = options?.allowed_origins ?? 'https://app.example.com';
|
|
178
|
+
const mock_validate = vi.mocked(query_validate_api_token);
|
|
179
|
+
const mock_find_by_id = vi.mocked(query_account_by_id);
|
|
180
|
+
const mock_find_by_account = vi.mocked(query_actor_by_account);
|
|
181
|
+
const mock_find_active_for_actor = vi.mocked(query_permit_find_active_for_actor);
|
|
182
|
+
mock_validate.mockReset().mockImplementation(() => Promise.resolve(undefined));
|
|
183
|
+
mock_find_by_id.mockReset().mockImplementation(() => Promise.resolve(undefined));
|
|
184
|
+
mock_find_by_account.mockReset().mockImplementation(() => Promise.resolve(undefined));
|
|
185
|
+
mock_find_active_for_actor.mockReset().mockImplementation(() => Promise.resolve([]));
|
|
186
|
+
const get_connection_ip = typeof options?.connection_ip === 'function'
|
|
187
|
+
? options.connection_ip
|
|
188
|
+
: () => options?.connection_ip ?? trusted_proxies[0];
|
|
189
|
+
const proxy_mw = create_proxy_middleware({
|
|
190
|
+
trusted_proxies,
|
|
191
|
+
get_connection_ip: get_connection_ip,
|
|
192
|
+
});
|
|
193
|
+
const allowed_patterns = parse_allowed_origins(allowed_origins_str);
|
|
194
|
+
const origin_mw = verify_request_source(allowed_patterns);
|
|
195
|
+
const bearer_mw = create_bearer_auth_middleware(STUB_DEPS, options?.ip_rate_limiter ?? null, new Logger('test', { level: 'off' }));
|
|
196
|
+
const app = new Hono();
|
|
197
|
+
app.use('*', proxy_mw);
|
|
198
|
+
app.use('/api/*', origin_mw);
|
|
199
|
+
app.use('/api/*', bearer_mw);
|
|
200
|
+
// echo route for assertions
|
|
201
|
+
app.get(TEST_MIDDLEWARE_PATH, (c) => {
|
|
202
|
+
const ctx = c.get(REQUEST_CONTEXT_KEY);
|
|
203
|
+
return c.json({
|
|
204
|
+
ok: true,
|
|
205
|
+
client_ip: get_client_ip(c),
|
|
206
|
+
has_context: ctx != null,
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
return { app, mock_validate, mock_find_by_id, mock_find_by_account, mock_find_active_for_actor };
|
|
210
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
import type { SessionOptions } from '../auth/session_cookie.js';
|
|
3
|
+
import type { AppServerContext, AppServerOptions } from '../server/app_server.js';
|
|
4
|
+
import type { RouteSpec } from '../http/route_spec.js';
|
|
5
|
+
import { type DbFactory } from './db.js';
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for `describe_rate_limiting_tests`.
|
|
8
|
+
*/
|
|
9
|
+
export interface RateLimitingTestOptions {
|
|
10
|
+
/** Session config for cookie-based auth. */
|
|
11
|
+
session_options: SessionOptions<string>;
|
|
12
|
+
/** Route spec factory — same one used in production. */
|
|
13
|
+
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
14
|
+
/** Optional overrides for `AppServerOptions`. */
|
|
15
|
+
app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
|
|
16
|
+
/**
|
|
17
|
+
* Database factories to run tests against. Default: pglite only.
|
|
18
|
+
*/
|
|
19
|
+
db_factories?: Array<DbFactory>;
|
|
20
|
+
/**
|
|
21
|
+
* Maximum attempts before rate limiting kicks in.
|
|
22
|
+
* Default: `2` (tight limit for fast tests).
|
|
23
|
+
*/
|
|
24
|
+
max_attempts?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Standard rate limiting integration test suite.
|
|
28
|
+
*
|
|
29
|
+
* Creates 3 test groups:
|
|
30
|
+
* 1. IP rate limiting on login — fires `max_attempts + 1` login requests,
|
|
31
|
+
* verifies the last returns 429 with a valid `RateLimitError` body.
|
|
32
|
+
* 2. Per-account rate limiting on login — fires `max_attempts + 1` login
|
|
33
|
+
* requests with the same username, verifies the last returns 429.
|
|
34
|
+
* 3. Bearer auth IP rate limiting — fires `max_attempts + 1` bearer requests
|
|
35
|
+
* with an invalid token, verifies the last returns 429.
|
|
36
|
+
*
|
|
37
|
+
* Each test group asserts that required routes exist, failing with a descriptive
|
|
38
|
+
* message if the consumer's route specs are misconfigured.
|
|
39
|
+
*
|
|
40
|
+
* @param options - session config and route factory
|
|
41
|
+
*/
|
|
42
|
+
export declare const describe_rate_limiting_tests: (options: RateLimitingTestOptions) => void;
|
|
43
|
+
//# sourceMappingURL=rate_limiting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate_limiting.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rate_limiting.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAKrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,uBAAuB,KAAG,IA+N/E,CAAC"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Rate limiting integration test suite.
|
|
4
|
+
*
|
|
5
|
+
* Verifies that sensitive routes (login, bootstrap, token creation) enforce
|
|
6
|
+
* rate limits when rate limiters are enabled. Tests create a tight rate limiter
|
|
7
|
+
* (2 attempts / 1 minute) and fire requests until 429 is returned.
|
|
8
|
+
*
|
|
9
|
+
* Consumers call `describe_rate_limiting_tests` with their route factory and
|
|
10
|
+
* session config — rate limit enforcement tests come for free.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { describe, test, assert } from 'vitest';
|
|
15
|
+
import { RateLimiter } from '../rate_limiter.js';
|
|
16
|
+
import { RateLimitError } from '../http/error_schemas.js';
|
|
17
|
+
import { AUTH_MIGRATION_NS } from '../auth/migrations.js';
|
|
18
|
+
import { create_test_app } from './app_server.js';
|
|
19
|
+
import { create_pglite_factory, create_describe_db, AUTH_INTEGRATION_TRUNCATE_TABLES, } from './db.js';
|
|
20
|
+
import { find_auth_route, assert_rate_limit_retry_after_header } from './integration_helpers.js';
|
|
21
|
+
import { run_migrations } from '../db/migrate.js';
|
|
22
|
+
/**
|
|
23
|
+
* Standard rate limiting integration test suite.
|
|
24
|
+
*
|
|
25
|
+
* Creates 3 test groups:
|
|
26
|
+
* 1. IP rate limiting on login — fires `max_attempts + 1` login requests,
|
|
27
|
+
* verifies the last returns 429 with a valid `RateLimitError` body.
|
|
28
|
+
* 2. Per-account rate limiting on login — fires `max_attempts + 1` login
|
|
29
|
+
* requests with the same username, verifies the last returns 429.
|
|
30
|
+
* 3. Bearer auth IP rate limiting — fires `max_attempts + 1` bearer requests
|
|
31
|
+
* with an invalid token, verifies the last returns 429.
|
|
32
|
+
*
|
|
33
|
+
* Each test group asserts that required routes exist, failing with a descriptive
|
|
34
|
+
* message if the consumer's route specs are misconfigured.
|
|
35
|
+
*
|
|
36
|
+
* @param options - session config and route factory
|
|
37
|
+
*/
|
|
38
|
+
export const describe_rate_limiting_tests = (options) => {
|
|
39
|
+
const max_attempts = options.max_attempts ?? 2;
|
|
40
|
+
const init_schema = async (db) => {
|
|
41
|
+
await run_migrations(db, [AUTH_MIGRATION_NS]);
|
|
42
|
+
};
|
|
43
|
+
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
44
|
+
const describe_db = create_describe_db(factories, AUTH_INTEGRATION_TRUNCATE_TABLES);
|
|
45
|
+
/** Create a tight rate limiter for testing — low attempt count, long window. */
|
|
46
|
+
const create_test_rate_limiter = () => new RateLimiter({ max_attempts, window_ms: 60_000, cleanup_interval_ms: 0 });
|
|
47
|
+
describe_db('rate_limiting', (get_db) => {
|
|
48
|
+
// --- 1. IP rate limiting on login ---
|
|
49
|
+
describe('IP rate limiting on login', () => {
|
|
50
|
+
test(`login is blocked after ${max_attempts} failed attempts`, async () => {
|
|
51
|
+
const ip_rate_limiter = create_test_rate_limiter();
|
|
52
|
+
try {
|
|
53
|
+
const test_app = await create_test_app({
|
|
54
|
+
session_options: options.session_options,
|
|
55
|
+
create_route_specs: options.create_route_specs,
|
|
56
|
+
db: get_db(),
|
|
57
|
+
app_options: {
|
|
58
|
+
...options.app_options,
|
|
59
|
+
ip_rate_limiter,
|
|
60
|
+
login_account_rate_limiter: null,
|
|
61
|
+
bearer_ip_rate_limiter: null,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
|
|
65
|
+
assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
|
|
66
|
+
// Fire max_attempts failed login requests (sequential — must exhaust the window)
|
|
67
|
+
/* eslint-disable no-await-in-loop */
|
|
68
|
+
for (let i = 0; i < max_attempts; i++) {
|
|
69
|
+
const res = await test_app.app.request(login_route.path, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
host: 'localhost',
|
|
73
|
+
origin: 'http://localhost:5173',
|
|
74
|
+
'content-type': 'application/json',
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify({ username: 'nonexistent', password: 'wrong' }),
|
|
77
|
+
});
|
|
78
|
+
assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
|
|
79
|
+
}
|
|
80
|
+
/* eslint-enable no-await-in-loop */
|
|
81
|
+
// The next request should be rate limited
|
|
82
|
+
const blocked_res = await test_app.app.request(login_route.path, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
host: 'localhost',
|
|
86
|
+
origin: 'http://localhost:5173',
|
|
87
|
+
'content-type': 'application/json',
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({ username: 'nonexistent', password: 'wrong' }),
|
|
90
|
+
});
|
|
91
|
+
assert.strictEqual(blocked_res.status, 429);
|
|
92
|
+
const body = await blocked_res.json();
|
|
93
|
+
RateLimitError.parse(body);
|
|
94
|
+
assert.ok(typeof body.retry_after === 'number' && body.retry_after > 0, 'Expected positive retry_after');
|
|
95
|
+
assert_rate_limit_retry_after_header(blocked_res, body);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
ip_rate_limiter.dispose();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
// --- 2. Per-account rate limiting on login ---
|
|
103
|
+
describe('per-account rate limiting on login', () => {
|
|
104
|
+
test(`login is blocked after ${max_attempts} failed attempts for the same username`, async () => {
|
|
105
|
+
const login_account_rate_limiter = create_test_rate_limiter();
|
|
106
|
+
try {
|
|
107
|
+
const test_app = await create_test_app({
|
|
108
|
+
session_options: options.session_options,
|
|
109
|
+
create_route_specs: options.create_route_specs,
|
|
110
|
+
db: get_db(),
|
|
111
|
+
app_options: {
|
|
112
|
+
...options.app_options,
|
|
113
|
+
ip_rate_limiter: null,
|
|
114
|
+
login_account_rate_limiter,
|
|
115
|
+
bearer_ip_rate_limiter: null,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
|
|
119
|
+
assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
|
|
120
|
+
const target_username = 'rate_limit_target';
|
|
121
|
+
// Fire max_attempts failed login requests for the same username
|
|
122
|
+
/* eslint-disable no-await-in-loop */
|
|
123
|
+
for (let i = 0; i < max_attempts; i++) {
|
|
124
|
+
const res = await test_app.app.request(login_route.path, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: {
|
|
127
|
+
host: 'localhost',
|
|
128
|
+
origin: 'http://localhost:5173',
|
|
129
|
+
'content-type': 'application/json',
|
|
130
|
+
},
|
|
131
|
+
body: JSON.stringify({ username: target_username, password: 'wrong' }),
|
|
132
|
+
});
|
|
133
|
+
assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
|
|
134
|
+
}
|
|
135
|
+
/* eslint-enable no-await-in-loop */
|
|
136
|
+
// The next request for the same username should be rate limited
|
|
137
|
+
const blocked_res = await test_app.app.request(login_route.path, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: {
|
|
140
|
+
host: 'localhost',
|
|
141
|
+
origin: 'http://localhost:5173',
|
|
142
|
+
'content-type': 'application/json',
|
|
143
|
+
},
|
|
144
|
+
body: JSON.stringify({ username: target_username, password: 'wrong' }),
|
|
145
|
+
});
|
|
146
|
+
assert.strictEqual(blocked_res.status, 429);
|
|
147
|
+
const body = await blocked_res.json();
|
|
148
|
+
RateLimitError.parse(body);
|
|
149
|
+
assert.ok(typeof body.retry_after === 'number' && body.retry_after > 0, 'Expected positive retry_after');
|
|
150
|
+
assert_rate_limit_retry_after_header(blocked_res, body);
|
|
151
|
+
// A different username should NOT be rate limited
|
|
152
|
+
const other_res = await test_app.app.request(login_route.path, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: {
|
|
155
|
+
host: 'localhost',
|
|
156
|
+
origin: 'http://localhost:5173',
|
|
157
|
+
'content-type': 'application/json',
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify({ username: 'different_user', password: 'wrong' }),
|
|
160
|
+
});
|
|
161
|
+
assert.notStrictEqual(other_res.status, 429, 'Different username should not be rate limited');
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
login_account_rate_limiter.dispose();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
// --- 3. Bearer auth IP rate limiting ---
|
|
169
|
+
describe('bearer auth IP rate limiting', () => {
|
|
170
|
+
test(`bearer auth is blocked after ${max_attempts} invalid token attempts`, async () => {
|
|
171
|
+
const bearer_ip_rate_limiter = create_test_rate_limiter();
|
|
172
|
+
try {
|
|
173
|
+
const test_app = await create_test_app({
|
|
174
|
+
session_options: options.session_options,
|
|
175
|
+
create_route_specs: options.create_route_specs,
|
|
176
|
+
db: get_db(),
|
|
177
|
+
app_options: {
|
|
178
|
+
...options.app_options,
|
|
179
|
+
ip_rate_limiter: null,
|
|
180
|
+
login_account_rate_limiter: null,
|
|
181
|
+
bearer_ip_rate_limiter,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
const verify_route = find_auth_route(test_app.route_specs, '/verify', 'GET');
|
|
185
|
+
assert.ok(verify_route, 'Expected GET /verify route — ensure create_route_specs includes account routes');
|
|
186
|
+
// Fire max_attempts invalid bearer requests (sequential — must exhaust the window)
|
|
187
|
+
/* eslint-disable no-await-in-loop */
|
|
188
|
+
for (let i = 0; i < max_attempts; i++) {
|
|
189
|
+
const res = await test_app.app.request(verify_route.path, {
|
|
190
|
+
headers: {
|
|
191
|
+
host: 'localhost',
|
|
192
|
+
authorization: 'Bearer secret_fuz_token_invalid',
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
|
|
196
|
+
}
|
|
197
|
+
/* eslint-enable no-await-in-loop */
|
|
198
|
+
// The next request should be rate limited
|
|
199
|
+
const blocked_res = await test_app.app.request(verify_route.path, {
|
|
200
|
+
headers: {
|
|
201
|
+
host: 'localhost',
|
|
202
|
+
authorization: 'Bearer secret_fuz_token_invalid',
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
assert.strictEqual(blocked_res.status, 429);
|
|
206
|
+
const body = await blocked_res.json();
|
|
207
|
+
RateLimitError.parse(body);
|
|
208
|
+
assert_rate_limit_retry_after_header(blocked_res, body);
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
bearer_ip_rate_limiter.dispose();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
import type { RouteSpec } from '../http/route_spec.js';
|
|
3
|
+
import type { AppServerContext, AppServerOptions } from '../server/app_server.js';
|
|
4
|
+
import type { SessionOptions } from '../auth/session_cookie.js';
|
|
5
|
+
import { type DbFactory } from './db.js';
|
|
6
|
+
/** Options for `describe_round_trip_validation`. */
|
|
7
|
+
export interface RoundTripTestOptions {
|
|
8
|
+
/** Session config for cookie-based auth. */
|
|
9
|
+
session_options: SessionOptions<string>;
|
|
10
|
+
/** Route spec factory — same one used in production. */
|
|
11
|
+
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
12
|
+
/** Optional overrides for `AppServerOptions`. */
|
|
13
|
+
app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
|
|
14
|
+
/** Database factories to run tests against. Default: pglite only. */
|
|
15
|
+
db_factories?: Array<DbFactory>;
|
|
16
|
+
/** Routes to skip, in `'METHOD /path'` format. */
|
|
17
|
+
skip_routes?: Array<string>;
|
|
18
|
+
/** Override generated bodies for specific routes (`'METHOD /path'` → body). */
|
|
19
|
+
input_overrides?: Map<string, Record<string, unknown>>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run schema-driven round-trip validation tests.
|
|
23
|
+
*
|
|
24
|
+
* For each route:
|
|
25
|
+
* 1. Resolve URL with valid params
|
|
26
|
+
* 2. Generate a valid request body (or use override)
|
|
27
|
+
* 3. Pick auth headers matching the route's auth requirement
|
|
28
|
+
* 4. Fire the request and validate the response against declared schemas
|
|
29
|
+
*
|
|
30
|
+
* SSE routes are skipped (Content-Type `text/event-stream`).
|
|
31
|
+
* Routes returning non-2xx with valid input are still validated against
|
|
32
|
+
* their declared error schemas.
|
|
33
|
+
*
|
|
34
|
+
* @param options - round-trip test configuration
|
|
35
|
+
*/
|
|
36
|
+
export declare const describe_round_trip_validation: (options: RoundTripTestOptions) => void;
|
|
37
|
+
//# sourceMappingURL=round_trip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAc7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG9D,OAAO,EAAwB,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAO9D,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACpC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC,kDAAkD;IAClD,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IAsF9E,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Schema-driven round-trip validation test suite.
|
|
4
|
+
*
|
|
5
|
+
* For every route spec, generates a valid request (auth, params, body)
|
|
6
|
+
* and validates the response against declared output or error schemas.
|
|
7
|
+
* DB-backed via `create_test_app` — exercises the full middleware stack.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { describe, test, beforeAll, afterAll } from 'vitest';
|
|
12
|
+
import { ROLE_ADMIN } from '../auth/role_schema.js';
|
|
13
|
+
import { create_test_app } from './app_server.js';
|
|
14
|
+
import { create_pglite_factory } from './db.js';
|
|
15
|
+
import { assert_response_matches_spec } from './integration_helpers.js';
|
|
16
|
+
import { resolve_valid_path, generate_valid_body } from './schema_generators.js';
|
|
17
|
+
import { run_migrations } from '../db/migrate.js';
|
|
18
|
+
import { AUTH_MIGRATION_NS } from '../auth/migrations.js';
|
|
19
|
+
/**
|
|
20
|
+
* Run schema-driven round-trip validation tests.
|
|
21
|
+
*
|
|
22
|
+
* For each route:
|
|
23
|
+
* 1. Resolve URL with valid params
|
|
24
|
+
* 2. Generate a valid request body (or use override)
|
|
25
|
+
* 3. Pick auth headers matching the route's auth requirement
|
|
26
|
+
* 4. Fire the request and validate the response against declared schemas
|
|
27
|
+
*
|
|
28
|
+
* SSE routes are skipped (Content-Type `text/event-stream`).
|
|
29
|
+
* Routes returning non-2xx with valid input are still validated against
|
|
30
|
+
* their declared error schemas.
|
|
31
|
+
*
|
|
32
|
+
* @param options - round-trip test configuration
|
|
33
|
+
*/
|
|
34
|
+
export const describe_round_trip_validation = (options) => {
|
|
35
|
+
const skip_set = new Set(options.skip_routes);
|
|
36
|
+
const init_schema = async (db) => {
|
|
37
|
+
await run_migrations(db, [AUTH_MIGRATION_NS]);
|
|
38
|
+
};
|
|
39
|
+
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
40
|
+
for (const factory of factories) {
|
|
41
|
+
describe(`round-trip validation (${factory.name})`, () => {
|
|
42
|
+
if (factory.skip)
|
|
43
|
+
return;
|
|
44
|
+
let test_app;
|
|
45
|
+
let authed_account;
|
|
46
|
+
let admin_account;
|
|
47
|
+
let db;
|
|
48
|
+
beforeAll(async () => {
|
|
49
|
+
db = await factory.create();
|
|
50
|
+
test_app = await create_test_app({
|
|
51
|
+
session_options: options.session_options,
|
|
52
|
+
create_route_specs: options.create_route_specs,
|
|
53
|
+
db,
|
|
54
|
+
app_options: options.app_options,
|
|
55
|
+
});
|
|
56
|
+
// Create accounts at each auth level
|
|
57
|
+
authed_account = await test_app.create_account({
|
|
58
|
+
username: 'round_trip_authed',
|
|
59
|
+
roles: [],
|
|
60
|
+
});
|
|
61
|
+
admin_account = await test_app.create_account({
|
|
62
|
+
username: 'round_trip_admin',
|
|
63
|
+
roles: [ROLE_ADMIN],
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
afterAll(async () => {
|
|
67
|
+
await test_app.cleanup();
|
|
68
|
+
await factory.close(db);
|
|
69
|
+
});
|
|
70
|
+
test('all routes produce schema-valid responses', async () => {
|
|
71
|
+
for (const spec of test_app.route_specs) {
|
|
72
|
+
const route_key = `${spec.method} ${spec.path}`;
|
|
73
|
+
if (skip_set.has(route_key))
|
|
74
|
+
continue;
|
|
75
|
+
// Resolve URL with valid param values
|
|
76
|
+
const url = resolve_valid_path(spec.path, spec.params);
|
|
77
|
+
// Generate or override request body
|
|
78
|
+
const override = options.input_overrides?.get(route_key);
|
|
79
|
+
const body = override ?? generate_valid_body(spec.input);
|
|
80
|
+
// Pick auth headers based on route auth requirement
|
|
81
|
+
const headers = pick_auth_headers(spec, test_app, authed_account, admin_account);
|
|
82
|
+
// Fire request
|
|
83
|
+
const request_init = {
|
|
84
|
+
method: spec.method,
|
|
85
|
+
headers: {
|
|
86
|
+
...headers,
|
|
87
|
+
...(body ? { 'content-type': 'application/json' } : {}),
|
|
88
|
+
},
|
|
89
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
90
|
+
};
|
|
91
|
+
const res = await test_app.app.request(url, request_init); // eslint-disable-line no-await-in-loop
|
|
92
|
+
// Skip SSE responses — streaming bodies can't be parsed as JSON
|
|
93
|
+
if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
|
|
94
|
+
await res.body?.cancel(); // eslint-disable-line no-await-in-loop
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// Validate response against declared schemas
|
|
98
|
+
try {
|
|
99
|
+
await assert_response_matches_spec(test_app.route_specs, spec.method, url, res); // eslint-disable-line no-await-in-loop
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
// Re-throw with route context for easier debugging
|
|
103
|
+
throw new Error(`Round-trip validation failed for ${route_key} (status ${res.status}): ${e.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Pick auth headers matching a route spec's auth requirement.
|
|
112
|
+
*/
|
|
113
|
+
const pick_auth_headers = (spec, test_app, authed_account, admin_account) => {
|
|
114
|
+
switch (spec.auth.type) {
|
|
115
|
+
case 'none':
|
|
116
|
+
return { host: 'localhost', origin: 'http://localhost:5173' };
|
|
117
|
+
case 'authenticated':
|
|
118
|
+
return authed_account.create_session_headers();
|
|
119
|
+
case 'role':
|
|
120
|
+
if (spec.auth.role === ROLE_ADMIN) {
|
|
121
|
+
return admin_account.create_session_headers();
|
|
122
|
+
}
|
|
123
|
+
// Keeper role uses the bootstrapped account (which has keeper role)
|
|
124
|
+
return test_app.create_session_headers();
|
|
125
|
+
case 'keeper':
|
|
126
|
+
return test_app.create_bearer_headers();
|
|
127
|
+
}
|
|
128
|
+
};
|