@fuzdev/fuz_app 0.128.0 → 0.130.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 +23 -6
- package/dist/actions/connection_closer.d.ts +3 -1
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/register_action_ws.d.ts +46 -1
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +105 -14
- package/dist/actions/transports.d.ts +2 -1
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +2 -1
- package/dist/actions/transports_ws_backend.d.ts +37 -4
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +68 -4
- package/dist/auth/CLAUDE.md +1 -1
- package/dist/auth/actor_lookup_action_specs.d.ts +2 -2
- package/dist/auth/actor_lookup_action_specs.js +2 -2
- package/dist/auth/audit_emitter.d.ts +14 -7
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +6 -8
- package/dist/auth/audit_log_routes.d.ts +7 -3
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +48 -26
- package/dist/auth/bearer_auth.d.ts +3 -2
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +3 -2
- package/dist/auth/request_context.d.ts +1 -1
- package/dist/auth/request_context.js +1 -1
- package/dist/auth/token_scope.d.ts +4 -2
- package/dist/auth/token_scope.d.ts.map +1 -1
- package/dist/auth/token_scope.js +10 -2
- package/dist/http/CLAUDE.md +4 -5
- package/dist/http/auth_shape.d.ts +3 -2
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +3 -2
- package/dist/http/common_routes.d.ts +1 -14
- package/dist/http/common_routes.d.ts.map +1 -1
- package/dist/http/common_routes.js +1 -19
- package/dist/http/ip_canonical.d.ts +1 -1
- package/dist/http/ip_canonical.js +1 -1
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +7 -5
- package/dist/realtime/sse.d.ts +14 -1
- package/dist/realtime/sse.d.ts.map +1 -1
- package/dist/realtime/sse.js +77 -1
- package/dist/realtime/sse_auth_guard.d.ts +3 -1
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +4 -2
- package/dist/realtime/subscriber_registry.d.ts +44 -8
- package/dist/realtime/subscriber_registry.d.ts.map +1 -1
- package/dist/realtime/subscriber_registry.js +64 -10
- package/dist/server/app_server.d.ts +122 -8
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +136 -91
- package/dist/server/surface_route.d.ts +32 -0
- package/dist/server/surface_route.d.ts.map +1 -0
- package/dist/server/surface_route.js +46 -0
- package/dist/testing/CLAUDE.md +33 -12
- package/dist/testing/app_server.d.ts +39 -4
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +84 -68
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/audit_drift_guard.d.ts +1 -1
- package/dist/testing/cross_backend/default_spine_surface.d.ts +2 -2
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +5 -2
- package/dist/testing/cross_backend/identity_parity.js +1 -1
- package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/in_process_setup.js +4 -1
- package/dist/testing/cross_backend/method_coverage.d.ts +1 -1
- package/dist/testing/cross_backend/method_coverage.d.ts.map +1 -1
- package/dist/testing/cross_backend/method_coverage.js +2 -2
- package/dist/testing/cross_backend/peer_ping_ws.d.ts +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.js +4 -4
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.d.ts +4 -4
- package/dist/testing/cross_backend/setup.js +1 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts +1 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +24 -14
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +40 -32
- package/dist/testing/cross_backend/testing_server_bun.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_bun.js +80 -64
- package/dist/testing/cross_backend/testing_server_core.d.ts +10 -4
- package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_deno.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_deno.js +1 -1
- package/dist/testing/cross_backend/testing_server_node.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_node.js +2 -2
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +20 -0
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_inbound_dispatch.js +177 -0
- package/dist/testing/cross_backend/ws_round_trip.d.ts +1 -1
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +11 -9
- package/dist/testing/stubs.d.ts +45 -7
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +60 -31
- package/dist/testing/ws_round_trip.d.ts +1 -3
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/ui/AdminSurface.svelte +3 -1
- package/dist/ui/AdminSurface.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +3 -1
- package/dist/ui/async_slot.svelte.d.ts +4 -4
- package/dist/ui/async_slot.svelte.js +4 -4
- package/dist/ui/keyed_async_slot.svelte.d.ts +4 -4
- package/dist/ui/keyed_async_slot.svelte.js +3 -3
- package/package.json +1 -1
|
@@ -10,18 +10,18 @@
|
|
|
10
10
|
import { Hono, type Context } from 'hono';
|
|
11
11
|
import type { UpgradeWebSocket } from 'hono/ws';
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
-
import { type SessionOptions } from '../auth/session_cookie.js';
|
|
13
|
+
import { type SessionOptions, type SessionCookieOptions } from '../auth/session_cookie.js';
|
|
14
14
|
import type { BootstrapAccountSuccess } from '../auth/bootstrap_account.js';
|
|
15
15
|
import type { EventSpec } from '../realtime/sse.js';
|
|
16
16
|
import { type AuditLogSse } from '../realtime/sse_auth_guard.js';
|
|
17
|
-
import { RateLimiter, type RateLimiterMode } from '../rate_limiter.js';
|
|
17
|
+
import { RateLimiter, type RateLimiterMode, type RateLimiterOptions } from '../rate_limiter.js';
|
|
18
18
|
import type { DaemonTokenState } from '../auth/daemon_token.js';
|
|
19
19
|
import type { MigrationResult } from '../db/migrate.js';
|
|
20
20
|
import type { AppBackend } from './app_backend.js';
|
|
21
21
|
import type { AppServerContext } from './app_server_context.js';
|
|
22
22
|
import '../hono_context.js';
|
|
23
23
|
import { type ServeStaticFactory } from './static.js';
|
|
24
|
-
import { type AppSurfaceSpec, type RpcEndpointSpec } from '../http/surface.js';
|
|
24
|
+
import { type AppSurfaceSpec, type AppSurfaceDiagnostic, type RpcEndpointSpec } from '../http/surface.js';
|
|
25
25
|
import { type RouteSpec } from '../http/route_spec.js';
|
|
26
26
|
import type { MiddlewareSpec } from '../http/middleware_spec.js';
|
|
27
27
|
import { type BootstrapStatus } from '../auth/bootstrap_routes.js';
|
|
@@ -186,10 +186,19 @@ export interface AppServerOptions {
|
|
|
186
186
|
/** Bootstrap options. Omit to skip bootstrap status check and routes. */
|
|
187
187
|
bootstrap?: BootstrapServerOptions;
|
|
188
188
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
189
|
+
* Mount `GET /api/surface`, which serves this server's generated
|
|
190
|
+
* `AppSurface` (every route, its auth, and its schemas) to an admin UI such
|
|
191
|
+
* as `AdminSurface.svelte`. Off by default — the Rust spine serves no such
|
|
192
|
+
* route, and a map of the API is nothing to publish unasked.
|
|
193
|
+
*
|
|
194
|
+
* When mounted the route is admin-only (`roles: ['admin']`, so it takes the
|
|
195
|
+
* `?acting=` selector) and a rule-3 token surface
|
|
196
|
+
* (`required_scope: 'surface:app_surface'`): a narrowed API token is refused
|
|
197
|
+
* it whatever its method list says. See `server/surface_route.ts`.
|
|
198
|
+
*
|
|
199
|
+
* @default false
|
|
191
200
|
*/
|
|
192
|
-
surface_route?:
|
|
201
|
+
surface_route?: boolean;
|
|
193
202
|
/**
|
|
194
203
|
* Build route specs from the initialized backend.
|
|
195
204
|
* Called after all middleware is ready.
|
|
@@ -424,7 +433,10 @@ export interface AppServer {
|
|
|
424
433
|
* audit stream — including transports supplied through
|
|
425
434
|
* `WsEndpointSpec.transport` or added by hand, since they are registered
|
|
426
435
|
* on the backend's closer and the backend is going away; a close that
|
|
427
|
-
* throws is logged and the shutdown goes on
|
|
436
|
+
* throws is logged and the shutdown goes on. Each transport and registry
|
|
437
|
+
* is left closing, so an upgrade or stream that registers afterward is
|
|
438
|
+
* born closed — refused at once rather than outliving the server — and
|
|
439
|
+
* a closed transport hosts no other server
|
|
428
440
|
* 3. release what assembly acquired, last acquired first: remove the
|
|
429
441
|
* audit listeners `create_app_server` registered on `deps.audit` (the
|
|
430
442
|
* audit-log SSE listener, the WS auth guards, each
|
|
@@ -469,6 +481,106 @@ export declare const require_audit_sse: (source: {
|
|
|
469
481
|
}) => AuditLogSse;
|
|
470
482
|
/** Default maximum request body size: 1 MiB. */
|
|
471
483
|
export declare const DEFAULT_MAX_BODY_SIZE: number;
|
|
484
|
+
/**
|
|
485
|
+
* Each `AppServerOptions` rate-limiter field, with the defaults an omitted one
|
|
486
|
+
* is built from under `rate_limiters: 'enforced'` and the name its
|
|
487
|
+
* explicitly-null diagnostic uses. Insertion order is the order limiters are
|
|
488
|
+
* built and their diagnostics are listed.
|
|
489
|
+
*/
|
|
490
|
+
declare const rate_limiter_slots: {
|
|
491
|
+
readonly login_ip_rate_limiter: {
|
|
492
|
+
readonly label: "login IP";
|
|
493
|
+
readonly defaults: RateLimiterOptions;
|
|
494
|
+
};
|
|
495
|
+
readonly signup_ip_rate_limiter: {
|
|
496
|
+
readonly label: "signup IP";
|
|
497
|
+
readonly defaults: RateLimiterOptions;
|
|
498
|
+
};
|
|
499
|
+
readonly bootstrap_ip_rate_limiter: {
|
|
500
|
+
readonly label: "bootstrap IP";
|
|
501
|
+
readonly defaults: RateLimiterOptions;
|
|
502
|
+
};
|
|
503
|
+
readonly login_account_rate_limiter: {
|
|
504
|
+
readonly label: "login account";
|
|
505
|
+
readonly defaults: RateLimiterOptions;
|
|
506
|
+
};
|
|
507
|
+
readonly signup_account_rate_limiter: {
|
|
508
|
+
readonly label: "signup account";
|
|
509
|
+
readonly defaults: RateLimiterOptions;
|
|
510
|
+
};
|
|
511
|
+
readonly action_ip_rate_limiter: {
|
|
512
|
+
readonly label: "action IP";
|
|
513
|
+
readonly defaults: RateLimiterOptions;
|
|
514
|
+
};
|
|
515
|
+
readonly action_account_rate_limiter: {
|
|
516
|
+
readonly label: "action account";
|
|
517
|
+
readonly defaults: RateLimiterOptions;
|
|
518
|
+
};
|
|
519
|
+
};
|
|
520
|
+
/**
|
|
521
|
+
* Name of an `AppServerOptions` rate-limiter field — also the
|
|
522
|
+
* `AppServerContext` field its resolved limiter lands on.
|
|
523
|
+
*
|
|
524
|
+
* @internal
|
|
525
|
+
*/
|
|
526
|
+
export type AppServerRateLimiterKey = keyof typeof rate_limiter_slots;
|
|
527
|
+
/**
|
|
528
|
+
* Every `AppServerOptions` rate-limiter field, in build order.
|
|
529
|
+
*
|
|
530
|
+
* @internal
|
|
531
|
+
*/
|
|
532
|
+
export declare const APP_SERVER_RATE_LIMITER_KEYS: ReadonlyArray<AppServerRateLimiterKey>;
|
|
533
|
+
/**
|
|
534
|
+
* The rate limiters `create_app_server` resolved from its options.
|
|
535
|
+
*
|
|
536
|
+
* @internal
|
|
537
|
+
*/
|
|
538
|
+
export interface ResolvedRateLimiters {
|
|
539
|
+
/** The `rate_limiters` mode, defaulted to `'enforced'`. */
|
|
540
|
+
mode: RateLimiterMode;
|
|
541
|
+
/** Each limiter by option name — `null` when off. */
|
|
542
|
+
limiters: Record<AppServerRateLimiterKey, RateLimiter | null>;
|
|
543
|
+
/**
|
|
544
|
+
* The limiters built here for omitted options, in build order — the
|
|
545
|
+
* server's to dispose. Never one passed in, which its caller owns.
|
|
546
|
+
*/
|
|
547
|
+
built: Array<RateLimiter>;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Resolve each rate-limiter option: an explicit option (instance or `null`)
|
|
551
|
+
* wins; an omitted one is a default instance under `'enforced'` and `null`
|
|
552
|
+
* under `'disabled_for_testing'`, which also announces the mode on stderr
|
|
553
|
+
* (`announce_rate_limiters_disabled`). One instance per surface — see
|
|
554
|
+
* `AppServerOptions.login_ip_rate_limiter` for why these are not a single
|
|
555
|
+
* shared bucket.
|
|
556
|
+
*
|
|
557
|
+
* @param options - the rate-limiter fields and `rate_limiters` mode of `AppServerOptions`
|
|
558
|
+
* @internal
|
|
559
|
+
*/
|
|
560
|
+
export declare const resolve_rate_limiters: (options: Pick<AppServerOptions, AppServerRateLimiterKey | "rate_limiters">) => ResolvedRateLimiters;
|
|
561
|
+
/**
|
|
562
|
+
* Inputs to `collect_config_diagnostics`.
|
|
563
|
+
*
|
|
564
|
+
* @internal
|
|
565
|
+
*/
|
|
566
|
+
export interface ConfigDiagnosticsInput {
|
|
567
|
+
/** The session cookie overrides, from `AppServerOptions.session_options`. */
|
|
568
|
+
cookie_options: Partial<SessionCookieOptions> | undefined;
|
|
569
|
+
/** The `rate_limiters` mode, defaulted. */
|
|
570
|
+
rate_limiter_mode: RateLimiterMode;
|
|
571
|
+
/** The resolved limiters, from `resolve_rate_limiters`. */
|
|
572
|
+
rate_limiters: Record<AppServerRateLimiterKey, RateLimiter | null>;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* The config-level diagnostics `create_app_server` appends to the surface's
|
|
576
|
+
* spec-level ones: a weakened session cookie, and an open rate-limited
|
|
577
|
+
* surface — one warning for `'disabled_for_testing'`, otherwise one per
|
|
578
|
+
* explicitly-null limiter, so a deployment that disables login's limiter but
|
|
579
|
+
* keeps signup's reads differently from one that disables both.
|
|
580
|
+
*
|
|
581
|
+
* @internal
|
|
582
|
+
*/
|
|
583
|
+
export declare const collect_config_diagnostics: (input: ConfigDiagnosticsInput) => Array<AppSurfaceDiagnostic>;
|
|
472
584
|
/**
|
|
473
585
|
* Create a fully assembled Hono app with auth, middleware, and routes.
|
|
474
586
|
*
|
|
@@ -503,8 +615,10 @@ export declare const DEFAULT_MAX_BODY_SIZE: number;
|
|
|
503
615
|
* live connection on the backend's closer, runs the same releases, and
|
|
504
616
|
* closes the database (`AppServer.close`).
|
|
505
617
|
*
|
|
506
|
-
* @returns assembled Hono app
|
|
618
|
+
* @returns the assembled Hono app with its surface, bootstrap status, migration results,
|
|
619
|
+
* audit SSE, mounted WS transports, and `close`
|
|
507
620
|
* @throws Error from any assembly step, after releasing what assembly acquired
|
|
508
621
|
*/
|
|
509
622
|
export declare const create_app_server: (options: AppServerOptions) => Promise<AppServer>;
|
|
623
|
+
export {};
|
|
510
624
|
//# sourceMappingURL=app_server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAEN,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ import { log_startup_summary } from './startup.js';
|
|
|
24
24
|
import { create_app_surface_spec } from '../http/surface.js';
|
|
25
25
|
import { apply_middleware_specs, apply_route_specs, prefix_route_specs } from '../http/route_spec.js';
|
|
26
26
|
import { check_bootstrap_status, create_bootstrap_route_specs } from '../auth/bootstrap_routes.js';
|
|
27
|
-
import { create_surface_route_spec } from '
|
|
27
|
+
import { create_surface_route_spec } from './surface_route.js';
|
|
28
28
|
import { flush_pending_effects, flush_post_commit_effects } from '../http/pending_effects.js';
|
|
29
29
|
import { assert_endpoint_in_auth_scope, assert_middleware_stack_mounted, create_auth_middleware_specs } from '../auth/middleware.js';
|
|
30
30
|
import { fuz_auth_guard_resolver } from '../auth/auth_guard_resolver.js';
|
|
@@ -60,6 +60,125 @@ export const require_audit_sse = (source) => {
|
|
|
60
60
|
};
|
|
61
61
|
/** Default maximum request body size: 1 MiB. */
|
|
62
62
|
export const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
|
|
63
|
+
/**
|
|
64
|
+
* Each `AppServerOptions` rate-limiter field, with the defaults an omitted one
|
|
65
|
+
* is built from under `rate_limiters: 'enforced'` and the name its
|
|
66
|
+
* explicitly-null diagnostic uses. Insertion order is the order limiters are
|
|
67
|
+
* built and their diagnostics are listed.
|
|
68
|
+
*/
|
|
69
|
+
const rate_limiter_slots = {
|
|
70
|
+
login_ip_rate_limiter: { label: 'login IP', defaults: default_login_ip_rate_limit },
|
|
71
|
+
signup_ip_rate_limiter: { label: 'signup IP', defaults: default_login_ip_rate_limit },
|
|
72
|
+
bootstrap_ip_rate_limiter: { label: 'bootstrap IP', defaults: default_login_ip_rate_limit },
|
|
73
|
+
login_account_rate_limiter: {
|
|
74
|
+
label: 'login account',
|
|
75
|
+
defaults: default_login_account_rate_limit
|
|
76
|
+
},
|
|
77
|
+
signup_account_rate_limiter: {
|
|
78
|
+
label: 'signup account',
|
|
79
|
+
defaults: default_login_account_rate_limit
|
|
80
|
+
},
|
|
81
|
+
action_ip_rate_limiter: { label: 'action IP', defaults: default_action_ip_rate_limit },
|
|
82
|
+
action_account_rate_limiter: {
|
|
83
|
+
label: 'action account',
|
|
84
|
+
defaults: default_action_account_rate_limit
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Every `AppServerOptions` rate-limiter field, in build order.
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
export const APP_SERVER_RATE_LIMITER_KEYS = Object.keys(rate_limiter_slots);
|
|
93
|
+
/**
|
|
94
|
+
* Resolve each rate-limiter option: an explicit option (instance or `null`)
|
|
95
|
+
* wins; an omitted one is a default instance under `'enforced'` and `null`
|
|
96
|
+
* under `'disabled_for_testing'`, which also announces the mode on stderr
|
|
97
|
+
* (`announce_rate_limiters_disabled`). One instance per surface — see
|
|
98
|
+
* `AppServerOptions.login_ip_rate_limiter` for why these are not a single
|
|
99
|
+
* shared bucket.
|
|
100
|
+
*
|
|
101
|
+
* @param options - the rate-limiter fields and `rate_limiters` mode of `AppServerOptions`
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
export const resolve_rate_limiters = (options) => {
|
|
105
|
+
const mode = options.rate_limiters ?? 'enforced';
|
|
106
|
+
const limiters = {};
|
|
107
|
+
const built = [];
|
|
108
|
+
for (const key of APP_SERVER_RATE_LIMITER_KEYS) {
|
|
109
|
+
const option = options[key];
|
|
110
|
+
if (option !== undefined) {
|
|
111
|
+
limiters[key] = option;
|
|
112
|
+
}
|
|
113
|
+
else if (mode === 'disabled_for_testing') {
|
|
114
|
+
announce_rate_limiters_disabled();
|
|
115
|
+
limiters[key] = null;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const limiter = new RateLimiter({ ...rate_limiter_slots[key].defaults });
|
|
119
|
+
built.push(limiter);
|
|
120
|
+
limiters[key] = limiter;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { mode, limiters, built };
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* The config-level diagnostics `create_app_server` appends to the surface's
|
|
127
|
+
* spec-level ones: a weakened session cookie, and an open rate-limited
|
|
128
|
+
* surface — one warning for `'disabled_for_testing'`, otherwise one per
|
|
129
|
+
* explicitly-null limiter, so a deployment that disables login's limiter but
|
|
130
|
+
* keeps signup's reads differently from one that disables both.
|
|
131
|
+
*
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
export const collect_config_diagnostics = (input) => {
|
|
135
|
+
const diagnostics = [];
|
|
136
|
+
const { cookie_options } = input;
|
|
137
|
+
if (cookie_options) {
|
|
138
|
+
if (cookie_options.secure === false) {
|
|
139
|
+
diagnostics.push({
|
|
140
|
+
level: 'warning',
|
|
141
|
+
category: 'security',
|
|
142
|
+
message: 'Session cookie secure=false — cookies sent over HTTP'
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (cookie_options.sameSite && cookie_options.sameSite !== session_cookie_options.sameSite) {
|
|
146
|
+
diagnostics.push({
|
|
147
|
+
level: 'warning',
|
|
148
|
+
category: 'security',
|
|
149
|
+
message: `Session cookie sameSite='${cookie_options.sameSite}' — weakened from default '${session_cookie_options.sameSite}'`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (cookie_options.httpOnly === false) {
|
|
153
|
+
diagnostics.push({
|
|
154
|
+
level: 'warning',
|
|
155
|
+
category: 'security',
|
|
156
|
+
message: 'Session cookie httpOnly=false — cookie accessible to JS'
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (input.rate_limiter_mode === 'disabled_for_testing') {
|
|
161
|
+
// one warning for the mode rather than one per limiter it nulled
|
|
162
|
+
diagnostics.push({
|
|
163
|
+
level: 'warning',
|
|
164
|
+
category: 'security',
|
|
165
|
+
message: "rate limiters disabled for testing (rate_limiters: 'disabled_for_testing') — " +
|
|
166
|
+
'every limiter not passed explicitly is off; never use in production'
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
for (const key of APP_SERVER_RATE_LIMITER_KEYS) {
|
|
171
|
+
if (input.rate_limiters[key] === null) {
|
|
172
|
+
diagnostics.push({
|
|
173
|
+
level: 'warning',
|
|
174
|
+
category: 'security',
|
|
175
|
+
message: `${rate_limiter_slots[key].label} rate limiter explicitly disabled (null)`
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return diagnostics;
|
|
181
|
+
};
|
|
63
182
|
/**
|
|
64
183
|
* Create a fully assembled Hono app with auth, middleware, and routes.
|
|
65
184
|
*
|
|
@@ -94,7 +213,8 @@ export const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
|
|
|
94
213
|
* live connection on the backend's closer, runs the same releases, and
|
|
95
214
|
* closes the database (`AppServer.close`).
|
|
96
215
|
*
|
|
97
|
-
* @returns assembled Hono app
|
|
216
|
+
* @returns the assembled Hono app with its surface, bootstrap status, migration results,
|
|
217
|
+
* audit SSE, mounted WS transports, and `close`
|
|
98
218
|
* @throws Error from any assembly step, after releasing what assembly acquired
|
|
99
219
|
*/
|
|
100
220
|
export const create_app_server = async (options) => {
|
|
@@ -130,31 +250,13 @@ const assemble_app_server = async (options, releases) => {
|
|
|
130
250
|
const { backend } = options;
|
|
131
251
|
const { deps } = backend;
|
|
132
252
|
const { log } = deps;
|
|
133
|
-
// Rate limiters
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// shared bucket. The ones built here are the server's to dispose — on
|
|
138
|
-
// close, or when assembly fails.
|
|
139
|
-
const rate_limiter_mode = options.rate_limiters ?? 'enforced';
|
|
140
|
-
const resolve_rate_limiter = (option, defaults) => {
|
|
141
|
-
if (option !== undefined)
|
|
142
|
-
return option;
|
|
143
|
-
if (rate_limiter_mode === 'disabled_for_testing') {
|
|
144
|
-
announce_rate_limiters_disabled();
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
const limiter = new RateLimiter({ ...defaults });
|
|
253
|
+
// Rate limiters — the ones built here are the server's to dispose, on
|
|
254
|
+
// close or when assembly fails.
|
|
255
|
+
const { mode: rate_limiter_mode, limiters: rate_limiters, built: built_rate_limiters } = resolve_rate_limiters(options);
|
|
256
|
+
for (const limiter of built_rate_limiters) {
|
|
148
257
|
releases.push(() => limiter.dispose());
|
|
149
|
-
|
|
150
|
-
};
|
|
151
|
-
const login_ip_rate_limiter = resolve_rate_limiter(options.login_ip_rate_limiter, default_login_ip_rate_limit);
|
|
152
|
-
const signup_ip_rate_limiter = resolve_rate_limiter(options.signup_ip_rate_limiter, default_login_ip_rate_limit);
|
|
153
|
-
const bootstrap_ip_rate_limiter = resolve_rate_limiter(options.bootstrap_ip_rate_limiter, default_login_ip_rate_limit);
|
|
154
|
-
const login_account_rate_limiter = resolve_rate_limiter(options.login_account_rate_limiter, default_login_account_rate_limit);
|
|
155
|
-
const signup_account_rate_limiter = resolve_rate_limiter(options.signup_account_rate_limiter, default_login_account_rate_limit);
|
|
156
|
-
const action_ip_rate_limiter = resolve_rate_limiter(options.action_ip_rate_limiter, default_action_ip_rate_limit);
|
|
157
|
-
const action_account_rate_limiter = resolve_rate_limiter(options.action_account_rate_limiter, default_action_account_rate_limit);
|
|
258
|
+
}
|
|
259
|
+
const { bootstrap_ip_rate_limiter, action_ip_rate_limiter, action_account_rate_limiter } = rate_limiters;
|
|
158
260
|
// Factory-managed audit SSE — registers a listener on the bound emitter
|
|
159
261
|
// so SSE fan-out runs alongside the consumer's `on_audit_event`
|
|
160
262
|
// without rebuilding `AppDeps`, and adds the registry to the backend's
|
|
@@ -213,13 +315,7 @@ const assemble_app_server = async (options, releases) => {
|
|
|
213
315
|
backend,
|
|
214
316
|
bootstrap_status,
|
|
215
317
|
session_options: options.session_options,
|
|
216
|
-
|
|
217
|
-
signup_ip_rate_limiter,
|
|
218
|
-
bootstrap_ip_rate_limiter,
|
|
219
|
-
login_account_rate_limiter,
|
|
220
|
-
signup_account_rate_limiter,
|
|
221
|
-
action_ip_rate_limiter,
|
|
222
|
-
action_account_rate_limiter,
|
|
318
|
+
...rate_limiters,
|
|
223
319
|
audit_sse
|
|
224
320
|
};
|
|
225
321
|
const consumer_routes = options.create_route_specs(context);
|
|
@@ -280,8 +376,8 @@ const assemble_app_server = async (options, releases) => {
|
|
|
280
376
|
}
|
|
281
377
|
ws_mount = { endpoints: resolved_ws_endpoints, create_upgrade_websocket };
|
|
282
378
|
}
|
|
283
|
-
// Surface route (
|
|
284
|
-
if (options.surface_route
|
|
379
|
+
// Surface route (opt-in)
|
|
380
|
+
if (options.surface_route) {
|
|
285
381
|
factory_routes.push(create_surface_route_spec(surface_ref));
|
|
286
382
|
}
|
|
287
383
|
const route_specs = [...consumer_routes, ...factory_routes];
|
|
@@ -323,62 +419,11 @@ const assemble_app_server = async (options, releases) => {
|
|
|
323
419
|
ws_endpoints: resolved_ws_endpoints
|
|
324
420
|
});
|
|
325
421
|
// Config-level diagnostics (concatenated after spec-level from generate_app_surface)
|
|
326
|
-
const config_diagnostics =
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
level: 'warning',
|
|
332
|
-
category: 'security',
|
|
333
|
-
message: 'Session cookie secure=false — cookies sent over HTTP'
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
if (cookie_opts.sameSite && cookie_opts.sameSite !== session_cookie_options.sameSite) {
|
|
337
|
-
config_diagnostics.push({
|
|
338
|
-
level: 'warning',
|
|
339
|
-
category: 'security',
|
|
340
|
-
message: `Session cookie sameSite='${cookie_opts.sameSite}' — weakened from default '${session_cookie_options.sameSite}'`
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
if (cookie_opts.httpOnly === false) {
|
|
344
|
-
config_diagnostics.push({
|
|
345
|
-
level: 'warning',
|
|
346
|
-
category: 'security',
|
|
347
|
-
message: 'Session cookie httpOnly=false — cookie accessible to JS'
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
if (rate_limiter_mode === 'disabled_for_testing') {
|
|
352
|
-
// one warning for the mode rather than one per limiter it nulled
|
|
353
|
-
config_diagnostics.push({
|
|
354
|
-
level: 'warning',
|
|
355
|
-
category: 'security',
|
|
356
|
-
message: "rate limiters disabled for testing (rate_limiters: 'disabled_for_testing') — " +
|
|
357
|
-
'every limiter not passed explicitly is off; never use in production'
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
361
|
-
// One diagnostic per explicitly-null limiter — a deployment that disables
|
|
362
|
-
// login's limiter but keeps signup's is a different posture than one that
|
|
363
|
-
// disables both, and a single collapsed warning hid which surface was open.
|
|
364
|
-
for (const [name, limiter] of [
|
|
365
|
-
['login IP', login_ip_rate_limiter],
|
|
366
|
-
['signup IP', signup_ip_rate_limiter],
|
|
367
|
-
['bootstrap IP', bootstrap_ip_rate_limiter],
|
|
368
|
-
['login account', login_account_rate_limiter],
|
|
369
|
-
['signup account', signup_account_rate_limiter],
|
|
370
|
-
['action IP', action_ip_rate_limiter],
|
|
371
|
-
['action account', action_account_rate_limiter]
|
|
372
|
-
]) {
|
|
373
|
-
if (limiter === null) {
|
|
374
|
-
config_diagnostics.push({
|
|
375
|
-
level: 'warning',
|
|
376
|
-
category: 'security',
|
|
377
|
-
message: `${name} rate limiter explicitly disabled (null)`
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
422
|
+
const config_diagnostics = collect_config_diagnostics({
|
|
423
|
+
cookie_options: options.session_options.cookie_options,
|
|
424
|
+
rate_limiter_mode,
|
|
425
|
+
rate_limiters
|
|
426
|
+
});
|
|
382
427
|
if (config_diagnostics.length) {
|
|
383
428
|
surface_spec.surface.diagnostics = [...surface_spec.surface.diagnostics, ...config_diagnostics];
|
|
384
429
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The surface explorer route — `GET /api/surface`, serving the generated
|
|
3
|
+
* `AppSurface` as JSON.
|
|
4
|
+
*
|
|
5
|
+
* Opt-in on `create_app_server` (`surface_route: true`), and admin-only when
|
|
6
|
+
* mounted: the surface is a map of every route, its auth, and its schemas.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { RouteSpec } from '../http/route_spec.js';
|
|
11
|
+
import type { AppSurface } from '../http/surface.js';
|
|
12
|
+
/** Options for the surface explorer route. */
|
|
13
|
+
export interface SurfaceRouteOptions {
|
|
14
|
+
/** The generated app surface to serve — read per request, so a backfilled ref serves the final surface. */
|
|
15
|
+
surface: AppSurface;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create the admin-only route spec that serves the `AppSurface` as JSON.
|
|
19
|
+
*
|
|
20
|
+
* Surface data reveals API structure (routes, auth, schemas), so the route
|
|
21
|
+
* requires the global admin role. It is also a non-RPC surface: it declares
|
|
22
|
+
* `required_scope: 'surface:app_surface'`, so a narrowed API token is refused
|
|
23
|
+
* it whatever its method list says (403 `token_scope_required`, ahead of the
|
|
24
|
+
* role gate), while a full-scope admin bearer passes. The admin role implies
|
|
25
|
+
* `actor: 'required'`, so the route takes the `?acting=` selector a
|
|
26
|
+
* multi-actor admin disambiguates with.
|
|
27
|
+
*
|
|
28
|
+
* @param options - the surface to serve
|
|
29
|
+
* @returns the `GET /api/surface` route spec
|
|
30
|
+
*/
|
|
31
|
+
export declare const create_surface_route_spec: (options: SurfaceRouteOptions) => RouteSpec;
|
|
32
|
+
//# sourceMappingURL=surface_route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface_route.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/surface_route.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIrD,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IACnC,2GAA2G;IAC3G,OAAO,EAAE,UAAU,CAAC;CACpB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,mBAAmB,KAAG,SAmBvE,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The surface explorer route — `GET /api/surface`, serving the generated
|
|
3
|
+
* `AppSurface` as JSON.
|
|
4
|
+
*
|
|
5
|
+
* Opt-in on `create_app_server` (`surface_route: true`), and admin-only when
|
|
6
|
+
* mounted: the surface is a map of every route, its auth, and its schemas.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { ActingActor } from '../http/auth_shape.js';
|
|
12
|
+
import { ROLE_ADMIN } from '../auth/role_schema.js';
|
|
13
|
+
/**
|
|
14
|
+
* Create the admin-only route spec that serves the `AppSurface` as JSON.
|
|
15
|
+
*
|
|
16
|
+
* Surface data reveals API structure (routes, auth, schemas), so the route
|
|
17
|
+
* requires the global admin role. It is also a non-RPC surface: it declares
|
|
18
|
+
* `required_scope: 'surface:app_surface'`, so a narrowed API token is refused
|
|
19
|
+
* it whatever its method list says (403 `token_scope_required`, ahead of the
|
|
20
|
+
* role gate), while a full-scope admin bearer passes. The admin role implies
|
|
21
|
+
* `actor: 'required'`, so the route takes the `?acting=` selector a
|
|
22
|
+
* multi-actor admin disambiguates with.
|
|
23
|
+
*
|
|
24
|
+
* @param options - the surface to serve
|
|
25
|
+
* @returns the `GET /api/surface` route spec
|
|
26
|
+
*/
|
|
27
|
+
export const create_surface_route_spec = (options) => ({
|
|
28
|
+
method: 'GET',
|
|
29
|
+
path: '/api/surface',
|
|
30
|
+
auth: {
|
|
31
|
+
account: 'required',
|
|
32
|
+
actor: 'required',
|
|
33
|
+
roles: [ROLE_ADMIN],
|
|
34
|
+
// rule 3 — the surface map is not an RPC method, so a narrowed token
|
|
35
|
+
// gets none of it
|
|
36
|
+
required_scope: 'surface:app_surface'
|
|
37
|
+
},
|
|
38
|
+
handler: (c) => c.json(options.surface),
|
|
39
|
+
description: 'Application surface (routes, middleware, schemas) — admin only',
|
|
40
|
+
query: z.strictObject({ acting: ActingActor }),
|
|
41
|
+
input: z.null(),
|
|
42
|
+
output: z.looseObject({
|
|
43
|
+
routes: z.array(z.looseObject({})),
|
|
44
|
+
middleware: z.array(z.looseObject({}))
|
|
45
|
+
})
|
|
46
|
+
});
|