@fuzdev/fuz_app 0.117.1 → 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 +125 -31
- 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 +72 -44
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +60 -16
- 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/bootstrap_account.d.ts +3 -2
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +3 -3
- package/dist/auth/cleanup.d.ts +219 -30
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +260 -45
- package/dist/auth/deps.d.ts +29 -1
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +27 -0
- 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/role_grant_offer_ddl.d.ts +4 -2
- package/dist/auth/role_grant_offer_ddl.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_ddl.js +10 -3
- package/dist/auth/role_grant_offer_queries.d.ts +16 -7
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +33 -15
- package/dist/auth/role_grant_offer_schema.d.ts +9 -0
- package/dist/auth/role_grant_offer_schema.d.ts.map +1 -1
- 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/migrate.d.ts +8 -13
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +9 -38
- package/dist/db/status.d.ts +58 -7
- package/dist/db/status.d.ts.map +1 -1
- package/dist/db/status.js +205 -97
- 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/runtime/deno.d.ts.map +1 -1
- package/dist/runtime/deno.js +9 -6
- package/dist/runtime/deps.d.ts +3 -2
- package/dist/runtime/deps.d.ts.map +1 -1
- package/dist/runtime/secure_file.d.ts +23 -9
- package/dist/runtime/secure_file.d.ts.map +1 -1
- package/dist/runtime/secure_file.js +29 -10
- package/dist/server/app_backend.d.ts +13 -2
- 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 +3 -52
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +2 -46
- 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/expected_schema.json +1 -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 +2 -2
package/dist/auth/deps.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { Keyring } from './keyring.js';
|
|
|
13
13
|
import type { PasswordHashDeps } from './password.js';
|
|
14
14
|
import type { Db } from '../db/db.js';
|
|
15
15
|
import type { AuditEmitter } from './audit_emitter.js';
|
|
16
|
+
import type { ConnectionCloser, RealtimeCloser } from '../actions/connection_closer.js';
|
|
16
17
|
/**
|
|
17
18
|
* Stateless capabilities bundle for fuz_app backends.
|
|
18
19
|
*
|
|
@@ -24,7 +25,7 @@ export interface AppDeps {
|
|
|
24
25
|
* Hardened secret-file read — used for the bootstrap token (the file that
|
|
25
26
|
* mints the keeper account). Production wiring passes the runtime's
|
|
26
27
|
* `read_secure_file` (`FsSecureReadDeps`), which rejects symlinks,
|
|
27
|
-
* group/other-accessible modes, and oversized files; both the boot-time
|
|
28
|
+
* non-regular files, group/other-accessible modes, and oversized files; both the boot-time
|
|
28
29
|
* availability probe and the request-time read go through this one
|
|
29
30
|
* capability so the probe can never be laxer than the read it gates.
|
|
30
31
|
*/
|
|
@@ -47,6 +48,16 @@ export interface AppDeps {
|
|
|
47
48
|
* context.
|
|
48
49
|
*/
|
|
49
50
|
audit: AuditEmitter;
|
|
51
|
+
/**
|
|
52
|
+
* Closes live connections — WebSocket and SSE — when a credential is
|
|
53
|
+
* revoked. Every revocation handler queues its close on this
|
|
54
|
+
* (`queue_connection_close`), so it must reach every transport of the
|
|
55
|
+
* backend: `create_app_backend` creates it empty, and `create_app_server`
|
|
56
|
+
* adds each WebSocket transport it mounts and its audit stream registry. A
|
|
57
|
+
* transport mounted by hand is added through the `connection_closer`
|
|
58
|
+
* option of `register_ws_endpoint` / `create_audit_log_sse`.
|
|
59
|
+
*/
|
|
60
|
+
connection_closer: RealtimeCloser;
|
|
50
61
|
/**
|
|
51
62
|
* Optional content-addressed byte store. Present only on backends that
|
|
52
63
|
* serve binary content (facts) — minimal consumers leave it unset. The
|
|
@@ -78,4 +89,21 @@ export interface ActionFactoryDeps {
|
|
|
78
89
|
/** Bound audit emitter for fire-and-forget audit writes. */
|
|
79
90
|
audit: AuditEmitter;
|
|
80
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Capabilities for the action factories whose handlers end credentials —
|
|
94
|
+
* `create_account_actions`, `create_admin_actions`, and the
|
|
95
|
+
* `create_standard_rpc_actions` bundle over them. `ActionFactoryDeps` plus
|
|
96
|
+
* the closer their revocations close live connections through. `AppDeps`
|
|
97
|
+
* and `RouteFactoryDeps` satisfy it structurally.
|
|
98
|
+
*/
|
|
99
|
+
export interface RevokingActionFactoryDeps extends ActionFactoryDeps {
|
|
100
|
+
/**
|
|
101
|
+
* Closes the live connections of a revoked credential, after the
|
|
102
|
+
* revocation commits. Required rather than optional: a revocation that
|
|
103
|
+
* closes nothing leaves an open WebSocket or SSE stream running on a dead
|
|
104
|
+
* credential. A backend with no live-connection surface passes
|
|
105
|
+
* `noop_connection_closer`.
|
|
106
|
+
*/
|
|
107
|
+
connection_closer: ConnectionCloser;
|
|
108
|
+
}
|
|
81
109
|
//# sourceMappingURL=deps.d.ts.map
|
package/dist/auth/deps.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAExF;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACvB;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yBAAyB;IACzB,EAAE,EAAE,EAAE,CAAC;IACP,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB;;;;;;;;OAQG;IACH,iBAAiB,EAAE,cAAc,CAAC;IAClC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,KAAK,EAAE,YAAY,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,yBAA0B,SAAQ,iBAAiB;IACnE;;;;;;OAMG;IACH,iBAAiB,EAAE,gBAAgB,CAAC;CACpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AA+BH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CAAC,MAAM,CAA8B,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AA+BH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CAAC,MAAM,CAA8B,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CA0M5C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
|
package/dist/auth/migrations.js
CHANGED
|
@@ -237,6 +237,33 @@ export const auth_migrations = [
|
|
|
237
237
|
up: async (db) => {
|
|
238
238
|
await db.query('ALTER TABLE auth_session DROP COLUMN IF EXISTS last_seen_at');
|
|
239
239
|
}
|
|
240
|
+
},
|
|
241
|
+
// The expiry-audit stamp that makes the offer sweep idempotent.
|
|
242
|
+
//
|
|
243
|
+
// An expired pending offer has no terminal state — expiry is computed from
|
|
244
|
+
// `expires_at`, never written — so without a stamp every
|
|
245
|
+
// `cleanup_expired_role_grant_offers` run re-audited every expired offer.
|
|
246
|
+
// The sweep now claims rows by setting `expire_audited_at` in the same
|
|
247
|
+
// transaction as their `role_grant_offer_expire` audit rows, so each expiry
|
|
248
|
+
// is audited exactly once and concurrent sweeps claim disjoint rows.
|
|
249
|
+
//
|
|
250
|
+
// Not a lifecycle state: the column stays out of the
|
|
251
|
+
// `role_grant_offer_single_terminal` CHECK and the pending predicate, and
|
|
252
|
+
// off the wire. A re-offer of a stamped row clears it (the create upsert),
|
|
253
|
+
// so the refreshed offer's own expiry is audited too. The partial index
|
|
254
|
+
// serves exactly the sweep's claim predicate.
|
|
255
|
+
{
|
|
256
|
+
name: 'role_grant_offer_expire_audited_at',
|
|
257
|
+
up: async (db) => {
|
|
258
|
+
await db.query('ALTER TABLE role_grant_offer ADD COLUMN IF NOT EXISTS expire_audited_at TIMESTAMPTZ NULL');
|
|
259
|
+
await db.query(`CREATE INDEX IF NOT EXISTS role_grant_offer_expire_sweep
|
|
260
|
+
ON role_grant_offer (expires_at)
|
|
261
|
+
WHERE accepted_at IS NULL
|
|
262
|
+
AND declined_at IS NULL
|
|
263
|
+
AND retracted_at IS NULL
|
|
264
|
+
AND superseded_at IS NULL
|
|
265
|
+
AND expire_audited_at IS NULL`);
|
|
266
|
+
}
|
|
240
267
|
}
|
|
241
268
|
];
|
|
242
269
|
/** Pre-composed migration namespace for auth tables. */
|
|
@@ -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
|
+
};
|
|
@@ -27,13 +27,15 @@ export declare const ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN = "GLOBAL";
|
|
|
27
27
|
* The full `role_grant_offer` column set, named explicitly so a row read
|
|
28
28
|
* fails loud on schema drift (see `ACCOUNT_COLUMNS` in
|
|
29
29
|
* `auth/account_queries.ts` for the outage class). Column order follows
|
|
30
|
-
* `RoleGrantOffer` and `ROLE_GRANT_OFFER_SCHEMA` below
|
|
30
|
+
* `RoleGrantOffer` and `ROLE_GRANT_OFFER_SCHEMA` below, with
|
|
31
|
+
* `expire_audited_at` last — the appended `role_grant_offer_expire_audited_at`
|
|
32
|
+
* migration adds it after the frozen `CREATE TABLE`.
|
|
31
33
|
*
|
|
32
34
|
* Lives here rather than in `auth/role_grant_offer_queries.ts` because two
|
|
33
35
|
* query modules project this table — see the placement rule in
|
|
34
36
|
* `db/sql_columns.ts`.
|
|
35
37
|
*/
|
|
36
|
-
export declare const ROLE_GRANT_OFFER_COLUMNS: readonly ["id", "from_actor_id", "to_account_id", "to_actor_id", "role", "scope_kind", "scope_id", "message", "created_at", "expires_at", "accepted_at", "declined_at", "decline_reason", "retracted_at", "superseded_at", "resulting_role_grant_id"];
|
|
38
|
+
export declare const ROLE_GRANT_OFFER_COLUMNS: readonly ["id", "from_actor_id", "to_account_id", "to_actor_id", "role", "scope_kind", "scope_id", "message", "created_at", "expires_at", "accepted_at", "declined_at", "decline_reason", "retracted_at", "superseded_at", "resulting_role_grant_id", "expire_audited_at"];
|
|
37
39
|
/**
|
|
38
40
|
* The `role_grant_offer` timestamp override, by row qualifier (`''` bare,
|
|
39
41
|
* `o` for the history JOIN, `u` for the `updated` CTE's outer select). Lives
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,mHAAmH;AACnH,eAAO,MAAM,oCAAoC,yCAAyC,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,WAAW,CAAC;AAEjE
|
|
1
|
+
{"version":3,"file":"role_grant_offer_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,mHAAmH;AACnH,eAAO,MAAM,oCAAoC,yCAAyC,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,WAAW,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,4QAkBmB,CAAC;AAEzD;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,8DAQhC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,oCAAoC,QAMI,CAAC;AAKtD,eAAO,MAAM,uBAAuB,qzCAkClC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC,2XAYpB,CAAC;AAE/B,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B,kOAMX,CAAC"}
|
|
@@ -28,7 +28,9 @@ export const ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN = 'GLOBAL';
|
|
|
28
28
|
* The full `role_grant_offer` column set, named explicitly so a row read
|
|
29
29
|
* fails loud on schema drift (see `ACCOUNT_COLUMNS` in
|
|
30
30
|
* `auth/account_queries.ts` for the outage class). Column order follows
|
|
31
|
-
* `RoleGrantOffer` and `ROLE_GRANT_OFFER_SCHEMA` below
|
|
31
|
+
* `RoleGrantOffer` and `ROLE_GRANT_OFFER_SCHEMA` below, with
|
|
32
|
+
* `expire_audited_at` last — the appended `role_grant_offer_expire_audited_at`
|
|
33
|
+
* migration adds it after the frozen `CREATE TABLE`.
|
|
32
34
|
*
|
|
33
35
|
* Lives here rather than in `auth/role_grant_offer_queries.ts` because two
|
|
34
36
|
* query modules project this table — see the placement rule in
|
|
@@ -50,7 +52,8 @@ export const ROLE_GRANT_OFFER_COLUMNS = [
|
|
|
50
52
|
'decline_reason',
|
|
51
53
|
'retracted_at',
|
|
52
54
|
'superseded_at',
|
|
53
|
-
'resulting_role_grant_id'
|
|
55
|
+
'resulting_role_grant_id',
|
|
56
|
+
'expire_audited_at'
|
|
54
57
|
];
|
|
55
58
|
/**
|
|
56
59
|
* The `role_grant_offer` timestamp override, by row qualifier (`''` bare,
|
|
@@ -68,7 +71,8 @@ export const role_grant_offer_expr = iso8601_timestamp_expr(ROLE_GRANT_OFFER_COL
|
|
|
68
71
|
'accepted_at',
|
|
69
72
|
'declined_at',
|
|
70
73
|
'retracted_at',
|
|
71
|
-
'superseded_at'
|
|
74
|
+
'superseded_at',
|
|
75
|
+
'expire_audited_at'
|
|
72
76
|
]);
|
|
73
77
|
/**
|
|
74
78
|
* Outer `SELECT` over an `updated` CTE of `role_grant_offer` rows, joining
|
|
@@ -81,6 +85,9 @@ export const role_grant_offer_expr = iso8601_timestamp_expr(ROLE_GRANT_OFFER_COL
|
|
|
81
85
|
export const ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT = `SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'u', role_grant_offer_expr('u'))}, grantor.account_id AS from_account_id
|
|
82
86
|
FROM updated u
|
|
83
87
|
JOIN actor grantor ON grantor.id = u.from_actor_id`;
|
|
88
|
+
// Frozen v1 body — `expire_audited_at` is added by the appended
|
|
89
|
+
// `role_grant_offer_expire_audited_at` migration (with its partial sweep
|
|
90
|
+
// index), so a fresh bootstrap and an old deployed DB converge on one shape.
|
|
84
91
|
export const ROLE_GRANT_OFFER_SCHEMA = `
|
|
85
92
|
CREATE TABLE IF NOT EXISTS role_grant_offer (
|
|
86
93
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
@@ -112,7 +112,9 @@ export declare class RoleGrantOfferActorAccountMismatchError extends Error {
|
|
|
112
112
|
* `(to_account, role, scope)` while pending upserts the existing row,
|
|
113
113
|
* refreshing `message` and `expires_at` (and `to_actor_id` — supplying
|
|
114
114
|
* a different `to_actor_id` on re-offer narrows the existing row to the
|
|
115
|
-
* named actor; supplying null widens it back to account-grain)
|
|
115
|
+
* named actor; supplying null widens it back to account-grain) and clearing
|
|
116
|
+
* `expire_audited_at`, so a re-offer of an expired, already-audited row gets
|
|
117
|
+
* its own `role_grant_offer_expire` when the refreshed expiry passes. A
|
|
116
118
|
* different grantor offering the same `(to_account, role, scope)` creates
|
|
117
119
|
* a distinct row — multiple pending grantors coexist. After a terminal
|
|
118
120
|
* state, a re-offer is a fresh INSERT.
|
|
@@ -176,7 +178,7 @@ export declare const query_role_grant_offer_retract: (deps: QueryDeps, offer_id:
|
|
|
176
178
|
*
|
|
177
179
|
* Expired offers are filtered server-side (`expires_at > NOW()`) so the
|
|
178
180
|
* inbox never surfaces a row that can no longer be accepted. The periodic
|
|
179
|
-
* sweep (`query_role_grant_offer_sweep_expired`)
|
|
181
|
+
* sweep (`query_role_grant_offer_sweep_expired`) audits each expiry once.
|
|
180
182
|
*/
|
|
181
183
|
export declare const query_role_grant_offer_list: (deps: QueryDeps, to_account_id: string) => Promise<Array<RoleGrantOffer>>;
|
|
182
184
|
/**
|
|
@@ -191,12 +193,19 @@ export declare const query_role_grant_offer_history_for_account: (deps: QueryDep
|
|
|
191
193
|
*/
|
|
192
194
|
export declare const query_role_grant_offer_find_pending: (deps: QueryDeps, offer_id: string) => Promise<RoleGrantOffer | null>;
|
|
193
195
|
/**
|
|
194
|
-
*
|
|
196
|
+
* Claim the pending offers whose `expires_at` has passed and whose expiry has
|
|
197
|
+
* not been audited yet, stamping `expire_audited_at` on each, and return them
|
|
198
|
+
* soonest-expiry first.
|
|
195
199
|
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
+
* The stamp is the sweep's idempotency: a claimed row never matches again, so
|
|
201
|
+
* each expiry is audited exactly once. It must share a transaction with the
|
|
202
|
+
* caller's `role_grant_offer_expire` audit inserts — committed together, or
|
|
203
|
+
* rolled back together so the next sweep retries. Concurrent sweeps claim
|
|
204
|
+
* disjoint rows: a row another sweep has stamped but not committed blocks on
|
|
205
|
+
* its row lock, then fails the re-checked `expire_audited_at IS NULL`.
|
|
206
|
+
* `cleanup_expired_role_grant_offers` is the caller.
|
|
207
|
+
*
|
|
208
|
+
* @mutates `role_grant_offer` rows - stamps `expire_audited_at` on each claimed row
|
|
200
209
|
*/
|
|
201
210
|
export declare const query_role_grant_offer_sweep_expired: (deps: QueryDeps) => Promise<Array<RoleGrantOffer>>;
|
|
202
211
|
/** Input for `query_accept_offer`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AASrD,OAAO,KAAK,EACX,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D;;;;;GAKG;AACH,qBAAa,kCAAmC,SAAQ,KAAK;gBAChD,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;gBACxC,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;;;;GAQG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;;CAKvD;AAED;;;;;;;GAOG;AACH,qBAAa,gCAAiC,SAAQ,KAAK;gBAC9C,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,GAAG,QAAQ,GAAG,wBAAwB,CAAC;AAEzF;;;;;GAKG;AACH,qBAAa,iCAAkC,SAAQ,KAAK;IAC3D,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;gBAC1B,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,mBAAmB;CAM/D;AAED;;;;;GAKG;AACH,qBAAa,uCAAwC,SAAQ,KAAK;;CAKjE;AAED
|
|
1
|
+
{"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -118,7 +118,9 @@ export class RoleGrantOfferActorAccountMismatchError extends Error {
|
|
|
118
118
|
* `(to_account, role, scope)` while pending upserts the existing row,
|
|
119
119
|
* refreshing `message` and `expires_at` (and `to_actor_id` — supplying
|
|
120
120
|
* a different `to_actor_id` on re-offer narrows the existing row to the
|
|
121
|
-
* named actor; supplying null widens it back to account-grain)
|
|
121
|
+
* named actor; supplying null widens it back to account-grain) and clearing
|
|
122
|
+
* `expire_audited_at`, so a re-offer of an expired, already-audited row gets
|
|
123
|
+
* its own `role_grant_offer_expire` when the refreshed expiry passes. A
|
|
122
124
|
* different grantor offering the same `(to_account, role, scope)` creates
|
|
123
125
|
* a distinct row — multiple pending grantors coexist. After a terminal
|
|
124
126
|
* state, a re-offer is a fresh INSERT.
|
|
@@ -170,7 +172,8 @@ export const query_role_grant_offer_create = async (deps, input) => {
|
|
|
170
172
|
DO UPDATE SET
|
|
171
173
|
to_actor_id = EXCLUDED.to_actor_id,
|
|
172
174
|
message = EXCLUDED.message,
|
|
173
|
-
expires_at = EXCLUDED.expires_at
|
|
175
|
+
expires_at = EXCLUDED.expires_at,
|
|
176
|
+
expire_audited_at = NULL
|
|
174
177
|
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))}`, [
|
|
175
178
|
input.from_actor_id,
|
|
176
179
|
input.to_account_id,
|
|
@@ -269,7 +272,7 @@ const resolve_terminal_or_missing = async (deps, offer_id, scope) => {
|
|
|
269
272
|
*
|
|
270
273
|
* Expired offers are filtered server-side (`expires_at > NOW()`) so the
|
|
271
274
|
* inbox never surfaces a row that can no longer be accepted. The periodic
|
|
272
|
-
* sweep (`query_role_grant_offer_sweep_expired`)
|
|
275
|
+
* sweep (`query_role_grant_offer_sweep_expired`) audits each expiry once.
|
|
273
276
|
*/
|
|
274
277
|
export const query_role_grant_offer_list = async (deps, to_account_id) => {
|
|
275
278
|
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))} FROM role_grant_offer
|
|
@@ -308,21 +311,36 @@ export const query_role_grant_offer_find_pending = async (deps, offer_id) => {
|
|
|
308
311
|
return row ?? null;
|
|
309
312
|
};
|
|
310
313
|
/**
|
|
311
|
-
*
|
|
314
|
+
* Claim the pending offers whose `expires_at` has passed and whose expiry has
|
|
315
|
+
* not been audited yet, stamping `expire_audited_at` on each, and return them
|
|
316
|
+
* soonest-expiry first.
|
|
312
317
|
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
318
|
+
* The stamp is the sweep's idempotency: a claimed row never matches again, so
|
|
319
|
+
* each expiry is audited exactly once. It must share a transaction with the
|
|
320
|
+
* caller's `role_grant_offer_expire` audit inserts — committed together, or
|
|
321
|
+
* rolled back together so the next sweep retries. Concurrent sweeps claim
|
|
322
|
+
* disjoint rows: a row another sweep has stamped but not committed blocks on
|
|
323
|
+
* its row lock, then fails the re-checked `expire_audited_at IS NULL`.
|
|
324
|
+
* `cleanup_expired_role_grant_offers` is the caller.
|
|
325
|
+
*
|
|
326
|
+
* @mutates `role_grant_offer` rows - stamps `expire_audited_at` on each claimed row
|
|
317
327
|
*/
|
|
318
328
|
export const query_role_grant_offer_sweep_expired = async (deps) => {
|
|
319
|
-
return deps.db.query(`
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
329
|
+
return deps.db.query(`WITH claimed AS (
|
|
330
|
+
UPDATE role_grant_offer
|
|
331
|
+
SET expire_audited_at = NOW()
|
|
332
|
+
WHERE accepted_at IS NULL
|
|
333
|
+
AND declined_at IS NULL
|
|
334
|
+
AND retracted_at IS NULL
|
|
335
|
+
AND superseded_at IS NULL
|
|
336
|
+
AND expires_at <= NOW()
|
|
337
|
+
AND expire_audited_at IS NULL
|
|
338
|
+
-- raw columns; the outer select formats them
|
|
339
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}
|
|
340
|
+
)
|
|
341
|
+
SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'c', role_grant_offer_expr('c'))}
|
|
342
|
+
FROM claimed c
|
|
343
|
+
ORDER BY c.expires_at ASC, c.id ASC`);
|
|
326
344
|
};
|
|
327
345
|
/**
|
|
328
346
|
* Whether `grantor_actor_id` holds an active **global** `admin` on an active
|
|
@@ -60,6 +60,15 @@ export interface RoleGrantOffer {
|
|
|
60
60
|
*/
|
|
61
61
|
superseded_at: string | null;
|
|
62
62
|
resulting_role_grant_id: Uuid | null;
|
|
63
|
+
/**
|
|
64
|
+
* When the expiry sweep audited this offer's `role_grant_offer_expire` —
|
|
65
|
+
* the claim stamp `query_role_grant_offer_sweep_expired` sets in the same
|
|
66
|
+
* transaction as the audit row, so each expiry is audited once. Not a
|
|
67
|
+
* lifecycle state (an expired offer is still pending by the terminal
|
|
68
|
+
* columns) and server-only: `to_role_grant_offer_json` drops it, and the
|
|
69
|
+
* strict `RoleGrantOfferJson` would reject it. A re-offer clears it.
|
|
70
|
+
*/
|
|
71
|
+
expire_audited_at: string | null;
|
|
63
72
|
}
|
|
64
73
|
/**
|
|
65
74
|
* A superseded offer row annotated with the grantor's `account_id`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAI9C,mEAAmE;AACnE,eAAO,MAAM,mCAAmC,MAAM,CAAC;AAEvD,yFAAyF;AACzF,eAAO,MAAM,+BAA+B,QAA2B,CAAC;AAExE,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,EAAE,IAAI,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"role_grant_offer_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAI9C,mEAAmE;AACnE,eAAO,MAAM,mCAAmC,MAAM,CAAC;AAEvD,yFAAyF;AACzF,eAAO,MAAM,+BAA+B,QAA2B,CAAC;AAExE,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,EAAE,IAAI,GAAG,IAAI,CAAC;IACrC;;;;;;;OAOG;IACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACtD,eAAe,EAAE,IAAI,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,IAAI,CAAC;CACjB;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;kBAoD4D,CAAC;AAC5F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,gEAAgE;AAChE,eAAO,MAAM,wBAAwB,GAAI,OAAO,cAAc,KAAG,kBAiB/D,CAAC"}
|
|
@@ -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
|
};
|