@fuzdev/fuz_app 0.105.0 → 0.106.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/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +2 -1
- package/dist/actions/perform_action.d.ts +8 -0
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +30 -12
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +27 -3
- package/dist/auth/account_action_specs.d.ts +28 -5
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +19 -6
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +15 -2
- package/dist/auth/account_schema.d.ts +13 -0
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +8 -1
- package/dist/auth/api_token_queries.d.ts +2 -1
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +21 -5
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +9 -1
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +6 -1
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
- package/dist/auth/daemon_token_middleware.js +5 -1
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +33 -0
- package/dist/auth/request_context.d.ts +18 -0
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +27 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts +12 -0
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +12 -1
- package/dist/auth/self_service_role_action_specs.d.ts +7 -0
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
- package/dist/auth/self_service_role_action_specs.js +7 -1
- package/dist/auth/token_scope.d.ts +155 -0
- package/dist/auth/token_scope.d.ts.map +1 -0
- package/dist/auth/token_scope.js +190 -0
- package/dist/db/CLAUDE.md +1 -1
- package/dist/hono_context.d.ts +23 -0
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +14 -0
- package/dist/http/error_schemas.d.ts +12 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -0
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +9 -1
- package/dist/server/x_accel.d.ts +2 -4
- package/dist/server/x_accel.d.ts.map +1 -1
- package/dist/server/x_accel.js +2 -4
- package/dist/testing/admin_integration.js +2 -2
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +2 -1
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/cross_backend/conformance_case.d.ts +23 -0
- package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_case.js +21 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +58 -5
- package/dist/testing/cross_backend/expected_schema.json +1 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +5 -1
- package/dist/testing/integration.js +3 -3
- package/package.json +1 -1
- package/dist/server/fact_write.d.ts +0 -32
- package/dist/server/fact_write.d.ts.map +0 -1
- package/dist/server/fact_write.js +0 -57
- package/dist/server/validate_nginx.d.ts +0 -34
- package/dist/server/validate_nginx.d.ts.map +0 -1
- package/dist/server/validate_nginx.js +0 -169
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE1E,OAAO,EAEN,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,
|
|
1
|
+
{"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@ import { DEV } from 'esm-env';
|
|
|
16
16
|
import {} from '../http/route_spec.js';
|
|
17
17
|
import { get_client_ip } from '../http/client_ip.js';
|
|
18
18
|
import { get_request_context } from '../auth/request_context.js';
|
|
19
|
-
import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
19
|
+
import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
20
20
|
import { compile_action_registry } from './compile_action_registry.js';
|
|
21
21
|
import { JSONRPC_VERSION, JsonrpcRequest } from '../http/jsonrpc.js';
|
|
22
22
|
import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, JSONRPC_ERROR_CODES, dev_only } from '../http/jsonrpc_errors.js';
|
|
@@ -163,6 +163,7 @@ export const create_rpc_endpoint = (options) => {
|
|
|
163
163
|
request_id: id,
|
|
164
164
|
account_id: c.get(ACCOUNT_ID_KEY) ?? null,
|
|
165
165
|
credential_type: c.get(CREDENTIAL_TYPE_KEY) ?? null,
|
|
166
|
+
token_scope: c.get(TOKEN_SCOPE_KEY) ?? null,
|
|
166
167
|
client_ip: get_client_ip(c),
|
|
167
168
|
signal: c.req.raw.signal,
|
|
168
169
|
notify,
|
|
@@ -46,6 +46,7 @@ import { type JsonrpcRequestId, type JsonrpcErrorObject } from '../http/jsonrpc.
|
|
|
46
46
|
import type { RateLimiter } from '../rate_limiter.js';
|
|
47
47
|
import type { RpcAction } from './action_rpc.js';
|
|
48
48
|
import type { RequestClient } from './peer_request.js';
|
|
49
|
+
import { type TokenScope } from '../auth/token_scope.js';
|
|
49
50
|
/**
|
|
50
51
|
* Per-call inputs to `perform_action`. Each transport assembles this from
|
|
51
52
|
* its wire envelope + connection identity.
|
|
@@ -61,6 +62,13 @@ export interface PerformActionInput {
|
|
|
61
62
|
account_id: string | null;
|
|
62
63
|
/** Credential type the request arrived on, or `null` for anonymous. */
|
|
63
64
|
credential_type: CredentialType | null;
|
|
65
|
+
/**
|
|
66
|
+
* The credential's authority narrowing — `null` only for the anonymous
|
|
67
|
+
* caller, who holds no credential to narrow. Every resolved credential
|
|
68
|
+
* carries one, so this mirrors the `null` shape `credential_type` already
|
|
69
|
+
* has rather than introducing a permissive absent case.
|
|
70
|
+
*/
|
|
71
|
+
token_scope: TokenScope | null;
|
|
64
72
|
/** Resolved client IP (`'unknown'` if upstream couldn't resolve). */
|
|
65
73
|
client_ip: string;
|
|
66
74
|
/** Per-request abort signal. HTTP: `c.req.raw.signal`. WS: `AbortSignal.any([socket, request])`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAGtC,OAAO,EAEN,KAAK,gBAAgB,EAErB,KAAK,kBAAkB,EACvB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,8 +44,9 @@ import { is_void_schema } from '../http/schema_helpers.js';
|
|
|
44
44
|
import { dispatch_with_post_commit_rollback } from '../http/pending_effects.js';
|
|
45
45
|
import { JSONRPC_VERSION } from '../http/jsonrpc.js';
|
|
46
46
|
import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, http_status_to_jsonrpc_error_code, JSONRPC_ERROR_CODES, dev_only } from '../http/jsonrpc_errors.js';
|
|
47
|
-
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, ERROR_CREDENTIAL_TYPE_REQUIRED } from '../http/error_schemas.js';
|
|
47
|
+
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_TOKEN_SCOPE_REQUIRED } from '../http/error_schemas.js';
|
|
48
48
|
import { is_public_auth } from '../http/auth_shape.js';
|
|
49
|
+
import { token_scope_admits_method } from '../auth/token_scope.js';
|
|
49
50
|
/**
|
|
50
51
|
* The shared dispatch core. Pure data — no Hono context, no socket. Each
|
|
51
52
|
* transport calls into this with pre-parsed inputs and binds the result
|
|
@@ -57,7 +58,7 @@ import { is_public_auth } from '../http/auth_shape.js';
|
|
|
57
58
|
* still fires when the harness omits `account_id`.
|
|
58
59
|
*/
|
|
59
60
|
export const perform_action = async (input, deps) => {
|
|
60
|
-
const { action, raw_params, request_id: id, account_id, credential_type, client_ip, signal, notify, connection_id, request_client, preset } = input;
|
|
61
|
+
const { action, raw_params, request_id: id, account_id, credential_type, token_scope, client_ip, signal, notify, connection_id, request_client, preset } = input;
|
|
61
62
|
const { db, pending_effects, post_commit_effects, log, action_ip_rate_limiter, action_account_rate_limiter } = deps;
|
|
62
63
|
const { spec, handler } = action;
|
|
63
64
|
const action_auth = spec.auth;
|
|
@@ -102,7 +103,7 @@ export const perform_action = async (input, deps) => {
|
|
|
102
103
|
request_context = result.request_context;
|
|
103
104
|
}
|
|
104
105
|
// step 4: post-authorization auth — credential gate first, role gate second.
|
|
105
|
-
const post = check_action_auth_post_authorization(action_auth, request_context, credential_type);
|
|
106
|
+
const post = check_action_auth_post_authorization(action_auth, request_context, credential_type, action.spec.method, token_scope);
|
|
106
107
|
if (post)
|
|
107
108
|
return error_result(post);
|
|
108
109
|
// step 5: rate limit — throttle-requests semantics (record on every
|
|
@@ -230,17 +231,26 @@ const check_action_auth_pre_validation = (auth, account_id) => {
|
|
|
230
231
|
* resolved the actor + role_grants. Enforces `auth.credential_types` and
|
|
231
232
|
* `auth.roles`.
|
|
232
233
|
*
|
|
233
|
-
*
|
|
234
|
-
* and the request didn't arrive on one of them, emit
|
|
235
|
-
* `ERROR_CREDENTIAL_TYPE_REQUIRED` 403 with `required_credential_types`
|
|
236
|
-
* echoing the spec's allowlist (symmetric with the role gate's
|
|
237
|
-
* `required_roles`).
|
|
234
|
+
* ## Gate order: credential → scope → role
|
|
238
235
|
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
236
|
+
* **Credential first** because it is the coarser fact ("this channel may never
|
|
237
|
+
* call me" outranks "this token may not"), because it preserves every existing
|
|
238
|
+
* wire assertion — a session-only spec called over bearer keeps returning
|
|
239
|
+
* `ERROR_CREDENTIAL_TYPE_REQUIRED` — and because it leaks less: telling a
|
|
240
|
+
* bearer caller "your token lacks `rpc:X`" on an endpoint no bearer may ever
|
|
241
|
+
* reach confirms the endpoint to a channel that should not learn it.
|
|
242
|
+
*
|
|
243
|
+
* **Scope second**, before the role gate, because scope is a fact about the
|
|
244
|
+
* credential decided without a DB read, while the role gate reads the resolved
|
|
245
|
+
* actor's grants. Coarse → fine, and an under-scoped token learns nothing about
|
|
246
|
+
* the deployment's role structure. `token_scope` is `null` only for the
|
|
247
|
+
* anonymous caller, who has no credential to narrow.
|
|
248
|
+
*
|
|
249
|
+
* **Role gate third**: if the spec declares any roles and the actor doesn't hold
|
|
250
|
+
* one globally, emit `ERROR_INSUFFICIENT_PERMISSIONS` 403 with `required_roles`
|
|
251
|
+
* (the multi-role disjunction).
|
|
242
252
|
*/
|
|
243
|
-
const check_action_auth_post_authorization = (auth, request_context, credential_type) => {
|
|
253
|
+
const check_action_auth_post_authorization = (auth, request_context, credential_type, method, token_scope) => {
|
|
244
254
|
if (auth.credential_types?.length) {
|
|
245
255
|
if (!credential_type || !auth.credential_types.includes(credential_type)) {
|
|
246
256
|
return jsonrpc_error_messages.forbidden('forbidden', {
|
|
@@ -249,6 +259,14 @@ const check_action_auth_post_authorization = (auth, request_context, credential_
|
|
|
249
259
|
});
|
|
250
260
|
}
|
|
251
261
|
}
|
|
262
|
+
// Token-scope gate — second of three. See the doc comment for why the order
|
|
263
|
+
// is credential → scope → role.
|
|
264
|
+
if (token_scope && !token_scope_admits_method(token_scope, method)) {
|
|
265
|
+
return jsonrpc_error_messages.forbidden('forbidden', {
|
|
266
|
+
reason: ERROR_TOKEN_SCOPE_REQUIRED,
|
|
267
|
+
required_scope: `rpc:${method}`
|
|
268
|
+
});
|
|
269
|
+
}
|
|
252
270
|
if (auth.roles?.length) {
|
|
253
271
|
if (!has_any_scoped_role(request_context, auth.roles, null)) {
|
|
254
272
|
return jsonrpc_error_messages.forbidden(`requires role: ${auth.roles.join(' or ')}`, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,EAAU,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,EAAU,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAWnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAmBtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAIhD,OAAO,EAAE,yBAAyB,EAAE,KAAK,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAIhG,YAAY,EAAE,MAAM,EAAE,CAAC;AAEvB,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB;IACvC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACjD;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,uBAAuB,KAAG,sBAuZrE,CAAC"}
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { wait } from '@fuzdev/fuz_util/async.ts';
|
|
30
30
|
import { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
31
|
-
import { get_request_context, require_request_context } from '../auth/request_context.js';
|
|
31
|
+
import { get_request_context, require_request_context, token_scope_surface_denial } from '../auth/request_context.js';
|
|
32
32
|
import { hash_session_token } from '../auth/session_queries.js';
|
|
33
33
|
import { get_client_ip } from '../http/client_ip.js';
|
|
34
34
|
import { flush_pending_effects, flush_post_commit_effects } from '../http/pending_effects.js';
|
|
35
35
|
import { jsonrpc_error_messages } from '../http/jsonrpc_errors.js';
|
|
36
36
|
import { create_jsonrpc_error_response, create_jsonrpc_notification, to_jsonrpc_message_id, to_jsonrpc_params, is_jsonrpc_request, is_jsonrpc_response, is_jsonrpc_error_response } from '../http/jsonrpc_helpers.js';
|
|
37
|
-
import { CREDENTIAL_TYPE_KEY, AUTH_API_TOKEN_ID_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
37
|
+
import { CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY, AUTH_API_TOKEN_ID_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
38
38
|
import {} from './action_types.js';
|
|
39
39
|
import { compile_action_registry } from './compile_action_registry.js';
|
|
40
40
|
import { cancel_action_spec, CancelNotificationParams } from './cancel.js';
|
|
@@ -84,7 +84,25 @@ export const register_action_ws = (options) => {
|
|
|
84
84
|
// itself — a dead-because-blocked socket is close enough to
|
|
85
85
|
// dead-because-unresponsive that closing is arguably correct.
|
|
86
86
|
const heartbeat_tick_interval = Math.max(100, Math.floor(heartbeat_timeout / 2));
|
|
87
|
-
app.get(path,
|
|
87
|
+
app.get(path,
|
|
88
|
+
// Token-scope surface gate — a narrowed token cannot hold a socket.
|
|
89
|
+
//
|
|
90
|
+
// Mounted as pre-upgrade middleware rather than inside the
|
|
91
|
+
// `upgradeWebSocket` callback, which must return `WSEvents` and so has no
|
|
92
|
+
// way to produce a denial response.
|
|
93
|
+
//
|
|
94
|
+
// Rule 3, doing real work rather than being conservative for its own
|
|
95
|
+
// sake: server-initiated pushes (broadcasts, role-grant-offer
|
|
96
|
+
// notifications, peer requests) reach whatever the account's connection
|
|
97
|
+
// reaches, and there is no per-recipient filter to narrow them by. So a
|
|
98
|
+
// scoped token that could upgrade would observe far more than it was
|
|
99
|
+
// granted.
|
|
100
|
+
async (c, next) => {
|
|
101
|
+
const denied = token_scope_surface_denial(c, 'ws_upgrade');
|
|
102
|
+
if (denied)
|
|
103
|
+
return denied;
|
|
104
|
+
return next();
|
|
105
|
+
}, upgradeWebSocket((c) => {
|
|
88
106
|
// Upgrade-time identity capture. `require_auth` middleware has
|
|
89
107
|
// already rejected unauthenticated upgrades, so request_context is
|
|
90
108
|
// non-null here. Per-message dispatch reads `account_id` +
|
|
@@ -103,6 +121,11 @@ export const register_action_ws = (options) => {
|
|
|
103
121
|
const account_id = upgrade_context.account.id;
|
|
104
122
|
const client_ip = get_client_ip(c);
|
|
105
123
|
const credential_type = c.get(CREDENTIAL_TYPE_KEY);
|
|
124
|
+
// Captured at upgrade and consulted per message. Rule 3 rejects a
|
|
125
|
+
// narrowed token at the boundary below, so in practice this is always
|
|
126
|
+
// `full` on a live socket — but the per-message gate runs regardless,
|
|
127
|
+
// so the invariant is enforced rather than assumed.
|
|
128
|
+
const token_scope = c.get(TOKEN_SCOPE_KEY) ?? null;
|
|
106
129
|
// Session-based connections have a token hash for targeted revocation.
|
|
107
130
|
// Bearer/daemon connections pass null — still reachable via
|
|
108
131
|
// `close_sockets_for_account` / `close_sockets_for_token`.
|
|
@@ -326,6 +349,7 @@ export const register_action_ws = (options) => {
|
|
|
326
349
|
request_id: id,
|
|
327
350
|
account_id,
|
|
328
351
|
credential_type,
|
|
352
|
+
token_scope,
|
|
329
353
|
client_ip,
|
|
330
354
|
signal,
|
|
331
355
|
notify,
|
|
@@ -46,10 +46,25 @@ export declare const SessionRevokeAllOutput: z.ZodObject<{
|
|
|
46
46
|
count: z.ZodNumber;
|
|
47
47
|
}, z.core.$strict>;
|
|
48
48
|
export type SessionRevokeAllOutput = z.infer<typeof SessionRevokeAllOutput>;
|
|
49
|
-
/**
|
|
50
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Input for `account_token_create`.
|
|
51
|
+
*
|
|
52
|
+
* `scope` is **required** — default-deny at mint. There is deliberately no
|
|
53
|
+
* permissive default: a caller who wants full authority spells
|
|
54
|
+
* `{kind: 'full'}`, and the stored document records that they did. That is the
|
|
55
|
+
* reversal of the 2026-02 design, where the field existed but nothing depended
|
|
56
|
+
* on it. Because `scope` has no default, the object as a whole no longer
|
|
57
|
+
* carries `.prefault({})`.
|
|
58
|
+
*/
|
|
59
|
+
export declare const TokenCreateInput: z.ZodObject<{
|
|
51
60
|
name: z.ZodDefault<z.ZodString>;
|
|
52
|
-
|
|
61
|
+
scope: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
62
|
+
kind: z.ZodLiteral<"full">;
|
|
63
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
64
|
+
kind: z.ZodLiteral<"methods">;
|
|
65
|
+
methods: z.ZodArray<z.ZodString>;
|
|
66
|
+
}, z.core.$strict>], "kind">;
|
|
67
|
+
}, z.core.$strict>;
|
|
53
68
|
export type TokenCreateInput = z.infer<typeof TokenCreateInput>;
|
|
54
69
|
/** Output for `account_token_create`. `token` is returned exactly once. */
|
|
55
70
|
export declare const TokenCreateOutput: z.ZodObject<{
|
|
@@ -72,6 +87,7 @@ export declare const TokenListOutput: z.ZodObject<{
|
|
|
72
87
|
last_used_at: z.ZodNullable<z.ZodString>;
|
|
73
88
|
last_used_ip: z.ZodNullable<z.ZodString>;
|
|
74
89
|
created_at: z.ZodString;
|
|
90
|
+
scope: z.ZodString;
|
|
75
91
|
}, z.core.$strict>>;
|
|
76
92
|
}, z.core.$strict>;
|
|
77
93
|
export type TokenListOutput = z.infer<typeof TokenListOutput>;
|
|
@@ -186,9 +202,15 @@ export declare const account_token_create_action_spec: {
|
|
|
186
202
|
credential_types: string[];
|
|
187
203
|
};
|
|
188
204
|
side_effects: true;
|
|
189
|
-
input: z.
|
|
205
|
+
input: z.ZodObject<{
|
|
190
206
|
name: z.ZodDefault<z.ZodString>;
|
|
191
|
-
|
|
207
|
+
scope: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
208
|
+
kind: z.ZodLiteral<"full">;
|
|
209
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
210
|
+
kind: z.ZodLiteral<"methods">;
|
|
211
|
+
methods: z.ZodArray<z.ZodString>;
|
|
212
|
+
}, z.core.$strict>], "kind">;
|
|
213
|
+
}, z.core.$strict>;
|
|
192
214
|
output: z.ZodObject<{
|
|
193
215
|
ok: z.ZodLiteral<true>;
|
|
194
216
|
token: z.ZodString;
|
|
@@ -218,6 +240,7 @@ export declare const account_token_list_action_spec: {
|
|
|
218
240
|
last_used_at: z.ZodNullable<z.ZodString>;
|
|
219
241
|
last_used_ip: z.ZodNullable<z.ZodString>;
|
|
220
242
|
created_at: z.ZodString;
|
|
243
|
+
scope: z.ZodString;
|
|
221
244
|
}, z.core.$strict>>;
|
|
222
245
|
}, z.core.$strict>;
|
|
223
246
|
async: true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"account_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_action_specs.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
+
import { TokenScopeInput } from './token_scope.js';
|
|
11
12
|
import { AuthSessionJson, ClientApiTokenJson, SessionAccountJson, SessionId } from './account_schema.js';
|
|
12
13
|
import { ApiTokenId } from './api_token.js';
|
|
13
14
|
// -- Input/output schemas ---------------------------------------------------
|
|
@@ -35,15 +36,27 @@ export const SessionRevokeAllOutput = z.strictObject({
|
|
|
35
36
|
ok: z.literal(true),
|
|
36
37
|
count: z.number()
|
|
37
38
|
});
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Input for `account_token_create`.
|
|
41
|
+
*
|
|
42
|
+
* `scope` is **required** — default-deny at mint. There is deliberately no
|
|
43
|
+
* permissive default: a caller who wants full authority spells
|
|
44
|
+
* `{kind: 'full'}`, and the stored document records that they did. That is the
|
|
45
|
+
* reversal of the 2026-02 design, where the field existed but nothing depended
|
|
46
|
+
* on it. Because `scope` has no default, the object as a whole no longer
|
|
47
|
+
* carries `.prefault({})`.
|
|
48
|
+
*/
|
|
49
|
+
export const TokenCreateInput = z.strictObject({
|
|
41
50
|
name: z
|
|
42
51
|
.string()
|
|
43
52
|
.default('CLI token')
|
|
44
|
-
.meta({ description: 'Human-friendly label; shown in the token list.' })
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
.meta({ description: 'Human-friendly label; shown in the token list.' }),
|
|
54
|
+
scope: TokenScopeInput.meta({
|
|
55
|
+
description: "What the token may do. `{kind:'full'}` for full account authority, or " +
|
|
56
|
+
"`{kind:'methods',methods:[…]}` to narrow it to named RPC methods — a " +
|
|
57
|
+
'narrowed token is RPC-only and reaches no non-RPC surface.'
|
|
58
|
+
})
|
|
59
|
+
});
|
|
47
60
|
/** Output for `account_token_create`. `token` is returned exactly once. */
|
|
48
61
|
export const TokenCreateOutput = z.strictObject({
|
|
49
62
|
ok: z.literal(true),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"account_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAoBxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAwBnD,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,iBAAiB,EACvB,UAAS,oBAAyB,KAChC,KAAK,CAAC,SAAS,CAqJjB,CAAC"}
|
|
@@ -26,6 +26,7 @@ import { rpc_action } from '../actions/action_rpc.js';
|
|
|
26
26
|
import { to_session_account } from './account_schema.js';
|
|
27
27
|
import { query_session_list_for_account, query_session_revoke_for_account, query_session_revoke_all_for_account } from './session_queries.js';
|
|
28
28
|
import { query_api_token_enforce_limit, query_api_token_list_for_account, query_create_api_token, query_revoke_api_token_for_account } from './api_token_queries.js';
|
|
29
|
+
import { parse_stored_token_scope, token_scope_from_input, token_scope_label } from './token_scope.js';
|
|
29
30
|
import { generate_api_token } from './api_token.js';
|
|
30
31
|
import { DEFAULT_MAX_TOKENS } from './account_route_schema.js';
|
|
31
32
|
import { account_verify_action_spec, account_session_list_action_spec, account_session_revoke_action_spec, account_session_revoke_all_action_spec, account_token_create_action_spec, account_token_list_action_spec, account_token_revoke_action_spec } from './account_action_specs.js';
|
|
@@ -97,7 +98,11 @@ export const create_account_actions = (deps, options = {}) => {
|
|
|
97
98
|
};
|
|
98
99
|
const token_create_handler = async (input, ctx) => {
|
|
99
100
|
const { token, id, token_hash } = generate_api_token();
|
|
100
|
-
|
|
101
|
+
// `input.scope` is required by `TokenCreateInput` — default-deny at mint.
|
|
102
|
+
// There is deliberately no permissive fallback here: an omitted scope is
|
|
103
|
+
// a validation error upstream, not a full-authority token.
|
|
104
|
+
const scope = token_scope_from_input(input.scope);
|
|
105
|
+
await query_create_api_token(ctx, id, ctx.auth.account.id, input.name, token_hash, scope);
|
|
101
106
|
if (max_tokens != null) {
|
|
102
107
|
await query_api_token_enforce_limit(ctx, ctx.auth.account.id, max_tokens);
|
|
103
108
|
}
|
|
@@ -114,7 +119,15 @@ export const create_account_actions = (deps, options = {}) => {
|
|
|
114
119
|
return { ok: true, token, id, name: input.name };
|
|
115
120
|
};
|
|
116
121
|
const token_list_handler = async (_input, ctx) => {
|
|
117
|
-
const
|
|
122
|
+
const rows = await query_api_token_list_for_account(ctx, ctx.auth.account.id);
|
|
123
|
+
// Project the stored document down to its display label. Display-only, so
|
|
124
|
+
// an unreadable document degrades to a marker rather than failing the
|
|
125
|
+
// list — the opposite of the resolve path, and deliberately so: this is
|
|
126
|
+
// the surface an operator uses to *find* a bad token.
|
|
127
|
+
const tokens = rows.map(({ scope, ...rest }) => {
|
|
128
|
+
const parsed = parse_stored_token_scope(scope);
|
|
129
|
+
return { ...rest, scope: parsed ? token_scope_label(parsed) : 'unreadable' };
|
|
130
|
+
});
|
|
118
131
|
return { tokens };
|
|
119
132
|
};
|
|
120
133
|
const token_revoke_handler = async (input, ctx) => {
|
|
@@ -130,6 +130,12 @@ export interface ApiToken {
|
|
|
130
130
|
last_used_at: string | null;
|
|
131
131
|
last_used_ip: string | null;
|
|
132
132
|
created_at: string;
|
|
133
|
+
/**
|
|
134
|
+
* The token's authority narrowing (`api_token.scope`, `NOT NULL`). Stored as
|
|
135
|
+
* JSONB; read back through `parse_token_scope`, which is fail-closed — an
|
|
136
|
+
* unreadable document refuses the credential rather than widening it.
|
|
137
|
+
*/
|
|
138
|
+
scope: unknown;
|
|
133
139
|
}
|
|
134
140
|
/** Zod schema for `SessionAccount` — account without sensitive fields. */
|
|
135
141
|
export declare const SessionAccountJson: z.ZodObject<{
|
|
@@ -158,6 +164,13 @@ export declare const ClientApiTokenJson: z.ZodObject<{
|
|
|
158
164
|
last_used_at: z.ZodNullable<z.ZodString>;
|
|
159
165
|
last_used_ip: z.ZodNullable<z.ZodString>;
|
|
160
166
|
created_at: z.ZodString;
|
|
167
|
+
/**
|
|
168
|
+
* Human-readable scope label (`full` / `full (grandfathered)` /
|
|
169
|
+
* `N methods`). A label rather than the document so the list stays a display
|
|
170
|
+
* surface — nothing authorizes off this, and a grandfathered token reads
|
|
171
|
+
* differently from a deliberately-full one.
|
|
172
|
+
*/
|
|
173
|
+
scope: z.ZodString;
|
|
161
174
|
}, z.core.$strict>;
|
|
162
175
|
export type ClientApiTokenJson = z.infer<typeof ClientApiTokenJson>;
|
|
163
176
|
/** Zod schema for the role_grant summary returned in admin account listings. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAG9C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAI1D,mEAAmE;AACnE,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB;AAED,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,4FAA4F;AAC5F,MAAM,WAAW,KAAK;IACrB,EAAE,EAAE,IAAI,CAAC;IACT,UAAU,EAAE,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,sEAAsE;IACtE,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,MAAM,CAAC;AAExD,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACzB,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,IAAI,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iGAAiG;IACjG,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,sHAAsH;IACtH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,mGAAmG;IACnG,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;CAC7B;AAED,eAAO,MAAM,oBAAoB,GAChC,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAC5D,MAAK,IAAiB,KACpB,OAA2E,CAAC;AAE/E;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,qDAAgC,CAAC;AACvD,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAElD,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,SAAS,CAAC;IACd,UAAU,EAAE,IAAI,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,6CAA6C;AAC7C,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,KAAK,EAAE,OAAO,CAAC;CACf;AAID,0EAA0E;AAC1E,eAAO,MAAM,kBAAkB;;;;;;kBAM7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,6EAA6E;AAC7E,eAAO,MAAM,eAAe;;;;;;kBAM1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB;;;;;;;;IAQ9B;;;;;OAKG;;kBAEF,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,gFAAgF;AAChF,eAAO,MAAM,oBAAoB;;;;;;;;kBAQ/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB;;;kBAG3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,iGAAiG;AACjG,eAAO,MAAM,gBAAgB;;;;;;;;;kBAU3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;kBASlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E,sGAAsG;AACtG,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAKhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,IAAI,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,0GAA0G;IAC1G,eAAe,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,OAAO,KAAG,cAMpD,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,OAAO,KAAG,gBAKlD,CAAC"}
|
|
@@ -63,7 +63,14 @@ export const ClientApiTokenJson = z.strictObject({
|
|
|
63
63
|
expires_at: z.string().nullable(),
|
|
64
64
|
last_used_at: z.string().nullable(),
|
|
65
65
|
last_used_ip: z.string().nullable(),
|
|
66
|
-
created_at: z.string()
|
|
66
|
+
created_at: z.string(),
|
|
67
|
+
/**
|
|
68
|
+
* Human-readable scope label (`full` / `full (grandfathered)` /
|
|
69
|
+
* `N methods`). A label rather than the document so the list stays a display
|
|
70
|
+
* surface — nothing authorizes off this, and a grandfathered token reads
|
|
71
|
+
* differently from a deliberately-full one.
|
|
72
|
+
*/
|
|
73
|
+
scope: z.string()
|
|
67
74
|
});
|
|
68
75
|
/** Zod schema for the role_grant summary returned in admin account listings. */
|
|
69
76
|
export const RoleGrantSummaryJson = z.strictObject({
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
7
7
|
import type { QueryDeps } from '../db/query_deps.js';
|
|
8
8
|
import type { ApiToken } from './account_schema.js';
|
|
9
|
+
import { type TokenScope } from './token_scope.js';
|
|
9
10
|
/** Extended deps for `query_validate_api_token` which needs a logger. */
|
|
10
11
|
export interface ApiTokenQueryDeps extends QueryDeps {
|
|
11
12
|
log: Logger;
|
|
@@ -22,7 +23,7 @@ export interface ApiTokenQueryDeps extends QueryDeps {
|
|
|
22
23
|
* @returns the stored token record
|
|
23
24
|
* @mutates `api_token` table - inserts the new row keyed by `id`
|
|
24
25
|
*/
|
|
25
|
-
export declare const query_create_api_token: (deps: QueryDeps, id: string, account_id: string, name: string, token_hash: string, expires_at?: Date | null) => Promise<ApiToken>;
|
|
26
|
+
export declare const query_create_api_token: (deps: QueryDeps, id: string, account_id: string, name: string, token_hash: string, scope: TokenScope, expires_at?: Date | null) => Promise<ApiToken>;
|
|
26
27
|
/**
|
|
27
28
|
* Validate a raw API token and return the token record.
|
|
28
29
|
*
|
|
@@ -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;AAErD,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;AAErD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,KAAK,UAAU,EAAmD,MAAM,kBAAkB,CAAC;AAEpG,yEAAyE;AACzE,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IACnD,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;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;;;;;;;;;;;;;;GAcG;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,QAAQ,GAAG,SAAS,CAgC9B,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;;;;;GAKG;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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAYhB,CAAC"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { assert_row } from '../db/assert_row.js';
|
|
7
7
|
import { hash_api_token } from './api_token.js';
|
|
8
|
+
import { parse_stored_token_scope, serialize_token_scope } from './token_scope.js';
|
|
8
9
|
/**
|
|
9
10
|
* Store a new API token (the hash, not the raw token).
|
|
10
11
|
*
|
|
@@ -17,10 +18,17 @@ import { hash_api_token } from './api_token.js';
|
|
|
17
18
|
* @returns the stored token record
|
|
18
19
|
* @mutates `api_token` table - inserts the new row keyed by `id`
|
|
19
20
|
*/
|
|
20
|
-
export const query_create_api_token = async (deps, id, account_id, name, token_hash, expires_at) => {
|
|
21
|
-
const row = await deps.db.query_one(`INSERT INTO api_token (id, account_id, name, token_hash, expires_at)
|
|
22
|
-
VALUES ($1, $2, $3, $4, $5)
|
|
23
|
-
RETURNING *`, [
|
|
21
|
+
export const query_create_api_token = async (deps, id, account_id, name, token_hash, scope, expires_at) => {
|
|
22
|
+
const row = await deps.db.query_one(`INSERT INTO api_token (id, account_id, name, token_hash, scope, expires_at)
|
|
23
|
+
VALUES ($1, $2, $3, $4, $5::jsonb, $6)
|
|
24
|
+
RETURNING *`, [
|
|
25
|
+
id,
|
|
26
|
+
account_id,
|
|
27
|
+
name,
|
|
28
|
+
token_hash,
|
|
29
|
+
serialize_token_scope(scope),
|
|
30
|
+
expires_at?.toISOString() ?? null
|
|
31
|
+
]);
|
|
24
32
|
return assert_row(row, 'INSERT INTO api_token');
|
|
25
33
|
};
|
|
26
34
|
/**
|
|
@@ -45,6 +53,14 @@ export const query_validate_api_token = async (deps, raw_token, ip, pending_effe
|
|
|
45
53
|
AND (expires_at IS NULL OR expires_at > NOW())`, [token_hash]);
|
|
46
54
|
if (!row)
|
|
47
55
|
return undefined;
|
|
56
|
+
// **Fail-closed on an unreadable scope**: refuse the credential, the same
|
|
57
|
+
// outcome as an unknown token hash. The scope *is* the gate here, so a parse
|
|
58
|
+
// failure must not fall through to full authority — that would hand a
|
|
59
|
+
// narrowed token everything precisely when something is already wrong.
|
|
60
|
+
if (parse_stored_token_scope(row.scope) === null) {
|
|
61
|
+
deps.log.warn(`api_token.scope is unreadable for ${row.id}; refusing the credential`);
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
48
64
|
// Fire-and-forget usage tracking
|
|
49
65
|
const p = deps.db
|
|
50
66
|
.query(`UPDATE api_token SET last_used_at = NOW(), last_used_ip = $1 WHERE id = $2`, [
|
|
@@ -92,7 +108,7 @@ export const query_revoke_api_token_for_account = async (deps, id, account_id) =
|
|
|
92
108
|
* Must be updated if the `api_token` table gains new columns.
|
|
93
109
|
*/
|
|
94
110
|
export const query_api_token_list_for_account = async (deps, account_id) => {
|
|
95
|
-
return deps.db.query(`SELECT id, account_id, name, expires_at, last_used_at, last_used_ip, created_at
|
|
111
|
+
return deps.db.query(`SELECT id, account_id, name, expires_at, last_used_at, last_used_ip, created_at, scope
|
|
96
112
|
FROM api_token WHERE account_id = $1 ORDER BY created_at DESC`, [account_id]);
|
|
97
113
|
};
|
|
98
114
|
/**
|
|
@@ -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;AAEvD,OAAO,EAAuB,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/F,OAAO,KAAK,EAAE,gBAAgB,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;AAEvD,OAAO,EAAuB,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAQ3E,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACpC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QACR,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;QAC1F,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GAAI,UAAU,oBAAoB,KAAG,KAAK,CAAC,SAAS,CAiC5F,CAAC"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { create_audit_log_route_shape } from './audit_log_route_schema.js';
|
|
15
15
|
import { create_sse_response } from '../realtime/sse.js';
|
|
16
|
-
import { AUTH_SESSION_TOKEN_HASH_KEY, require_request_context } from './request_context.js';
|
|
16
|
+
import { AUTH_SESSION_TOKEN_HASH_KEY, require_request_context, token_scope_surface_denial } from './request_context.js';
|
|
17
17
|
import { AUDIT_LOG_CHANNEL } from '../realtime/sse_auth_guard.js';
|
|
18
18
|
/**
|
|
19
19
|
* Create the optional audit-log SSE route spec.
|
|
@@ -32,6 +32,14 @@ export const create_audit_log_route_specs = (options) => {
|
|
|
32
32
|
{
|
|
33
33
|
...create_audit_log_route_shape(options.required_role),
|
|
34
34
|
handler: (c) => {
|
|
35
|
+
// Rule 3 — a narrowed token holds no audit feed. Same reasoning as
|
|
36
|
+
// the WS upgrade: this is a long-lived server→client stream whose
|
|
37
|
+
// contents are decided by the account's role, not by any
|
|
38
|
+
// per-message scope check, so there is no point after open at
|
|
39
|
+
// which a narrowing could apply.
|
|
40
|
+
const denied = token_scope_surface_denial(c, 'audit_stream');
|
|
41
|
+
if (denied)
|
|
42
|
+
return denied;
|
|
35
43
|
const ctx = require_request_context(c);
|
|
36
44
|
// scope = session hash (capped → tabs-per-session limit and
|
|
37
45
|
// session-specific `session_revoke` close). groups = [account_id]
|
|
@@ -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;AAUtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,iBAAiB,WAAW,GAAG,IAAI,EACnC,KAAK,MAAM,KACT,iBAoFF,CAAC"}
|
package/dist/auth/bearer_auth.js
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
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 } from '../hono_context.js';
|
|
18
|
+
import { AUTH_API_TOKEN_ID_KEY, ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY } from '../hono_context.js';
|
|
19
|
+
import { parse_stored_token_scope, token_scope_full } from './token_scope.js';
|
|
19
20
|
import { query_validate_api_token } from './api_token_queries.js';
|
|
20
21
|
import { get_client_ip } from '../http/client_ip.js';
|
|
21
22
|
import { is_browser_context } from '../http/origin.js';
|
|
@@ -112,6 +113,10 @@ export const create_bearer_auth_middleware = (deps, ip_rate_limiter, log) => {
|
|
|
112
113
|
c.set(ACCOUNT_ID_KEY, api_token.account_id);
|
|
113
114
|
c.set(CREDENTIAL_TYPE_KEY, 'api_token');
|
|
114
115
|
c.set(AUTH_API_TOKEN_ID_KEY, api_token.id);
|
|
116
|
+
// `query_validate_api_token` is fail-closed on an unreadable scope, so a
|
|
117
|
+
// token that got this far has a readable one. The `?? token_scope_full()`
|
|
118
|
+
// is unreachable defensive cover, not a fallback with meaning.
|
|
119
|
+
c.set(TOKEN_SCOPE_KEY, parse_stored_token_scope(api_token.scope) ?? token_scope_full());
|
|
115
120
|
await next();
|
|
116
121
|
};
|
|
117
122
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAYvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,8DAA8D;AAC9D,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,iDAAiD;AACjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAC1D,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,iBAAiB,GAAG,QAAQ,CAAC,GAAG;IAC3D,6FAA6F;IAC7F,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EACjC,MAAM,MAAM,KACV,MAAM,GAAG,IAGX,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAKd,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IAC1C;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,gBAAgB,CAAC;IACxB,kGAAkG;IAClG,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,oBAAoB,GAAG,YAAY,EAC5C,MAAM,SAAS,EACf,SAAS,0BAA0B,EACnC,KAAK,MAAM,KACT,OAAO,CAAC,mBAAmB,CAqD7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,EACf,KAAK,MAAM,KACT,iBA0EF,CAAC"}
|