@fuzdev/fuz_app 0.29.0 → 0.31.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 +630 -0
- package/dist/actions/action_rpc.d.ts +29 -0
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +42 -6
- package/dist/actions/action_types.d.ts +2 -2
- package/dist/actions/cancel.d.ts +12 -13
- package/dist/actions/cancel.d.ts.map +1 -1
- package/dist/actions/cancel.js +10 -13
- package/dist/actions/heartbeat.d.ts +8 -13
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -8
- package/dist/actions/register_action_ws.d.ts +3 -3
- package/dist/actions/register_action_ws.js +2 -2
- package/dist/actions/register_ws_endpoint.d.ts +4 -4
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +3 -3
- package/dist/actions/socket.svelte.d.ts +16 -16
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +15 -15
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.d.ts +15 -0
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +17 -0
- package/dist/auth/CLAUDE.md +923 -0
- package/dist/auth/account_action_specs.d.ts +216 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -0
- package/dist/auth/account_action_specs.js +159 -0
- package/dist/auth/account_actions.d.ts +51 -0
- package/dist/auth/account_actions.d.ts.map +1 -0
- package/dist/auth/account_actions.js +119 -0
- package/dist/auth/account_queries.d.ts +6 -2
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +40 -4
- package/dist/auth/account_routes.d.ts +94 -16
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +108 -180
- package/dist/auth/account_schema.d.ts +85 -30
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +40 -8
- package/dist/auth/admin_action_specs.d.ts +674 -0
- package/dist/auth/admin_action_specs.d.ts.map +1 -0
- package/dist/auth/admin_action_specs.js +287 -0
- package/dist/auth/admin_actions.d.ts +69 -0
- package/dist/auth/admin_actions.d.ts.map +1 -0
- package/dist/auth/admin_actions.js +256 -0
- package/dist/auth/api_token.d.ts +10 -0
- package/dist/auth/api_token.d.ts.map +1 -1
- package/dist/auth/api_token.js +9 -0
- package/dist/auth/api_token_queries.d.ts +3 -3
- package/dist/auth/api_token_queries.js +3 -3
- package/dist/auth/app_settings_schema.d.ts +4 -3
- package/dist/auth/app_settings_schema.d.ts.map +1 -1
- package/dist/auth/app_settings_schema.js +2 -1
- package/dist/auth/audit_log_routes.d.ts +14 -6
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +22 -79
- package/dist/auth/audit_log_schema.d.ts +100 -29
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +83 -11
- package/dist/auth/bootstrap_routes.d.ts +14 -0
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +10 -3
- package/dist/auth/cleanup.d.ts +63 -0
- package/dist/auth/cleanup.d.ts.map +1 -0
- package/dist/auth/cleanup.js +80 -0
- package/dist/auth/invite_schema.d.ts +11 -10
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +4 -3
- package/dist/auth/migrations.d.ts +6 -0
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +28 -0
- package/dist/auth/permit_offer_action_specs.d.ts +364 -0
- package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/permit_offer_action_specs.js +216 -0
- package/dist/auth/permit_offer_actions.d.ts +96 -0
- package/dist/auth/permit_offer_actions.d.ts.map +1 -0
- package/dist/auth/permit_offer_actions.js +428 -0
- package/dist/auth/permit_offer_notifications.d.ts +361 -0
- package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
- package/dist/auth/permit_offer_notifications.js +179 -0
- package/dist/auth/permit_offer_queries.d.ts +165 -0
- package/dist/auth/permit_offer_queries.d.ts.map +1 -0
- package/dist/auth/permit_offer_queries.js +390 -0
- package/dist/auth/permit_offer_schema.d.ts +103 -0
- package/dist/auth/permit_offer_schema.d.ts.map +1 -0
- package/dist/auth/permit_offer_schema.js +142 -0
- package/dist/auth/permit_queries.d.ts +77 -14
- package/dist/auth/permit_queries.d.ts.map +1 -1
- package/dist/auth/permit_queries.js +119 -24
- package/dist/auth/session_queries.d.ts +4 -2
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +4 -2
- package/dist/auth/signup_routes.d.ts +13 -0
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +14 -7
- package/dist/http/CLAUDE.md +584 -0
- package/dist/http/pending_effects.d.ts +29 -0
- package/dist/http/pending_effects.d.ts.map +1 -0
- package/dist/http/pending_effects.js +31 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +4 -3
- package/dist/rate_limiter.d.ts +30 -0
- package/dist/rate_limiter.d.ts.map +1 -1
- package/dist/rate_limiter.js +25 -2
- package/dist/realtime/sse_auth_guard.d.ts +2 -0
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +5 -3
- package/dist/testing/CLAUDE.md +668 -1
- package/dist/testing/admin_integration.d.ts +10 -7
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +382 -482
- package/dist/testing/app_server.d.ts +7 -6
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/attack_surface.d.ts +9 -3
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +4 -4
- package/dist/testing/audit_completeness.d.ts +6 -0
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +158 -134
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +4 -33
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +2 -0
- package/dist/testing/entities.d.ts +35 -13
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +17 -0
- package/dist/testing/integration.d.ts +10 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +352 -340
- package/dist/testing/integration_helpers.d.ts +16 -5
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +24 -4
- package/dist/testing/rate_limiting.d.ts +7 -0
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +41 -10
- package/dist/testing/rpc_helpers.d.ts +153 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +184 -8
- package/dist/testing/sse_round_trip.d.ts +8 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +10 -3
- package/dist/testing/standard.d.ts +9 -1
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +6 -2
- package/dist/testing/surface_invariants.d.ts +7 -3
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +5 -4
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +9 -38
- package/dist/ui/AccountSessions.svelte +8 -4
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +61 -33
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -2
- package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
- package/dist/ui/AdminInvites.svelte +3 -2
- package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +14 -9
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminPermitHistory.svelte +3 -2
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +29 -25
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +351 -0
- package/dist/ui/OpenSignupToggle.svelte +6 -3
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/PermitOfferForm.svelte +141 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferHistory.svelte +109 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferInbox.svelte +121 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +39 -16
- package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +99 -23
- package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +38 -26
- package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +35 -21
- package/dist/ui/app_settings_state.svelte.d.ts +39 -0
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +34 -18
- package/dist/ui/audit_log_state.svelte.d.ts +40 -3
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +36 -42
- package/dist/ui/auth_state.svelte.d.ts +4 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +4 -1
- package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
- package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/permit_offers_state.svelte.js +197 -0
- package/package.json +3 -3
- package/dist/auth/admin_routes.d.ts +0 -29
- package/dist/auth/admin_routes.d.ts.map +0 -1
- package/dist/auth/admin_routes.js +0 -226
- package/dist/auth/app_settings_routes.d.ts +0 -27
- package/dist/auth/app_settings_routes.d.ts.map +0 -1
- package/dist/auth/app_settings_routes.js +0 -66
- package/dist/auth/invite_routes.d.ts +0 -18
- package/dist/auth/invite_routes.d.ts.map +0 -1
- package/dist/auth/invite_routes.js +0 -129
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
+
import { z } from 'zod';
|
|
9
10
|
import type { Context } from 'hono';
|
|
10
11
|
import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
11
12
|
import type { SessionOptions } from './session_cookie.js';
|
|
@@ -15,6 +16,19 @@ import { type RouteSpec } from '../http/route_spec.js';
|
|
|
15
16
|
import { type RateLimiter } from '../rate_limiter.js';
|
|
16
17
|
import type { RouteFactoryDeps } from './deps.js';
|
|
17
18
|
import type { StatResult } from '../runtime/deps.js';
|
|
19
|
+
/** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
|
|
20
|
+
export declare const BootstrapInput: z.ZodObject<{
|
|
21
|
+
token: z.ZodString;
|
|
22
|
+
username: z.ZodString;
|
|
23
|
+
password: z.ZodString;
|
|
24
|
+
}, z.core.$strict>;
|
|
25
|
+
export type BootstrapInput = z.infer<typeof BootstrapInput>;
|
|
26
|
+
/** Output for `POST /bootstrap`. Session cookie is the operative side effect. */
|
|
27
|
+
export declare const BootstrapOutput: z.ZodObject<{
|
|
28
|
+
ok: z.ZodLiteral<true>;
|
|
29
|
+
username: z.ZodString;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
export type BootstrapOutput = z.infer<typeof BootstrapOutput>;
|
|
18
32
|
/**
|
|
19
33
|
* Bootstrap status — runtime state computed once at startup.
|
|
20
34
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAoB,KAAK,uBAAuB,EAAC,MAAM,wBAAwB,CAAC;AAGvF,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAWnD,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;kBAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,4EAA4E;IAC5E,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,EAAE,EAAE,EAAE,CAAC;IACP,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,KAClC,OAAO,CAAC,eAAe,CAwBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAsHjB,CAAC"}
|
|
@@ -16,11 +16,18 @@ import { get_client_ip } from '../http/proxy.js';
|
|
|
16
16
|
import { rate_limit_exceeded_response } from '../rate_limiter.js';
|
|
17
17
|
import { ERROR_BOOTSTRAP_NOT_CONFIGURED, ERROR_INVALID_TOKEN, ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING, } from '../http/error_schemas.js';
|
|
18
18
|
import { audit_log_fire_and_forget } from './audit_log_queries.js';
|
|
19
|
-
|
|
19
|
+
// -- Input/output schemas ---------------------------------------------------
|
|
20
|
+
/** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
|
|
21
|
+
export const BootstrapInput = z.strictObject({
|
|
20
22
|
token: z.string().min(1).meta({ sensitivity: 'secret' }),
|
|
21
23
|
username: Username,
|
|
22
24
|
password: Password,
|
|
23
25
|
});
|
|
26
|
+
/** Output for `POST /bootstrap`. Session cookie is the operative side effect. */
|
|
27
|
+
export const BootstrapOutput = z.strictObject({
|
|
28
|
+
ok: z.literal(true),
|
|
29
|
+
username: z.string(),
|
|
30
|
+
});
|
|
24
31
|
/**
|
|
25
32
|
* Check bootstrap availability at startup.
|
|
26
33
|
*
|
|
@@ -70,8 +77,8 @@ export const create_bootstrap_route_specs = (deps, options) => {
|
|
|
70
77
|
auth: { type: 'none' },
|
|
71
78
|
description: 'Create initial keeper account (one-shot)',
|
|
72
79
|
transaction: false, // bootstrap_account manages its own transaction
|
|
73
|
-
input:
|
|
74
|
-
output:
|
|
80
|
+
input: BootstrapInput,
|
|
81
|
+
output: BootstrapOutput,
|
|
75
82
|
rate_limit: 'ip',
|
|
76
83
|
errors: {
|
|
77
84
|
401: z.looseObject({ error: z.literal(ERROR_INVALID_TOKEN) }),
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic auth cleanup — sweeps expired sessions and permit offers.
|
|
3
|
+
*
|
|
4
|
+
* Single entry point for consumers scheduling auth maintenance. Internally
|
|
5
|
+
* runs every known sweep and emits the corresponding audit events so
|
|
6
|
+
* consumer code only manages cadence, not per-task wiring.
|
|
7
|
+
*
|
|
8
|
+
* The per-task primitives remain exported from their home modules
|
|
9
|
+
* (`query_session_cleanup_expired`, `query_permit_offer_sweep_expired`);
|
|
10
|
+
* `cleanup_expired_permit_offers` here wraps the latter with the required
|
|
11
|
+
* `permit_offer_expire` audit emission and is the piece most likely to be
|
|
12
|
+
* reused in a consumer's bespoke scheduler.
|
|
13
|
+
*
|
|
14
|
+
* Idempotency: the audit log has no tombstone on `permit_offer_expire`, so
|
|
15
|
+
* concurrent sweep runs double-audit. The expected deployment pattern is a
|
|
16
|
+
* single scheduled invocation per instance — matching
|
|
17
|
+
* `query_session_cleanup_expired`.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
22
|
+
import type { QueryDeps } from '../db/query_deps.js';
|
|
23
|
+
import type { AuditLogEvent } from './audit_log_schema.js';
|
|
24
|
+
/** Dependencies for the cleanup helpers. */
|
|
25
|
+
export interface AuthCleanupDeps extends QueryDeps {
|
|
26
|
+
log: Logger;
|
|
27
|
+
/**
|
|
28
|
+
* Called after each audit event INSERT succeeds. Typically the same
|
|
29
|
+
* callback wired into `AppDeps.on_audit_event` (SSE broadcast). Omit
|
|
30
|
+
* to skip broadcast — the audit rows still land in the DB.
|
|
31
|
+
*/
|
|
32
|
+
on_audit_event?: ((event: AuditLogEvent) => void) | null;
|
|
33
|
+
}
|
|
34
|
+
/** Result of `run_auth_cleanup`. */
|
|
35
|
+
export interface AuthCleanupResult {
|
|
36
|
+
/** Number of expired session rows deleted. */
|
|
37
|
+
expired_sessions: number;
|
|
38
|
+
/** Number of expired permit offer rows audit-stamped. */
|
|
39
|
+
expired_offers: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Sweep expired permit offers and emit one `permit_offer_expire` audit
|
|
43
|
+
* event per row.
|
|
44
|
+
*
|
|
45
|
+
* Returns the count of offers audit-stamped. The offer rows themselves are
|
|
46
|
+
* preserved — offers carry audit value for the history view even after
|
|
47
|
+
* expiry, and accepted rows are the provenance for the resulting permit
|
|
48
|
+
* (deleting expired rows would not threaten that, but keeping them uniform
|
|
49
|
+
* with the retention policy for terminal rows is simpler).
|
|
50
|
+
*/
|
|
51
|
+
export declare const cleanup_expired_permit_offers: (deps: AuthCleanupDeps) => Promise<number>;
|
|
52
|
+
/**
|
|
53
|
+
* Run every auth cleanup sweep — expired sessions and expired permit
|
|
54
|
+
* offers — and return the counts.
|
|
55
|
+
*
|
|
56
|
+
* Consumers call this from a scheduled task (setInterval, cron, etc.)
|
|
57
|
+
* alongside their own domain cleanup. Errors from individual sweeps are
|
|
58
|
+
* re-thrown so the caller's scheduler can log/alert; use the per-task
|
|
59
|
+
* helpers (`query_session_cleanup_expired`, `cleanup_expired_permit_offers`)
|
|
60
|
+
* directly if you need finer error isolation.
|
|
61
|
+
*/
|
|
62
|
+
export declare const run_auth_cleanup: (deps: AuthCleanupDeps) => Promise<AuthCleanupResult>;
|
|
63
|
+
//# sourceMappingURL=cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAInD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEzD,4CAA4C;AAC5C,MAAM,WAAW,eAAgB,SAAQ,SAAS;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CACzD;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,MAAM,CA6BzF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,iBAAiB,CAIvF,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic auth cleanup — sweeps expired sessions and permit offers.
|
|
3
|
+
*
|
|
4
|
+
* Single entry point for consumers scheduling auth maintenance. Internally
|
|
5
|
+
* runs every known sweep and emits the corresponding audit events so
|
|
6
|
+
* consumer code only manages cadence, not per-task wiring.
|
|
7
|
+
*
|
|
8
|
+
* The per-task primitives remain exported from their home modules
|
|
9
|
+
* (`query_session_cleanup_expired`, `query_permit_offer_sweep_expired`);
|
|
10
|
+
* `cleanup_expired_permit_offers` here wraps the latter with the required
|
|
11
|
+
* `permit_offer_expire` audit emission and is the piece most likely to be
|
|
12
|
+
* reused in a consumer's bespoke scheduler.
|
|
13
|
+
*
|
|
14
|
+
* Idempotency: the audit log has no tombstone on `permit_offer_expire`, so
|
|
15
|
+
* concurrent sweep runs double-audit. The expected deployment pattern is a
|
|
16
|
+
* single scheduled invocation per instance — matching
|
|
17
|
+
* `query_session_cleanup_expired`.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import { query_session_cleanup_expired } from './session_queries.js';
|
|
22
|
+
import { query_permit_offer_sweep_expired } from './permit_offer_queries.js';
|
|
23
|
+
import { query_audit_log } from './audit_log_queries.js';
|
|
24
|
+
/**
|
|
25
|
+
* Sweep expired permit offers and emit one `permit_offer_expire` audit
|
|
26
|
+
* event per row.
|
|
27
|
+
*
|
|
28
|
+
* Returns the count of offers audit-stamped. The offer rows themselves are
|
|
29
|
+
* preserved — offers carry audit value for the history view even after
|
|
30
|
+
* expiry, and accepted rows are the provenance for the resulting permit
|
|
31
|
+
* (deleting expired rows would not threaten that, but keeping them uniform
|
|
32
|
+
* with the retention policy for terminal rows is simpler).
|
|
33
|
+
*/
|
|
34
|
+
export const cleanup_expired_permit_offers = async (deps) => {
|
|
35
|
+
const expired = await query_permit_offer_sweep_expired(deps);
|
|
36
|
+
const { on_audit_event } = deps;
|
|
37
|
+
for (const offer of expired) {
|
|
38
|
+
try {
|
|
39
|
+
const event = await query_audit_log(deps, {
|
|
40
|
+
event_type: 'permit_offer_expire',
|
|
41
|
+
actor_id: offer.from_actor_id,
|
|
42
|
+
target_account_id: offer.to_account_id,
|
|
43
|
+
ip: null,
|
|
44
|
+
metadata: {
|
|
45
|
+
offer_id: offer.id,
|
|
46
|
+
role: offer.role,
|
|
47
|
+
scope_id: offer.scope_id,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
if (on_audit_event) {
|
|
51
|
+
try {
|
|
52
|
+
on_audit_event(event);
|
|
53
|
+
}
|
|
54
|
+
catch (callback_err) {
|
|
55
|
+
deps.log.error('on_audit_event callback failed:', callback_err);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
// One failed audit write must not starve siblings — log and continue.
|
|
61
|
+
deps.log.error('permit_offer_expire audit write failed:', err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return expired.length;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Run every auth cleanup sweep — expired sessions and expired permit
|
|
68
|
+
* offers — and return the counts.
|
|
69
|
+
*
|
|
70
|
+
* Consumers call this from a scheduled task (setInterval, cron, etc.)
|
|
71
|
+
* alongside their own domain cleanup. Errors from individual sweeps are
|
|
72
|
+
* re-thrown so the caller's scheduler can log/alert; use the per-task
|
|
73
|
+
* helpers (`query_session_cleanup_expired`, `cleanup_expired_permit_offers`)
|
|
74
|
+
* directly if you need finer error isolation.
|
|
75
|
+
*/
|
|
76
|
+
export const run_auth_cleanup = async (deps) => {
|
|
77
|
+
const expired_sessions = await query_session_cleanup_expired(deps);
|
|
78
|
+
const expired_offers = await cleanup_expired_permit_offers(deps);
|
|
79
|
+
return { expired_sessions, expired_offers };
|
|
80
|
+
};
|
|
@@ -7,37 +7,38 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
+
import { Uuid } from '../uuid.js';
|
|
10
11
|
import { Username, Email } from './account_schema.js';
|
|
11
12
|
/** Invite row from the database. */
|
|
12
13
|
export interface Invite {
|
|
13
|
-
id:
|
|
14
|
+
id: Uuid;
|
|
14
15
|
email: Email | null;
|
|
15
16
|
username: Username | null;
|
|
16
|
-
claimed_by:
|
|
17
|
+
claimed_by: Uuid | null;
|
|
17
18
|
claimed_at: string | null;
|
|
18
19
|
created_at: string;
|
|
19
|
-
created_by:
|
|
20
|
+
created_by: Uuid | null;
|
|
20
21
|
}
|
|
21
22
|
/** Zod schema for client-safe invite data. */
|
|
22
23
|
export declare const InviteJson: z.ZodObject<{
|
|
23
|
-
id: z.
|
|
24
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
24
25
|
email: z.ZodNullable<z.ZodEmail>;
|
|
25
26
|
username: z.ZodNullable<z.ZodString>;
|
|
26
|
-
claimed_by: z.ZodNullable<z.
|
|
27
|
+
claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
27
28
|
claimed_at: z.ZodNullable<z.ZodString>;
|
|
28
29
|
created_at: z.ZodString;
|
|
29
|
-
created_by: z.ZodNullable<z.
|
|
30
|
+
created_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
30
31
|
}, z.core.$strict>;
|
|
31
32
|
export type InviteJson = z.infer<typeof InviteJson>;
|
|
32
33
|
/** Zod schema for invite data with resolved creator/claimer usernames. */
|
|
33
34
|
export declare const InviteWithUsernamesJson: z.ZodObject<{
|
|
34
|
-
id: z.
|
|
35
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
35
36
|
email: z.ZodNullable<z.ZodEmail>;
|
|
36
37
|
username: z.ZodNullable<z.ZodString>;
|
|
37
|
-
claimed_by: z.ZodNullable<z.
|
|
38
|
+
claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
38
39
|
claimed_at: z.ZodNullable<z.ZodString>;
|
|
39
40
|
created_at: z.ZodString;
|
|
40
|
-
created_by: z.ZodNullable<z.
|
|
41
|
+
created_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
41
42
|
created_by_username: z.ZodNullable<z.ZodString>;
|
|
42
43
|
claimed_by_username: z.ZodNullable<z.ZodString>;
|
|
43
44
|
}, z.core.$strict>;
|
|
@@ -46,6 +47,6 @@ export type InviteWithUsernamesJson = z.infer<typeof InviteWithUsernamesJson>;
|
|
|
46
47
|
export interface CreateInviteInput {
|
|
47
48
|
email?: Email | null;
|
|
48
49
|
username?: Username | null;
|
|
49
|
-
created_by:
|
|
50
|
+
created_by: Uuid | null;
|
|
50
51
|
}
|
|
51
52
|
//# sourceMappingURL=invite_schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invite_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAC,QAAQ,EAAE,KAAK,EAAC,MAAM,qBAAqB,CAAC;AAEpD,oCAAoC;AACpC,MAAM,WAAW,MAAM;IACtB,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"invite_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AAChC,OAAO,EAAC,QAAQ,EAAE,KAAK,EAAC,MAAM,qBAAqB,CAAC;AAEpD,oCAAoC;AACpC,MAAM,WAAW,MAAM;IACtB,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB;AAED,8CAA8C;AAC9C,eAAO,MAAM,UAAU;;;;;;;;kBAQrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB;;;;;;;;;;kBAGlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB"}
|
|
@@ -7,16 +7,17 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
+
import { Uuid } from '../uuid.js';
|
|
10
11
|
import { Username, Email } from './account_schema.js';
|
|
11
12
|
/** Zod schema for client-safe invite data. */
|
|
12
13
|
export const InviteJson = z.strictObject({
|
|
13
|
-
id:
|
|
14
|
+
id: Uuid,
|
|
14
15
|
email: Email.nullable(),
|
|
15
16
|
username: Username.nullable(),
|
|
16
|
-
claimed_by:
|
|
17
|
+
claimed_by: Uuid.nullable(),
|
|
17
18
|
claimed_at: z.string().nullable(),
|
|
18
19
|
created_at: z.string(),
|
|
19
|
-
created_by:
|
|
20
|
+
created_by: Uuid.nullable(),
|
|
20
21
|
});
|
|
21
22
|
/** Zod schema for invite data with resolved creator/claimer usernames. */
|
|
22
23
|
export const InviteWithUsernamesJson = InviteJson.extend({
|
|
@@ -35,6 +35,12 @@ export declare const AUTH_MIGRATION_NAMESPACE = "fuz_auth";
|
|
|
35
35
|
* - v0: Full auth schema — account (with email_verified), actor, permit,
|
|
36
36
|
* auth_session, api_token, audit_log (with seq), bootstrap_lock, invite,
|
|
37
37
|
* app_settings, plus all indexes and seeds.
|
|
38
|
+
* - v1: `permit_offer` table for consentful grants; adds `scope_id` /
|
|
39
|
+
* `source_offer_id` / `revoked_reason` to `permit` and swaps the
|
|
40
|
+
* `(actor_id, role)` partial unique index for a scope-aware variant using
|
|
41
|
+
* the all-zeros sentinel UUID. The `permit_offer` table carries a
|
|
42
|
+
* `superseded_at` terminal state; its partial unique index is scoped by
|
|
43
|
+
* `(to_account, role, scope, from_actor)` so multiple grantors may coexist.
|
|
38
44
|
*/
|
|
39
45
|
export declare const AUTH_MIGRATIONS: Array<Migration>;
|
|
40
46
|
/** Pre-composed migration namespace for auth tables. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA6BH,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AAEpE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CA6D5C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
|
package/dist/auth/migrations.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { ACCOUNT_SCHEMA, ACCOUNT_EMAIL_INDEX, ACCOUNT_USERNAME_CI_INDEX, ACTOR_SCHEMA, ACTOR_INDEX, PERMIT_SCHEMA, PERMIT_INDEXES, AUTH_SESSION_SCHEMA, AUTH_SESSION_INDEXES, API_TOKEN_SCHEMA, API_TOKEN_INDEX, BOOTSTRAP_LOCK_SCHEMA, BOOTSTRAP_LOCK_SEED, INVITE_SCHEMA, INVITE_INDEXES, APP_SETTINGS_SCHEMA, APP_SETTINGS_SEED, } from './ddl.js';
|
|
30
30
|
import { AUDIT_LOG_SCHEMA, AUDIT_LOG_INDEXES } from './audit_log_schema.js';
|
|
31
|
+
import { PERMIT_OFFER_SCHEMA, PERMIT_OFFER_PENDING_UNIQUE_INDEX, PERMIT_OFFER_INBOX_INDEX, PERMIT_OFFER_SCOPE_SENTINEL_UUID, } from './permit_offer_schema.js';
|
|
31
32
|
/** Namespace identifier for fuz_app auth migrations. */
|
|
32
33
|
export const AUTH_MIGRATION_NAMESPACE = 'fuz_auth';
|
|
33
34
|
/**
|
|
@@ -36,6 +37,12 @@ export const AUTH_MIGRATION_NAMESPACE = 'fuz_auth';
|
|
|
36
37
|
* - v0: Full auth schema — account (with email_verified), actor, permit,
|
|
37
38
|
* auth_session, api_token, audit_log (with seq), bootstrap_lock, invite,
|
|
38
39
|
* app_settings, plus all indexes and seeds.
|
|
40
|
+
* - v1: `permit_offer` table for consentful grants; adds `scope_id` /
|
|
41
|
+
* `source_offer_id` / `revoked_reason` to `permit` and swaps the
|
|
42
|
+
* `(actor_id, role)` partial unique index for a scope-aware variant using
|
|
43
|
+
* the all-zeros sentinel UUID. The `permit_offer` table carries a
|
|
44
|
+
* `superseded_at` terminal state; its partial unique index is scoped by
|
|
45
|
+
* `(to_account, role, scope, from_actor)` so multiple grantors may coexist.
|
|
39
46
|
*/
|
|
40
47
|
export const AUTH_MIGRATIONS = [
|
|
41
48
|
// v0: full auth schema — all IF NOT EXISTS, safe for existing databases
|
|
@@ -71,6 +78,27 @@ export const AUTH_MIGRATIONS = [
|
|
|
71
78
|
await db.query(APP_SETTINGS_SEED);
|
|
72
79
|
},
|
|
73
80
|
},
|
|
81
|
+
// v1: consentful permits — permit_offer table + scoped permits
|
|
82
|
+
{
|
|
83
|
+
name: 'permit_offer_and_scoped_permits',
|
|
84
|
+
up: async (db) => {
|
|
85
|
+
await db.query(PERMIT_OFFER_SCHEMA);
|
|
86
|
+
await db.query(PERMIT_OFFER_PENDING_UNIQUE_INDEX);
|
|
87
|
+
await db.query(PERMIT_OFFER_INBOX_INDEX);
|
|
88
|
+
await db.query('ALTER TABLE permit ADD COLUMN IF NOT EXISTS scope_id UUID NULL');
|
|
89
|
+
await db.query('ALTER TABLE permit ADD COLUMN IF NOT EXISTS source_offer_id UUID NULL REFERENCES permit_offer(id) ON DELETE SET NULL');
|
|
90
|
+
await db.query('ALTER TABLE permit ADD COLUMN IF NOT EXISTS revoked_reason TEXT NULL');
|
|
91
|
+
// swap the (actor_id, role) partial unique for a scope-aware variant.
|
|
92
|
+
// Existing rows have `scope_id = NULL` and collapse to the sentinel.
|
|
93
|
+
await db.query('DROP INDEX IF EXISTS permit_actor_role_active_unique');
|
|
94
|
+
await db.query(`CREATE UNIQUE INDEX IF NOT EXISTS permit_actor_role_scope_active_unique
|
|
95
|
+
ON permit (actor_id, role, COALESCE(scope_id, '${PERMIT_OFFER_SCOPE_SENTINEL_UUID}'::uuid))
|
|
96
|
+
WHERE revoked_at IS NULL`);
|
|
97
|
+
await db.query(`CREATE INDEX IF NOT EXISTS permit_scope_active
|
|
98
|
+
ON permit (actor_id, role, scope_id)
|
|
99
|
+
WHERE revoked_at IS NULL`);
|
|
100
|
+
},
|
|
101
|
+
},
|
|
74
102
|
];
|
|
75
103
|
/** Pre-composed migration namespace for auth tables. */
|
|
76
104
|
export const AUTH_MIGRATION_NS = {
|