@fuzdev/fuz_app 0.118.0 → 0.119.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 +301 -73
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +16 -1
- package/dist/actions/connection_closer.d.ts +136 -51
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +133 -34
- package/dist/actions/register_action_ws.d.ts +176 -24
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +581 -241
- package/dist/actions/register_ws_endpoint.d.ts +1 -0
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +1 -0
- package/dist/actions/socket.svelte.d.ts +43 -9
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +59 -8
- package/dist/actions/transports.d.ts +55 -1
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +55 -1
- package/dist/actions/transports_ws_auth_guard.d.ts +53 -67
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +87 -111
- package/dist/actions/transports_ws_backend.d.ts +212 -30
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +327 -70
- package/dist/actions/ws_endpoint_spec.d.ts +26 -13
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- package/dist/auth/CLAUDE.md +113 -32
- package/dist/auth/account_actions.d.ts +12 -18
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +47 -33
- package/dist/auth/account_routes.d.ts +5 -11
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +40 -36
- package/dist/auth/account_schema.d.ts +27 -0
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +22 -0
- package/dist/auth/admin_actions.d.ts +8 -17
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +33 -21
- package/dist/auth/api_token_queries.d.ts +23 -2
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +39 -3
- package/dist/auth/audit_emitter.d.ts +61 -38
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +57 -14
- package/dist/auth/audit_log_route_schema.js +7 -7
- package/dist/auth/audit_log_routes.d.ts +31 -6
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +85 -11
- package/dist/auth/audit_log_schema.d.ts +48 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +58 -0
- package/dist/auth/cleanup.d.ts +203 -28
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +222 -26
- package/dist/auth/deps.d.ts +28 -0
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/resolved_auth.d.ts +105 -0
- package/dist/auth/resolved_auth.d.ts.map +1 -0
- package/dist/auth/resolved_auth.js +124 -0
- package/dist/auth/session_middleware.d.ts +8 -1
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +9 -3
- package/dist/auth/session_queries.d.ts +31 -6
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +31 -6
- package/dist/auth/standard_rpc_actions.d.ts +9 -9
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +3 -3
- package/dist/db/CLAUDE.md +7 -1
- package/dist/db/status.d.ts +58 -1
- package/dist/db/status.d.ts.map +1 -1
- package/dist/db/status.js +205 -77
- package/dist/http/CLAUDE.md +7 -3
- package/dist/http/db_routes.d.ts +6 -2
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +2 -2
- package/dist/periodic.d.ts +80 -0
- package/dist/periodic.d.ts.map +1 -0
- package/dist/periodic.js +122 -0
- package/dist/realtime/sse_auth_guard.d.ts +79 -49
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +103 -73
- package/dist/realtime/subscriber_registry.d.ts +119 -8
- package/dist/realtime/subscriber_registry.d.ts.map +1 -1
- package/dist/realtime/subscriber_registry.js +184 -25
- package/dist/server/app_backend.d.ts +12 -1
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +5 -2
- package/dist/server/app_server.d.ts +55 -15
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +41 -13
- package/dist/testing/CLAUDE.md +140 -48
- package/dist/testing/app_server.d.ts +2 -2
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +11 -2
- package/dist/testing/audit_drift_guard.d.ts +1 -48
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +0 -42
- package/dist/testing/connection_closer_helpers.d.ts +9 -22
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
- package/dist/testing/connection_closer_helpers.js +11 -17
- package/dist/testing/cross_backend/capabilities.d.ts +67 -13
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +27 -12
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +21 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +13 -4
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +4 -1
- package/dist/testing/cross_backend/session_cap.d.ts +13 -0
- package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
- package/dist/testing/cross_backend/session_cap.js +55 -19
- package/dist/testing/cross_backend/sse_round_trip.d.ts +10 -2
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/sse_round_trip.js +93 -26
- package/dist/testing/cross_backend/testing_server_bun.js +4 -4
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +10 -2
- package/dist/testing/cross_backend/ws_connection_cap.d.ts +35 -0
- package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_connection_cap.js +131 -0
- package/dist/testing/cross_backend/ws_round_trip.d.ts +2 -1
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +156 -19
- package/dist/testing/sse_round_trip.d.ts +6 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/stubs.d.ts +1 -1
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- package/dist/testing/transports/sse_transport.d.ts +1 -1
- package/dist/testing/transports/ws_client.d.ts +7 -0
- package/dist/testing/transports/ws_client.d.ts.map +1 -1
- package/dist/testing/transports/ws_raw_client.d.ts +51 -0
- package/dist/testing/transports/ws_raw_client.d.ts.map +1 -0
- package/dist/testing/transports/ws_raw_client.js +156 -0
- package/dist/testing/transports/ws_transport.d.ts +28 -0
- package/dist/testing/transports/ws_transport.d.ts.map +1 -1
- package/dist/testing/transports/ws_transport.js +45 -1
- package/dist/testing/ws_round_trip.d.ts +8 -3
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +19 -4
- package/dist/ui/AdminAccounts.svelte +4 -3
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +3 -1
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +22 -6
- package/dist/ui/admin_accounts_state.svelte.d.ts +15 -1
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +16 -0
- package/dist/ui/auth_state.svelte.d.ts +25 -1
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +28 -1
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account-grain credential a request resolved to, and the re-check a
|
|
3
|
+
* long-lived connection runs on it once it is registered.
|
|
4
|
+
*
|
|
5
|
+
* The auth middleware chain (daemon token → session cookie → bearer) resolves a
|
|
6
|
+
* credential and leaves it on the Hono context as separate keys.
|
|
7
|
+
* `ResolvedAuth` is that credential gathered into one value
|
|
8
|
+
* (`get_resolved_auth`), so the connections that outlive their request — a
|
|
9
|
+
* WebSocket, an SSE stream — can carry it past the handshake and re-read it
|
|
10
|
+
* with `revalidate_resolved_auth`.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from 'hono';
|
|
15
|
+
import type { QueryDeps } from '../db/query_deps.js';
|
|
16
|
+
import { type CredentialType } from '../hono_context.js';
|
|
17
|
+
/**
|
|
18
|
+
* An authenticated request's credential, account-grain — no actor.
|
|
19
|
+
*
|
|
20
|
+
* `token_hash` is set for a session, `api_token_id` for an API token, and both
|
|
21
|
+
* are `null` for a daemon token. The twin of the Rust spine's `ResolvedAuth`,
|
|
22
|
+
* which carries the account row and token scope as well; here those stay on
|
|
23
|
+
* the Hono context.
|
|
24
|
+
*/
|
|
25
|
+
export interface ResolvedAuth {
|
|
26
|
+
/** The authenticated account. */
|
|
27
|
+
account_id: string;
|
|
28
|
+
/** How the request was authenticated. */
|
|
29
|
+
credential_type: CredentialType;
|
|
30
|
+
/** Blake3 hash of the session token, or `null` for non-session credentials. */
|
|
31
|
+
token_hash: string | null;
|
|
32
|
+
/** `api_token.id` for a bearer credential, else `null`. */
|
|
33
|
+
api_token_id: string | null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Gather the credential the auth middleware resolved for this request.
|
|
37
|
+
*
|
|
38
|
+
* Reads the keys the session, bearer, and daemon-token middleware set and
|
|
39
|
+
* keeps only the one that belongs to the winning credential type — a daemon
|
|
40
|
+
* token overrides a session cookie sent alongside it without clearing that
|
|
41
|
+
* session's hash, so the hash is carried for a session credential alone.
|
|
42
|
+
*
|
|
43
|
+
* @param c - the request context, after the auth middleware chain
|
|
44
|
+
* @returns the resolved credential, or `null` for an unauthenticated request
|
|
45
|
+
*/
|
|
46
|
+
export declare const get_resolved_auth: (c: Context) => ResolvedAuth | null;
|
|
47
|
+
/**
|
|
48
|
+
* Re-check that a resolved credential still authenticates: `true` when it
|
|
49
|
+
* does, `false` when it has been revoked, has expired, or its account is gone.
|
|
50
|
+
*
|
|
51
|
+
* For a connection that outlives its request — a WebSocket, an SSE stream —
|
|
52
|
+
* and is authorized once, at open. Such a connection is closed on revocation
|
|
53
|
+
* by identity, but only once it is *registered*; between the credential read
|
|
54
|
+
* and the registration a revocation closes nothing. So the opener registers
|
|
55
|
+
* first (un-admitted — `BackendWebsocketTransport.register_pending` /
|
|
56
|
+
* `SubscriberRegistry.subscribe_pending`), then calls this, then admits. A
|
|
57
|
+
* revocation that committed before this read is seen by it; one whose close
|
|
58
|
+
* ran after the registration found the pending entry.
|
|
59
|
+
*
|
|
60
|
+
* That needs each statement here to see everything committed before it
|
|
61
|
+
* started, so `deps.db` must be the pool-level `Db` (autocommit) or a
|
|
62
|
+
* `READ COMMITTED` transaction — never a `REPEATABLE READ` / `SERIALIZABLE`
|
|
63
|
+
* transaction opened before the registration, whose snapshot would predate
|
|
64
|
+
* it.
|
|
65
|
+
*
|
|
66
|
+
* What is re-read, per credential — each through the same predicate the
|
|
67
|
+
* resolve path used, so the two cannot disagree about what "valid" means (the
|
|
68
|
+
* session and account legs call the middleware's own queries; the token leg
|
|
69
|
+
* shares its `WHERE` fragment):
|
|
70
|
+
*
|
|
71
|
+
* - **Session** — the `auth_session` row still exists, is unexpired, and
|
|
72
|
+
* belongs to the resolved account.
|
|
73
|
+
* - **API token** — the `api_token` row still exists, is unexpired, and
|
|
74
|
+
* belongs to the resolved account (by id; the raw token is not kept).
|
|
75
|
+
* - **Daemon token** — nothing of its own. The token has no row, and no
|
|
76
|
+
* revocation event: rotation closes no connection, so there is no close an
|
|
77
|
+
* opening connection could have missed. Its keeper account is still
|
|
78
|
+
* re-read.
|
|
79
|
+
* - **Every credential** — the account row still exists and is not
|
|
80
|
+
* soft-deleted.
|
|
81
|
+
*
|
|
82
|
+
* It re-checks the **credential**, not what a revocation chose to close: a
|
|
83
|
+
* `logout` closes every connection of the account but deletes only its own
|
|
84
|
+
* session, so another session's connection opening concurrently passes this
|
|
85
|
+
* check and is refused only if that account-wide close found its pending
|
|
86
|
+
* registration — the same two outcomes an already-open connection has.
|
|
87
|
+
*
|
|
88
|
+
* **No side effects.** Unlike the bearer middleware this never touches
|
|
89
|
+
* `api_token.last_used_at` / `last_used_ip` — re-checking a credential is not
|
|
90
|
+
* using it — and it writes nothing else.
|
|
91
|
+
*
|
|
92
|
+
* A `ResolvedAuth` whose credential type lacks its own key (a session with no
|
|
93
|
+
* `token_hash`, a bearer with no `api_token_id`) is a shape the middleware
|
|
94
|
+
* never builds; it is answered `false`.
|
|
95
|
+
*
|
|
96
|
+
* The twin of the Rust spine's `revalidate_resolved_auth`.
|
|
97
|
+
*
|
|
98
|
+
* @param deps - query dependencies; the pool-level `Db`, not a snapshot transaction
|
|
99
|
+
* @param resolved - the credential the request authenticated with
|
|
100
|
+
* @returns whether the credential still authenticates
|
|
101
|
+
* @throws Error propagated from the database. The caller must fail closed — an
|
|
102
|
+
* unchecked credential is not a valid one.
|
|
103
|
+
*/
|
|
104
|
+
export declare const revalidate_resolved_auth: (deps: QueryDeps, resolved: ResolvedAuth) => Promise<boolean>;
|
|
105
|
+
//# sourceMappingURL=resolved_auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolved_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/resolved_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAIN,KAAK,cAAc,EACnB,MAAM,oBAAoB,CAAC;AAM5B;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC5B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,eAAe,EAAE,cAAc,CAAC;IAChC,+EAA+E;IAC/E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,2DAA2D;IAC3D,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAAI,GAAG,OAAO,KAAG,YAAY,GAAG,IAU7D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,SAAS,EACf,UAAU,YAAY,KACpB,OAAO,CAAC,OAAO,CAsBjB,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account-grain credential a request resolved to, and the re-check a
|
|
3
|
+
* long-lived connection runs on it once it is registered.
|
|
4
|
+
*
|
|
5
|
+
* The auth middleware chain (daemon token → session cookie → bearer) resolves a
|
|
6
|
+
* credential and leaves it on the Hono context as separate keys.
|
|
7
|
+
* `ResolvedAuth` is that credential gathered into one value
|
|
8
|
+
* (`get_resolved_auth`), so the connections that outlive their request — a
|
|
9
|
+
* WebSocket, an SSE stream — can carry it past the handshake and re-read it
|
|
10
|
+
* with `revalidate_resolved_auth`.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
|
|
15
|
+
import { query_account_by_id } from './account_queries.js';
|
|
16
|
+
import { query_api_token_live_account } from './api_token_queries.js';
|
|
17
|
+
import { AUTH_SESSION_TOKEN_HASH_KEY } from './request_context.js';
|
|
18
|
+
import { query_session_get_valid } from './session_queries.js';
|
|
19
|
+
/**
|
|
20
|
+
* Gather the credential the auth middleware resolved for this request.
|
|
21
|
+
*
|
|
22
|
+
* Reads the keys the session, bearer, and daemon-token middleware set and
|
|
23
|
+
* keeps only the one that belongs to the winning credential type — a daemon
|
|
24
|
+
* token overrides a session cookie sent alongside it without clearing that
|
|
25
|
+
* session's hash, so the hash is carried for a session credential alone.
|
|
26
|
+
*
|
|
27
|
+
* @param c - the request context, after the auth middleware chain
|
|
28
|
+
* @returns the resolved credential, or `null` for an unauthenticated request
|
|
29
|
+
*/
|
|
30
|
+
export const get_resolved_auth = (c) => {
|
|
31
|
+
const account_id = c.get(ACCOUNT_ID_KEY) ?? null;
|
|
32
|
+
const credential_type = c.get(CREDENTIAL_TYPE_KEY) ?? null;
|
|
33
|
+
if (account_id === null || credential_type === null)
|
|
34
|
+
return null;
|
|
35
|
+
return {
|
|
36
|
+
account_id,
|
|
37
|
+
credential_type,
|
|
38
|
+
token_hash: credential_type === 'session' ? (c.get(AUTH_SESSION_TOKEN_HASH_KEY) ?? null) : null,
|
|
39
|
+
api_token_id: credential_type === 'api_token' ? (c.get(AUTH_API_TOKEN_ID_KEY) ?? null) : null
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Re-check that a resolved credential still authenticates: `true` when it
|
|
44
|
+
* does, `false` when it has been revoked, has expired, or its account is gone.
|
|
45
|
+
*
|
|
46
|
+
* For a connection that outlives its request — a WebSocket, an SSE stream —
|
|
47
|
+
* and is authorized once, at open. Such a connection is closed on revocation
|
|
48
|
+
* by identity, but only once it is *registered*; between the credential read
|
|
49
|
+
* and the registration a revocation closes nothing. So the opener registers
|
|
50
|
+
* first (un-admitted — `BackendWebsocketTransport.register_pending` /
|
|
51
|
+
* `SubscriberRegistry.subscribe_pending`), then calls this, then admits. A
|
|
52
|
+
* revocation that committed before this read is seen by it; one whose close
|
|
53
|
+
* ran after the registration found the pending entry.
|
|
54
|
+
*
|
|
55
|
+
* That needs each statement here to see everything committed before it
|
|
56
|
+
* started, so `deps.db` must be the pool-level `Db` (autocommit) or a
|
|
57
|
+
* `READ COMMITTED` transaction — never a `REPEATABLE READ` / `SERIALIZABLE`
|
|
58
|
+
* transaction opened before the registration, whose snapshot would predate
|
|
59
|
+
* it.
|
|
60
|
+
*
|
|
61
|
+
* What is re-read, per credential — each through the same predicate the
|
|
62
|
+
* resolve path used, so the two cannot disagree about what "valid" means (the
|
|
63
|
+
* session and account legs call the middleware's own queries; the token leg
|
|
64
|
+
* shares its `WHERE` fragment):
|
|
65
|
+
*
|
|
66
|
+
* - **Session** — the `auth_session` row still exists, is unexpired, and
|
|
67
|
+
* belongs to the resolved account.
|
|
68
|
+
* - **API token** — the `api_token` row still exists, is unexpired, and
|
|
69
|
+
* belongs to the resolved account (by id; the raw token is not kept).
|
|
70
|
+
* - **Daemon token** — nothing of its own. The token has no row, and no
|
|
71
|
+
* revocation event: rotation closes no connection, so there is no close an
|
|
72
|
+
* opening connection could have missed. Its keeper account is still
|
|
73
|
+
* re-read.
|
|
74
|
+
* - **Every credential** — the account row still exists and is not
|
|
75
|
+
* soft-deleted.
|
|
76
|
+
*
|
|
77
|
+
* It re-checks the **credential**, not what a revocation chose to close: a
|
|
78
|
+
* `logout` closes every connection of the account but deletes only its own
|
|
79
|
+
* session, so another session's connection opening concurrently passes this
|
|
80
|
+
* check and is refused only if that account-wide close found its pending
|
|
81
|
+
* registration — the same two outcomes an already-open connection has.
|
|
82
|
+
*
|
|
83
|
+
* **No side effects.** Unlike the bearer middleware this never touches
|
|
84
|
+
* `api_token.last_used_at` / `last_used_ip` — re-checking a credential is not
|
|
85
|
+
* using it — and it writes nothing else.
|
|
86
|
+
*
|
|
87
|
+
* A `ResolvedAuth` whose credential type lacks its own key (a session with no
|
|
88
|
+
* `token_hash`, a bearer with no `api_token_id`) is a shape the middleware
|
|
89
|
+
* never builds; it is answered `false`.
|
|
90
|
+
*
|
|
91
|
+
* The twin of the Rust spine's `revalidate_resolved_auth`.
|
|
92
|
+
*
|
|
93
|
+
* @param deps - query dependencies; the pool-level `Db`, not a snapshot transaction
|
|
94
|
+
* @param resolved - the credential the request authenticated with
|
|
95
|
+
* @returns whether the credential still authenticates
|
|
96
|
+
* @throws Error propagated from the database. The caller must fail closed — an
|
|
97
|
+
* unchecked credential is not a valid one.
|
|
98
|
+
*/
|
|
99
|
+
export const revalidate_resolved_auth = async (deps, resolved) => {
|
|
100
|
+
const { account_id } = resolved;
|
|
101
|
+
let credential_is_live;
|
|
102
|
+
switch (resolved.credential_type) {
|
|
103
|
+
case 'session': {
|
|
104
|
+
if (resolved.token_hash === null)
|
|
105
|
+
return false;
|
|
106
|
+
const session = await query_session_get_valid(deps, resolved.token_hash);
|
|
107
|
+
credential_is_live = session?.account_id === account_id;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case 'api_token': {
|
|
111
|
+
if (resolved.api_token_id === null)
|
|
112
|
+
return false;
|
|
113
|
+
const owner = await query_api_token_live_account(deps, resolved.api_token_id);
|
|
114
|
+
credential_is_live = owner === account_id;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case 'daemon_token':
|
|
118
|
+
credential_is_live = true;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
if (!credential_is_live)
|
|
122
|
+
return false;
|
|
123
|
+
return (await query_account_by_id(deps, account_id)) !== undefined;
|
|
124
|
+
};
|
|
@@ -62,7 +62,14 @@ export interface CreateSessionAndSetCookieOptions {
|
|
|
62
62
|
* persists the session row, optionally enforces a per-account session limit,
|
|
63
63
|
* and sets the signed cookie.
|
|
64
64
|
*
|
|
65
|
+
* The cap deletes the sessions it evicts, but not the connections they
|
|
66
|
+
* opened. A caller that passes `max_sessions` closes the returned sessions'
|
|
67
|
+
* connections once its transaction commits — `queue_connection_close` with a
|
|
68
|
+
* `session` target, as `POST /login` does. A caller that mints for an account
|
|
69
|
+
* created in the same transaction (signup, bootstrap) evicts nothing.
|
|
70
|
+
*
|
|
71
|
+
* @returns the token hashes of the sessions the cap evicted, empty without a cap
|
|
65
72
|
* @mutates `auth_session` table - inserts the new session row (and evicts older rows when `max_sessions` is set)
|
|
66
73
|
*/
|
|
67
|
-
export declare const create_session_and_set_cookie: (options: CreateSessionAndSetCookieOptions) => Promise<
|
|
74
|
+
export declare const create_session_and_set_cookie: (options: CreateSessionAndSetCookieOptions) => Promise<Array<string>>;
|
|
68
75
|
//# sourceMappingURL=session_middleware.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_middleware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACN,KAAK,cAAc,EAKnB,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EACnC,GAAG,OAAO,EACV,SAAS,cAAc,CAAC,CAAC,CAAC,KACxB,MAAM,GAAG,SAEX,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EACnC,GAAG,OAAO,EACV,OAAO,MAAM,EACb,SAAS,cAAc,CAAC,CAAC,CAAC,KACxB,IAOF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,cAAc,CAAC,CAAC,CAAC,KAAG,IAMhF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,EAClD,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,KAChC,iBAcF,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAChD,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,IAAI,EAAE,SAAS,CAAC;IAChB,2CAA2C;IAC3C,CAAC,EAAE,OAAO,CAAC;IACX,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED
|
|
1
|
+
{"version":3,"file":"session_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_middleware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACN,KAAK,cAAc,EAKnB,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EACnC,GAAG,OAAO,EACV,SAAS,cAAc,CAAC,CAAC,CAAC,KACxB,MAAM,GAAG,SAEX,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EACnC,GAAG,OAAO,EACV,OAAO,MAAM,EACb,SAAS,cAAc,CAAC,CAAC,CAAC,KACxB,IAOF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,cAAc,CAAC,CAAC,CAAC,KAAG,IAMhF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,EAClD,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,KAChC,iBAcF,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAChD,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,IAAI,EAAE,SAAS,CAAC;IAChB,2CAA2C;IAC3C,CAAC,EAAE,OAAO,CAAC;IACX,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,gCAAgC,KACvC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAavB,CAAC"}
|
|
@@ -70,6 +70,13 @@ export const create_session_middleware = (keyring, options) => {
|
|
|
70
70
|
* persists the session row, optionally enforces a per-account session limit,
|
|
71
71
|
* and sets the signed cookie.
|
|
72
72
|
*
|
|
73
|
+
* The cap deletes the sessions it evicts, but not the connections they
|
|
74
|
+
* opened. A caller that passes `max_sessions` closes the returned sessions'
|
|
75
|
+
* connections once its transaction commits — `queue_connection_close` with a
|
|
76
|
+
* `session` target, as `POST /login` does. A caller that mints for an account
|
|
77
|
+
* created in the same transaction (signup, bootstrap) evicts nothing.
|
|
78
|
+
*
|
|
79
|
+
* @returns the token hashes of the sessions the cap evicted, empty without a cap
|
|
73
80
|
* @mutates `auth_session` table - inserts the new session row (and evicts older rows when `max_sessions` is set)
|
|
74
81
|
*/
|
|
75
82
|
export const create_session_and_set_cookie = async (options) => {
|
|
@@ -78,9 +85,8 @@ export const create_session_and_set_cookie = async (options) => {
|
|
|
78
85
|
const token_hash = hash_session_token(session_token);
|
|
79
86
|
const expires_at = new Date(Date.now() + AUTH_SESSION_LIFETIME_MS);
|
|
80
87
|
await query_create_session(deps, token_hash, account_id, expires_at);
|
|
81
|
-
|
|
82
|
-
await query_session_enforce_limit(deps, account_id, max_sessions);
|
|
83
|
-
}
|
|
88
|
+
const evicted = max_sessions != null ? await query_session_enforce_limit(deps, account_id, max_sessions) : [];
|
|
84
89
|
const cookie_value = await create_session_cookie_value(keyring, session_token, session_options);
|
|
85
90
|
set_session_cookie(c, cookie_value, session_options);
|
|
91
|
+
return evicted;
|
|
86
92
|
};
|
|
@@ -132,13 +132,19 @@ export declare const query_session_list_for_account: (deps: QueryDeps, account_i
|
|
|
132
132
|
* Expired-but-unreaped rows count toward the cap (the predicate is `account_id`
|
|
133
133
|
* alone). Matches the Rust twin `query_session_enforce_limit`.
|
|
134
134
|
*
|
|
135
|
+
* Deleting a session row does not end the connections it opened: a WebSocket
|
|
136
|
+
* keeps the authority it resolved at upgrade and an SSE stream never rechecks.
|
|
137
|
+
* The returned ids are the token hashes those connections are registered
|
|
138
|
+
* under, so the caller closes them once the transaction commits
|
|
139
|
+
* (`queue_connection_close`) — `POST /login` does.
|
|
140
|
+
*
|
|
135
141
|
* @param deps - query dependencies (must be transaction-scoped)
|
|
136
142
|
* @param account_id - the account to enforce the limit for
|
|
137
143
|
* @param max_sessions - maximum number of sessions to keep
|
|
138
|
-
* @returns the
|
|
144
|
+
* @returns the evicted sessions' ids (their token hashes), empty when nothing was evicted
|
|
139
145
|
* @mutates `auth_session` table - deletes the oldest rows past the cap
|
|
140
146
|
*/
|
|
141
|
-
export declare const query_session_enforce_limit: (deps: QueryDeps, account_id: string, max_sessions: number) => Promise<
|
|
147
|
+
export declare const query_session_enforce_limit: (deps: QueryDeps, account_id: string, max_sessions: number) => Promise<Array<string>>;
|
|
142
148
|
/**
|
|
143
149
|
* List all active sessions across all accounts with usernames.
|
|
144
150
|
*
|
|
@@ -153,10 +159,29 @@ export declare const query_session_list_all_active: (deps: QueryDeps, limit?: nu
|
|
|
153
159
|
username: string;
|
|
154
160
|
}>>;
|
|
155
161
|
/**
|
|
156
|
-
* Delete expired
|
|
157
|
-
*
|
|
158
|
-
*
|
|
162
|
+
* Delete every expired session, returning the ids (token hashes) of the rows
|
|
163
|
+
* it deleted.
|
|
164
|
+
*
|
|
165
|
+
* The ids are the keys `ConnectionCloser.close_sockets_for_session` closes by.
|
|
166
|
+
* A row past `expires_at` already fails `query_session_get_valid`, but
|
|
167
|
+
* deleting it does not end the connections it opened: a WebSocket keeps the
|
|
168
|
+
* authority it resolved at upgrade and an SSE stream never rechecks. The row
|
|
169
|
+
* was also the only per-session handle on those connections —
|
|
170
|
+
* `account_session_revoke` closes only for a row it deleted — so the caller
|
|
171
|
+
* closes each returned session's connections once the delete has committed.
|
|
172
|
+
* `cleanup_expired_sessions` (`auth/cleanup.ts`) is that caller; reach for it
|
|
173
|
+
* rather than this query.
|
|
174
|
+
*
|
|
175
|
+
* One statement, so the returned ids are exactly the rows this call removed:
|
|
176
|
+
* a concurrent pass gets a disjoint set. Run it in a transaction, as the
|
|
177
|
+
* caller above does, so the ids are in hand before the delete commits — on a
|
|
178
|
+
* pool-level `Db` a connection lost on the response leaves the rows deleted
|
|
179
|
+
* and their ids unread. Served by `idx_auth_session_expires`. Twin of the
|
|
180
|
+
* Rust `query_session_cleanup_expired`.
|
|
181
|
+
*
|
|
182
|
+
* @param deps - query dependencies (transaction-scoped, see above)
|
|
183
|
+
* @returns the deleted sessions' ids (their token hashes), empty when nothing had expired
|
|
159
184
|
* @mutates `auth_session` table - deletes every row past `expires_at`
|
|
160
185
|
*/
|
|
161
|
-
export declare const query_session_cleanup_expired: (deps: QueryDeps) => Promise<
|
|
186
|
+
export declare const query_session_cleanup_expired: (deps: QueryDeps) => Promise<Array<string>>;
|
|
162
187
|
//# sourceMappingURL=session_queries.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;AAQtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;AAQtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAYvB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CASnD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAK1F,CAAC"}
|
|
@@ -165,10 +165,16 @@ export const query_session_list_for_account = async (deps, account_id, limit = 5
|
|
|
165
165
|
* Expired-but-unreaped rows count toward the cap (the predicate is `account_id`
|
|
166
166
|
* alone). Matches the Rust twin `query_session_enforce_limit`.
|
|
167
167
|
*
|
|
168
|
+
* Deleting a session row does not end the connections it opened: a WebSocket
|
|
169
|
+
* keeps the authority it resolved at upgrade and an SSE stream never rechecks.
|
|
170
|
+
* The returned ids are the token hashes those connections are registered
|
|
171
|
+
* under, so the caller closes them once the transaction commits
|
|
172
|
+
* (`queue_connection_close`) — `POST /login` does.
|
|
173
|
+
*
|
|
168
174
|
* @param deps - query dependencies (must be transaction-scoped)
|
|
169
175
|
* @param account_id - the account to enforce the limit for
|
|
170
176
|
* @param max_sessions - maximum number of sessions to keep
|
|
171
|
-
* @returns the
|
|
177
|
+
* @returns the evicted sessions' ids (their token hashes), empty when nothing was evicted
|
|
172
178
|
* @mutates `auth_session` table - deletes the oldest rows past the cap
|
|
173
179
|
*/
|
|
174
180
|
export const query_session_enforce_limit = async (deps, account_id, max_sessions) => {
|
|
@@ -179,7 +185,7 @@ export const query_session_enforce_limit = async (deps, account_id, max_sessions
|
|
|
179
185
|
ORDER BY created_at DESC, id DESC
|
|
180
186
|
OFFSET $2
|
|
181
187
|
) RETURNING id`, [account_id, max_sessions]);
|
|
182
|
-
return rows.
|
|
188
|
+
return rows.map((row) => row.id);
|
|
183
189
|
};
|
|
184
190
|
/**
|
|
185
191
|
* List all active sessions across all accounts with usernames.
|
|
@@ -199,12 +205,31 @@ export const query_session_list_all_active = async (deps, limit = 200) => {
|
|
|
199
205
|
ORDER BY s.created_at DESC, s.id DESC LIMIT $1`, [limit]);
|
|
200
206
|
};
|
|
201
207
|
/**
|
|
202
|
-
* Delete expired
|
|
203
|
-
*
|
|
204
|
-
*
|
|
208
|
+
* Delete every expired session, returning the ids (token hashes) of the rows
|
|
209
|
+
* it deleted.
|
|
210
|
+
*
|
|
211
|
+
* The ids are the keys `ConnectionCloser.close_sockets_for_session` closes by.
|
|
212
|
+
* A row past `expires_at` already fails `query_session_get_valid`, but
|
|
213
|
+
* deleting it does not end the connections it opened: a WebSocket keeps the
|
|
214
|
+
* authority it resolved at upgrade and an SSE stream never rechecks. The row
|
|
215
|
+
* was also the only per-session handle on those connections —
|
|
216
|
+
* `account_session_revoke` closes only for a row it deleted — so the caller
|
|
217
|
+
* closes each returned session's connections once the delete has committed.
|
|
218
|
+
* `cleanup_expired_sessions` (`auth/cleanup.ts`) is that caller; reach for it
|
|
219
|
+
* rather than this query.
|
|
220
|
+
*
|
|
221
|
+
* One statement, so the returned ids are exactly the rows this call removed:
|
|
222
|
+
* a concurrent pass gets a disjoint set. Run it in a transaction, as the
|
|
223
|
+
* caller above does, so the ids are in hand before the delete commits — on a
|
|
224
|
+
* pool-level `Db` a connection lost on the response leaves the rows deleted
|
|
225
|
+
* and their ids unread. Served by `idx_auth_session_expires`. Twin of the
|
|
226
|
+
* Rust `query_session_cleanup_expired`.
|
|
227
|
+
*
|
|
228
|
+
* @param deps - query dependencies (transaction-scoped, see above)
|
|
229
|
+
* @returns the deleted sessions' ids (their token hashes), empty when nothing had expired
|
|
205
230
|
* @mutates `auth_session` table - deletes every row past `expires_at`
|
|
206
231
|
*/
|
|
207
232
|
export const query_session_cleanup_expired = async (deps) => {
|
|
208
233
|
const rows = await deps.db.query(`DELETE FROM auth_session WHERE expires_at <= NOW() RETURNING id`);
|
|
209
|
-
return rows.
|
|
234
|
+
return rows.map((row) => row.id);
|
|
210
235
|
};
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* Option routing: shared `roles` flows to both admin and role-grant-offer;
|
|
11
11
|
* `default_ttl_ms` and `authorize` go to role-grant-offer only; `max_tokens`
|
|
12
12
|
* goes to account only;
|
|
13
|
-
* shared `connection_closer` flows to admin + account (role-grant-offer ignores);
|
|
14
13
|
* `notification_sender` reaches role-grant-offer transparently (admin + account
|
|
15
|
-
* ignore it).
|
|
14
|
+
* ignore it). `deps.connection_closer` is read by admin + account, whose
|
|
15
|
+
* revocation handlers close live connections through it.
|
|
16
16
|
*
|
|
17
17
|
* Paired with `create_admin_rpc_adapters` on the UI side.
|
|
18
18
|
*
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { type AdminActionOptions } from './admin_actions.js';
|
|
22
22
|
import { type RoleGrantOfferActionOptions } from './role_grant_offer_actions.js';
|
|
23
23
|
import { type AccountActionOptions } from './account_actions.js';
|
|
24
|
-
import type {
|
|
24
|
+
import type { RevokingActionFactoryDeps } from './deps.js';
|
|
25
25
|
import type { NotificationSender } from './role_grant_offer_notifications.js';
|
|
26
26
|
import type { RpcAction } from '../actions/action_rpc.js';
|
|
27
27
|
/**
|
|
@@ -38,12 +38,12 @@ export interface StandardRpcActionsOptions extends AdminActionOptions, RoleGrant
|
|
|
38
38
|
/**
|
|
39
39
|
* Dependencies for `create_standard_rpc_actions`.
|
|
40
40
|
*
|
|
41
|
-
* Stack-standard `
|
|
42
|
-
* `notification_sender` consumed only by the
|
|
43
|
-
* for WS fan-out. Admin and account
|
|
44
|
-
* `notification_sender`.
|
|
41
|
+
* Stack-standard `RevokingActionFactoryDeps` (`log`, `audit`,
|
|
42
|
+
* `connection_closer`) plus an optional `notification_sender` consumed only by the
|
|
43
|
+
* role-grant-offer sub-factory for WS fan-out. Admin and account
|
|
44
|
+
* sub-factories ignore `notification_sender`.
|
|
45
45
|
*/
|
|
46
|
-
export interface StandardRpcActionsDeps extends
|
|
46
|
+
export interface StandardRpcActionsDeps extends RevokingActionFactoryDeps {
|
|
47
47
|
notification_sender?: NotificationSender | null;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
@@ -54,7 +54,7 @@ export interface StandardRpcActionsDeps extends ActionFactoryDeps {
|
|
|
54
54
|
* and `create_account_actions(deps, {max_tokens})`. The shared `roles`
|
|
55
55
|
* option flows to admin + role-grant-offer.
|
|
56
56
|
*
|
|
57
|
-
* @param deps - `StandardRpcActionsDeps` (`log`, `audit` from `
|
|
57
|
+
* @param deps - `StandardRpcActionsDeps` (`log`, `audit`, `connection_closer` from `RevokingActionFactoryDeps`; optional `notification_sender` for WS fan-out)
|
|
58
58
|
* @param options - role schema, role-grant-offer config, account config
|
|
59
59
|
* @returns RPC actions to pass as `rpc_endpoints` or spread into `create_rpc_endpoint`
|
|
60
60
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standard_rpc_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/standard_rpc_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAEN,KAAK,2BAA2B,EAChC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"standard_rpc_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/standard_rpc_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAEN,KAAK,2BAA2B,EAChC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAChB,SAAQ,kBAAkB,EAAE,2BAA2B,EAAE,oBAAoB;CAAG;AAEjF;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAuB,SAAQ,yBAAyB;IACxE,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,sBAAsB,EAC5B,UAAS,yBAA8B,KACrC,KAAK,CAAC,SAAS,CAIjB,CAAC"}
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* Option routing: shared `roles` flows to both admin and role-grant-offer;
|
|
11
11
|
* `default_ttl_ms` and `authorize` go to role-grant-offer only; `max_tokens`
|
|
12
12
|
* goes to account only;
|
|
13
|
-
* shared `connection_closer` flows to admin + account (role-grant-offer ignores);
|
|
14
13
|
* `notification_sender` reaches role-grant-offer transparently (admin + account
|
|
15
|
-
* ignore it).
|
|
14
|
+
* ignore it). `deps.connection_closer` is read by admin + account, whose
|
|
15
|
+
* revocation handlers close live connections through it.
|
|
16
16
|
*
|
|
17
17
|
* Paired with `create_admin_rpc_adapters` on the UI side.
|
|
18
18
|
*
|
|
@@ -29,7 +29,7 @@ import { create_account_actions } from './account_actions.js';
|
|
|
29
29
|
* and `create_account_actions(deps, {max_tokens})`. The shared `roles`
|
|
30
30
|
* option flows to admin + role-grant-offer.
|
|
31
31
|
*
|
|
32
|
-
* @param deps - `StandardRpcActionsDeps` (`log`, `audit` from `
|
|
32
|
+
* @param deps - `StandardRpcActionsDeps` (`log`, `audit`, `connection_closer` from `RevokingActionFactoryDeps`; optional `notification_sender` for WS fan-out)
|
|
33
33
|
* @param options - role schema, role-grant-offer config, account config
|
|
34
34
|
* @returns RPC actions to pass as `rpc_endpoints` or spread into `create_rpc_endpoint`
|
|
35
35
|
*/
|
package/dist/db/CLAUDE.md
CHANGED
|
@@ -50,7 +50,13 @@ the code.
|
|
|
50
50
|
`fact`'s store rows are the deliberate exception: `PgFactStore` feeds
|
|
51
51
|
`FactMeta.created_at`, a `Date` in the `@fuzdev/fuz_util` `FactStore`
|
|
52
52
|
contract, and nothing serializes it.
|
|
53
|
-
- `status.ts` — CLI DB status utility
|
|
53
|
+
- `status.ts` — CLI DB status utility: `query_db_status` (read-only;
|
|
54
|
+
per-namespace applied/pending plus name-divergence, mirroring the
|
|
55
|
+
runner's boot check), `format_db_status` (the report body — the
|
|
56
|
+
`database status` / `url:` header is the calling script's, using
|
|
57
|
+
`display_db_url`, which renders the URL from its parse without the
|
|
58
|
+
password or query). The connected body is byte-identical with the Rust
|
|
59
|
+
`fuz_db` twin's; `src/test/db/status.test.ts` pins the shared fixture.
|
|
54
60
|
- `schema_ready.ts` — `/ready` deploy-gate core: `query_public_columns`
|
|
55
61
|
(keeps `schema_version`, unlike `query_schema_snapshot`), pure
|
|
56
62
|
`check_schema_drift` / `format_schema_drift`, `READY_ERROR`. Column-presence
|
package/dist/db/status.d.ts
CHANGED
|
@@ -97,10 +97,67 @@ export interface DbStatus {
|
|
|
97
97
|
*/
|
|
98
98
|
export declare const query_db_status: (db: Db, namespaces?: Array<MigrationNamespace>) => Promise<DbStatus>;
|
|
99
99
|
/**
|
|
100
|
-
* Format a `DbStatus` as
|
|
100
|
+
* Format a `DbStatus` as the body of the human-facing operator report.
|
|
101
|
+
*
|
|
102
|
+
* A `tables:` count with one aligned `name N rows` line per table, then a
|
|
103
|
+
* `migrations:` header (always printed, even with no namespaces) and one
|
|
104
|
+
* aligned line per namespace: its state (`up-to-date`, `PENDING <n>`, or
|
|
105
|
+
* `DIVERGED`), `applied/declared`, and the latest applied name (`(none)` when
|
|
106
|
+
* nothing is applied). A diverged namespace adds a `diverged:` line, a pending
|
|
107
|
+
* one a `pending:` line naming the tail. A failed connection renders the single
|
|
108
|
+
* line `connection: FAILED (<error>)`. Every line ends in a newline.
|
|
109
|
+
*
|
|
110
|
+
* The body only — the `database status` / `url:` header belongs to the CLI
|
|
111
|
+
* script, which holds the URL a `DbStatus` never sees. For a connected status
|
|
112
|
+
* the output is byte-identical with the Rust twin's `format_db_status`; both
|
|
113
|
+
* test suites pin the same fixture report.
|
|
101
114
|
*
|
|
102
115
|
* @param status - the status to format
|
|
103
116
|
* @returns multi-line string suitable for console output
|
|
104
117
|
*/
|
|
105
118
|
export declare const format_db_status: (status: DbStatus) => string;
|
|
119
|
+
/**
|
|
120
|
+
* Render a database URL for printing from the parse node-postgres dials —
|
|
121
|
+
* `new URL`, with pg-connection-string's precedence, where a `user`, `host`, or
|
|
122
|
+
* `port` query parameter overrides the URL's own — so the line names the server
|
|
123
|
+
* dialed and no credential can leak however the input spells it.
|
|
124
|
+
*
|
|
125
|
+
* A `postgres://` or `postgresql://` URL renders as
|
|
126
|
+
* `postgres://[user@]host:port[/dbname]`: never the password, and never the
|
|
127
|
+
* query or fragment (`sslmode` and the like are dropped by design). The scheme
|
|
128
|
+
* always prints as `postgres://`. The port always prints, `5432` when the URL
|
|
129
|
+
* names none. The user and database name print percent-encoded with
|
|
130
|
+
* `encodeURIComponent`, so no control character, escape sequence, or bidi
|
|
131
|
+
* override reaches the terminal and an `@` can't pose as the separator; each
|
|
132
|
+
* prints only when non-empty. An IP-literal host prints canonically (IPv6
|
|
133
|
+
* bracketed); a Unix-socket directory prints with each `/` as `%2F`. A PGlite
|
|
134
|
+
* `file://` or `memory://` URL prints as scheme, host, and path.
|
|
135
|
+
*
|
|
136
|
+
* The whole line is `(not a URL; hidden)` for any other scheme, when `new URL`
|
|
137
|
+
* rejects the input, when it holds a malformed percent-escape (`%ZZ`), when
|
|
138
|
+
* there is no host, when the host is neither an IP literal, a hostname of
|
|
139
|
+
* `A-Z a-z 0-9 . _ -`, nor a socket directory of those characters and `/`, when
|
|
140
|
+
* the user holds `?`, `=`, `&`, `/`, or `#`, or when the port isn't a number up
|
|
141
|
+
* to 65535. The host and user rules match the Rust twin's, where they catch
|
|
142
|
+
* tokio-postgres's split at the first `@`.
|
|
143
|
+
*
|
|
144
|
+
* Twin of the Rust `fuz_db` CLI's `display_db_url`, byte-identical on the URLs
|
|
145
|
+
* both drivers read alike. They part where the drivers do: a `host=` or `port=`
|
|
146
|
+
* query parameter overrides the URL's here (tokio-postgres appends it to a list
|
|
147
|
+
* whose first entry the pool dials); a `dbname=` query parameter, which
|
|
148
|
+
* tokio-postgres honours and node-postgres ignores, and a `#…` fragment, which
|
|
149
|
+
* tokio-postgres keeps as part of the database name; tokio-postgres also
|
|
150
|
+
* accepts `key=value` strings, host lists, and an unencoded `/` or `?` in the
|
|
151
|
+
* password (here, the placeholder); an unencoded `@` in the userinfo splits at
|
|
152
|
+
* the last `@` here, rendering the user and host node-postgres dials, where the
|
|
153
|
+
* Rust twin prints the placeholder; node-postgres decodes the database name
|
|
154
|
+
* with `decodeURI`, so a reserved-character escape such as `%2F` stays literal
|
|
155
|
+
* and prints as `%252F`; an IPv4-mapped IPv6 address serializes differently;
|
|
156
|
+
* and tokio-postgres refuses an unknown query parameter (`sslpassword=`) that
|
|
157
|
+
* node-postgres passes.
|
|
158
|
+
*
|
|
159
|
+
* @param url - the database URL
|
|
160
|
+
* @returns the URL without its password, query, or fragment, or the placeholder
|
|
161
|
+
*/
|
|
162
|
+
export declare const display_db_url: (url: string) => string;
|
|
106
163
|
//# sourceMappingURL=status.d.ts.map
|
package/dist/db/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GACnB;IACA,mEAAmE;IACnE,IAAI,EAAE,eAAe,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;CAChB,GACD;IACA,kEAAkE;IAClE,IAAI,EAAE,cAAc,CAAC;IACrB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAChB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,2EAA2E;IAC3E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACnC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GACnB;IACA,mEAAmE;IACnE,IAAI,EAAE,eAAe,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;CAChB,GACD;IACA,kEAAkE;IAClE,IAAI,EAAE,cAAc,CAAC;IACrB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAChB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,2EAA2E;IAC3E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACnC;AA6DD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,EAAE,EACN,aAAa,KAAK,CAAC,kBAAkB,CAAC,KACpC,OAAO,CAAC,QAAQ,CA2DlB,CAAC;AA0BF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,QAAQ,KAAG,MAmCnD,CAAC;AA8BF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAoC5C,CAAC"}
|