@fuzdev/fuz_app 0.13.1 → 0.15.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.
@@ -37,6 +37,22 @@ export interface ActionContext {
37
37
  pending_effects: Array<Promise<void>>;
38
38
  /** Logger instance. */
39
39
  log: Logger;
40
+ /**
41
+ * Send a request-scoped JSON-RPC notification to the originator.
42
+ *
43
+ * On streaming transports (WebSocket) this routes to the originating
44
+ * connection only. On the HTTP RPC transport this is a no-op with a
45
+ * DEV-mode warn — non-streaming transports have no channel for mid-
46
+ * request notifications. The `streams` field on an `ActionSpec` names
47
+ * the notification method this handler is expected to emit.
48
+ */
49
+ notify: (method: string, params: unknown) => void;
50
+ /**
51
+ * AbortSignal that fires when the originating request is cancelled
52
+ * (client disconnect on HTTP, socket close on WebSocket). Streaming
53
+ * handlers should check this for early termination.
54
+ */
55
+ signal: AbortSignal;
40
56
  }
41
57
  /**
42
58
  * Handler function for an RPC action.
@@ -1 +1 @@
1
- {"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAoB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAgC,KAAK,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAE9F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,OAAO,EAGN,KAAK,gBAAgB,EAGrB,MAAM,oBAAoB,CAAC;AAO5B;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,8DAA8D;IAC9D,EAAE,EAAE,EAAE,CAAC;IACP,oFAAoF;IACpF,aAAa,EAAE,EAAE,CAAC;IAClB,2EAA2E;IAC3E,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,CACxD,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,aAAa,CAAC;CACvB;AAED,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;CACZ;AAkDD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,wBAAwB,KAAG,KAAK,CAAC,SAAS,CA0OtF,CAAC"}
1
+ {"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAoB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAgC,KAAK,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAE9F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,OAAO,EAGN,KAAK,gBAAgB,EAGrB,MAAM,oBAAoB,CAAC;AAO5B;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,8DAA8D;IAC9D,EAAE,EAAE,EAAE,CAAC;IACP,oFAAoF;IACpF,aAAa,EAAE,EAAE,CAAC;IAClB,2EAA2E;IAC3E,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,CACxD,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,aAAa,CAAC;CACvB;AAED,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;CACZ;AAkDD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,wBAAwB,KAAG,KAAK,CAAC,SAAS,CAqPtF,CAAC"}
@@ -139,6 +139,12 @@ export const create_rpc_endpoint = (options) => {
139
139
  }
140
140
  // step 5: dispatch — transaction for mutations, pool for reads
141
141
  const use_transaction = action.spec.side_effects;
142
+ const notify = (notify_method, _notify_params) => {
143
+ if (DEV) {
144
+ log.warn(`ctx.notify('${notify_method}') called on non-streaming transport; notification dropped (method=${method_name})`);
145
+ }
146
+ };
147
+ const signal = c.req.raw.signal;
142
148
  const execute = async (db) => {
143
149
  const action_context = {
144
150
  auth: request_context,
@@ -147,6 +153,8 @@ export const create_rpc_endpoint = (options) => {
147
153
  background_db: route.background_db,
148
154
  pending_effects: route.pending_effects,
149
155
  log,
156
+ notify,
157
+ signal,
150
158
  };
151
159
  const output = await action.handler(parse_result.data, action_context);
152
160
  // DEV-only output validation
@@ -51,6 +51,14 @@ export declare const ActionSpec: z.ZodObject<{
51
51
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
52
52
  async: z.ZodBoolean;
53
53
  description: z.ZodString;
54
+ /**
55
+ * Names the notification method this action emits as request-scoped
56
+ * progress. Forward-compatible handshake — transport-agnostic, does not
57
+ * imply a specific delivery mechanism. Registry-time validation (e.g.,
58
+ * ensuring the named method is a `remote_notification` spec) is a
59
+ * consumer-side concern.
60
+ */
61
+ streams: z.ZodOptional<z.ZodString>;
54
62
  }, z.core.$strict>;
55
63
  export type ActionSpec = z.infer<typeof ActionSpec>;
56
64
  export declare const RequestResponseActionSpec: z.ZodObject<{
@@ -64,6 +72,7 @@ export declare const RequestResponseActionSpec: z.ZodObject<{
64
72
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
65
73
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
66
74
  description: z.ZodString;
75
+ streams: z.ZodOptional<z.ZodString>;
67
76
  kind: z.ZodDefault<z.ZodLiteral<"request_response">>;
68
77
  auth: z.ZodUnion<readonly [z.ZodLiteral<"public">, z.ZodLiteral<"authenticated">, z.ZodLiteral<"keeper">, z.ZodObject<{
69
78
  role: z.ZodString;
@@ -80,6 +89,7 @@ export declare const RemoteNotificationActionSpec: z.ZodObject<{
80
89
  }>;
81
90
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
82
91
  description: z.ZodString;
92
+ streams: z.ZodOptional<z.ZodString>;
83
93
  kind: z.ZodDefault<z.ZodLiteral<"remote_notification">>;
84
94
  auth: z.ZodDefault<z.ZodNull>;
85
95
  side_effects: z.ZodDefault<z.ZodLiteral<true>>;
@@ -103,6 +113,7 @@ export declare const LocalCallActionSpec: z.ZodObject<{
103
113
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
104
114
  async: z.ZodBoolean;
105
115
  description: z.ZodString;
116
+ streams: z.ZodOptional<z.ZodString>;
106
117
  kind: z.ZodDefault<z.ZodLiteral<"local_call">>;
107
118
  auth: z.ZodDefault<z.ZodNull>;
108
119
  }, z.core.$strict>;
@@ -118,6 +129,7 @@ export declare const ActionSpecUnion: z.ZodUnion<readonly [z.ZodObject<{
118
129
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
119
130
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
120
131
  description: z.ZodString;
132
+ streams: z.ZodOptional<z.ZodString>;
121
133
  kind: z.ZodDefault<z.ZodLiteral<"request_response">>;
122
134
  auth: z.ZodUnion<readonly [z.ZodLiteral<"public">, z.ZodLiteral<"authenticated">, z.ZodLiteral<"keeper">, z.ZodObject<{
123
135
  role: z.ZodString;
@@ -132,6 +144,7 @@ export declare const ActionSpecUnion: z.ZodUnion<readonly [z.ZodObject<{
132
144
  }>;
133
145
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
134
146
  description: z.ZodString;
147
+ streams: z.ZodOptional<z.ZodString>;
135
148
  kind: z.ZodDefault<z.ZodLiteral<"remote_notification">>;
136
149
  auth: z.ZodDefault<z.ZodNull>;
137
150
  side_effects: z.ZodDefault<z.ZodLiteral<true>>;
@@ -149,6 +162,7 @@ export declare const ActionSpecUnion: z.ZodUnion<readonly [z.ZodObject<{
149
162
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
150
163
  async: z.ZodBoolean;
151
164
  description: z.ZodString;
165
+ streams: z.ZodOptional<z.ZodString>;
152
166
  kind: z.ZodDefault<z.ZodLiteral<"local_call">>;
153
167
  auth: z.ZodDefault<z.ZodNull>;
154
168
  }, z.core.$strict>]>;
@@ -1 +1 @@
1
- {"version":3,"file":"action_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,UAAU;;;;EAAoE,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;EAA0C,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,UAAU;;oBAKrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;kBAUrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;kBAIpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;kBAMvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAKoB,CAAC;AAE9E,eAAO,MAAM,gBAAgB;;;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"action_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,UAAU;;;;EAAoE,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;EAA0C,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,UAAU;;oBAKrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;IAUtB;;;;;;OAMG;;kBAEF,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;kBAIpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;kBAMvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAKoB,CAAC;AAE9E,eAAO,MAAM,gBAAgB;;;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
@@ -32,6 +32,14 @@ export const ActionSpec = z.strictObject({
32
32
  output: z.custom((v) => v instanceof z.ZodType),
33
33
  async: z.boolean(),
34
34
  description: z.string(),
35
+ /**
36
+ * Names the notification method this action emits as request-scoped
37
+ * progress. Forward-compatible handshake — transport-agnostic, does not
38
+ * imply a specific delivery mechanism. Registry-time validation (e.g.,
39
+ * ensuring the named method is a `remote_notification` spec) is a
40
+ * consumer-side concern.
41
+ */
42
+ streams: z.string().optional(),
35
43
  });
36
44
  export const RequestResponseActionSpec = ActionSpec.extend({
37
45
  kind: z.literal('request_response').default('request_response'),
@@ -0,0 +1,43 @@
1
+ /**
2
+ * WebSocket auth guard — bridges audit events to {@link BackendWebsocketTransport}.
3
+ *
4
+ * Mirror of `realtime/sse_auth_guard.ts` for the backend WebSocket transport.
5
+ * Dispatches audit events to the right `close_sockets_for_*` method so
6
+ * consumers do not re-implement the switch themselves.
7
+ *
8
+ * Consumers wire it as `on_audit_event` on their `AppBackend` (or compose
9
+ * it with other callbacks via `create_app_server`'s `audit_log_sse` path).
10
+ *
11
+ * @module
12
+ */
13
+ import type { Logger } from '@fuzdev/fuz_util/log.js';
14
+ import type { AuditLogEvent } from '../auth/audit_log_schema.js';
15
+ import type { BackendWebsocketTransport } from './transports_ws_backend.js';
16
+ /**
17
+ * Audit event types that trigger WebSocket socket closure.
18
+ *
19
+ * - `session_revoke` — close only the socket tied to the revoked session hash.
20
+ * - `token_revoke` — close only the socket(s) authenticated with the revoked `api_token.id`.
21
+ * - `session_revoke_all` / `token_revoke_all` / `password_change` — close every socket
22
+ * for the affected account (all credentials invalidated).
23
+ *
24
+ * `permit_revoke` is intentionally omitted: the WS transport does not track
25
+ * per-connection role requirements, so role-scoped disconnection would
26
+ * require either closing all sockets (too aggressive) or new tracking
27
+ * (out of scope). Consumers that need it compose their own callback.
28
+ */
29
+ export declare const WS_DISCONNECT_EVENT_TYPES: ReadonlySet<string>;
30
+ /**
31
+ * Create an audit event handler that closes WebSocket connections on auth changes.
32
+ *
33
+ * Ignores `outcome === 'failure'` events — they carry attacker-controlled
34
+ * identifiers (e.g. a `session_revoke` that the DB rejected still records
35
+ * the submitted session_id), so reacting to them would let any authenticated
36
+ * user close another user's socket by guessing a session hash or token id.
37
+ *
38
+ * @param transport - the backend WebSocket transport to guard
39
+ * @param log - logger for disconnect events (info level on non-zero closures)
40
+ * @returns an `on_audit_event` callback suitable for `CreateAppBackendOptions`
41
+ */
42
+ export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport, log: Logger) => ((event: AuditLogEvent) => void);
43
+ //# sourceMappingURL=transports_ws_auth_guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAE/D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAMxD,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA6CjC,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * WebSocket auth guard — bridges audit events to {@link BackendWebsocketTransport}.
3
+ *
4
+ * Mirror of `realtime/sse_auth_guard.ts` for the backend WebSocket transport.
5
+ * Dispatches audit events to the right `close_sockets_for_*` method so
6
+ * consumers do not re-implement the switch themselves.
7
+ *
8
+ * Consumers wire it as `on_audit_event` on their `AppBackend` (or compose
9
+ * it with other callbacks via `create_app_server`'s `audit_log_sse` path).
10
+ *
11
+ * @module
12
+ */
13
+ /**
14
+ * Audit event types that trigger WebSocket socket closure.
15
+ *
16
+ * - `session_revoke` — close only the socket tied to the revoked session hash.
17
+ * - `token_revoke` — close only the socket(s) authenticated with the revoked `api_token.id`.
18
+ * - `session_revoke_all` / `token_revoke_all` / `password_change` — close every socket
19
+ * for the affected account (all credentials invalidated).
20
+ *
21
+ * `permit_revoke` is intentionally omitted: the WS transport does not track
22
+ * per-connection role requirements, so role-scoped disconnection would
23
+ * require either closing all sockets (too aggressive) or new tracking
24
+ * (out of scope). Consumers that need it compose their own callback.
25
+ */
26
+ export const WS_DISCONNECT_EVENT_TYPES = new Set([
27
+ 'session_revoke',
28
+ 'token_revoke',
29
+ 'session_revoke_all',
30
+ 'token_revoke_all',
31
+ 'password_change',
32
+ ]);
33
+ /**
34
+ * Create an audit event handler that closes WebSocket connections on auth changes.
35
+ *
36
+ * Ignores `outcome === 'failure'` events — they carry attacker-controlled
37
+ * identifiers (e.g. a `session_revoke` that the DB rejected still records
38
+ * the submitted session_id), so reacting to them would let any authenticated
39
+ * user close another user's socket by guessing a session hash or token id.
40
+ *
41
+ * @param transport - the backend WebSocket transport to guard
42
+ * @param log - logger for disconnect events (info level on non-zero closures)
43
+ * @returns an `on_audit_event` callback suitable for `CreateAppBackendOptions`
44
+ */
45
+ export const create_ws_auth_guard = (transport, log) => {
46
+ return (event) => {
47
+ if (!WS_DISCONNECT_EVENT_TYPES.has(event.event_type))
48
+ return;
49
+ // Failed mutations carry attacker-controlled metadata — never act on them.
50
+ if (event.outcome === 'failure')
51
+ return;
52
+ if (event.event_type === 'session_revoke') {
53
+ const session_id = event.metadata?.session_id;
54
+ if (typeof session_id !== 'string' || session_id.length === 0)
55
+ return;
56
+ const closed = transport.close_sockets_for_session(session_id);
57
+ if (closed > 0) {
58
+ log.info(`WS auth guard: closed ${closed} socket(s) for session ${session_id} (session_revoke)`);
59
+ }
60
+ return;
61
+ }
62
+ if (event.event_type === 'token_revoke') {
63
+ const token_id = event.metadata?.token_id;
64
+ if (typeof token_id !== 'string' || token_id.length === 0)
65
+ return;
66
+ const closed = transport.close_sockets_for_token(token_id);
67
+ if (closed > 0) {
68
+ log.info(`WS auth guard: closed ${closed} socket(s) for token ${token_id} (token_revoke)`);
69
+ }
70
+ return;
71
+ }
72
+ // session_revoke_all / token_revoke_all / password_change — all of the
73
+ // account's credentials are invalidated; close every socket on the account.
74
+ // Admin actions set `target_account_id`; self-service actions only set `account_id`.
75
+ const target = event.target_account_id ?? event.account_id;
76
+ if (!target)
77
+ return;
78
+ // `target` is a DB account id (string); the transport's account map is
79
+ // keyed by the branded `Uuid` used elsewhere in fuz_app. Same value,
80
+ // differing type disciplines across the audit-log and transport layers.
81
+ const closed = transport.close_sockets_for_account(target);
82
+ if (closed > 0) {
83
+ log.info(`WS auth guard: closed ${closed} socket(s) for account ${target} (${event.event_type})`);
84
+ }
85
+ };
86
+ };
@@ -8,16 +8,35 @@ import type { WSContext } from 'hono/ws';
8
8
  import type { JsonrpcNotification, JsonrpcRequest, JsonrpcResponseOrError, JsonrpcErrorResponse } from '../http/jsonrpc.js';
9
9
  import { type Uuid } from '../uuid.js';
10
10
  import { type Transport } from './transports.js';
11
+ /**
12
+ * Auth identity attached to a single WebSocket connection.
13
+ *
14
+ * One record per connection. `token_hash` is set for cookie-session
15
+ * connections, `api_token_id` for bearer (`api_token`) connections, and
16
+ * both are null for daemon-token connections (reachable only via
17
+ * {@link BackendWebsocketTransport.close_sockets_for_account}).
18
+ */
19
+ export interface ConnectionIdentity {
20
+ /** Blake3 session token hash, or null for non-session credentials. */
21
+ token_hash: string | null;
22
+ /** Authenticated account id. Always set. */
23
+ account_id: Uuid;
24
+ /** `api_token.id` for bearer-authenticated connections, else null. */
25
+ api_token_id: string | null;
26
+ }
11
27
  export declare class BackendWebsocketTransport implements Transport {
12
28
  #private;
13
29
  readonly transport_name: "backend_websocket_rpc";
14
30
  /**
15
31
  * Add a new WebSocket connection with auth info.
16
32
  * Session connections pass a token hash for targeted revocation.
17
- * Bearer token connections (api_token, daemon_token) pass null
18
- * they're still reachable via {@link close_sockets_for_account}.
33
+ * Bearer token connections (api_token) pass the `api_token.id` so the
34
+ * socket can be closed when that specific token is revoked without
35
+ * tearing down the account's other sockets. Daemon-token connections
36
+ * pass `null` for both — they're only reachable via
37
+ * {@link close_sockets_for_account}.
19
38
  */
20
- add_connection(ws: WSContext, token_hash: string | null, account_id: Uuid): Uuid;
39
+ add_connection(ws: WSContext, token_hash: string | null, account_id: Uuid, api_token_id?: string | null): Uuid;
21
40
  /**
22
41
  * Remove a WebSocket connection and its auth tracking data.
23
42
  * Idempotent — safe to call after revocation has already cleaned up.
@@ -35,6 +54,16 @@ export declare class BackendWebsocketTransport implements Transport {
35
54
  * @returns the number of sockets closed
36
55
  */
37
56
  close_sockets_for_account(account_id: Uuid): number;
57
+ /**
58
+ * Close all sockets associated with a specific API token.
59
+ *
60
+ * Used on `token_revoke` audit events so revoking one token doesn't
61
+ * tear down the account's session-authenticated sockets or other
62
+ * tokens' sockets.
63
+ *
64
+ * @returns the number of sockets closed
65
+ */
66
+ close_sockets_for_token(api_token_id: string): number;
38
67
  send(message: JsonrpcRequest): Promise<JsonrpcResponseOrError>;
39
68
  send(message: JsonrpcNotification): Promise<JsonrpcErrorResponse | null>;
40
69
  is_ready(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"transports_ws_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_backend.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAGX,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAc,KAAK,IAAI,EAAC,MAAM,YAAY,CAAC;AAClD,OAAO,EAA2B,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAIzE,qBAAa,yBAA0B,YAAW,SAAS;;IAC1D,QAAQ,CAAC,cAAc,EAAG,uBAAuB,CAAU;IAY3D;;;;;OAKG;IACH,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,IAAI,GAAG,IAAI;IAWhF;;;OAGG;IACH,iBAAiB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAOtC;;;;OAIG;IACH,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAcrD;;;;OAIG;IACH,yBAAyB,CAAC,UAAU,EAAE,IAAI,GAAG,MAAM;IAiC7C,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAC9D,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA4C9E,QAAQ,IAAI,OAAO;CAGnB"}
1
+ {"version":3,"file":"transports_ws_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_backend.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAGX,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAc,KAAK,IAAI,EAAC,MAAM,YAAY,CAAC;AAClD,OAAO,EAA2B,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAIzE;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,sEAAsE;IACtE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,UAAU,EAAE,IAAI,CAAC;IACjB,sEAAsE;IACtE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,qBAAa,yBAA0B,YAAW,SAAS;;IAC1D,QAAQ,CAAC,cAAc,EAAG,uBAAuB,CAAU;IAY3D;;;;;;;;OAQG;IACH,cAAc,CACb,EAAE,EAAE,SAAS,EACb,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,UAAU,EAAE,IAAI,EAChB,YAAY,GAAE,MAAM,GAAG,IAAW,GAChC,IAAI;IAQP;;;OAGG;IACH,iBAAiB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IA0BtC;;;;OAIG;IACH,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAIrD;;;;OAIG;IACH,yBAAyB,CAAC,UAAU,EAAE,IAAI,GAAG,MAAM;IAInD;;;;;;;;OAQG;IACH,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAsB/C,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAC9D,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA4C9E,QAAQ,IAAI,OAAO;CAGnB"}
@@ -8,30 +8,29 @@ import { jsonrpc_error_messages } from '../http/jsonrpc_errors.js';
8
8
  import { create_jsonrpc_error_response, to_jsonrpc_message_id, is_jsonrpc_request, } from '../http/jsonrpc_helpers.js';
9
9
  import { create_uuid } from '../uuid.js';
10
10
  import { WS_CLOSE_SESSION_REVOKED } from './transports.js';
11
- // TODO support a SSE backend transport
12
11
  export class BackendWebsocketTransport {
13
12
  transport_name = 'backend_websocket_rpc';
14
13
  // Map connection IDs to WebSocket contexts
15
14
  #connections = new Map();
16
15
  // Reverse map to find connection ID by socket
17
16
  #connection_ids = new WeakMap();
18
- // Session auth tracking parallel maps keyed by connection ID
19
- #connection_token_hashes = new Map();
20
- #connection_account_ids = new Map();
17
+ // Auth identity per connection. Adding a new identity scope (e.g.
18
+ // `device_id`) means adding a field here, not a new parallel map.
19
+ #connection_identities = new Map();
21
20
  /**
22
21
  * Add a new WebSocket connection with auth info.
23
22
  * Session connections pass a token hash for targeted revocation.
24
- * Bearer token connections (api_token, daemon_token) pass null
25
- * they're still reachable via {@link close_sockets_for_account}.
23
+ * Bearer token connections (api_token) pass the `api_token.id` so the
24
+ * socket can be closed when that specific token is revoked without
25
+ * tearing down the account's other sockets. Daemon-token connections
26
+ * pass `null` for both — they're only reachable via
27
+ * {@link close_sockets_for_account}.
26
28
  */
27
- add_connection(ws, token_hash, account_id) {
29
+ add_connection(ws, token_hash, account_id, api_token_id = null) {
28
30
  const connection_id = create_uuid();
29
31
  this.#connections.set(connection_id, ws);
30
32
  this.#connection_ids.set(ws, connection_id);
31
- if (token_hash !== null) {
32
- this.#connection_token_hashes.set(connection_id, token_hash);
33
- }
34
- this.#connection_account_ids.set(connection_id, account_id);
33
+ this.#connection_identities.set(connection_id, { token_hash, account_id, api_token_id });
35
34
  return connection_id;
36
35
  }
37
36
  /**
@@ -45,14 +44,14 @@ export class BackendWebsocketTransport {
45
44
  }
46
45
  }
47
46
  /**
48
- * Close all sockets associated with a specific session token hash.
47
+ * Close every connection whose identity matches the predicate.
49
48
  *
50
49
  * @returns the number of sockets closed
51
50
  */
52
- close_sockets_for_session(token_hash) {
51
+ #close_where(predicate) {
53
52
  let count = 0;
54
- for (const [connection_id, hash] of this.#connection_token_hashes) {
55
- if (hash === token_hash) {
53
+ for (const [connection_id, identity] of this.#connection_identities) {
54
+ if (predicate(identity)) {
56
55
  const ws = this.#connections.get(connection_id);
57
56
  if (ws) {
58
57
  this.#revoke_connection(connection_id, ws);
@@ -62,23 +61,33 @@ export class BackendWebsocketTransport {
62
61
  }
63
62
  return count;
64
63
  }
64
+ /**
65
+ * Close all sockets associated with a specific session token hash.
66
+ *
67
+ * @returns the number of sockets closed
68
+ */
69
+ close_sockets_for_session(token_hash) {
70
+ return this.#close_where((id) => id.token_hash === token_hash);
71
+ }
65
72
  /**
66
73
  * Close all sockets associated with a specific account.
67
74
  *
68
75
  * @returns the number of sockets closed
69
76
  */
70
77
  close_sockets_for_account(account_id) {
71
- let count = 0;
72
- for (const [connection_id, id] of this.#connection_account_ids) {
73
- if (id === account_id) {
74
- const ws = this.#connections.get(connection_id);
75
- if (ws) {
76
- this.#revoke_connection(connection_id, ws);
77
- count++;
78
- }
79
- }
80
- }
81
- return count;
78
+ return this.#close_where((id) => id.account_id === account_id);
79
+ }
80
+ /**
81
+ * Close all sockets associated with a specific API token.
82
+ *
83
+ * Used on `token_revoke` audit events so revoking one token doesn't
84
+ * tear down the account's session-authenticated sockets or other
85
+ * tokens' sockets.
86
+ *
87
+ * @returns the number of sockets closed
88
+ */
89
+ close_sockets_for_token(api_token_id) {
90
+ return this.#close_where((id) => id.api_token_id === api_token_id);
82
91
  }
83
92
  /**
84
93
  * Remove all tracking state for a connection.
@@ -86,8 +95,7 @@ export class BackendWebsocketTransport {
86
95
  #cleanup_connection(connection_id, ws) {
87
96
  this.#connections.delete(connection_id);
88
97
  this.#connection_ids.delete(ws);
89
- this.#connection_token_hashes.delete(connection_id);
90
- this.#connection_account_ids.delete(connection_id);
98
+ this.#connection_identities.delete(connection_id);
91
99
  }
92
100
  /**
93
101
  * Clean up a connection and close its socket with a revocation code.
@@ -1 +1 @@
1
- {"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAKpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,iBAAiB,WAAW,GAAG,IAAI,EACnC,KAAK,MAAM,KACT,iBAqFF,CAAC"}
1
+ {"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAKpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,iBAAiB,WAAW,GAAG,IAAI,EACnC,KAAK,MAAM,KACT,iBAsFF,CAAC"}
@@ -11,7 +11,7 @@
11
11
  * @module
12
12
  */
13
13
  import { REQUEST_CONTEXT_KEY, build_request_context } from './request_context.js';
14
- import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
14
+ import { AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
15
15
  import { query_validate_api_token } from './api_token_queries.js';
16
16
  import { get_client_ip } from '../http/proxy.js';
17
17
  import { rate_limit_exceeded_response } from '../rate_limiter.js';
@@ -107,6 +107,7 @@ export const create_bearer_auth_middleware = (deps, ip_rate_limiter, log) => {
107
107
  }
108
108
  c.set(REQUEST_CONTEXT_KEY, ctx);
109
109
  c.set(CREDENTIAL_TYPE_KEY, 'api_token');
110
+ c.set(AUTH_API_TOKEN_ID_KEY, api_token.id);
110
111
  await next();
111
112
  };
112
113
  };
@@ -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;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAWrF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,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;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAKd,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IAC1C,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,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;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,oBAAoB,GAAG,YAAY,EAC5C,MAAM,SAAS,EACf,SAAS,0BAA0B,EACnC,KAAK,MAAM,KACT,OAAO,CAAC,mBAAmB,CAwD7B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,KACb,iBAoCF,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;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAWrF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,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;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAKd,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IAC1C,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,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;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,oBAAoB,GAAG,YAAY,EAC5C,MAAM,SAAS,EACf,SAAS,0BAA0B,EACnC,KAAK,MAAM,KACT,OAAO,CAAC,mBAAmB,CAwD7B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,KACb,iBAqCF,CAAC"}
@@ -13,7 +13,7 @@ import {} from '../runtime/deps.js';
13
13
  import { write_file_atomic } from '../runtime/fs.js';
14
14
  import { get_app_dir } from '../cli/config.js';
15
15
  import { REQUEST_CONTEXT_KEY, build_request_context } from './request_context.js';
16
- import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
16
+ import { AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
17
17
  import { ERROR_INVALID_DAEMON_TOKEN, ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED, ERROR_KEEPER_ACCOUNT_NOT_FOUND, } from '../http/error_schemas.js';
18
18
  import { query_permit_find_account_id_for_role } from './permit_queries.js';
19
19
  import { ROLE_KEEPER } from './role_schema.js';
@@ -162,6 +162,7 @@ export const create_daemon_token_middleware = (state, deps) => {
162
162
  }
163
163
  c.set(REQUEST_CONTEXT_KEY, ctx);
164
164
  c.set(CREDENTIAL_TYPE_KEY, 'daemon_token');
165
+ c.set(AUTH_API_TOKEN_ID_KEY, null);
165
166
  await next();
166
167
  };
167
168
  };
@@ -1 +1 @@
1
- {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAC;AACrD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAC,KAAK,OAAO,EAAE,KAAK,KAAK,EAAoB,KAAK,MAAM,EAAC,MAAM,qBAAqB,CAAC;AAQ5F,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAOnD,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACvB;AAED,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,GAAG,OAAO,KAAG,cAAc,GAAG,IAEjE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,GAAI,GAAG,OAAO,KAAG,cAMpD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,cAAc,EAAE,MAAM,MAAM,EAAE,MAAK,IAAiB,KAAG,OAChB,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,KAAK,MAAM,EACX,4BAAuC,KACrC,iBAyCF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,iBAM1B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,iBAW3C,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,GAC3B,KAAK,cAAc,EACnB,MAAM,SAAS,KACb,OAAO,CAAC,cAAc,CAGxB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAS/B,CAAC"}
1
+ {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAC;AACrD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAC,KAAK,OAAO,EAAE,KAAK,KAAK,EAAoB,KAAK,MAAM,EAAC,MAAM,qBAAqB,CAAC;AAQ5F,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAOnD,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACvB;AAED,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,GAAG,OAAO,KAAG,cAAc,GAAG,IAEjE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,GAAI,GAAG,OAAO,KAAG,cAMpD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,cAAc,EAAE,MAAM,MAAM,EAAE,MAAK,IAAiB,KAAG,OAChB,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,KAAK,MAAM,EACX,4BAAuC,KACrC,iBA6CF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,iBAM1B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,iBAW3C,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,GAC3B,KAAK,cAAc,EACnB,MAAM,SAAS,KACb,OAAO,CAAC,cAAc,CAGxB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAS/B,CAAC"}
@@ -15,7 +15,7 @@ import { is_permit_active } from './account_schema.js';
15
15
  import { hash_session_token, session_touch_fire_and_forget, query_session_get_valid, } from './session_queries.js';
16
16
  import { query_actor_by_account, query_account_by_id } from './account_queries.js';
17
17
  import { query_permit_find_active_for_actor } from './permit_queries.js';
18
- import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
18
+ import { AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
19
19
  import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, } from '../http/error_schemas.js';
20
20
  /** Hono context variable name for the request context. */
21
21
  export const REQUEST_CONTEXT_KEY = 'request_context';
@@ -89,6 +89,7 @@ export const create_request_context_middleware = (deps, log, session_context_key
89
89
  c.set(REQUEST_CONTEXT_KEY, null);
90
90
  c.set(CREDENTIAL_TYPE_KEY, null);
91
91
  c.set(AUTH_SESSION_TOKEN_HASH_KEY, null);
92
+ c.set(AUTH_API_TOKEN_ID_KEY, null);
92
93
  await next();
93
94
  return;
94
95
  }
@@ -98,6 +99,7 @@ export const create_request_context_middleware = (deps, log, session_context_key
98
99
  c.set(REQUEST_CONTEXT_KEY, null);
99
100
  c.set(CREDENTIAL_TYPE_KEY, null);
100
101
  c.set(AUTH_SESSION_TOKEN_HASH_KEY, null);
102
+ c.set(AUTH_API_TOKEN_ID_KEY, null);
101
103
  await next();
102
104
  return;
103
105
  }
@@ -106,12 +108,14 @@ export const create_request_context_middleware = (deps, log, session_context_key
106
108
  c.set(REQUEST_CONTEXT_KEY, null);
107
109
  c.set(CREDENTIAL_TYPE_KEY, null);
108
110
  c.set(AUTH_SESSION_TOKEN_HASH_KEY, null);
111
+ c.set(AUTH_API_TOKEN_ID_KEY, null);
109
112
  await next();
110
113
  return;
111
114
  }
112
115
  c.set(REQUEST_CONTEXT_KEY, ctx);
113
116
  c.set(CREDENTIAL_TYPE_KEY, 'session');
114
117
  c.set(AUTH_SESSION_TOKEN_HASH_KEY, token_hash);
118
+ c.set(AUTH_API_TOKEN_ID_KEY, null);
115
119
  // Touch session (fire-and-forget, don't block the request)
116
120
  void session_touch_fire_and_forget(deps, token_hash, c.var.pending_effects, log);
117
121
  await next();
@@ -26,6 +26,8 @@ export declare const CredentialType: z.ZodEnum<{
26
26
  export type CredentialType = z.infer<typeof CredentialType>;
27
27
  /** Hono context variable name for the credential type. */
28
28
  export declare const CREDENTIAL_TYPE_KEY = "credential_type";
29
+ /** Hono context variable name for the authenticated API token id. */
30
+ export declare const AUTH_API_TOKEN_ID_KEY = "auth_api_token_id";
29
31
  declare module 'hono' {
30
32
  interface ContextVariableMap {
31
33
  /** Resolved client IP, set by the trusted proxy middleware. */
@@ -44,6 +46,14 @@ declare module 'hono' {
44
46
  * disconnection) without re-hashing the cookie in every handler.
45
47
  */
46
48
  auth_session_token_hash: string | null;
49
+ /**
50
+ * `api_token.id` when the request authenticated via `Authorization: Bearer`,
51
+ * or `null` for session/daemon-token/unauthenticated requests. Set by
52
+ * `create_bearer_auth_middleware`. Used to scope per-token resources
53
+ * (e.g., WS connection revocation on `token_revoke`) without re-looking
54
+ * up the token.
55
+ */
56
+ auth_api_token_id: string | null;
47
57
  /**
48
58
  * Pending fire-and-forget effects for this request (audit logs, usage tracking, etc.).
49
59
  * Initialized by `create_app_server`. In test mode (`await_pending_effects: true`),
@@ -1 +1 @@
1
- {"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE9D,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,mDAAoD,CAAC;AAElF,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;WAIG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;KACtC;CACD"}
1
+ {"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE9D,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,mDAAoD,CAAC;AAElF,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC;;;;WAIG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;KACtC;CACD"}
@@ -20,3 +20,5 @@ export const CREDENTIAL_TYPES = ['session', 'api_token', 'daemon_token'];
20
20
  export const CredentialType = z.enum(CREDENTIAL_TYPES);
21
21
  /** Hono context variable name for the credential type. */
22
22
  export const CREDENTIAL_TYPE_KEY = 'credential_type';
23
+ /** Hono context variable name for the authenticated API token id. */
24
+ export const AUTH_API_TOKEN_ID_KEY = 'auth_api_token_id';
@@ -42,6 +42,8 @@ export interface BearerAuthTestCase extends BearerAuthTestOptions {
42
42
  validate_expectation: 'called' | 'not_called';
43
43
  /** If true, assert `REQUEST_CONTEXT_KEY` and `CREDENTIAL_TYPE_KEY` were set to api_token values. */
44
44
  assert_context_set?: boolean;
45
+ /** If set, assert `AUTH_API_TOKEN_ID_KEY` was set to this value after a successful bearer auth. */
46
+ expected_api_token_id?: string;
45
47
  /** If true, assert the pre-existing session context and credential type are preserved. */
46
48
  assert_context_preserved?: boolean;
47
49
  /** Optional callback for custom spy assertions on the mocks bundle. */
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;GAQG;AAEH,OAAO,EAAC,EAAE,EAAyB,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAC1B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAU3B,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAsB,KAAK,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAqBpF,gEAAgE;AAChE,MAAM,WAAW,qBAAqB;IACrC,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oEAAoE;IACpE,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,4CAA4C;IAC5C,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,+CAA+C;IAC/C,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,2DAA2D;IAC3D,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gFAAgF;IAChF,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;CAClC;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAmB,SAAQ,qBAAqB;IAChE,+EAA+E;IAC/E,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC9C,oGAAoG;IACpG,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,0FAA0F;IAC1F,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,uEAAuE;IACvE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CAChD;AAID,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC/B,aAAa,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IACxC,eAAe,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1C,oBAAoB,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/C,0BAA0B,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;CACrD;AAKD;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GAAI,IAAI,qBAAqB,KAAG,eAoBpE,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,cAAc,cAAc,CAAC;AAE1C;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GACvC,IAAI,qBAAqB,EACzB,kBAAiB,WAAW,GAAG,IAAW,KACxC;IAAC,GAAG,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CA2CpC,CAAC;AAIF;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,GACtC,YAAY,MAAM,EAClB,OAAO,KAAK,CAAC,kBAAkB,CAAC,EAChC,kBAAiB,WAAW,GAAG,IAAW,KACxC,IA0DF,CAAC;AAIF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAEhD,sDAAsD;AACtD,MAAM,WAAW,0BAA0B;IAC1C,iDAAiD;IACjD,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC;IACpD,oDAAoD;IACpD,eAAe,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACrC;AAED,yDAAyD;AACzD,MAAM,WAAW,sBAAsB;IACtC,GAAG,EAAE,IAAI,CAAC;IACV,aAAa,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IACxC,eAAe,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1C,oBAAoB,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/C,0BAA0B,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;CACrD;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,GAC5C,UAAU,0BAA0B,KAClC,sBAiDF,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/middleware.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,7 @@ import { query_permit_find_active_for_actor } from '../auth/permit_queries.js';
18
18
  import { create_proxy_middleware, get_client_ip } from '../http/proxy.js';
19
19
  import { verify_request_source, parse_allowed_origins } from '../http/origin.js';
20
20
  import { REQUEST_CONTEXT_KEY } from '../auth/request_context.js';
21
- import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
21
+ import { AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
22
22
  import { ApiError } from '../http/error_schemas.js';
23
23
  // Mock the query modules so test cases can control return values.
24
24
  // vi.mock() is hoisted by vitest, so these run before any imports resolve.
@@ -97,6 +97,7 @@ export const create_bearer_auth_test_app = (tc, ip_rate_limiter = null) => {
97
97
  app.get('/api/test', (c) => {
98
98
  const ctx = c.get(REQUEST_CONTEXT_KEY);
99
99
  const cred = c.get(CREDENTIAL_TYPE_KEY);
100
+ const api_token_id = c.get(AUTH_API_TOKEN_ID_KEY);
100
101
  return c.json({
101
102
  ok: true,
102
103
  has_context: ctx != null,
@@ -104,6 +105,7 @@ export const create_bearer_auth_test_app = (tc, ip_rate_limiter = null) => {
104
105
  account_id: ctx?.account.id ?? null,
105
106
  actor_id: ctx?.actor.id ?? null,
106
107
  permit_count: ctx?.permits.length ?? 0,
108
+ api_token_id: api_token_id ?? null,
107
109
  });
108
110
  });
109
111
  return { app, mocks };
@@ -149,6 +151,9 @@ export const describe_bearer_auth_cases = (suite_name, cases, ip_rate_limiter =
149
151
  assert.strictEqual(body.has_context, true, 'REQUEST_CONTEXT_KEY should be set');
150
152
  assert.strictEqual(body.credential_type, 'api_token', 'CREDENTIAL_TYPE_KEY should be api_token');
151
153
  }
154
+ if (tc.expected_api_token_id !== undefined) {
155
+ assert.strictEqual(body.api_token_id, tc.expected_api_token_id, 'AUTH_API_TOKEN_ID_KEY should match the validated api_token.id');
156
+ }
152
157
  if (tc.assert_context_preserved) {
153
158
  assert.strictEqual(body.has_context, true, 'original context should be preserved');
154
159
  assert.strictEqual(body.credential_type, 'session', 'credential type should remain session');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.13.1",
3
+ "version": "0.15.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",