@fuzdev/fuz_app 0.140.0 → 0.141.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 +62 -17
- package/dist/actions/action_rpc.d.ts +5 -1
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +9 -1
- package/dist/actions/connection_closer.d.ts +25 -2
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +8 -1
- package/dist/actions/register_action_ws.d.ts +42 -6
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +87 -19
- package/dist/actions/register_ws_endpoint.d.ts +15 -6
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +5 -2
- package/dist/actions/socket.svelte.d.ts +19 -4
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +31 -4
- package/dist/actions/transports.d.ts +30 -1
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +30 -1
- package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +24 -10
- package/dist/actions/transports_ws_backend.d.ts +51 -5
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +62 -9
- package/dist/actions/ws_endpoint_spec.d.ts +11 -10
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- package/dist/auth/CLAUDE.md +11 -9
- package/dist/auth/account_route_schema.d.ts +1 -0
- package/dist/auth/account_route_schema.d.ts.map +1 -1
- package/dist/auth/account_route_schema.js +4 -0
- package/dist/auth/api_token.d.ts +20 -0
- package/dist/auth/api_token.d.ts.map +1 -1
- package/dist/auth/api_token.js +21 -1
- package/dist/auth/api_token_queries.d.ts +22 -4
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +11 -6
- package/dist/auth/audit_log_routes.d.ts +10 -0
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +20 -0
- package/dist/auth/audit_log_schema.d.ts +30 -5
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +30 -0
- package/dist/auth/bearer_auth.d.ts +4 -2
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +19 -8
- package/dist/auth/bootstrap_route_schema.d.ts +1 -0
- package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
- package/dist/auth/bootstrap_route_schema.js +1 -0
- package/dist/auth/cleanup.d.ts +9 -9
- package/dist/auth/cleanup.js +9 -9
- package/dist/auth/credential_deadline.d.ts +95 -0
- package/dist/auth/credential_deadline.d.ts.map +1 -0
- package/dist/auth/credential_deadline.js +139 -0
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
- package/dist/auth/daemon_token_middleware.js +4 -1
- package/dist/auth/middleware.d.ts +90 -12
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +115 -16
- package/dist/auth/request_context.d.ts +4 -2
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +14 -3
- package/dist/auth/resolved_auth.d.ts +13 -2
- package/dist/auth/resolved_auth.d.ts.map +1 -1
- package/dist/auth/resolved_auth.js +5 -4
- package/dist/auth/session_cookie.d.ts +4 -0
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +3 -2
- package/dist/auth/session_middleware.d.ts +1 -1
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +4 -1
- package/dist/auth/session_queries.d.ts +19 -2
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +8 -2
- package/dist/auth/signup_route_schema.d.ts.map +1 -1
- package/dist/auth/signup_route_schema.js +1 -0
- package/dist/hono_context.d.ts +23 -0
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +19 -0
- package/dist/http/CLAUDE.md +41 -15
- package/dist/http/middleware_spec.d.ts +16 -0
- package/dist/http/middleware_spec.d.ts.map +1 -1
- package/dist/http/origin.d.ts +113 -18
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +247 -127
- package/dist/http/route_spec.d.ts +21 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/schema_helpers.d.ts +22 -0
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +24 -0
- package/dist/http/surface.d.ts +6 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +12 -5
- package/dist/realtime/sse_auth_guard.d.ts +22 -6
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +31 -12
- package/dist/realtime/subscriber_registry.d.ts +10 -1
- package/dist/realtime/subscriber_registry.d.ts.map +1 -1
- package/dist/realtime/subscriber_registry.js +10 -1
- package/dist/server/app_server.d.ts +20 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +8 -3
- package/dist/server/env.d.ts +2 -1
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +7 -17
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +1 -0
- package/dist/testing/CLAUDE.md +154 -22
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -6
- package/dist/testing/connection_closer_helpers.d.ts +6 -1
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
- package/dist/testing/connection_closer_helpers.js +4 -0
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +14 -65
- package/dist/testing/cross_backend/capabilities.d.ts +26 -2
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +9 -3
- package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
- package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_case.js +6 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +20 -19
- package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
- package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
- package/dist/testing/cross_backend/credential_deadline.js +185 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +13 -2
- package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +7 -1
- package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
- package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
- package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
- package/dist/testing/cross_backend/origin.d.ts.map +1 -1
- package/dist/testing/cross_backend/origin.js +6 -26
- package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
- package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
- package/dist/testing/cross_backend/origin_conformance.js +136 -0
- package/dist/testing/cross_backend/origin_conformance.json +1679 -0
- package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
- package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
- package/dist/testing/cross_backend/origin_scope.js +140 -0
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -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 +44 -33
- 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 +6 -4
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +19 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +22 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +20 -7
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +25 -21
- package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
- package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
- package/dist/testing/cross_backend/transport_composition.js +364 -0
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
- package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
- package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_role_gated.js +142 -0
- package/dist/testing/entities.d.ts +23 -0
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +37 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +3 -1
- package/dist/testing/middleware.d.ts +10 -0
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +31 -7
- package/dist/testing/stubs.d.ts +16 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +20 -7
- package/dist/testing/surface_invariants.js +2 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api_token_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/api_token_queries.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"api_token_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/api_token_queries.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,KAAK,UAAU,EAAmD,MAAM,kBAAkB,CAAC;AAEpG;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG;IACzD,KAAK,EAAE,UAAU,CAAC;IAClB;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,yEAAyE;AACzE,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IACnD,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,0HAUoB,CAAC;AA6BnD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,EAClB,MAAM,MAAM,EACZ,YAAY,MAAM,EAClB,OAAO,UAAU,EACjB,aAAa,IAAI,GAAG,IAAI,KACtB,OAAO,CAAC,QAAQ,CAelB,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,iBAAiB,EACvB,WAAW,MAAM,EACjB,IAAI,MAAM,GAAG,SAAS,EACtB,iBAAiB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,KAC/C,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAkCvC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,MAAM,GAAG,SAAS,CAM5B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,GAC9C,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAM7C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAYvB,CAAC"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { assert_row } from '../db/assert_row.js';
|
|
7
7
|
import { columns_sql, iso8601_timestamp_expr, omit_columns } from '../db/sql_columns.js';
|
|
8
8
|
import { hash_api_token } from './api_token.js';
|
|
9
|
+
import { EXPIRES_IN_MS_SQL } from './credential_deadline.js';
|
|
9
10
|
import { parse_stored_token_scope, serialize_token_scope } from './token_scope.js';
|
|
10
11
|
/**
|
|
11
12
|
* The full `api_token` column set, named explicitly so a row read fails loud
|
|
@@ -74,23 +75,26 @@ export const query_create_api_token = async (deps, id, account_id, name, token_h
|
|
|
74
75
|
return assert_row(row, 'INSERT INTO api_token');
|
|
75
76
|
};
|
|
76
77
|
/**
|
|
77
|
-
* Validate a raw API token and return the token record.
|
|
78
|
+
* Validate a raw API token and return the token record with its scope parsed.
|
|
78
79
|
*
|
|
79
80
|
* Hashes the token with blake3, looks up the hash, and checks
|
|
80
|
-
* expiration
|
|
81
|
+
* expiration, reading how long the token has left on the database clock in
|
|
82
|
+
* the same statement. A row whose stored scope is unreadable is refused like an
|
|
83
|
+
* unknown hash. Updates `last_used_at` and `last_used_ip` on success
|
|
81
84
|
* (fire-and-forget — errors logged, never thrown).
|
|
82
85
|
*
|
|
83
86
|
* @param deps - query dependencies with logger
|
|
84
87
|
* @param raw_token - the raw API token from the Authorization header
|
|
85
88
|
* @param ip - the client IP address (for audit)
|
|
86
89
|
* @param pending_effects - optional array to register the usage-tracking effect for later awaiting
|
|
87
|
-
* @returns the token record if valid, or `undefined`
|
|
90
|
+
* @returns the token record with its parsed scope if valid, or `undefined`
|
|
88
91
|
* @mutates `api_token` row - fire-and-forget UPDATE of `last_used_at` / `last_used_ip` on a valid token
|
|
89
92
|
* @mutates `pending_effects` - pushes the in-flight tracking promise when provided
|
|
90
93
|
*/
|
|
91
94
|
export const query_validate_api_token = async (deps, raw_token, ip, pending_effects) => {
|
|
92
95
|
const token_hash = hash_api_token(raw_token);
|
|
93
|
-
const row = await deps.db.query_one(`SELECT ${columns_sql(API_TOKEN_COLUMNS, api_token_expr)}
|
|
96
|
+
const row = await deps.db.query_one(`SELECT ${columns_sql(API_TOKEN_COLUMNS, api_token_expr)}, ${EXPIRES_IN_MS_SQL} AS expires_in_ms
|
|
97
|
+
FROM api_token
|
|
94
98
|
WHERE token_hash = $1
|
|
95
99
|
AND ${API_TOKEN_IS_LIVE}`, [token_hash]);
|
|
96
100
|
if (!row)
|
|
@@ -99,7 +103,8 @@ export const query_validate_api_token = async (deps, raw_token, ip, pending_effe
|
|
|
99
103
|
// outcome as an unknown token hash. The scope *is* the gate here, so a parse
|
|
100
104
|
// failure must not fall through to full authority — that would hand a
|
|
101
105
|
// narrowed token everything precisely when something is already wrong.
|
|
102
|
-
|
|
106
|
+
const scope = parse_stored_token_scope(row.scope);
|
|
107
|
+
if (scope === null) {
|
|
103
108
|
deps.log.warn(`api_token.scope is unreadable for ${row.id}; refusing the credential`);
|
|
104
109
|
return undefined;
|
|
105
110
|
}
|
|
@@ -114,7 +119,7 @@ export const query_validate_api_token = async (deps, raw_token, ip, pending_effe
|
|
|
114
119
|
deps.log.error('Failed to update last_used_at:', err);
|
|
115
120
|
});
|
|
116
121
|
pending_effects?.push(p);
|
|
117
|
-
return row;
|
|
122
|
+
return { ...row, scope };
|
|
118
123
|
};
|
|
119
124
|
/**
|
|
120
125
|
* Look up the owning account of a live API token — one that still exists and
|
|
@@ -64,6 +64,16 @@ export interface AuditLogRouteOptions {
|
|
|
64
64
|
* Rust spine's `audit_stream_router`, which registers before its role read
|
|
65
65
|
* rather than repeating it.
|
|
66
66
|
*
|
|
67
|
+
* ## Credential deadline
|
|
68
|
+
*
|
|
69
|
+
* Expiry writes nothing a revocation listener could hear, so an admitted
|
|
70
|
+
* stream watches its own credential's expiry
|
|
71
|
+
* (`ResolvedAuth.credential_deadline` — for the session-only stock route, the
|
|
72
|
+
* earlier of the cookie's and the row's) and is closed at it, as a revoked
|
|
73
|
+
* stream is: the body ends with no final frame, and the client's reconnect
|
|
74
|
+
* meets the gates. The wait is `schedule_credential_deadline`'s chain of
|
|
75
|
+
* bounded timers, cancelled when the stream closes.
|
|
76
|
+
*
|
|
67
77
|
* @param options - optional stream wiring + role override
|
|
68
78
|
* @returns the SSE route spec (when `options.stream` is provided) or an empty array
|
|
69
79
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAUvD,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"audit_log_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAUvD,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAW7E,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACpC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE;QACR,QAAQ,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;QAC9C,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,eAAO,MAAM,4BAA4B,GAAI,UAAU,oBAAoB,KAAG,KAAK,CAAC,SAAS,CAmH5F,CAAC"}
|
|
@@ -17,6 +17,7 @@ import { create_audit_log_route_shape, DEFAULT_AUDIT_STREAM_ROLE } from './audit
|
|
|
17
17
|
import { create_sse_response } from '../realtime/sse.js';
|
|
18
18
|
import { AUTH_SESSION_TOKEN_HASH_KEY, has_scoped_role, refresh_role_grants, require_request_context } from './request_context.js';
|
|
19
19
|
import { get_resolved_auth, revalidate_resolved_auth } from './resolved_auth.js';
|
|
20
|
+
import { schedule_credential_deadline } from './credential_deadline.js';
|
|
20
21
|
import { AUDIT_LOG_CHANNEL } from '../realtime/sse_auth_guard.js';
|
|
21
22
|
/**
|
|
22
23
|
* Create the optional audit-log SSE route spec.
|
|
@@ -50,6 +51,16 @@ import { AUDIT_LOG_CHANNEL } from '../realtime/sse_auth_guard.js';
|
|
|
50
51
|
* Rust spine's `audit_stream_router`, which registers before its role read
|
|
51
52
|
* rather than repeating it.
|
|
52
53
|
*
|
|
54
|
+
* ## Credential deadline
|
|
55
|
+
*
|
|
56
|
+
* Expiry writes nothing a revocation listener could hear, so an admitted
|
|
57
|
+
* stream watches its own credential's expiry
|
|
58
|
+
* (`ResolvedAuth.credential_deadline` — for the session-only stock route, the
|
|
59
|
+
* earlier of the cookie's and the row's) and is closed at it, as a revoked
|
|
60
|
+
* stream is: the body ends with no final frame, and the client's reconnect
|
|
61
|
+
* meets the gates. The wait is `schedule_credential_deadline`'s chain of
|
|
62
|
+
* bounded timers, cancelled when the stream closes.
|
|
63
|
+
*
|
|
53
64
|
* @param options - optional stream wiring + role override
|
|
54
65
|
* @returns the SSE route spec (when `options.stream` is provided) or an empty array
|
|
55
66
|
*/
|
|
@@ -143,6 +154,15 @@ export const create_audit_log_route_specs = (options) => {
|
|
|
143
154
|
log.info('audit stream: closed before admission', ctx.account.id);
|
|
144
155
|
stream.close();
|
|
145
156
|
}
|
|
157
|
+
else {
|
|
158
|
+
// The stream ends when the credential it opened on expires —
|
|
159
|
+
// closed as a revocation closes it (TSDoc, "Credential
|
|
160
|
+
// deadline").
|
|
161
|
+
stream.on_close(schedule_credential_deadline(get_resolved_auth(c)?.credential_deadline ?? null, () => {
|
|
162
|
+
log.info('audit stream: credential expired', ctx.account.id);
|
|
163
|
+
stream.close();
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
146
166
|
return response;
|
|
147
167
|
}
|
|
148
168
|
finally {
|
|
@@ -60,12 +60,15 @@ export type AuditEventType = z.infer<typeof AuditEventType>;
|
|
|
60
60
|
* row's `metadata.token_id`.
|
|
61
61
|
* - `account` — every connection of `target_account_id ?? account_id`.
|
|
62
62
|
* - `role` — the target account's connections gated on the row's
|
|
63
|
-
* `metadata.role
|
|
63
|
+
* `metadata.role`, when the revoked grant was global
|
|
64
|
+
* (`to_global_role_revocation`).
|
|
64
65
|
*
|
|
65
|
-
* The scope says what the row invalidated
|
|
66
|
-
*
|
|
67
|
-
* re-
|
|
68
|
-
*
|
|
66
|
+
* The scope says what the row invalidated; both listeners act on every scope
|
|
67
|
+
* alike. On `role` that matters for the WebSocket too: per-message dispatch
|
|
68
|
+
* re-authorizes each request against its own action, but a socket admitted by
|
|
69
|
+
* a role gate also receives every server push its endpoint sends, which no
|
|
70
|
+
* dispatch ever re-checks — so a socket that lost the gating role is closed,
|
|
71
|
+
* like a stream.
|
|
69
72
|
*
|
|
70
73
|
* Twin of the Rust spine's `RevocationScope`.
|
|
71
74
|
*/
|
|
@@ -97,6 +100,28 @@ export declare const audit_event_revocation_scopes: Readonly<Record<AuditEventTy
|
|
|
97
100
|
* with `queue_connection_close`.
|
|
98
101
|
*/
|
|
99
102
|
export declare const to_revocation_scope: (event_type: string) => RevocationScope;
|
|
103
|
+
/**
|
|
104
|
+
* The account and role a successful `role`-scope row took away, when the
|
|
105
|
+
* revoked grant was **global** — `null` otherwise.
|
|
106
|
+
*
|
|
107
|
+
* A role gate (`register_ws_endpoint`'s `required_roles`, the audit stream's
|
|
108
|
+
* `require_role`) admits on global grants only, so revoking a scoped grant
|
|
109
|
+
* takes away nothing a gate admitted on and closes nothing. A row with no
|
|
110
|
+
* `metadata.scope_id` reads as global: when unsure, closing is the safe side.
|
|
111
|
+
* The target is `target_account_id ?? account_id`, as for the `account` scope.
|
|
112
|
+
* Both revocation listeners read it, so a socket and a stream gated on one
|
|
113
|
+
* role close on the same rows.
|
|
114
|
+
*
|
|
115
|
+
* Twin of the Rust spine's `global_role_revocation`.
|
|
116
|
+
*
|
|
117
|
+
* @param event - a row whose scope is `role` (the caller dispatched on it)
|
|
118
|
+
* @returns the account and role to close on, or `null` when the row names no
|
|
119
|
+
* role or target, or revoked a scoped grant
|
|
120
|
+
*/
|
|
121
|
+
export declare const to_global_role_revocation: (event: AuditLogEvent) => {
|
|
122
|
+
account_id: Uuid;
|
|
123
|
+
role: string;
|
|
124
|
+
} | null;
|
|
100
125
|
/**
|
|
101
126
|
* Letter start, then letters, digits, `_`, `.`, `/`, `-`. Accepts snake_case,
|
|
102
127
|
* dotted, and namespaced consumer conventions; rejects empty strings, leading
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAS,MAAM,yBAAyB,CAAC;AAkBtD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,6iBA6BnB,CAAC;AAEZ,wCAAwC;AACxC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAA4B,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D
|
|
1
|
+
{"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -119,6 +119,36 @@ export const audit_event_revocation_scopes = Object.freeze({
|
|
|
119
119
|
export const to_revocation_scope = (event_type) => Object.hasOwn(audit_event_revocation_scopes, event_type)
|
|
120
120
|
? audit_event_revocation_scopes[event_type]
|
|
121
121
|
: 'none';
|
|
122
|
+
/**
|
|
123
|
+
* The account and role a successful `role`-scope row took away, when the
|
|
124
|
+
* revoked grant was **global** — `null` otherwise.
|
|
125
|
+
*
|
|
126
|
+
* A role gate (`register_ws_endpoint`'s `required_roles`, the audit stream's
|
|
127
|
+
* `require_role`) admits on global grants only, so revoking a scoped grant
|
|
128
|
+
* takes away nothing a gate admitted on and closes nothing. A row with no
|
|
129
|
+
* `metadata.scope_id` reads as global: when unsure, closing is the safe side.
|
|
130
|
+
* The target is `target_account_id ?? account_id`, as for the `account` scope.
|
|
131
|
+
* Both revocation listeners read it, so a socket and a stream gated on one
|
|
132
|
+
* role close on the same rows.
|
|
133
|
+
*
|
|
134
|
+
* Twin of the Rust spine's `global_role_revocation`.
|
|
135
|
+
*
|
|
136
|
+
* @param event - a row whose scope is `role` (the caller dispatched on it)
|
|
137
|
+
* @returns the account and role to close on, or `null` when the row names no
|
|
138
|
+
* role or target, or revoked a scoped grant
|
|
139
|
+
*/
|
|
140
|
+
export const to_global_role_revocation = (event) => {
|
|
141
|
+
const role = event.metadata?.role;
|
|
142
|
+
if (typeof role !== 'string' || role.length === 0)
|
|
143
|
+
return null;
|
|
144
|
+
const scope_id = event.metadata?.scope_id;
|
|
145
|
+
if (scope_id !== undefined && scope_id !== null)
|
|
146
|
+
return null;
|
|
147
|
+
const account_id = event.target_account_id ?? event.account_id;
|
|
148
|
+
if (!account_id)
|
|
149
|
+
return null;
|
|
150
|
+
return { account_id, role };
|
|
151
|
+
};
|
|
122
152
|
/**
|
|
123
153
|
* Letter start, then letters, digits, `_`, `.`, `/`, `-`. Accepts snake_case,
|
|
124
154
|
* dotted, and namespaced consumer conventions; rejects empty strings, leading
|
|
@@ -20,7 +20,7 @@ import type { QueryDeps } from '../db/query_deps.js';
|
|
|
20
20
|
/**
|
|
21
21
|
* Create middleware that authenticates via bearer token.
|
|
22
22
|
*
|
|
23
|
-
* Soft-fails for invalid, expired, or empty tokens — calls `next()` without
|
|
23
|
+
* Soft-fails for malformed, invalid, expired, or empty tokens — calls `next()` without
|
|
24
24
|
* setting account identity, letting downstream auth enforcement (the RPC
|
|
25
25
|
* dispatcher's pre-authorization / post-authorization auth gates or
|
|
26
26
|
* `require_auth`) return a consistent JSON-RPC or route-level error. This
|
|
@@ -31,6 +31,8 @@ import type { QueryDeps } from '../db/query_deps.js';
|
|
|
31
31
|
* Discards bearer tokens when an `Origin` or `Referer` header is present —
|
|
32
32
|
* `next()` with no identity, as for a bad token — so browsers must use cookie
|
|
33
33
|
* auth, reducing attack surface.
|
|
34
|
+
* A token that fails the `is_api_token_shaped` precheck soft-fails before
|
|
35
|
+
* the database is touched.
|
|
34
36
|
* Auth scheme matching is case-insensitive per RFC 7235.
|
|
35
37
|
* On success, sets `c.var.auth_account_id`, `CREDENTIAL_TYPE_KEY = 'api_token'`,
|
|
36
38
|
* and `AUTH_API_TOKEN_ID_KEY`. Skips when an account is already authenticated
|
|
@@ -48,7 +50,7 @@ import type { QueryDeps } from '../db/query_deps.js';
|
|
|
48
50
|
*
|
|
49
51
|
* @param deps - query dependencies (pool-level db for middleware)
|
|
50
52
|
* @param log - the logger instance
|
|
51
|
-
* @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `
|
|
53
|
+
* @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_API_TOKEN_ID_KEY`, and `CREDENTIAL_DEADLINE_KEY` on success
|
|
52
54
|
*/
|
|
53
55
|
export declare const create_bearer_auth_middleware: (deps: QueryDeps, log: Logger) => MiddlewareHandler;
|
|
54
56
|
//# sourceMappingURL=bearer_auth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAYtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,SAAS,EAAE,KAAK,MAAM,KAAG,iBAgF5E,CAAC"}
|
package/dist/auth/bearer_auth.js
CHANGED
|
@@ -15,15 +15,16 @@
|
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
17
17
|
import { DEV } from 'esm-env';
|
|
18
|
-
import { AUTH_API_TOKEN_ID_KEY, ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY } from '../hono_context.js';
|
|
19
|
-
import {
|
|
18
|
+
import { AUTH_API_TOKEN_ID_KEY, ACCOUNT_ID_KEY, CREDENTIAL_DEADLINE_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY } from '../hono_context.js';
|
|
19
|
+
import { credential_deadline_after } from './credential_deadline.js';
|
|
20
|
+
import { is_api_token_shaped } from './api_token.js';
|
|
20
21
|
import { query_validate_api_token } from './api_token_queries.js';
|
|
21
22
|
import { get_client_ip } from '../http/client_ip.js';
|
|
22
23
|
import { is_browser_context } from '../http/origin.js';
|
|
23
24
|
/**
|
|
24
25
|
* Create middleware that authenticates via bearer token.
|
|
25
26
|
*
|
|
26
|
-
* Soft-fails for invalid, expired, or empty tokens — calls `next()` without
|
|
27
|
+
* Soft-fails for malformed, invalid, expired, or empty tokens — calls `next()` without
|
|
27
28
|
* setting account identity, letting downstream auth enforcement (the RPC
|
|
28
29
|
* dispatcher's pre-authorization / post-authorization auth gates or
|
|
29
30
|
* `require_auth`) return a consistent JSON-RPC or route-level error. This
|
|
@@ -34,6 +35,8 @@ import { is_browser_context } from '../http/origin.js';
|
|
|
34
35
|
* Discards bearer tokens when an `Origin` or `Referer` header is present —
|
|
35
36
|
* `next()` with no identity, as for a bad token — so browsers must use cookie
|
|
36
37
|
* auth, reducing attack surface.
|
|
38
|
+
* A token that fails the `is_api_token_shaped` precheck soft-fails before
|
|
39
|
+
* the database is touched.
|
|
37
40
|
* Auth scheme matching is case-insensitive per RFC 7235.
|
|
38
41
|
* On success, sets `c.var.auth_account_id`, `CREDENTIAL_TYPE_KEY = 'api_token'`,
|
|
39
42
|
* and `AUTH_API_TOKEN_ID_KEY`. Skips when an account is already authenticated
|
|
@@ -51,7 +54,7 @@ import { is_browser_context } from '../http/origin.js';
|
|
|
51
54
|
*
|
|
52
55
|
* @param deps - query dependencies (pool-level db for middleware)
|
|
53
56
|
* @param log - the logger instance
|
|
54
|
-
* @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `
|
|
57
|
+
* @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_API_TOKEN_ID_KEY`, and `CREDENTIAL_DEADLINE_KEY` on success
|
|
55
58
|
*/
|
|
56
59
|
export const create_bearer_auth_middleware = (deps, log) => {
|
|
57
60
|
return async (c, next) => {
|
|
@@ -92,6 +95,13 @@ export const create_bearer_auth_middleware = (deps, log) => {
|
|
|
92
95
|
await next();
|
|
93
96
|
return;
|
|
94
97
|
}
|
|
98
|
+
// A value that cannot be a token soft-fails before any database work —
|
|
99
|
+
// no pool checkout, no query — so junk bearers cost nothing to refuse.
|
|
100
|
+
if (!is_api_token_shaped(raw_token)) {
|
|
101
|
+
log.debug('bearer auth soft-fail: malformed token');
|
|
102
|
+
await next();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
95
105
|
// `ip` feeds the token's `last_used_ip` bookkeeping, not a limiter.
|
|
96
106
|
const ip = get_client_ip(c);
|
|
97
107
|
const api_token = await query_validate_api_token({ ...deps, log }, raw_token, ip, c.var.pending_effects);
|
|
@@ -105,10 +115,11 @@ export const create_bearer_auth_middleware = (deps, log) => {
|
|
|
105
115
|
c.set(ACCOUNT_ID_KEY, api_token.account_id);
|
|
106
116
|
c.set(CREDENTIAL_TYPE_KEY, 'api_token');
|
|
107
117
|
c.set(AUTH_API_TOKEN_ID_KEY, api_token.id);
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
// Parsed by the query, which refuses a token whose stored scope is
|
|
119
|
+
// unreadable — there is no default to fall back to.
|
|
120
|
+
c.set(TOKEN_SCOPE_KEY, api_token.scope);
|
|
121
|
+
// the row's expiry, read on the database clock; none for an eternal token
|
|
122
|
+
c.set(CREDENTIAL_DEADLINE_KEY, credential_deadline_after([api_token.expires_in_ms]));
|
|
112
123
|
await next();
|
|
113
124
|
};
|
|
114
125
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;;;;;;;kBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB
|
|
1
|
+
{"version":3,"file":"bootstrap_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;;;;;;;kBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkBI,CAAC"}
|
|
@@ -39,6 +39,7 @@ export const bootstrap_route_shape = {
|
|
|
39
39
|
method: 'POST',
|
|
40
40
|
path: '/bootstrap',
|
|
41
41
|
auth: { account: 'none', actor: 'none' },
|
|
42
|
+
mints_credential: true,
|
|
42
43
|
description: 'Create initial keeper account (one-shot)',
|
|
43
44
|
transaction: false, // bootstrap_account manages its own transaction
|
|
44
45
|
input: BootstrapInput,
|
package/dist/auth/cleanup.d.ts
CHANGED
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
* stream ends). Otherwise a swept session's connections would be reachable
|
|
23
23
|
* only by an account-wide revocation.
|
|
24
24
|
*
|
|
25
|
-
* The
|
|
26
|
-
* at
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
25
|
+
* The sweep's close is the second on expiry, not the first: every connection
|
|
26
|
+
* already closes at its credential's deadline (`auth/credential_deadline.ts`)
|
|
27
|
+
* in the process holding it, whether or not a sweep runs. The sweep is for
|
|
28
|
+
* sessions only: an expired API token's row is not swept — its connections
|
|
29
|
+
* close at their deadline, and `account_token_revoke` still finds the row to
|
|
30
|
+
* close for.
|
|
31
31
|
*
|
|
32
32
|
* The closes run after the delete's commit, which is the rule every
|
|
33
33
|
* revocation close keeps (see `actions/connection_closer.ts`). An expired
|
|
@@ -118,9 +118,9 @@ import type { AuditEmitter } from './audit_emitter.js';
|
|
|
118
118
|
* `DEFAULT_AUTH_CLEANUP_INTERVAL`.
|
|
119
119
|
*
|
|
120
120
|
* An expired session or offer is already refused by every check that reads
|
|
121
|
-
* it,
|
|
122
|
-
*
|
|
123
|
-
*
|
|
121
|
+
* it, and a connection closes at its credential's deadline on its own, so the
|
|
122
|
+
* interval bounds two things: how late an offer expiry's audit row lands, and
|
|
123
|
+
* how long dead rows linger.
|
|
124
124
|
*/
|
|
125
125
|
export declare const DEFAULT_AUTH_CLEANUP_INTERVAL_MS: number;
|
|
126
126
|
/**
|
package/dist/auth/cleanup.js
CHANGED
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
* stream ends). Otherwise a swept session's connections would be reachable
|
|
23
23
|
* only by an account-wide revocation.
|
|
24
24
|
*
|
|
25
|
-
* The
|
|
26
|
-
* at
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
25
|
+
* The sweep's close is the second on expiry, not the first: every connection
|
|
26
|
+
* already closes at its credential's deadline (`auth/credential_deadline.ts`)
|
|
27
|
+
* in the process holding it, whether or not a sweep runs. The sweep is for
|
|
28
|
+
* sessions only: an expired API token's row is not swept — its connections
|
|
29
|
+
* close at their deadline, and `account_token_revoke` still finds the row to
|
|
30
|
+
* close for.
|
|
31
31
|
*
|
|
32
32
|
* The closes run after the delete's commit, which is the rule every
|
|
33
33
|
* revocation close keeps (see `actions/connection_closer.ts`). An expired
|
|
@@ -117,9 +117,9 @@ import { query_audit_log } from './audit_log_queries.js';
|
|
|
117
117
|
* `DEFAULT_AUTH_CLEANUP_INTERVAL`.
|
|
118
118
|
*
|
|
119
119
|
* An expired session or offer is already refused by every check that reads
|
|
120
|
-
* it,
|
|
121
|
-
*
|
|
122
|
-
*
|
|
120
|
+
* it, and a connection closes at its credential's deadline on its own, so the
|
|
121
|
+
* interval bounds two things: how late an offer expiry's audit row lands, and
|
|
122
|
+
* how long dead rows linger.
|
|
123
123
|
*/
|
|
124
124
|
export const DEFAULT_AUTH_CLEANUP_INTERVAL_MS = 10 * 60 * 1000;
|
|
125
125
|
/**
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The credential deadline — the moment a resolved credential stops
|
|
3
|
+
* authenticating, which a long-lived connection opened on it must not outlive.
|
|
4
|
+
*
|
|
5
|
+
* A WebSocket or an SSE stream is authorized once, at open, and closed on
|
|
6
|
+
* revocation by identity. Expiry has no revocation event: nothing is written
|
|
7
|
+
* when a session or token passes its `expires_at`. So the auth middleware
|
|
8
|
+
* computes the deadline once (`CREDENTIAL_DEADLINE_KEY`, gathered onto
|
|
9
|
+
* `ResolvedAuth.credential_deadline`) and the transport closes the connection
|
|
10
|
+
* when it arrives (`schedule_credential_deadline`).
|
|
11
|
+
*
|
|
12
|
+
* The deadline is the earliest of what the credential carries:
|
|
13
|
+
*
|
|
14
|
+
* - **Session** — the signed cookie's embedded expiry and the `auth_session`
|
|
15
|
+
* row's `expires_at`.
|
|
16
|
+
* - **API token** — the `api_token` row's `expires_at`; an eternal token
|
|
17
|
+
* (`expires_at IS NULL`) has none.
|
|
18
|
+
* - **Daemon token** — none: it has no row and no expiry, and a rotation
|
|
19
|
+
* closes no connection.
|
|
20
|
+
*
|
|
21
|
+
* A row's expiry is read as **remaining time measured on the database clock**
|
|
22
|
+
* (`EXPIRES_IN_MS_SQL`, `expires_at - NOW()` in the same statement that checks
|
|
23
|
+
* `expires_at > NOW()`), then anchored to this process's monotonic clock
|
|
24
|
+
* (`performance.now()`) — so a skew between the database's clock and the
|
|
25
|
+
* server's moves no deadline. The cookie's expiry is a wall-clock second this
|
|
26
|
+
* server signed, read against the same wall clock the cookie check reads.
|
|
27
|
+
*
|
|
28
|
+
* The monotonic clock stops while the host is suspended, so a deadline on it
|
|
29
|
+
* alone would hold a connection past its credential by the time asleep. The
|
|
30
|
+
* wait (`schedule_credential_deadline`) therefore also carries the deadline on
|
|
31
|
+
* the wall clock (`Date.now()`), which keeps running through a suspend, and
|
|
32
|
+
* fires at whichever is reached first: after a resume, within one timer step
|
|
33
|
+
* (`CREDENTIAL_DEADLINE_TIMER_CAP_MS`, an hour). The wall deadline is taken
|
|
34
|
+
* from the time left when the wait starts, so a skew between the database's
|
|
35
|
+
* clock and the server's still moves nothing. A wall clock stepped forward
|
|
36
|
+
* can fire it early; the client reconnects through the gates, the fail-closed
|
|
37
|
+
* side of the trade.
|
|
38
|
+
*
|
|
39
|
+
* The twin of the Rust spine's `fuz_auth::ResolvedAuth::credential_deadline`
|
|
40
|
+
* and `fuz_realtime`'s deadline wait.
|
|
41
|
+
*
|
|
42
|
+
* @module
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* SQL expression for a row's remaining lifetime in milliseconds, measured on
|
|
46
|
+
* the database clock — `NULL` when `expires_at` is `NULL` (an eternal API
|
|
47
|
+
* token). `float8`, so every driver reads it as a JS number.
|
|
48
|
+
*/
|
|
49
|
+
export declare const EXPIRES_IN_MS_SQL = "(EXTRACT(EPOCH FROM (expires_at - NOW())) * 1000)::float8";
|
|
50
|
+
/**
|
|
51
|
+
* The longest single timer the deadline wait sets — one hour, far inside
|
|
52
|
+
* `setTimeout`'s ceiling (2^31 - 1 ms, about 24.8 days, past which a timer
|
|
53
|
+
* fires at once) and the Rust twin's tokio ceiling, and short enough that a
|
|
54
|
+
* resume from suspend is noticed within it. A longer deadline is reached
|
|
55
|
+
* through several.
|
|
56
|
+
*/
|
|
57
|
+
export declare const CREDENTIAL_DEADLINE_TIMER_CAP_MS = 3600000;
|
|
58
|
+
/**
|
|
59
|
+
* The deadline `now` plus the shortest of `remaining_ms`, as a monotonic
|
|
60
|
+
* `performance.now()` timestamp, or `null` when no entry carries one.
|
|
61
|
+
*
|
|
62
|
+
* A negative or `NaN` entry — a row its own `expires_at > NOW()` check
|
|
63
|
+
* already refused, read at the boundary — counts as zero.
|
|
64
|
+
*
|
|
65
|
+
* @param remaining_ms - each source's remaining lifetime in ms, `null` for none
|
|
66
|
+
* @param now - the monotonic clock reading to anchor to
|
|
67
|
+
* @returns the deadline, or `null` for a credential with no expiry
|
|
68
|
+
*/
|
|
69
|
+
export declare const credential_deadline_after: (remaining_ms: ReadonlyArray<number | null>, now?: number) => number | null;
|
|
70
|
+
/**
|
|
71
|
+
* The next timer toward `deadline` from `now`: what remains, capped at
|
|
72
|
+
* `CREDENTIAL_DEADLINE_TIMER_CAP_MS`, or `null` once the deadline is reached.
|
|
73
|
+
*
|
|
74
|
+
* @param deadline - the monotonic deadline
|
|
75
|
+
* @param now - the monotonic clock reading
|
|
76
|
+
* @returns the next delay in ms, or `null` when the deadline has passed
|
|
77
|
+
*/
|
|
78
|
+
export declare const next_credential_deadline_delay: (deadline: number, now: number) => number | null;
|
|
79
|
+
/**
|
|
80
|
+
* Call `on_expire` once `deadline` has passed, waiting in timers of at most
|
|
81
|
+
* `CREDENTIAL_DEADLINE_TIMER_CAP_MS`, each re-reading the clock against the
|
|
82
|
+
* absolute deadline — so a session's 30 days (past `setTimeout`'s ceiling)
|
|
83
|
+
* does not fire at once. Each wake-up also checks the same deadline on the
|
|
84
|
+
* wall clock, so a host suspend, which stops the monotonic clock, delays the
|
|
85
|
+
* close by at most one timer after the resume (module doc).
|
|
86
|
+
*
|
|
87
|
+
* A `null` deadline schedules nothing. The returned function cancels the wait
|
|
88
|
+
* and is safe to call more than once, before or after it fires.
|
|
89
|
+
*
|
|
90
|
+
* @param deadline - the monotonic deadline (`ResolvedAuth.credential_deadline`)
|
|
91
|
+
* @param on_expire - run once, when the deadline passes
|
|
92
|
+
* @returns a cancel function
|
|
93
|
+
*/
|
|
94
|
+
export declare const schedule_credential_deadline: (deadline: number | null, on_expire: () => void) => (() => void);
|
|
95
|
+
//# sourceMappingURL=credential_deadline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential_deadline.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/credential_deadline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,8DAA8D,CAAC;AAE7F;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,UAAY,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACrC,cAAc,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC,EAC1C,MAAK,MAA0B,KAC7B,MAAM,GAAG,IAQX,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAAI,UAAU,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,IAIvF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAU,MAAM,GAAG,IAAI,EACvB,WAAW,MAAM,IAAI,KACnB,CAAC,MAAM,IAAI,CA2Bb,CAAC"}
|