@fuzdev/fuz_app 0.108.0 → 0.110.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 (122) hide show
  1. package/README.md +4 -0
  2. package/dist/actions/CLAUDE.md +1 -1
  3. package/dist/auth/CLAUDE.md +7 -6
  4. package/dist/auth/account_action_specs.d.ts +18 -0
  5. package/dist/auth/account_action_specs.d.ts.map +1 -1
  6. package/dist/auth/account_action_specs.js +14 -1
  7. package/dist/auth/account_actions.d.ts.map +1 -1
  8. package/dist/auth/account_actions.js +6 -2
  9. package/dist/auth/audit_emitter.d.ts +9 -5
  10. package/dist/auth/audit_emitter.d.ts.map +1 -1
  11. package/dist/auth/audit_log_schema.d.ts +7 -1
  12. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  13. package/dist/auth/audit_log_schema.js +16 -1
  14. package/dist/auth/bootstrap_account.d.ts +7 -2
  15. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  16. package/dist/auth/bootstrap_account.js +9 -4
  17. package/dist/auth/bootstrap_routes.d.ts +12 -4
  18. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  19. package/dist/auth/bootstrap_routes.js +26 -7
  20. package/dist/auth/daemon_token_middleware.d.ts +7 -69
  21. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  22. package/dist/auth/daemon_token_middleware.js +8 -104
  23. package/dist/auth/deps.d.ts +9 -5
  24. package/dist/auth/deps.d.ts.map +1 -1
  25. package/dist/auth/middleware.js +1 -1
  26. package/dist/auth/migrations.d.ts +7 -0
  27. package/dist/auth/migrations.d.ts.map +1 -1
  28. package/dist/auth/migrations.js +25 -8
  29. package/dist/auth/request_context.d.ts +1 -3
  30. package/dist/auth/request_context.d.ts.map +1 -1
  31. package/dist/auth/request_context.js +2 -5
  32. package/dist/auth/session_cookie.d.ts +18 -36
  33. package/dist/auth/session_cookie.d.ts.map +1 -1
  34. package/dist/auth/session_cookie.js +16 -26
  35. package/dist/auth/session_middleware.d.ts +1 -1
  36. package/dist/auth/session_middleware.d.ts.map +1 -1
  37. package/dist/auth/session_middleware.js +1 -4
  38. package/dist/auth/session_queries.d.ts +14 -30
  39. package/dist/auth/session_queries.d.ts.map +1 -1
  40. package/dist/auth/session_queries.js +15 -45
  41. package/dist/auth/token_lifetime.d.ts +51 -0
  42. package/dist/auth/token_lifetime.d.ts.map +1 -0
  43. package/dist/auth/token_lifetime.js +54 -0
  44. package/dist/db/CLAUDE.md +9 -5
  45. package/dist/db/cell_audit_queries.d.ts +4 -2
  46. package/dist/db/cell_audit_queries.d.ts.map +1 -1
  47. package/dist/db/cell_audit_queries.js +4 -2
  48. package/dist/db/pg_error.d.ts +12 -0
  49. package/dist/db/pg_error.d.ts.map +1 -1
  50. package/dist/db/pg_error.js +15 -1
  51. package/dist/dev/setup.d.ts +5 -9
  52. package/dist/dev/setup.d.ts.map +1 -1
  53. package/dist/dev/setup.js +18 -21
  54. package/dist/http/CLAUDE.md +78 -18
  55. package/dist/http/db_routes.d.ts +96 -3
  56. package/dist/http/db_routes.d.ts.map +1 -1
  57. package/dist/http/db_routes.js +263 -46
  58. package/dist/http/error_schemas.d.ts +12 -1
  59. package/dist/http/error_schemas.d.ts.map +1 -1
  60. package/dist/http/error_schemas.js +12 -1
  61. package/dist/http/pending_effects.d.ts +5 -5
  62. package/dist/http/pending_effects.d.ts.map +1 -1
  63. package/dist/http/pending_effects.js +1 -1
  64. package/dist/runtime/deno.d.ts.map +1 -1
  65. package/dist/runtime/deno.js +27 -1
  66. package/dist/runtime/deps.d.ts +38 -4
  67. package/dist/runtime/deps.d.ts.map +1 -1
  68. package/dist/runtime/fs.d.ts +24 -5
  69. package/dist/runtime/fs.d.ts.map +1 -1
  70. package/dist/runtime/fs.js +38 -7
  71. package/dist/runtime/mock.d.ts +7 -0
  72. package/dist/runtime/mock.d.ts.map +1 -1
  73. package/dist/runtime/mock.js +41 -1
  74. package/dist/runtime/node.d.ts.map +1 -1
  75. package/dist/runtime/node.js +7 -1
  76. package/dist/runtime/secure_file.d.ts +71 -0
  77. package/dist/runtime/secure_file.d.ts.map +1 -0
  78. package/dist/runtime/secure_file.js +125 -0
  79. package/dist/server/app_backend.d.ts +6 -5
  80. package/dist/server/app_backend.d.ts.map +1 -1
  81. package/dist/server/app_backend.js +2 -3
  82. package/dist/testing/CLAUDE.md +36 -1
  83. package/dist/testing/admin_integration.d.ts.map +1 -1
  84. package/dist/testing/admin_integration.js +10 -2
  85. package/dist/testing/app_server.d.ts.map +1 -1
  86. package/dist/testing/app_server.js +13 -8
  87. package/dist/testing/audit_completeness.d.ts.map +1 -1
  88. package/dist/testing/audit_completeness.js +10 -2
  89. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  90. package/dist/testing/cross_backend/cell_grant_role.js +34 -1
  91. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  92. package/dist/testing/cross_backend/cell_relations.js +53 -1
  93. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  94. package/dist/testing/cross_backend/conformance_table.js +2 -1
  95. package/dist/testing/cross_backend/query_shape.d.ts +10 -0
  96. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -0
  97. package/dist/testing/cross_backend/query_shape.js +128 -0
  98. package/dist/testing/cross_backend/setup.js +1 -1
  99. package/dist/testing/cross_backend/token_lifetime.d.ts +15 -0
  100. package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -0
  101. package/dist/testing/cross_backend/token_lifetime.js +99 -0
  102. package/dist/testing/cross_backend/token_scope_surface.js +1 -1
  103. package/dist/testing/daemon_token_rotation.d.ts +83 -0
  104. package/dist/testing/daemon_token_rotation.d.ts.map +1 -0
  105. package/dist/testing/daemon_token_rotation.js +99 -0
  106. package/dist/testing/integration.d.ts.map +1 -1
  107. package/dist/testing/integration.js +15 -3
  108. package/dist/testing/stubs.d.ts.map +1 -1
  109. package/dist/testing/stubs.js +4 -4
  110. package/dist/ui/AccountSessions.svelte +0 -5
  111. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
  112. package/dist/ui/AdminOverview.svelte +7 -4
  113. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  114. package/dist/ui/AdminSessions.svelte +0 -5
  115. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  116. package/dist/ui/CLAUDE.md +9 -4
  117. package/dist/ui/table_state.svelte.d.ts +18 -5
  118. package/dist/ui/table_state.svelte.d.ts.map +1 -1
  119. package/dist/ui/table_state.svelte.js +22 -7
  120. package/dist/ui/ui_format.d.ts.map +1 -1
  121. package/dist/ui/ui_format.js +4 -0
  122. package/package.json +1 -1
@@ -1,8 +1,12 @@
1
1
  /**
2
- * Daemon token rotation, persistence, and middleware.
2
+ * Daemon token middleware — the credential **consumer**.
3
3
  *
4
- * Manages the lifecycle of filesystem-resident daemon tokens: writing to disk,
5
- * rotation on an interval, and HTTP middleware for authentication.
4
+ * Validates a presented `X-Daemon-Token` and resolves the keeper account.
5
+ * The producer half (rotation + file persistence) lives in
6
+ * `testing/daemon_token_rotation.ts` behind the dev-env guard: no production
7
+ * assembly mints daemon tokens — the credential's only remaining role is the
8
+ * cross-process test harness's keeper channel — mirroring the Rust spine,
9
+ * whose producer is confined to `fuz_testing`.
6
10
  *
7
11
  * Pure token primitives (schema, generation, validation) live in `auth/daemon_token.ts`.
8
12
  * See docs/identity.md for design rationale.
@@ -11,40 +15,8 @@
11
15
  */
12
16
  import type { MiddlewareHandler } from 'hono';
13
17
  import type { Logger } from '@fuzdev/fuz_util/log.ts';
14
- import { type FsWriteDeps, type FsRemoveDeps, type EnvDeps } from '../runtime/deps.js';
15
18
  import type { QueryDeps } from '../db/query_deps.js';
16
19
  import { type DaemonTokenState } from './daemon_token.js';
17
- /** Default rotation interval in milliseconds (30 seconds). */
18
- export declare const DEFAULT_ROTATION_INTERVAL_MS = 30000;
19
- /** Deps for writing the daemon token to disk. */
20
- export type DaemonTokenWriteDeps = Pick<EnvDeps, 'env_get'> & Pick<FsWriteDeps, 'mkdir' | 'write_text_file' | 'rename'> & {
21
- /** Set file permissions. Optional — consumers provide when available (e.g. `Deno.chmod`). */
22
- chmod?: (path: string, mode: number) => Promise<void>;
23
- };
24
- /**
25
- * Get the daemon token file path (`~/.{name}/run/daemon_token`).
26
- *
27
- * @param runtime - runtime with `env_get` capability
28
- * @param name - application name
29
- * @returns path to `daemon_token`, or `null` if `$HOME` is not set
30
- */
31
- export declare const get_daemon_token_path: (runtime: Pick<EnvDeps, "env_get">, name: string) => string | null;
32
- /**
33
- * Write the current token to disk atomically.
34
- *
35
- * Uses `write_file_atomic` (temp file + rename) and optionally sets mode 0600.
36
- *
37
- * On-disk format is JSON `{"token": "..."}` — the wrapper leaves room for
38
- * future fields (rotated_at, version) without changing every reader. Both
39
- * the TS cross-backend harness reader (`spawn_backend.read_daemon_token`)
40
- * and the Rust daemon-token writer match this shape.
41
- *
42
- * @param runtime - runtime with file write capabilities
43
- * @param token_path - path to write the token
44
- * @param token - the raw token string
45
- * @mutates filesystem - writes `token_path` atomically and `chmod 0600` when supported
46
- */
47
- export declare const write_daemon_token: (runtime: DaemonTokenWriteDeps, token_path: string, token: string) => Promise<void>;
48
20
  /**
49
21
  * Resolve the keeper account ID by querying for the account with an active
50
22
  * keeper role_grant.
@@ -61,40 +33,6 @@ export declare const write_daemon_token: (runtime: DaemonTokenWriteDeps, token_p
61
33
  * @returns the keeper account ID, or `null` if no keeper exists yet (pre-bootstrap)
62
34
  */
63
35
  export declare const resolve_keeper_account_id: (deps: QueryDeps) => Promise<string | null>;
64
- /** Options for daemon token rotation. */
65
- export interface DaemonTokenRotationOptions {
66
- /**
67
- * Absolute path the token file is written to. Caller computes from
68
- * its own conventions — e.g. `get_daemon_token_path(runtime, app_name)`
69
- * for the standard `~/.{name}/run/daemon_token` layout, or a path
70
- * derived from `PUBLIC_<APP>_DIR` for cross-process test setups that
71
- * isolate the app dir to a tmpdir.
72
- */
73
- token_path: string;
74
- /** Rotation interval in ms. Default: `30000` (30s). */
75
- rotation_interval_ms?: number;
76
- }
77
- /** Result of starting daemon token rotation. */
78
- export interface DaemonTokenRotation {
79
- /** The mutable runtime state. Pass to `create_daemon_token_middleware`. */
80
- state: DaemonTokenState;
81
- /** Stop rotation, clean up the interval, and delete the token file. Call on graceful shutdown. */
82
- stop: () => Promise<void>;
83
- }
84
- /**
85
- * Start daemon token rotation.
86
- *
87
- * Generates an initial token, writes it to disk, resolves the keeper account,
88
- * and sets up periodic rotation. Returns the mutable state object and a stop function.
89
- *
90
- * @param runtime - runtime with file and remove capabilities
91
- * @param deps - query dependencies for resolving keeper account
92
- * @param options - rotation configuration
93
- * @param log - the logger instance
94
- * @returns rotation state and stop function
95
- * @mutates filesystem - writes the token file on each rotation; `stop` removes it
96
- */
97
- export declare const start_daemon_token_rotation: (runtime: DaemonTokenWriteDeps & FsRemoveDeps, deps: QueryDeps, options: DaemonTokenRotationOptions, log: Logger) => Promise<DaemonTokenRotation>;
98
36
  /**
99
37
  * Create middleware that authenticates via daemon token.
100
38
  *
@@ -1 +1 @@
1
- {"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAYvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,8DAA8D;AAC9D,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,iDAAiD;AACjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAC1D,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,iBAAiB,GAAG,QAAQ,CAAC,GAAG;IAC3D,6FAA6F;IAC7F,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EACjC,MAAM,MAAM,KACV,MAAM,GAAG,IAGX,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAKd,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IAC1C;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,gBAAgB,CAAC;IACxB,kGAAkG;IAClG,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,oBAAoB,GAAG,YAAY,EAC5C,MAAM,SAAS,EACf,SAAS,0BAA0B,EACnC,KAAK,MAAM,KACT,OAAO,CAAC,mBAAmB,CAqD7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,EACf,KAAK,MAAM,KACT,iBA0EF,CAAC"}
1
+ {"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAWtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,EACf,KAAK,MAAM,KACT,iBA0EF,CAAC"}
@@ -1,8 +1,12 @@
1
1
  /**
2
- * Daemon token rotation, persistence, and middleware.
2
+ * Daemon token middleware — the credential **consumer**.
3
3
  *
4
- * Manages the lifecycle of filesystem-resident daemon tokens: writing to disk,
5
- * rotation on an interval, and HTTP middleware for authentication.
4
+ * Validates a presented `X-Daemon-Token` and resolves the keeper account.
5
+ * The producer half (rotation + file persistence) lives in
6
+ * `testing/daemon_token_rotation.ts` behind the dev-env guard: no production
7
+ * assembly mints daemon tokens — the credential's only remaining role is the
8
+ * cross-process test harness's keeper channel — mirroring the Rust spine,
9
+ * whose producer is confined to `fuz_testing`.
6
10
  *
7
11
  * Pure token primitives (schema, generation, validation) live in `auth/daemon_token.ts`.
8
12
  * See docs/identity.md for design rationale.
@@ -10,49 +14,12 @@
10
14
  * @module
11
15
  */
12
16
  import { DEV } from 'esm-env';
13
- import {} from '../runtime/deps.js';
14
- import { write_file_atomic } from '../runtime/fs.js';
15
- import { get_app_dir } from '../cli/config.js';
16
17
  import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY } from '../hono_context.js';
17
18
  import { token_scope_full } from './token_scope.js';
18
19
  import { is_browser_context } from '../http/origin.js';
19
20
  import { query_role_grant_find_account_id_for_role } from './role_grant_queries.js';
20
21
  import { ROLE_KEEPER } from './role_schema.js';
21
- import { DaemonToken, DAEMON_TOKEN_HEADER, generate_daemon_token, validate_daemon_token } from './daemon_token.js';
22
- /** Default rotation interval in milliseconds (30 seconds). */
23
- export const DEFAULT_ROTATION_INTERVAL_MS = 30_000;
24
- /**
25
- * Get the daemon token file path (`~/.{name}/run/daemon_token`).
26
- *
27
- * @param runtime - runtime with `env_get` capability
28
- * @param name - application name
29
- * @returns path to `daemon_token`, or `null` if `$HOME` is not set
30
- */
31
- export const get_daemon_token_path = (runtime, name) => {
32
- const app_dir = get_app_dir(runtime, name);
33
- return app_dir ? `${app_dir}/run/daemon_token` : null;
34
- };
35
- /**
36
- * Write the current token to disk atomically.
37
- *
38
- * Uses `write_file_atomic` (temp file + rename) and optionally sets mode 0600.
39
- *
40
- * On-disk format is JSON `{"token": "..."}` — the wrapper leaves room for
41
- * future fields (rotated_at, version) without changing every reader. Both
42
- * the TS cross-backend harness reader (`spawn_backend.read_daemon_token`)
43
- * and the Rust daemon-token writer match this shape.
44
- *
45
- * @param runtime - runtime with file write capabilities
46
- * @param token_path - path to write the token
47
- * @param token - the raw token string
48
- * @mutates filesystem - writes `token_path` atomically and `chmod 0600` when supported
49
- */
50
- export const write_daemon_token = async (runtime, token_path, token) => {
51
- await write_file_atomic(runtime, token_path, JSON.stringify({ token }) + '\n');
52
- if (runtime.chmod) {
53
- await runtime.chmod(token_path, 0o600);
54
- }
55
- };
22
+ import { DaemonToken, DAEMON_TOKEN_HEADER, validate_daemon_token } from './daemon_token.js';
56
23
  /**
57
24
  * Resolve the keeper account ID by querying for the account with an active
58
25
  * keeper role_grant.
@@ -71,69 +38,6 @@ export const write_daemon_token = async (runtime, token_path, token) => {
71
38
  export const resolve_keeper_account_id = async (deps) => {
72
39
  return query_role_grant_find_account_id_for_role(deps, ROLE_KEEPER);
73
40
  };
74
- /**
75
- * Start daemon token rotation.
76
- *
77
- * Generates an initial token, writes it to disk, resolves the keeper account,
78
- * and sets up periodic rotation. Returns the mutable state object and a stop function.
79
- *
80
- * @param runtime - runtime with file and remove capabilities
81
- * @param deps - query dependencies for resolving keeper account
82
- * @param options - rotation configuration
83
- * @param log - the logger instance
84
- * @returns rotation state and stop function
85
- * @mutates filesystem - writes the token file on each rotation; `stop` removes it
86
- */
87
- export const start_daemon_token_rotation = async (runtime, deps, options, log) => {
88
- const { token_path, rotation_interval_ms = DEFAULT_ROTATION_INTERVAL_MS } = options;
89
- // ensure parent directory exists
90
- const last_slash = token_path.lastIndexOf('/');
91
- if (last_slash > 0) {
92
- await runtime.mkdir(token_path.slice(0, last_slash), { recursive: true });
93
- }
94
- // resolve keeper account (may be null pre-bootstrap; the middleware
95
- // lazily refreshes on the first null hit to cover the
96
- // rotation-starts-before-bootstrap case)
97
- const keeper_account_id = await resolve_keeper_account_id(deps);
98
- // generate initial token and write to disk
99
- const initial_token = generate_daemon_token();
100
- await write_daemon_token(runtime, token_path, initial_token);
101
- const state = {
102
- current_token: initial_token,
103
- previous_token: null,
104
- rotated_at: new Date(),
105
- keeper_account_id
106
- };
107
- let writing = false;
108
- const interval_id = setInterval(async () => {
109
- if (writing)
110
- return; // skip if previous rotation write still in progress
111
- writing = true;
112
- try {
113
- const new_token = generate_daemon_token();
114
- state.previous_token = state.current_token;
115
- state.current_token = new_token;
116
- state.rotated_at = new Date();
117
- await write_daemon_token(runtime, token_path, new_token);
118
- }
119
- catch (err) {
120
- log.error('Failed to write rotated token:', err);
121
- }
122
- finally {
123
- writing = false;
124
- }
125
- }, rotation_interval_ms);
126
- const stop = async () => {
127
- clearInterval(interval_id);
128
- try {
129
- await runtime.remove(token_path);
130
- }
131
- catch {
132
- // already removed or never written
133
- }
134
- };
135
- return { state, stop };
136
- };
137
41
  /**
138
42
  * Create middleware that authenticates via daemon token.
139
43
  *
@@ -12,7 +12,6 @@ import type { FactStore } from '@fuzdev/fuz_util/fact_store.ts';
12
12
  import type { Keyring } from './keyring.js';
13
13
  import type { PasswordHashDeps } from './password.js';
14
14
  import type { Db } from '../db/db.js';
15
- import type { StatResult } from '../runtime/deps.js';
16
15
  import type { AuditEmitter } from './audit_emitter.js';
17
16
  /**
18
17
  * Stateless capabilities bundle for fuz_app backends.
@@ -21,10 +20,15 @@ import type { AuditEmitter } from './audit_emitter.js';
21
20
  * Does not contain config (static values) or runtime state (mutable refs).
22
21
  */
23
22
  export interface AppDeps {
24
- /** Get file/directory stats, or null if path doesn't exist. */
25
- stat: (path: string) => Promise<StatResult | null>;
26
- /** Read a file as text. */
27
- read_text_file: (path: string) => Promise<string>;
23
+ /**
24
+ * Hardened secret-file read — used for the bootstrap token (the file that
25
+ * mints the keeper account). Production wiring passes the runtime's
26
+ * `read_secure_file` (`FsSecureReadDeps`), which rejects symlinks,
27
+ * group/other-accessible modes, and oversized files; both the boot-time
28
+ * availability probe and the request-time read go through this one
29
+ * capability so the probe can never be laxer than the read it gates.
30
+ */
31
+ read_secure_file: (path: string) => Promise<Uint8Array>;
28
32
  /** Delete a file. */
29
33
  delete_file: (path: string) => Promise<void>;
30
34
  /** HMAC-SHA256 cookie signing keyring. */
@@ -1 +1 @@
1
- {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACvB,+DAA+D;IAC/D,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,2BAA2B;IAC3B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yBAAyB;IACzB,EAAE,EAAE,EAAE,CAAC;IACP,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,KAAK,EAAE,YAAY,CAAC;CACpB"}
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACvB;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yBAAyB;IACzB,EAAE,EAAE,EAAE,CAAC;IACP,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,KAAK,EAAE,YAAY,CAAC;CACpB"}
@@ -35,7 +35,7 @@ export const create_auth_middleware_specs = async (deps, options) => {
35
35
  import('./bearer_auth.js')
36
36
  ]);
37
37
  const session_middleware = create_session_middleware(keyring, session_options);
38
- const request_context_middleware = create_request_context_middleware(query_deps, deps.log);
38
+ const request_context_middleware = create_request_context_middleware(query_deps);
39
39
  const bearer_auth_middleware = create_bearer_auth_middleware(query_deps, deps.log);
40
40
  const specs = [
41
41
  {
@@ -37,6 +37,13 @@
37
37
  * DBs (no long-lived data) may still drop + re-bootstrap freely on a break —
38
38
  * the freeze is the contract for the deployed chain, not local iteration.
39
39
  *
40
+ * **Every entry is twinned on the Rust spine** (`fuz_auth/src/migrations.rs`),
41
+ * and the two names must match byte for byte: the `_testing_migration_tracker`
42
+ * parity gate compares the `(namespace, name, sequence)` rows both spines
43
+ * record, and a divergence there breaks the swap-freely invariant (a consumer
44
+ * pointing either implementation at one database). Append on both sides in the
45
+ * same change, so no entry below repeats the rule.
46
+ *
40
47
  * @module
41
48
  */
42
49
  import type { Migration, MigrationNamespace } from '../db/migrate.js';
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AA+BH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CAAC,MAAM,CAA8B,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CAuJ5C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AA+BH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CAAC,MAAM,CAA8B,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CAmK5C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
@@ -37,6 +37,13 @@
37
37
  * DBs (no long-lived data) may still drop + re-bootstrap freely on a break —
38
38
  * the freeze is the contract for the deployed chain, not local iteration.
39
39
  *
40
+ * **Every entry is twinned on the Rust spine** (`fuz_auth/src/migrations.rs`),
41
+ * and the two names must match byte for byte: the `_testing_migration_tracker`
42
+ * parity gate compares the `(namespace, name, sequence)` rows both spines
43
+ * record, and a divergence there breaks the swap-freely invariant (a consumer
44
+ * pointing either implementation at one database). Append on both sides in the
45
+ * same change, so no entry below repeats the rule.
46
+ *
40
47
  * @module
41
48
  */
42
49
  import { ACCOUNT_SCHEMA, ACCOUNT_EMAIL_INDEX, ACCOUNT_USERNAME_CI_INDEX, ACTOR_SCHEMA, ACTOR_INDEX, ACTOR_NAME_LOWER_INDEX, ROLE_GRANT_SCHEMA, ROLE_GRANT_INDEXES, AUTH_SESSION_SCHEMA, AUTH_SESSION_INDEXES, API_TOKEN_SCHEMA, API_TOKEN_INDEX, BOOTSTRAP_LOCK_SCHEMA, BOOTSTRAP_LOCK_SEED, INVITE_SCHEMA, INVITE_INDEXES, APP_SETTINGS_SCHEMA, APP_SETTINGS_SEED } from './auth_ddl.js';
@@ -156,10 +163,6 @@ export const auth_migrations = [
156
163
  },
157
164
  // v2: per-credential token scoping — `api_token.scope JSONB NOT NULL`.
158
165
  //
159
- // Name must match the Rust twin's migration byte for byte
160
- // (`fuz_auth/src/migrations.rs`) or the `_testing_migration_tracker` parity
161
- // gate fails and the swap-freely invariant breaks.
162
- //
163
166
  // Three statements, and the middle one is the whole compatibility story:
164
167
  //
165
168
  // 1. Add the column nullable, so the ALTER is instant on a populated table.
@@ -201,15 +204,29 @@ export const auth_migrations = [
201
204
  // would mean the two impls could silently disagree about which credential
202
205
  // answered; the constraint makes that unrepresentable instead of merely
203
206
  // improbable.
204
- //
205
- // The name must match the Rust twin's `api_token_hash_unique_index` byte for
206
- // byte or the `_testing_migration_tracker` parity gate fails and the
207
- // swap-freely invariant breaks.
208
207
  {
209
208
  name: 'api_token_hash_unique_index',
210
209
  up: async (db) => {
211
210
  await db.query('CREATE UNIQUE INDEX IF NOT EXISTS idx_api_token_hash ON api_token(token_hash)');
212
211
  }
212
+ },
213
+ // The GIN the per-cell audit timeline assumes.
214
+ //
215
+ // `full_auth_schema` ships only btree indexes (seq, account, event_type,
216
+ // targets), but `db/cell_audit_queries.ts` reconstructs the per-cell
217
+ // timeline by OR-ing `metadata @> '{...}'::jsonb` containment clauses —
218
+ // sequential scans without this index, degrading with audit volume.
219
+ //
220
+ // `jsonb_path_ops` rather than the default `jsonb_ops`: it serves exactly
221
+ // the `@>` containment operator these queries use, with a smaller, faster
222
+ // index. Server PG and PGlite honor the opclass; PG-compatible engines
223
+ // without operator classes parse and ignore it, serving `@>` from their
224
+ // generic GIN.
225
+ {
226
+ name: 'audit_log_metadata_gin_index',
227
+ up: async (db) => {
228
+ await db.query('CREATE INDEX IF NOT EXISTS idx_audit_log_metadata ON audit_log USING gin (metadata jsonb_path_ops)');
229
+ }
213
230
  }
214
231
  ];
215
232
  /** Pre-composed migration namespace for auth tables. */
@@ -35,7 +35,6 @@
35
35
  * @module
36
36
  */
37
37
  import type { Context, MiddlewareHandler } from 'hono';
38
- import type { Logger } from '@fuzdev/fuz_util/log.ts';
39
38
  import { type Account, type Actor, type RoleGrant } from './account_schema.js';
40
39
  import type { QueryDeps } from '../db/query_deps.js';
41
40
  import type { RouteSpec } from '../http/route_spec.js';
@@ -224,11 +223,10 @@ export declare const resolve_acting_actor: (deps: QueryDeps, account_id: string,
224
223
  * `require_auth` / `require_role` enforce.
225
224
  *
226
225
  * @param deps - query dependencies (pool-level db for middleware)
227
- * @param log - the logger instance
228
226
  * @param session_context_key - the Hono context key where session middleware stored the session token
229
227
  * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_SESSION_TOKEN_HASH_KEY`, and `AUTH_API_TOKEN_ID_KEY`
230
228
  */
231
- export declare const create_request_context_middleware: (deps: QueryDeps, log: Logger, session_context_key?: string) => MiddlewareHandler;
229
+ export declare const create_request_context_middleware: (deps: QueryDeps, session_context_key?: string) => MiddlewareHandler;
232
230
  /**
233
231
  * Refuse a non-RPC surface to a narrowed token — rule 3, for callers that are
234
232
  * not route specs. Under the Rust twin's name
@@ -1 +1 @@
1
- {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"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"}
@@ -35,7 +35,7 @@
35
35
  * @module
36
36
  */
37
37
  import { is_role_grant_active } from './account_schema.js';
38
- import { hash_session_token, session_touch_fire_and_forget, query_session_get_valid } from './session_queries.js';
38
+ import { hash_session_token, query_session_get_valid } from './session_queries.js';
39
39
  import { query_account_by_id, query_actor_by_id, query_active_actors_by_account } from './account_queries.js';
40
40
  import { query_role_grant_find_active_for_actor } from './role_grant_queries.js';
41
41
  import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
@@ -208,11 +208,10 @@ export const resolve_acting_actor = async (deps, account_id, acting_actor_id) =>
208
208
  * `require_auth` / `require_role` enforce.
209
209
  *
210
210
  * @param deps - query dependencies (pool-level db for middleware)
211
- * @param log - the logger instance
212
211
  * @param session_context_key - the Hono context key where session middleware stored the session token
213
212
  * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_SESSION_TOKEN_HASH_KEY`, and `AUTH_API_TOKEN_ID_KEY`
214
213
  */
215
- export const create_request_context_middleware = (deps, log, session_context_key = 'auth_session_id') => {
214
+ export const create_request_context_middleware = (deps, session_context_key = 'auth_session_id') => {
216
215
  return async (c, next) => {
217
216
  c.set(REQUEST_CONTEXT_KEY, null);
218
217
  c.set(ACCOUNT_ID_KEY, null);
@@ -235,8 +234,6 @@ export const create_request_context_middleware = (deps, log, session_context_key
235
234
  // A session is full account authority by construction.
236
235
  c.set(TOKEN_SCOPE_KEY, token_scope_full());
237
236
  c.set(AUTH_SESSION_TOKEN_HASH_KEY, token_hash);
238
- // Touch session (fire-and-forget, don't block the request)
239
- void session_touch_fire_and_forget(deps, token_hash, c.var.pending_effects, log);
240
237
  await next();
241
238
  };
242
239
  };
@@ -10,16 +10,15 @@
10
10
  * @module
11
11
  */
12
12
  import type { Keyring } from './keyring.js';
13
- /** Cookie max age in seconds (30 days — aligned with AUTH_SESSION_LIFETIME_MS). */
14
- export declare const SESSION_AGE_MAX: number;
15
13
  /**
16
- * Threshold (seconds) at which `process_session_cookie` re-signs a still-valid
17
- * cookie to extend its embedded expiration. Mirrors the DB-side
18
- * `AUTH_SESSION_EXTEND_THRESHOLD_MS` so a continuously-active user's cookie
19
- * stays in sync with their server-side session lifetime. Set
20
- * `SessionOptions.refresh_threshold_seconds = 0` to disable.
14
+ * Cookie max age in seconds (30 days — aligned with AUTH_SESSION_LIFETIME_MS).
15
+ *
16
+ * This is an **absolute** session lifetime: the cookie is signed once at
17
+ * mint (`expires_at = mint + max_age`) and never re-signed, matching the DB
18
+ * row's fixed `expires_at`. There is deliberately no sliding renewal on
19
+ * either leg — see `docs/security.md` §Session Security.
21
20
  */
22
- export declare const SESSION_REFRESH_THRESHOLD_S: number;
21
+ export declare const SESSION_AGE_MAX: number;
23
22
  /**
24
23
  * Cookie options for session cookies.
25
24
  */
@@ -91,14 +90,6 @@ export interface SessionOptions<TIdentity> {
91
90
  * cannot carry `maxAge` so the two values can't drift.
92
91
  */
93
92
  max_age?: number;
94
- /**
95
- * Threshold (seconds) for expiration-based cookie refresh. When a parsed
96
- * cookie's `expires_at - now <= refresh_threshold_seconds`,
97
- * `process_session_cookie` returns `action: 'refresh'` with a freshly-signed
98
- * value (extending the embedded expiration by `max_age`). Defaults to
99
- * `SESSION_REFRESH_THRESHOLD_S` (1 day). Set to `0` to disable.
100
- */
101
- refresh_threshold_seconds?: number;
102
93
  cookie_options?: Partial<Omit<SessionCookieOptions, 'maxAge'>>;
103
94
  /** Encode identity into the cookie payload (before the `:expires_at` suffix). */
104
95
  encode_identity: (identity: TIdentity) => string;
@@ -111,26 +102,14 @@ export interface SessionOptions<TIdentity> {
111
102
  export interface ParsedSession<TIdentity> {
112
103
  /** The decoded identity. */
113
104
  identity: TIdentity;
114
- /** True if verified with a non-primary key (needs re-signing). */
115
- should_refresh_signature: boolean;
116
- /**
117
- * True if the embedded `expires_at` is within
118
- * `options.refresh_threshold_seconds` of `now`. Signals that the cookie is
119
- * valid but should be re-signed to extend its lifetime — mirrors
120
- * `query_session_touch`'s DB-side extension so the cookie and server
121
- * session don't drift. Always false when the threshold is `0`.
122
- */
123
- should_refresh_expiration: boolean;
124
- /** Index of the key that verified the signature. */
105
+ /** Index of the key that verified the signature (0 = primary). */
125
106
  key_index: number;
126
107
  }
127
108
  /**
128
109
  * Parse a signed session cookie value.
129
110
  *
130
111
  * The signed value format is `${encode(identity)}:${expires_at}`.
131
- * Tries all keys in order to support key rotation. The result's
132
- * `should_refresh_expiration` flag fires when the cookie is within
133
- * `options.refresh_threshold_seconds` of `expires_at`.
112
+ * Tries all keys in order to support key rotation.
134
113
  *
135
114
  * @param signed_value - the raw cookie value (signed)
136
115
  * @param keyring - key ring for verification
@@ -159,9 +138,7 @@ export interface ProcessSessionResult<TIdentity> {
159
138
  /** Whether the session is valid. */
160
139
  valid: boolean;
161
140
  /** Action the adapter should take. */
162
- action: 'none' | 'clear' | 'refresh';
163
- /** New signed value when action is 'refresh'. */
164
- new_signed_value?: string;
141
+ action: 'none' | 'clear';
165
142
  /** The decoded identity if the cookie was valid. */
166
143
  identity?: TIdentity;
167
144
  }
@@ -181,9 +158,14 @@ export declare const fuz_session_config: SessionOptions<string>;
181
158
  /**
182
159
  * Process a session cookie and determine what action to take.
183
160
  *
184
- * `action: 'refresh'` fires on key rotation **or** impending expiration
185
- * (within `options.refresh_threshold_seconds`); both produce a freshly-signed
186
- * `new_signed_value`.
161
+ * There is deliberately no refresh action: any re-sign computes a fresh
162
+ * `now + max_age` expiry, extending the cookie past the DB row's fixed
163
+ * `expires_at` — the holder would go silently anonymous while carrying a
164
+ * self-perpetuating cookie. The absolute lifetime is one signing at mint,
165
+ * on both legs, matching the Rust spine. A cookie signed by a retired
166
+ * (non-primary) key keeps verifying until the key leaves the keyring;
167
+ * retired keys are safe to drop after `SESSION_AGE_MAX` (see
168
+ * `docs/security.md` §Cookie Key Rotation).
187
169
  *
188
170
  * @param signed_value - the raw cookie value (may be undefined)
189
171
  * @param keyring - key ring for verification and signing
@@ -1 +1 @@
1
- {"version":3,"file":"session_cookie.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_cookie.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session_cookie.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,QAAoB,CAAC;AAQjD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,oBAMpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,WAAW,cAAc,CAAC,SAAS;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,iFAAiF;IACjF,eAAe,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,MAAM,CAAC;IACjD,mEAAmE;IACnE,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;CACvD;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,SAAS;IACvC,4BAA4B;IAC5B,QAAQ,EAAE,SAAS,CAAC;IACpB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,GAAU,SAAS,EAC5C,cAAc,MAAM,GAAG,SAAS,EAChC,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,cAAc,MAAM,KAClB,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,CA+BrD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GAAU,SAAS,EAC1D,SAAS,OAAO,EAChB,UAAU,SAAS,EACnB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,SAAS;IAC9C,oCAAoC;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GAAI,aAAa,MAAM,KAAG,cAAc,CAAC,MAAM,CAK/E,CAAC;AAEH,iDAAiD;AACjD,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAE/F;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,sBAAsB,GAAU,SAAS,EACrD,cAAc,MAAM,GAAG,SAAS,EAChC,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,cAAc,MAAM,KAClB,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAgBzC,CAAC"}
@@ -9,16 +9,15 @@
9
9
  *
10
10
  * @module
11
11
  */
12
- /** Cookie max age in seconds (30 days — aligned with AUTH_SESSION_LIFETIME_MS). */
13
- export const SESSION_AGE_MAX = 60 * 60 * 24 * 30;
14
12
  /**
15
- * Threshold (seconds) at which `process_session_cookie` re-signs a still-valid
16
- * cookie to extend its embedded expiration. Mirrors the DB-side
17
- * `AUTH_SESSION_EXTEND_THRESHOLD_MS` so a continuously-active user's cookie
18
- * stays in sync with their server-side session lifetime. Set
19
- * `SessionOptions.refresh_threshold_seconds = 0` to disable.
13
+ * Cookie max age in seconds (30 days — aligned with AUTH_SESSION_LIFETIME_MS).
14
+ *
15
+ * This is an **absolute** session lifetime: the cookie is signed once at
16
+ * mint (`expires_at = mint + max_age`) and never re-signed, matching the DB
17
+ * row's fixed `expires_at`. There is deliberately no sliding renewal on
18
+ * either leg — see `docs/security.md` §Session Security.
20
19
  */
21
- export const SESSION_REFRESH_THRESHOLD_S = 60 * 60 * 24;
20
+ export const SESSION_AGE_MAX = 60 * 60 * 24 * 30;
22
21
  /** Separator between identity payload and expires_at in signed value. */
23
22
  const VALUE_SEPARATOR = ':';
24
23
  /** Strict integer matcher for the embedded `expires_at` field. */
@@ -42,9 +41,7 @@ export const session_cookie_options = {
42
41
  * Parse a signed session cookie value.
43
42
  *
44
43
  * The signed value format is `${encode(identity)}:${expires_at}`.
45
- * Tries all keys in order to support key rotation. The result's
46
- * `should_refresh_expiration` flag fires when the cookie is within
47
- * `options.refresh_threshold_seconds` of `expires_at`.
44
+ * Tries all keys in order to support key rotation.
48
45
  *
49
46
  * @param signed_value - the raw cookie value (signed)
50
47
  * @param keyring - key ring for verification
@@ -79,12 +76,8 @@ export const parse_session = async (signed_value, keyring, options, now_seconds)
79
76
  const now = now_seconds ?? Math.floor(Date.now() / 1000);
80
77
  if (expires_at <= now)
81
78
  return null;
82
- const refresh_threshold = options.refresh_threshold_seconds ?? SESSION_REFRESH_THRESHOLD_S;
83
- const should_refresh_expiration = refresh_threshold > 0 && expires_at - now <= refresh_threshold;
84
79
  return {
85
80
  identity,
86
- should_refresh_signature: result.key_index > 0,
87
- should_refresh_expiration,
88
81
  key_index: result.key_index
89
82
  };
90
83
  };
@@ -128,9 +121,14 @@ export const fuz_session_config = create_session_config('fuz_session');
128
121
  /**
129
122
  * Process a session cookie and determine what action to take.
130
123
  *
131
- * `action: 'refresh'` fires on key rotation **or** impending expiration
132
- * (within `options.refresh_threshold_seconds`); both produce a freshly-signed
133
- * `new_signed_value`.
124
+ * There is deliberately no refresh action: any re-sign computes a fresh
125
+ * `now + max_age` expiry, extending the cookie past the DB row's fixed
126
+ * `expires_at` — the holder would go silently anonymous while carrying a
127
+ * self-perpetuating cookie. The absolute lifetime is one signing at mint,
128
+ * on both legs, matching the Rust spine. A cookie signed by a retired
129
+ * (non-primary) key keeps verifying until the key leaves the keyring;
130
+ * retired keys are safe to drop after `SESSION_AGE_MAX` (see
131
+ * `docs/security.md` §Cookie Key Rotation).
134
132
  *
135
133
  * @param signed_value - the raw cookie value (may be undefined)
136
134
  * @param keyring - key ring for verification and signing
@@ -149,13 +147,5 @@ export const process_session_cookie = async (signed_value, keyring, options, now
149
147
  // Invalid cookie - should be cleared
150
148
  return { valid: false, action: 'clear' };
151
149
  }
152
- // Valid session — re-sign if the verifying key isn't primary OR if the
153
- // embedded expiration is approaching the threshold. The latter mirrors
154
- // `query_session_touch`'s DB-side extension so a continuously-active user's
155
- // cookie doesn't hard-expire while their server session is still alive.
156
- if (parsed.should_refresh_signature || parsed.should_refresh_expiration) {
157
- const new_signed_value = await create_session_cookie_value(keyring, parsed.identity, options, now);
158
- return { valid: true, action: 'refresh', new_signed_value, identity: parsed.identity };
159
- }
160
150
  return { valid: true, action: 'none', identity: parsed.identity };
161
151
  };