@fuzdev/fuz_app 0.7.1 → 0.9.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.
Files changed (42) hide show
  1. package/dist/actions/action_codegen.d.ts.map +1 -1
  2. package/dist/actions/action_codegen.js +1 -6
  3. package/dist/actions/action_rpc.d.ts +3 -0
  4. package/dist/actions/action_rpc.d.ts.map +1 -1
  5. package/dist/actions/action_rpc.js +22 -10
  6. package/dist/auth/bearer_auth.d.ts +10 -0
  7. package/dist/auth/bearer_auth.d.ts.map +1 -1
  8. package/dist/auth/bearer_auth.js +29 -7
  9. package/dist/auth/middleware.d.ts.map +1 -1
  10. package/dist/auth/middleware.js +5 -1
  11. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  12. package/dist/testing/adversarial_headers.js +7 -10
  13. package/dist/testing/app_server.d.ts +2 -0
  14. package/dist/testing/app_server.d.ts.map +1 -1
  15. package/dist/testing/app_server.js +17 -0
  16. package/dist/testing/data_exposure.js +1 -1
  17. package/dist/testing/integration.d.ts +1 -1
  18. package/dist/testing/integration.d.ts.map +1 -1
  19. package/dist/testing/integration.js +11 -17
  20. package/dist/testing/round_trip.js +1 -1
  21. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  22. package/dist/testing/rpc_attack_surface.js +25 -5
  23. package/dist/testing/rpc_round_trip.js +1 -1
  24. package/dist/ui/AdminOverview.svelte +33 -33
  25. package/dist/ui/BootstrapForm.svelte +13 -1
  26. package/dist/ui/BootstrapForm.svelte.d.ts +4 -1
  27. package/dist/ui/BootstrapForm.svelte.d.ts.map +1 -1
  28. package/dist/ui/ColumnLayout.svelte +6 -6
  29. package/dist/ui/Datatable.svelte +14 -14
  30. package/dist/ui/LoginForm.svelte +1 -1
  31. package/dist/ui/OpenSignupToggle.svelte +1 -1
  32. package/dist/ui/SignupForm.svelte +1 -1
  33. package/dist/ui/SurfaceExplorer.svelte +1 -5
  34. package/dist/ui/SurfaceExplorer.svelte.d.ts +3 -3
  35. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  36. package/dist/ui/auth_state.svelte.d.ts +9 -9
  37. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  38. package/dist/ui/auth_state.svelte.js +5 -5
  39. package/dist/ui/sidebar_state.svelte.d.ts +5 -5
  40. package/dist/ui/sidebar_state.svelte.d.ts.map +1 -1
  41. package/dist/ui/sidebar_state.svelte.js +1 -1
  42. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"action_codegen.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_codegen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,eAAe,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAKxE;;GAEG;AACH,UAAU,UAAU;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,aAAa;;IACzB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAa;IAE1D;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQrC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI1C;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAOrD;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAgCtD;;;OAGG;IACH,KAAK,IAAI,MAAM;IAIf;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC;IAIxB;;OAEG;IACH,KAAK,IAAI,IAAI;CAqDb;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,KAC9B,KAAK,CAAC,gBAAgB,CA4DxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,OAAO,gBAAgB,EACvB,SAAS,aAAa,EACtB,aAAa,MAAM,KACjB,MAkBF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,EAChC,SAAS,aAAa,KACpB,MA+BF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,MACU,CAAC"}
1
+ {"version":3,"file":"action_codegen.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_codegen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,eAAe,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAKxE;;GAEG;AACH,UAAU,UAAU;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,aAAa;;IACzB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAa;IAE1D;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQrC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI1C;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAOrD;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAgCtD;;;OAGG;IACH,KAAK,IAAI,MAAM;IAIf;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC;IAIxB;;OAEG;IACH,KAAK,IAAI,IAAI;CAqDb;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,KAC9B,KAAK,CAAC,gBAAgB,CA4DxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,OAAO,gBAAgB,EACvB,SAAS,aAAa,EACtB,aAAa,MAAM,KACjB,MAkBF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,EAChC,SAAS,aAAa,KACpB,MAyBF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,MACU,CAAC"}
@@ -257,18 +257,13 @@ export const generate_phase_handlers = (spec, executor, imports) => {
257
257
  // Backend types file is in server/ subdirectory, so needs different relative paths
258
258
  const path_prefix = executor === 'frontend' ? './' : '../';
259
259
  imports.add_type(`${path_prefix}action_event.js`, 'ActionEvent');
260
- // Add environment type import
261
- const environment_type = executor === 'frontend' ? 'Frontend' : 'Backend';
262
- const environment_module = executor === 'frontend' ? './frontend.svelte.js' : './backend.js';
263
- imports.add_type(environment_module, environment_type);
264
260
  // Generate handler definitions for each phase
265
261
  const phase_handlers = phases
266
262
  .map((phase) => {
267
263
  // Pass imports to get_handler_return_type so it can add necessary imports
268
264
  const return_type = get_handler_return_type(spec, phase, imports, path_prefix);
269
- // Use the new type parameter approach
270
265
  return `${phase}?: (
271
- action_event: ActionEvent<'${method}', ${environment_type}, '${phase}', 'handling'>
266
+ action_event: ActionEvent<'${method}', '${phase}', 'handling'>
272
267
  ) => ${return_type}`;
273
268
  })
274
269
  .join(';\n\t\t');
@@ -16,6 +16,7 @@ import type { RequestResponseActionSpec } from './action_spec.js';
16
16
  import { type RouteSpec } from '../http/route_spec.js';
17
17
  import { type RequestContext } from '../auth/request_context.js';
18
18
  import type { Db } from '../db/db.js';
19
+ import { type JsonrpcRequestId } from '../http/jsonrpc.js';
19
20
  /**
20
21
  * Per-request context provided to RPC action handlers.
21
22
  *
@@ -26,6 +27,8 @@ import type { Db } from '../db/db.js';
26
27
  export interface ActionContext {
27
28
  /** The authenticated identity, or `null` for public routes. */
28
29
  auth: RequestContext | null;
30
+ /** The JSON-RPC request ID from the envelope. */
31
+ request_id: JsonrpcRequestId;
29
32
  /** Transaction-scoped for mutations, pool-level for reads. */
30
33
  db: Db;
31
34
  /** Always pool-level — for fire-and-forget effects that outlive the transaction. */
@@ -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;AAC9F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAWpC;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,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;AA4CD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,wBAAwB,KAAG,KAAK,CAAC,SAAS,CA6NtF,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,EAAkC,KAAK,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AAS1F;;;;;;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,CAoOtF,CAAC"}
@@ -15,9 +15,10 @@ import { z } from 'zod';
15
15
  import { DEV } from 'esm-env';
16
16
  import {} from '../http/route_spec.js';
17
17
  import { get_request_context, has_role } from '../auth/request_context.js';
18
+ import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
18
19
  import { is_null_schema } from '../http/schema_helpers.js';
19
20
  import { JSONRPC_VERSION, JsonrpcRequest } from '../http/jsonrpc.js';
20
- import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, JSONRPC_ERROR_CODES, ThrownJsonrpcError, } from '../http/jsonrpc_errors.js';
21
+ import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, JSONRPC_ERROR_CODES, } from '../http/jsonrpc_errors.js';
21
22
  /**
22
23
  * Format a JSON-RPC error response.
23
24
  *
@@ -35,9 +36,10 @@ const jsonrpc_error_response = (id, error) => ({
35
36
  *
36
37
  * @param auth - the action's auth requirement
37
38
  * @param request_context - the resolved identity (null if unauthenticated)
39
+ * @param credential_type - how the request was authenticated (session, api_token, daemon_token)
38
40
  * @returns an error json if auth fails, or null if authorized
39
41
  */
40
- const check_action_auth = (auth, request_context) => {
42
+ const check_action_auth = (auth, request_context, credential_type) => {
41
43
  if (auth === 'public')
42
44
  return null;
43
45
  if (!request_context)
@@ -45,9 +47,12 @@ const check_action_auth = (auth, request_context) => {
45
47
  if (auth === 'authenticated')
46
48
  return null;
47
49
  if (auth === 'keeper') {
48
- // keeper requires the keeper role
49
- if (!has_role(request_context, 'keeper'))
50
+ // keeper requires daemon_token credential type AND the keeper role.
51
+ // API tokens and session cookies cannot access keeper actions even
52
+ // if the account has the keeper permit.
53
+ if (credential_type !== 'daemon_token' || !has_role(request_context, 'keeper')) {
50
54
  return jsonrpc_error_messages.forbidden();
55
+ }
51
56
  return null;
52
57
  }
53
58
  // role check
@@ -117,7 +122,8 @@ export const create_rpc_endpoint = (options) => {
117
122
  }
118
123
  // step 3: auth check
119
124
  const request_context = get_request_context(c);
120
- const auth_error = check_action_auth(action.spec.auth, request_context);
125
+ const credential_type = c.get(CREDENTIAL_TYPE_KEY) ?? null;
126
+ const auth_error = check_action_auth(action.spec.auth, request_context, credential_type);
121
127
  if (auth_error) {
122
128
  const error = jsonrpc_error_response(id, auth_error);
123
129
  return c.json(error, jsonrpc_error_code_to_http_status(auth_error.code));
@@ -136,6 +142,7 @@ export const create_rpc_endpoint = (options) => {
136
142
  const execute = async (db) => {
137
143
  const action_context = {
138
144
  auth: request_context,
145
+ request_id: id,
139
146
  db,
140
147
  background_db: route.background_db,
141
148
  pending_effects: route.pending_effects,
@@ -160,11 +167,16 @@ export const create_rpc_endpoint = (options) => {
160
167
  return await execute(route.db);
161
168
  }
162
169
  catch (err) {
163
- if (err instanceof ThrownJsonrpcError) {
164
- const status = jsonrpc_error_code_to_http_status(err.code);
165
- const error_json = { code: err.code, message: err.message };
166
- if (err.data !== undefined)
167
- error_json.data = err.data;
170
+ // Duck-type check: Error with numeric `code` signals a JSON-RPC error.
171
+ // Avoids instanceof which fails when consumers throw their own ThrownJsonrpcError
172
+ // (structurally identical but different class identity, e.g. zzz's copy).
173
+ if (err instanceof Error && typeof err.code === 'number') {
174
+ const code = err.code;
175
+ const data = err.data;
176
+ const status = jsonrpc_error_code_to_http_status(code);
177
+ const error_json = { code, message: err.message };
178
+ if (data !== undefined)
179
+ error_json.data = data;
168
180
  return c.json(jsonrpc_error_response(id, error_json), status);
169
181
  }
170
182
  // generic error
@@ -17,6 +17,13 @@ import { type RateLimiter } from '../rate_limiter.js';
17
17
  /**
18
18
  * Create middleware that authenticates via bearer token.
19
19
  *
20
+ * Soft-fails for invalid, expired, or empty tokens — calls `next()` without
21
+ * setting a request context, letting downstream auth enforcement (per-action
22
+ * `check_action_auth` or `require_auth`) return a consistent JSON-RPC or
23
+ * route-level error. This avoids leaking token-specific diagnostics
24
+ * (`invalid_token`, `account_not_found`) that could aid enumeration attacks,
25
+ * and ensures public actions are not blocked by bad credentials.
26
+ *
20
27
  * Rejects bearer tokens when an `Origin` or `Referer` header is present —
21
28
  * browsers must use cookie auth to reduce attack surface.
22
29
  * Auth scheme matching is case-insensitive per RFC 7235.
@@ -24,6 +31,9 @@ import { type RateLimiter } from '../rate_limiter.js';
24
31
  * and sets it on the Hono context. Skips if a request context is already set
25
32
  * (e.g. by session middleware).
26
33
  *
34
+ * Rate limiting (429) is the only hard-fail — it's a throttling concern
35
+ * independent of auth identity.
36
+ *
27
37
  * @param deps - query dependencies (pool-level db for middleware)
28
38
  * @param ip_rate_limiter - per-IP rate limiter for bearer token attempts (null to disable)
29
39
  * @param log - the logger instance
@@ -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;AAOlF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,iBAAiB,WAAW,GAAG,IAAI,EACnC,KAAK,MAAM,KACT,iBAwEF,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,iBAqFF,CAAC"}
@@ -15,10 +15,16 @@ import { 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';
18
- import { ERROR_BEARER_REJECTED_BROWSER, ERROR_INVALID_TOKEN, ERROR_ACCOUNT_NOT_FOUND, } from '../http/error_schemas.js';
19
18
  /**
20
19
  * Create middleware that authenticates via bearer token.
21
20
  *
21
+ * Soft-fails for invalid, expired, or empty tokens — calls `next()` without
22
+ * setting a request context, letting downstream auth enforcement (per-action
23
+ * `check_action_auth` or `require_auth`) return a consistent JSON-RPC or
24
+ * route-level error. This avoids leaking token-specific diagnostics
25
+ * (`invalid_token`, `account_not_found`) that could aid enumeration attacks,
26
+ * and ensures public actions are not blocked by bad credentials.
27
+ *
22
28
  * Rejects bearer tokens when an `Origin` or `Referer` header is present —
23
29
  * browsers must use cookie auth to reduce attack surface.
24
30
  * Auth scheme matching is case-insensitive per RFC 7235.
@@ -26,6 +32,9 @@ import { ERROR_BEARER_REJECTED_BROWSER, ERROR_INVALID_TOKEN, ERROR_ACCOUNT_NOT_F
26
32
  * and sets it on the Hono context. Skips if a request context is already set
27
33
  * (e.g. by session middleware).
28
34
  *
35
+ * Rate limiting (429) is the only hard-fail — it's a throttling concern
36
+ * independent of auth identity.
37
+ *
29
38
  * @param deps - query dependencies (pool-level db for middleware)
30
39
  * @param ip_rate_limiter - per-IP rate limiter for bearer token attempts (null to disable)
31
40
  * @param log - the logger instance
@@ -46,19 +55,24 @@ export const create_bearer_auth_middleware = (deps, ip_rate_limiter, log) => {
46
55
  await next();
47
56
  return;
48
57
  }
49
- // Reject bearer tokens in browser context — defense-in-depth:
58
+ // Silently discard bearer tokens in browser context — defense-in-depth:
50
59
  // checks both Origin and Referer (not just Origin) because some browser
51
60
  // requests send only Referer. Uses `!== undefined` so that empty-string
52
61
  // headers (e.g. `Origin: ''`) are still treated as browser context.
62
+ // Discards rather than returning 403 so that the RPC dispatcher can still
63
+ // handle public actions or fall through to cookie auth.
53
64
  if (c.req.header('Origin') !== undefined || c.req.header('Referer') !== undefined) {
54
- return c.json({ error: ERROR_BEARER_REJECTED_BROWSER }, 403);
65
+ log.debug('bearer auth rejected: browser context (Origin/Referer present)');
66
+ await next();
67
+ return;
55
68
  }
56
69
  const raw_token = auth_header.slice(7);
57
- // Reject empty token body before any hashing or DB work.
70
+ // Empty token body soft-fail (treat as "no credential").
58
71
  // (The Fetch API trims 'Bearer ' to 'Bearer' which skips this middleware entirely,
59
72
  // but raw HTTP clients may send 'Bearer ' with an empty token.)
60
73
  if (!raw_token) {
61
- return c.json({ error: ERROR_INVALID_TOKEN }, 401);
74
+ await next();
75
+ return;
62
76
  }
63
77
  const ip = get_client_ip(c);
64
78
  // Per-IP rate limit: record before async DB work to close the TOCTOU
@@ -73,7 +87,11 @@ export const create_bearer_auth_middleware = (deps, ip_rate_limiter, log) => {
73
87
  }
74
88
  const api_token = await query_validate_api_token({ ...deps, log }, raw_token, ip, c.var.pending_effects);
75
89
  if (!api_token) {
76
- return c.json({ error: ERROR_INVALID_TOKEN }, 401);
90
+ // Invalid or expired token — soft-fail. Rate limit counter stays
91
+ // incremented (recorded above), correctly penalizing bad attempts.
92
+ log.debug('bearer auth soft-fail: token not found or expired');
93
+ await next();
94
+ return;
77
95
  }
78
96
  // Valid token — reset rate limit counter
79
97
  if (ip_rate_limiter)
@@ -81,7 +99,11 @@ export const create_bearer_auth_middleware = (deps, ip_rate_limiter, log) => {
81
99
  // Build request context from the token's account
82
100
  const ctx = await build_request_context(deps, api_token.account_id);
83
101
  if (!ctx) {
84
- return c.json({ error: ERROR_ACCOUNT_NOT_FOUND }, 401);
102
+ // Token exists but account/actor missing — soft-fail to avoid
103
+ // leaking account lifecycle information.
104
+ log.debug('bearer auth soft-fail: account or actor not found for token');
105
+ await next();
106
+ return;
85
107
  }
86
108
  c.set(REQUEST_CONTEXT_KEY, ctx);
87
109
  c.set(CREDENTIAL_TYPE_KEY, 'api_token');
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IACtC,oFAAoF;IACpF,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CA+D/B,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IACtC,oFAAoF;IACpF,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAmE/B,CAAC"}
@@ -47,7 +47,11 @@ export const create_auth_middleware_specs = async (deps, options) => {
47
47
  name: 'bearer_auth',
48
48
  path,
49
49
  handler: bearer_auth_middleware,
50
- errors: { 401: ApiError, 403: ApiError, 429: RateLimitError },
50
+ // Bearer middleware soft-fails for invalid/expired tokens (calls next()
51
+ // without setting context). Only 429 is a hard-fail from this layer.
52
+ // Auth enforcement (401/403) happens downstream via check_action_auth
53
+ // or require_auth, producing consistent JSON-RPC or route-level errors.
54
+ errors: { 429: RateLimitError },
51
55
  },
52
56
  ];
53
57
  if (daemon_token_state) {
@@ -1 +1 @@
1
- {"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAS3B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAIzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,GAC7C,gBAAgB,MAAM,KACpB,KAAK,CAAC,qBAAqB,CAkE7B,CAAC;AAIF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qCAAqC,GACjD,YAAY,MAAM,EAClB,SAAS,0BAA0B,EACnC,gBAAgB,MAAM,EACtB,cAAc,KAAK,CAAC,qBAAqB,CAAC,KACxC,IAkCF,CAAC"}
1
+ {"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAIzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,GAC7C,gBAAgB,MAAM,KACpB,KAAK,CAAC,qBAAqB,CA+D7B,CAAC;AAIF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qCAAqC,GACjD,YAAY,MAAM,EAClB,SAAS,0BAA0B,EACnC,gBAAgB,MAAM,EACtB,cAAc,KAAK,CAAC,qBAAqB,CAAC,KACxC,IAkCF,CAAC"}
@@ -8,7 +8,7 @@ import './assert_dev_env.js';
8
8
  * @module
9
9
  */
10
10
  import { test, assert, describe } from 'vitest';
11
- import { ApiError, ERROR_FORBIDDEN_ORIGIN, ERROR_FORBIDDEN_REFERER, ERROR_BEARER_REJECTED_BROWSER, ERROR_INVALID_TOKEN, } from '../http/error_schemas.js';
11
+ import { ApiError, ERROR_FORBIDDEN_ORIGIN, ERROR_FORBIDDEN_REFERER } from '../http/error_schemas.js';
12
12
  import { create_test_middleware_stack_app, TEST_MIDDLEWARE_PATH, } from './middleware.js';
13
13
  // --- Standard adversarial header cases ---
14
14
  /**
@@ -29,13 +29,12 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
29
29
  validate_expectation: 'not_called',
30
30
  },
31
31
  {
32
- name: 'bearer token with allowed Origin is rejected as browser context',
32
+ name: 'bearer token with allowed Origin bearer silently discarded (browser context)',
33
33
  headers: {
34
34
  Authorization: 'Bearer secret_fuz_token_test',
35
35
  Origin: allowed_origin,
36
36
  },
37
- expected_status: 403,
38
- expected_error: ERROR_BEARER_REJECTED_BROWSER,
37
+ expected_status: 200,
39
38
  validate_expectation: 'not_called',
40
39
  },
41
40
  {
@@ -55,12 +54,11 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
55
54
  validate_expectation: 'not_called',
56
55
  },
57
56
  {
58
- name: 'lowercase bearer scheme is recognized (case-insensitive per RFC 7235)',
57
+ name: 'lowercase bearer scheme is recognized (case-insensitive per RFC 7235), soft-fails',
59
58
  headers: {
60
59
  Authorization: 'bearer secret_fuz_token_test',
61
60
  },
62
- expected_status: 401,
63
- expected_error: ERROR_INVALID_TOKEN,
61
+ expected_status: 200,
64
62
  validate_expectation: 'called',
65
63
  },
66
64
  {
@@ -74,13 +72,12 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
74
72
  validate_expectation: 'not_called',
75
73
  },
76
74
  {
77
- name: 'bearer token with Referer from allowed origin is rejected as browser context (defense-in-depth)',
75
+ name: 'bearer token with Referer from allowed origin bearer silently discarded (browser context)',
78
76
  headers: {
79
77
  Authorization: 'Bearer secret_fuz_token_test',
80
78
  Referer: `${allowed_origin}/page`,
81
79
  },
82
- expected_status: 403,
83
- expected_error: ERROR_BEARER_REJECTED_BROWSER,
80
+ expected_status: 200,
84
81
  validate_expectation: 'not_called',
85
82
  },
86
83
  ];
@@ -144,6 +144,8 @@ export interface TestApp {
144
144
  create_session_headers: (extra?: Record<string, string>) => Record<string, string>;
145
145
  /** Build request headers with the bootstrapped Bearer token. */
146
146
  create_bearer_headers: (extra?: Record<string, string>) => Record<string, string>;
147
+ /** Build request headers with the daemon token (keeper auth). */
148
+ create_daemon_token_headers: (extra?: Record<string, string>) => Record<string, string>;
147
149
  /** Create an additional account with credentials. */
148
150
  create_account: (options?: {
149
151
  username?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ import { create_session_cookie_value } from '../auth/session_cookie.js';
12
12
  import { run_migrations } from '../db/migrate.js';
13
13
  import { AUTH_MIGRATION_NS } from '../auth/migrations.js';
14
14
  import { create_app_server, } from '../server/app_server.js';
15
+ import { generate_daemon_token, DAEMON_TOKEN_HEADER, } from '../auth/daemon_token.js';
15
16
  import { create_pglite_factory } from './db.js';
16
17
  /* eslint-disable @typescript-eslint/require-await */
17
18
  /**
@@ -172,6 +173,15 @@ export const create_test_app_server = async (options) => {
172
173
  */
173
174
  export const create_test_app = async (options) => {
174
175
  const test_server = await create_test_app_server(options);
176
+ // Daemon token state for keeper auth in tests.
177
+ // Uses a static token (no rotation) — sufficient for request-level testing.
178
+ const test_daemon_token = generate_daemon_token();
179
+ const daemon_token_state = {
180
+ current_token: test_daemon_token,
181
+ previous_token: null,
182
+ rotated_at: new Date(),
183
+ keeper_account_id: test_server.account.id,
184
+ };
175
185
  const result = await create_app_server({
176
186
  backend: test_server,
177
187
  session_options: options.session_options,
@@ -183,6 +193,7 @@ export const create_test_app = async (options) => {
183
193
  signup_account_rate_limiter: null,
184
194
  bearer_ip_rate_limiter: null,
185
195
  await_pending_effects: true,
196
+ daemon_token_state,
186
197
  ...options.app_options,
187
198
  create_route_specs: options.create_route_specs,
188
199
  });
@@ -200,6 +211,11 @@ export const create_test_app = async (options) => {
200
211
  authorization: `Bearer ${test_server.api_token}`,
201
212
  ...extra,
202
213
  });
214
+ const create_daemon_token_headers = (extra) => ({
215
+ host: 'localhost',
216
+ [DAEMON_TOKEN_HEADER]: test_daemon_token,
217
+ ...extra,
218
+ });
203
219
  let account_counter = 0;
204
220
  const create_account = async (account_options) => {
205
221
  account_counter++;
@@ -235,6 +251,7 @@ export const create_test_app = async (options) => {
235
251
  route_specs: surface_spec.route_specs,
236
252
  create_session_headers,
237
253
  create_bearer_headers,
254
+ create_daemon_token_headers,
238
255
  create_account,
239
256
  cleanup: () => test_server.cleanup(),
240
257
  };
@@ -291,6 +291,6 @@ const pick_auth_headers = (spec, test_app, authed_account, admin_account) => {
291
291
  // keeper role uses the bootstrapped account
292
292
  return test_app.create_session_headers();
293
293
  case 'keeper':
294
- return test_app.create_bearer_headers();
294
+ return test_app.create_daemon_token_headers();
295
295
  }
296
296
  };
@@ -24,7 +24,7 @@ export interface StandardIntegrationTestOptions {
24
24
  *
25
25
  * Exercises login/logout, cookie attributes, session security, session
26
26
  * revocation, password change (incl. API token revocation), origin
27
- * verification, bearer auth (incl. browser context rejection on mutations),
27
+ * verification, bearer auth (incl. browser context discard on mutations),
28
28
  * token revocation, cross-account isolation, expired credential rejection,
29
29
  * signup invite edge cases, and response body validation.
30
30
  *
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAgBjB;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAeD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IAk+CF,CAAC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAgBjB;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAeD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IAo+CF,CAAC"}
@@ -38,7 +38,7 @@ const build_test_app_options = (options, db) => ({
38
38
  *
39
39
  * Exercises login/logout, cookie attributes, session security, session
40
40
  * revocation, password change (incl. API token revocation), origin
41
- * verification, bearer auth (incl. browser context rejection on mutations),
41
+ * verification, bearer auth (incl. browser context discard on mutations),
42
42
  * token revocation, cross-account isolation, expired credential rejection,
43
43
  * signup invite edge cases, and response body validation.
44
44
  *
@@ -525,17 +525,15 @@ export const describe_standard_integration_tests = (options) => {
525
525
  headers: bearer_headers,
526
526
  });
527
527
  assert.strictEqual(ok_res.status, 200);
528
- // With Origin — rejected (browser context)
528
+ // With Origin — bearer silently discarded (browser context), falls through to no auth
529
529
  const res = await test_app.app.request(verify_route.path, {
530
530
  headers: {
531
531
  ...bearer_headers,
532
532
  origin: 'http://localhost:5173',
533
533
  },
534
534
  });
535
- assert.strictEqual(res.status, 403);
536
- error_collector.record(test_app.route_specs, 'GET', verify_route.path, 403);
537
- const body = await res.json();
538
- assert.strictEqual(body.error, 'bearer_token_rejected_in_browser_context');
535
+ assert.strictEqual(res.status, 401);
536
+ error_collector.record(test_app.route_specs, 'GET', verify_route.path, 401);
539
537
  });
540
538
  });
541
539
  // --- 7. Token revocation ---
@@ -912,8 +910,8 @@ export const describe_standard_integration_tests = (options) => {
912
910
  });
913
911
  });
914
912
  // --- 12. Bearer token browser context on mutation routes ---
915
- describe('bearer token browser context rejection on mutations', () => {
916
- test('bearer token with Origin header rejected on POST logout', async () => {
913
+ describe('bearer token browser context silently discarded on mutations', () => {
914
+ test('bearer token with Origin header discarded on POST logout', async () => {
917
915
  const test_app = await create_test_app(build_test_app_options(options, get_db()));
918
916
  const logout_route = find_auth_route(test_app.route_specs, '/logout', 'POST');
919
917
  assert.ok(logout_route, 'Expected POST /logout route — ensure create_route_specs includes account routes');
@@ -924,12 +922,10 @@ export const describe_standard_integration_tests = (options) => {
924
922
  method: 'POST',
925
923
  headers: { ...bearer_headers, origin: 'http://localhost:5173' },
926
924
  });
927
- assert.strictEqual(res.status, 403, 'Bearer with Origin should be rejected on mutation');
928
- const body = await res.json();
929
- assert.strictEqual(body.error, 'bearer_token_rejected_in_browser_context');
930
- error_collector.record(test_app.route_specs, 'POST', logout_route.path, 403);
925
+ assert.strictEqual(res.status, 401, 'Bearer with Origin should be discarded unauthenticated');
926
+ error_collector.record(test_app.route_specs, 'POST', logout_route.path, 401);
931
927
  });
932
- test('bearer token with Referer header rejected on POST password', async () => {
928
+ test('bearer token with Referer header discarded on POST password', async () => {
933
929
  const test_app = await create_test_app(build_test_app_options(options, get_db()));
934
930
  const password_route = find_auth_route(test_app.route_specs, '/password', 'POST');
935
931
  assert.ok(password_route, 'Expected POST /password route — ensure create_route_specs includes account routes');
@@ -940,10 +936,8 @@ export const describe_standard_integration_tests = (options) => {
940
936
  method: 'POST',
941
937
  headers: { ...bearer_headers, referer: 'http://localhost:5173/admin' },
942
938
  });
943
- assert.strictEqual(res.status, 403, 'Bearer with Referer should be rejected on mutation');
944
- const body = await res.json();
945
- assert.strictEqual(body.error, 'bearer_token_rejected_in_browser_context');
946
- error_collector.record(test_app.route_specs, 'POST', password_route.path, 403);
939
+ assert.strictEqual(res.status, 401, 'Bearer with Referer should be discarded unauthenticated');
940
+ error_collector.record(test_app.route_specs, 'POST', password_route.path, 401);
947
941
  });
948
942
  });
949
943
  // --- 13. Password change revokes API tokens ---
@@ -122,6 +122,6 @@ const pick_auth_headers = (spec, test_app, authed_account, admin_account) => {
122
122
  // Keeper role uses the bootstrapped account (which has keeper role)
123
123
  return test_app.create_session_headers();
124
124
  case 'keeper':
125
- return test_app.create_bearer_headers();
125
+ return test_app.create_daemon_token_headers();
126
126
  }
127
127
  };
@@ -1 +1 @@
1
- {"version":3,"file":"rpc_attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAkB7B,OAAO,KAAK,EAA6C,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAenG,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACvC,+FAA+F;IAC/F,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AAkaD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,uBAAuB,KAAG,IAOpF,CAAC"}
1
+ {"version":3,"file":"rpc_attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAkB7B,OAAO,KAAK,EAA6C,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAoBnG,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACvC,+FAA+F;IAC/F,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AAodD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,uBAAuB,KAAG,IAOpF,CAAC"}
@@ -14,7 +14,7 @@ import './assert_dev_env.js';
14
14
  */
15
15
  import { test, assert, describe } from 'vitest';
16
16
  import { JSONRPC_ERROR_CODES } from '../http/jsonrpc_errors.js';
17
- import { create_auth_test_apps, select_auth_app } from './auth_apps.js';
17
+ import { create_auth_test_apps, create_test_app_from_specs, create_test_request_context, select_auth_app, } from './auth_apps.js';
18
18
  import { generate_input_test_cases } from './adversarial_input.js';
19
19
  import { ERROR_INVALID_JSON_BODY } from '../http/error_schemas.js';
20
20
  import { create_rpc_post_init, create_rpc_get_url, assert_jsonrpc_error_response, } from './rpc_helpers.js';
@@ -23,6 +23,8 @@ import { create_rpc_post_init, create_rpc_get_url, assert_jsonrpc_error_response
23
23
  const filter_protected_rpc_methods = (endpoint) => endpoint.methods.filter((m) => m.auth.type !== 'none');
24
24
  /** Filter RPC methods that require a specific role. */
25
25
  const filter_role_rpc_methods = (endpoint) => endpoint.methods.filter((m) => m.auth.type === 'role');
26
+ /** Filter RPC methods that require keeper auth (daemon_token + keeper role). */
27
+ const filter_keeper_rpc_methods = (endpoint) => endpoint.methods.filter((m) => m.auth.type === 'keeper');
26
28
  /** Find the `RpcAction` source spec for a surface method. */
27
29
  const find_rpc_action = (rpc_endpoint_specs, endpoint_path, method_name) => {
28
30
  const ep = rpc_endpoint_specs.find((e) => e.path === endpoint_path);
@@ -40,7 +42,7 @@ const find_rpc_action = (rpc_endpoint_specs, endpoint_path, method_name) => {
40
42
  * - unauthenticated → error code -32001 — every protected method
41
43
  * - wrong role → error code -32002 — every role method with non-matching roles
42
44
  * - authenticated without role → -32002 — every role method, no-role context
43
- * - keeper routes reject session credential → -32002
45
+ * - keeper rejects non-daemon credentials → -32002 — session and api_token rejected
44
46
  * - correct auth passes — every protected method, assert not 401/403
45
47
  */
46
48
  const describe_rpc_auth = (options) => {
@@ -94,9 +96,27 @@ const describe_rpc_auth = (options) => {
94
96
  }
95
97
  });
96
98
  }
97
- // NOTE: no "keeper rejects session credential" test for RPC — the RPC
98
- // dispatcher's check_action_auth only checks role, not credential type.
99
- // Credential type enforcement is a REST middleware concern (require_keeper).
99
+ const keeper_methods = filter_keeper_rpc_methods(endpoint);
100
+ if (keeper_methods.length > 0) {
101
+ describe('keeper rejects non-daemon credentials', () => {
102
+ const session_app = create_test_app_from_specs(route_specs, create_test_request_context('keeper'), 'session');
103
+ const api_token_app = create_test_app_from_specs(route_specs, create_test_request_context('keeper'), 'api_token');
104
+ for (const method of keeper_methods) {
105
+ test(`${method.name} rejects session credential`, async () => {
106
+ const res = await session_app.request(endpoint.path, create_rpc_post_init(method.name));
107
+ assert.strictEqual(res.status, 403, `${method.name} should reject session credential`);
108
+ const body = await res.json();
109
+ assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.forbidden);
110
+ });
111
+ test(`${method.name} rejects api_token credential`, async () => {
112
+ const res = await api_token_app.request(endpoint.path, create_rpc_post_init(method.name));
113
+ assert.strictEqual(res.status, 403, `${method.name} should reject api_token credential`);
114
+ const body = await res.json();
115
+ assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.forbidden);
116
+ });
117
+ }
118
+ });
119
+ }
100
120
  describe('correct auth passes', () => {
101
121
  for (const method of protected_methods) {
102
122
  test(method.name, async () => {
@@ -33,7 +33,7 @@ const pick_rpc_auth_headers = (method, test_app, authed_account, admin_account)
33
33
  // keeper role uses the bootstrapped account
34
34
  return test_app.create_session_headers();
35
35
  case 'keeper':
36
- return test_app.create_bearer_headers();
36
+ return test_app.create_daemon_token_headers();
37
37
  }
38
38
  };
39
39
  /**
@@ -67,20 +67,20 @@
67
67
  <!-- TODO: panels will be user-draggable/rearrangeable, hardcoded order for now -->
68
68
  <div class="overview">
69
69
  <section>
70
- <div class="panel_header">
70
+ <div class="panel-header">
71
71
  <h3>accounts</h3>
72
72
  <a href={resolve('/admin/accounts' as any)} class="text_50 font_size_sm">view all &rarr;</a>
73
73
  </div>
74
74
  {#if accounts.loading}
75
75
  <p class="text_50">loading...</p>
76
76
  {:else if accounts.error}
77
- <p class="color_c">{accounts.error}</p>
77
+ <p class="color_c_50">{accounts.error}</p>
78
78
  {:else}
79
- <div class="baseline_row gap_xs">
79
+ <div class="baseline-row gap_xs">
80
80
  <strong class="font_size_lg">{accounts.account_count}</strong>
81
81
  <span class="text_50">accounts</span>
82
82
  </div>
83
- <div class="baseline_row gap_xs flex-wrap:wrap font_size_sm mt_xs">
83
+ <div class="baseline-row gap_xs flex-wrap:wrap font_size_sm mt_xs">
84
84
  {#each role_counts as [role, count] (role)}
85
85
  <span>{count} {role}</span>
86
86
  <span class="text_50">&middot;</span>
@@ -88,7 +88,7 @@
88
88
  <span>{unroled_count} unroled</span>
89
89
  </div>
90
90
  {#if accounts.accounts.length > 0}
91
- <ul class="compact_list">
91
+ <ul class="compact-list">
92
92
  {#each accounts.accounts.slice(0, 6) as entry (entry)}
93
93
  <li>
94
94
  <strong>{entry.account.username}</strong>
@@ -109,25 +109,25 @@
109
109
  </section>
110
110
 
111
111
  <section>
112
- <div class="panel_header">
112
+ <div class="panel-header">
113
113
  <h3>sessions</h3>
114
114
  <a href={resolve('/admin/sessions' as any)} class="text_50 font_size_sm">view all &rarr;</a>
115
115
  </div>
116
116
  {#if sessions.loading}
117
117
  <p class="text_50">loading...</p>
118
118
  {:else if sessions.error}
119
- <p class="color_c">{sessions.error}</p>
119
+ <p class="color_c_50">{sessions.error}</p>
120
120
  {:else}
121
- <div class="baseline_row gap_xs">
121
+ <div class="baseline-row gap_xs">
122
122
  <strong class="font_size_lg">{sessions.active_count}</strong>
123
123
  <span class="text_50">active</span>
124
124
  </div>
125
- <div class="baseline_row gap_xs">
125
+ <div class="baseline-row gap_xs">
126
126
  <strong class="font_size_lg">{unique_users}</strong>
127
127
  <span class="text_50">unique users</span>
128
128
  </div>
129
129
  {#if most_recent}
130
- <div class="baseline_row gap_xs font_size_sm mt_sm">
130
+ <div class="baseline-row gap_xs font_size_sm mt_sm">
131
131
  <span class="text_50">last active:</span>
132
132
  <strong>{most_recent.username}</strong>
133
133
  <span class="text_50" title={format_datetime_local(most_recent.last_seen_at)}
@@ -139,16 +139,16 @@
139
139
  </section>
140
140
 
141
141
  <section>
142
- <div class="panel_header">
142
+ <div class="panel-header">
143
143
  <h3>invites</h3>
144
144
  <a href={resolve('/admin/invites' as any)} class="text_50 font_size_sm">view all &rarr;</a>
145
145
  </div>
146
146
  {#if invites.loading}
147
147
  <p class="text_50">loading...</p>
148
148
  {:else if invites.error}
149
- <p class="color_c">{invites.error}</p>
149
+ <p class="color_c_50">{invites.error}</p>
150
150
  {:else}
151
- <div class="baseline_row gap_sm">
151
+ <div class="baseline-row gap_sm">
152
152
  <span class="text_50">public signup</span>
153
153
  {#if app_settings.settings?.open_signup}
154
154
  <span class="chip color_b">open</span>
@@ -156,7 +156,7 @@
156
156
  <span class="chip">closed</span>
157
157
  {/if}
158
158
  </div>
159
- <div class="baseline_row gap_xs">
159
+ <div class="baseline-row gap_xs">
160
160
  <strong class="font_size_lg">{invites.unclaimed_count}</strong>
161
161
  <span class="text_50">unclaimed</span>
162
162
  <span class="text_50">/</span>
@@ -164,7 +164,7 @@
164
164
  <span class="text_50">total</span>
165
165
  </div>
166
166
  {#if invites.invites.length > 0}
167
- <ul class="compact_list">
167
+ <ul class="compact-list">
168
168
  {#each invites.invites.slice(0, 4) as invite (invite.id)}
169
169
  <li>
170
170
  <span>{invite.email || invite.username || '—'}</span>
@@ -184,18 +184,18 @@
184
184
  </section>
185
185
 
186
186
  <section>
187
- <div class="panel_header">
187
+ <div class="panel-header">
188
188
  <h3>recent activity</h3>
189
189
  <a href={resolve('/admin/audit-log' as any)} class="text_50 font_size_sm">view all &rarr;</a>
190
190
  </div>
191
191
  {#if audit_log.loading}
192
192
  <p class="text_50">loading...</p>
193
193
  {:else if audit_log.error}
194
- <p class="color_c">{audit_log.error}</p>
194
+ <p class="color_c_50">{audit_log.error}</p>
195
195
  {:else if recent_events.length === 0}
196
196
  <p class="text_50">no events</p>
197
197
  {:else}
198
- <ul class="compact_list">
198
+ <ul class="compact-list">
199
199
  {#each recent_events as event (event.id)}
200
200
  <li>
201
201
  <span class="text_50 font_size_sm" title={format_datetime_local(event.created_at)}
@@ -212,27 +212,27 @@
212
212
  </section>
213
213
 
214
214
  <section>
215
- <div class="panel_header">
215
+ <div class="panel-header">
216
216
  <h3>security</h3>
217
217
  <a href={resolve('/admin/audit-log' as any)} class="text_50 font_size_sm">audit log &rarr;</a>
218
218
  </div>
219
219
  {#if audit_log.loading}
220
220
  <p class="text_50">loading...</p>
221
221
  {:else if audit_log.error}
222
- <p class="color_c">{audit_log.error}</p>
222
+ <p class="color_c_50">{audit_log.error}</p>
223
223
  {:else}
224
- <div class="baseline_row gap_xs">
225
- <strong class="font_size_lg" class:color_c={failed_logins.length > 0}>
224
+ <div class="baseline-row gap_xs">
225
+ <strong class="font_size_lg" class:color_c_50={failed_logins.length > 0}>
226
226
  {failed_logins.length}
227
227
  </strong>
228
228
  <span class="text_50">failed logins</span>
229
229
  </div>
230
- <div class="baseline_row gap_xs">
230
+ <div class="baseline-row gap_xs">
231
231
  <strong class="font_size_lg">{permit_changes.length}</strong>
232
232
  <span class="text_50">permit changes</span>
233
233
  </div>
234
234
  {#if permit_changes.length > 0}
235
- <ul class="compact_list">
235
+ <ul class="compact-list">
236
236
  {#each permit_changes.slice(0, 4) as event (event.id)}
237
237
  <li class="font_size_sm">
238
238
  <span class="text_50" title={format_datetime_local(event.created_at)}
@@ -250,15 +250,15 @@
250
250
  </section>
251
251
 
252
252
  <section>
253
- <div class="panel_header">
253
+ <div class="panel-header">
254
254
  <h3>system</h3>
255
255
  </div>
256
256
  {#if app_settings.loading}
257
257
  <p class="text_50">loading...</p>
258
258
  {:else if app_settings.error}
259
- <p class="color_c">{app_settings.error}</p>
259
+ <p class="color_c_50">{app_settings.error}</p>
260
260
  {:else}
261
- <div class="baseline_row gap_sm">
261
+ <div class="baseline-row gap_sm">
262
262
  <span class="text_50">public signup</span>
263
263
  {#if app_settings.settings?.open_signup}
264
264
  <span class="chip color_b">open</span>
@@ -267,7 +267,7 @@
267
267
  {/if}
268
268
  </div>
269
269
  {#if app_settings.settings?.updated_at}
270
- <div class="baseline_row gap_xs font_size_sm mt_xs">
270
+ <div class="baseline-row gap_xs font_size_sm mt_xs">
271
271
  <span class="text_50">last changed:</span>
272
272
  <span title={format_datetime_local(app_settings.settings.updated_at)}>
273
273
  {format_relative_time(app_settings.settings.updated_at)}
@@ -280,7 +280,7 @@
280
280
  {/if}
281
281
  {/if}
282
282
  {#if auth_state.account}
283
- <div class="baseline_row gap_sm">
283
+ <div class="baseline-row gap_sm">
284
284
  <span class="text_50">logged in as</span>
285
285
  <strong>{auth_state.account.username}</strong>
286
286
  </div>
@@ -310,25 +310,25 @@
310
310
  gap: var(--space_lg);
311
311
  }
312
312
 
313
- .panel_header {
313
+ .panel-header {
314
314
  display: flex;
315
315
  justify-content: space-between;
316
316
  align-items: baseline;
317
317
  margin-bottom: var(--space_md);
318
318
  }
319
319
 
320
- .baseline_row {
320
+ .baseline-row {
321
321
  display: flex;
322
322
  align-items: baseline;
323
323
  }
324
324
 
325
- .compact_list {
325
+ .compact-list {
326
326
  list-style: none;
327
327
  padding: 0;
328
328
  margin: var(--space_sm) 0 0;
329
329
  }
330
330
 
331
- .compact_list li {
331
+ .compact-list li {
332
332
  display: flex;
333
333
  align-items: baseline;
334
334
  gap: var(--space_xs);
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import {goto} from '$app/navigation';
3
+ import {resolve} from '$app/paths';
2
4
  import PendingButton from '@fuzdev/fuz_ui/PendingButton.svelte';
3
5
  import {autofocus} from '@fuzdev/fuz_ui/autofocus.svelte.js';
4
6
 
@@ -7,6 +9,12 @@
7
9
  import {auth_state_context} from './auth_state.svelte.js';
8
10
  import {FormState} from './form_state.svelte.js';
9
11
 
12
+ const {
13
+ redirect_on_bootstrap = resolve('/'),
14
+ }: {
15
+ redirect_on_bootstrap?: string;
16
+ } = $props();
17
+
10
18
  const auth_state = auth_state_context.get();
11
19
  const form_state = new FormState();
12
20
 
@@ -35,7 +43,11 @@
35
43
  else if (!passwords_match) form_state.focus('password_confirm');
36
44
  return;
37
45
  }
38
- await auth_state.bootstrap(token.trim(), username.trim(), password);
46
+ const success = await auth_state.bootstrap(token.trim(), username.trim(), password);
47
+ if (success) {
48
+ form_state.reset();
49
+ await goto(redirect_on_bootstrap);
50
+ }
39
51
  };
40
52
  </script>
41
53
 
@@ -1,4 +1,7 @@
1
- declare const BootstrapForm: import("svelte").Component<Record<string, never>, {}, "">;
1
+ type $$ComponentProps = {
2
+ redirect_on_bootstrap?: string;
3
+ };
4
+ declare const BootstrapForm: import("svelte").Component<$$ComponentProps, {}, "">;
2
5
  type BootstrapForm = ReturnType<typeof BootstrapForm>;
3
6
  export default BootstrapForm;
4
7
  //# sourceMappingURL=BootstrapForm.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BootstrapForm.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/BootstrapForm.svelte"],"names":[],"mappings":"AAqGA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"BootstrapForm.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/BootstrapForm.svelte"],"names":[],"mappings":"AAaC,KAAK,gBAAgB,GAAI;IACxB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAqGH,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -15,29 +15,29 @@
15
15
  } = $props();
16
16
  </script>
17
17
 
18
- <div class="column_layout {class_name}" style:--column_width={column_width} {...rest}>
19
- <aside class="column_fixed unstyled">
18
+ <div class="column-layout {class_name}" style:--column_width={column_width} {...rest}>
19
+ <aside class="column-fixed unstyled">
20
20
  {@render aside()}
21
21
  </aside>
22
- <div class="column_fluid">
22
+ <div class="column-fluid">
23
23
  {@render children()}
24
24
  </div>
25
25
  </div>
26
26
 
27
27
  <style>
28
- .column_layout {
28
+ .column-layout {
29
29
  display: flex;
30
30
  height: 100%;
31
31
  }
32
32
 
33
- .column_fixed {
33
+ .column-fixed {
34
34
  width: var(--column_width, 280px);
35
35
  min-width: var(--column_width, 280px);
36
36
  height: 100%;
37
37
  overflow: auto;
38
38
  }
39
39
 
40
- .column_fluid {
40
+ .column-fluid {
41
41
  flex: 1;
42
42
  height: 100%;
43
43
  min-width: 0;
@@ -78,16 +78,16 @@
78
78
  aria-rowcount={rows.length + 1}
79
79
  >
80
80
  <!-- sticky header -->
81
- <div class="datatable_header" role="row" aria-rowindex={1}>
81
+ <div class="datatable-header" role="row" aria-rowindex={1}>
82
82
  {#each columns as column, i (column.key)}
83
- <div class="datatable_header_cell" role="columnheader">
83
+ <div class="datatable-header-cell" role="columnheader">
84
84
  {#if header}
85
85
  {@render header(column)}
86
86
  {:else}
87
87
  {column.label}
88
88
  {/if}
89
89
  <div
90
- class="datatable_resize_handle"
90
+ class="datatable-resize-handle"
91
91
  role="separator"
92
92
  onpointerdown={(e) => handle_resize_start(e, i)}
93
93
  onpointermove={handle_resize_move}
@@ -98,7 +98,7 @@
98
98
  </div>
99
99
 
100
100
  {#if rows.length === 0}
101
- <div class="datatable_empty">
101
+ <div class="datatable-empty">
102
102
  {#if empty}
103
103
  {@render empty()}
104
104
  {:else}
@@ -107,9 +107,9 @@
107
107
  </div>
108
108
  {:else}
109
109
  {#each rows as row, i (row[row_key] ?? i)}
110
- <div class="datatable_row" role="row" aria-rowindex={i + 2}>
110
+ <div class="datatable-row" role="row" aria-rowindex={i + 2}>
111
111
  {#each columns as column (column.key)}
112
- <div class="datatable_cell" role="gridcell">
112
+ <div class="datatable-cell" role="gridcell">
113
113
  {#if cell}
114
114
  {@render cell(column, row, row[column.key])}
115
115
  {:else if column.format}
@@ -131,7 +131,7 @@
131
131
  overflow: auto;
132
132
  }
133
133
 
134
- .datatable_header {
134
+ .datatable-header {
135
135
  display: grid;
136
136
  grid-column: 1 / -1;
137
137
  grid-template-columns: subgrid;
@@ -142,7 +142,7 @@
142
142
  border-bottom: var(--border_width, 1px) solid var(--border_color);
143
143
  }
144
144
 
145
- .datatable_header_cell {
145
+ .datatable-header-cell {
146
146
  position: relative;
147
147
  display: flex;
148
148
  align-items: center;
@@ -150,7 +150,7 @@
150
150
  font-weight: 600;
151
151
  }
152
152
 
153
- .datatable_resize_handle {
153
+ .datatable-resize-handle {
154
154
  position: absolute;
155
155
  top: 0;
156
156
  right: 0;
@@ -159,11 +159,11 @@
159
159
  cursor: col-resize;
160
160
  }
161
161
 
162
- .datatable_resize_handle:hover {
163
- background: var(--color_a_5);
162
+ .datatable-resize-handle:hover {
163
+ background: var(--color_a_10);
164
164
  }
165
165
 
166
- .datatable_row {
166
+ .datatable-row {
167
167
  display: grid;
168
168
  grid-column: 1 / -1;
169
169
  grid-template-columns: subgrid;
@@ -172,13 +172,13 @@
172
172
  border-bottom: var(--border_width, 1px) solid var(--border_color);
173
173
  }
174
174
 
175
- .datatable_cell {
175
+ .datatable-cell {
176
176
  padding: var(--space_xs);
177
177
  min-width: 0; /* override grid auto minimum to respect column widths */
178
178
  overflow-wrap: break-word;
179
179
  }
180
180
 
181
- .datatable_empty {
181
+ .datatable-empty {
182
182
  grid-column: 1 / -1;
183
183
  padding: var(--space_lg);
184
184
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  const {
11
11
  username_label = 'username or email',
12
- redirect_on_login = resolve('/account' as any),
12
+ redirect_on_login = resolve('/'),
13
13
  }: {
14
14
  username_label?: string;
15
15
  redirect_on_login?: string;
@@ -6,7 +6,7 @@
6
6
  void app_settings.fetch();
7
7
  </script>
8
8
 
9
- <div class="open_signup_toggle">
9
+ <div class="open-signup-toggle">
10
10
  {#if app_settings.loading}
11
11
  <p class="text_50">loading settings...</p>
12
12
  {:else if app_settings.settings}
@@ -10,7 +10,7 @@
10
10
  import {FormState} from './form_state.svelte.js';
11
11
 
12
12
  const {
13
- redirect_on_signup = resolve('/account' as any),
13
+ redirect_on_signup = resolve('/'),
14
14
  }: {
15
15
  redirect_on_signup?: string;
16
16
  } = $props();
@@ -4,11 +4,7 @@
4
4
  import type {AppSurface, AppSurfaceRoute, AppSurfaceDiagnostic} from '../http/surface.js';
5
5
  import {surface_auth_summary, format_route_key} from '../http/surface_query.js';
6
6
 
7
- interface Props {
8
- surface: AppSurface;
9
- }
10
-
11
- const {surface}: Props = $props();
7
+ const {surface}: {surface: AppSurface} = $props();
12
8
 
13
9
  const auth_types = ['all', 'none', 'authenticated', 'role', 'keeper'] as const;
14
10
 
@@ -1,8 +1,8 @@
1
1
  import type { AppSurface } from '../http/surface.js';
2
- interface Props {
2
+ type $$ComponentProps = {
3
3
  surface: AppSurface;
4
- }
5
- declare const SurfaceExplorer: import("svelte").Component<Props, {}, "">;
4
+ };
5
+ declare const SurfaceExplorer: import("svelte").Component<$$ComponentProps, {}, "">;
6
6
  type SurfaceExplorer = ReturnType<typeof SurfaceExplorer>;
7
7
  export default SurfaceExplorer;
8
8
  //# sourceMappingURL=SurfaceExplorer.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SurfaceExplorer.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/SurfaceExplorer.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,UAAU,EAAwC,MAAM,oBAAoB,CAAC;AAIzF,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,CAAC;CACpB;AAuRF,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"SurfaceExplorer.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/SurfaceExplorer.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,UAAU,EAAwC,MAAM,oBAAoB,CAAC;AAGzF,KAAK,gBAAgB,GAAI;IAAC,OAAO,EAAE,UAAU,CAAA;CAAC,CAAC;AAsRhD,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -32,6 +32,15 @@
32
32
  * @module
33
33
  */
34
34
  import { type Permit, type SessionAccount } from '../auth/account_schema.js';
35
+ /**
36
+ * Svelte context for `AuthState`.
37
+ * Use `auth_state_context.set(state)` in the provider and `auth_state_context.get()` to access.
38
+ */
39
+ export declare const auth_state_context: {
40
+ get: (error_message?: string) => AuthState;
41
+ get_maybe: () => AuthState | undefined;
42
+ set: (value: AuthState) => AuthState;
43
+ };
35
44
  export declare class AuthState {
36
45
  verifying: boolean;
37
46
  verified: boolean;
@@ -73,13 +82,4 @@ export declare class AuthState {
73
82
  */
74
83
  logout(): Promise<void>;
75
84
  }
76
- /**
77
- * Svelte context for `AuthState`.
78
- * Use `auth_state_context.set(state)` in the provider and `auth_state_context.get()` to access.
79
- */
80
- export declare const auth_state_context: {
81
- get: (error_message?: string) => AuthState;
82
- get_maybe: () => AuthState | undefined;
83
- set: (value: AuthState) => AuthState;
84
- };
85
85
  //# sourceMappingURL=auth_state.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/auth_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,OAAO,EAAC,KAAK,MAAM,EAAoB,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE7F,qBAAa,SAAS;IACrB,SAAS,UAAqB;IAC9B,QAAQ,UAAqB;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC/C,OAAO,EAAE,cAAc,GAAG,IAAI,CAAoB;IAClD,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAkB;IACxC,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,CAEpC;IACF,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAoD;IAEjF,gEAAgE;IAChE,eAAe,UAAqB;IAEpC;;;;;;OAMG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BpC;;;;OAIG;IACG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwCjE;;;;OAIG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCpF;;;;OAIG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4ClF;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAU7B;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;CAA8B,CAAC"}
1
+ {"version":3,"file":"auth_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/auth_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAKH,OAAO,EAAC,KAAK,MAAM,EAAoB,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE7F;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;CAA8B,CAAC;AAE9D,qBAAa,SAAS;IACrB,SAAS,UAAqB;IAC9B,QAAQ,UAAqB;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC/C,OAAO,EAAE,cAAc,GAAG,IAAI,CAAoB;IAClD,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAkB;IACxC,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,CAEpC;IACF,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAoD;IAEjF,gEAAgE;IAChE,eAAe,UAAqB;IAEpC;;;;;;OAMG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BpC;;;;OAIG;IACG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwCjE;;;;OAIG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCpF;;;;OAIG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4ClF;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAU7B"}
@@ -34,6 +34,11 @@
34
34
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
35
35
  import { ui_fetch } from './ui_fetch.js';
36
36
  import { is_permit_active } from '../auth/account_schema.js';
37
+ /**
38
+ * Svelte context for `AuthState`.
39
+ * Use `auth_state_context.set(state)` in the provider and `auth_state_context.get()` to access.
40
+ */
41
+ export const auth_state_context = create_context();
37
42
  export class AuthState {
38
43
  verifying = $state.raw(false);
39
44
  verified = $state.raw(false);
@@ -231,8 +236,3 @@ export class AuthState {
231
236
  this.permits = [];
232
237
  }
233
238
  }
234
- /**
235
- * Svelte context for `AuthState`.
236
- * Use `auth_state_context.set(state)` in the provider and `auth_state_context.get()` to access.
237
- */
238
- export const auth_state_context = create_context();
@@ -1,3 +1,8 @@
1
+ export declare const sidebar_state_context: {
2
+ get: (error_message?: string) => () => SidebarState;
3
+ get_maybe: () => (() => SidebarState) | undefined;
4
+ set: (value: () => SidebarState) => () => SidebarState;
5
+ };
1
6
  /**
2
7
  * Options for configuring a `SidebarState`.
3
8
  *
@@ -22,9 +27,4 @@ export declare class SidebarState {
22
27
  */
23
28
  activate(): () => void;
24
29
  }
25
- export declare const sidebar_state_context: {
26
- get: (error_message?: string) => () => SidebarState;
27
- get_maybe: () => (() => SidebarState) | undefined;
28
- set: (value: () => SidebarState) => () => SidebarState;
29
- };
30
30
  //# sourceMappingURL=sidebar_state.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/sidebar_state.svelte.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IACnC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;CACxB;AAED,qBAAa,YAAY;;IAKxB,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,YAAY,IAAI,OAAO,CAG1B;IAED,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,EAE9B;gBAEW,OAAO,CAAC,EAAE,mBAAmB;IAIzC,cAAc,CAAC,KAAK,GAAE,OAA4B,GAAG,IAAI;IAIzD;;;OAGG;IACH,QAAQ,IAAI,MAAM,IAAI;CAQtB;AAED,eAAO,MAAM,qBAAqB;2CAAwB,YAAY;4BAAZ,YAAY;uBAAZ,YAAY,WAAZ,YAAY;CAAG,CAAC"}
1
+ {"version":3,"file":"sidebar_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/sidebar_state.svelte.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,qBAAqB;2CAAwB,YAAY;4BAAZ,YAAY;uBAAZ,YAAY,WAAZ,YAAY;CAAG,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IACnC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;CACxB;AAED,qBAAa,YAAY;;IAKxB,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED,IAAI,YAAY,IAAI,OAAO,CAG1B;IAED,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,EAE9B;gBAEW,OAAO,CAAC,EAAE,mBAAmB;IAIzC,cAAc,CAAC,KAAK,GAAE,OAA4B,GAAG,IAAI;IAIzD;;;OAGG;IACH,QAAQ,IAAI,MAAM,IAAI;CAQtB"}
@@ -1,4 +1,5 @@
1
1
  import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
2
+ export const sidebar_state_context = create_context();
2
3
  export class SidebarState {
3
4
  #get_enabled;
4
5
  #enabled = $state.raw(true);
@@ -36,4 +37,3 @@ export class SidebarState {
36
37
  };
37
38
  }
38
39
  }
39
- export const sidebar_state_context = create_context();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.7.1",
3
+ "version": "0.9.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -46,7 +46,7 @@
46
46
  "@fuzdev/fuz_code": "^0.45.1",
47
47
  "@fuzdev/fuz_css": "^0.58.0",
48
48
  "@fuzdev/fuz_ui": "^0.191.2",
49
- "@fuzdev/fuz_util": "^0.55.0",
49
+ "@fuzdev/fuz_util": "^0.56.0",
50
50
  "@fuzdev/gro": "^0.197.3",
51
51
  "@jridgewell/trace-mapping": "^0.3.31",
52
52
  "@node-rs/argon2": "^2.0.2",