@fuzdev/fuz_app 0.109.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 (84) hide show
  1. package/dist/auth/CLAUDE.md +6 -5
  2. package/dist/auth/account_action_specs.d.ts +18 -0
  3. package/dist/auth/account_action_specs.d.ts.map +1 -1
  4. package/dist/auth/account_action_specs.js +14 -1
  5. package/dist/auth/account_actions.d.ts.map +1 -1
  6. package/dist/auth/account_actions.js +6 -2
  7. package/dist/auth/bootstrap_account.d.ts +7 -2
  8. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  9. package/dist/auth/bootstrap_account.js +9 -4
  10. package/dist/auth/bootstrap_routes.d.ts +12 -4
  11. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  12. package/dist/auth/bootstrap_routes.js +15 -6
  13. package/dist/auth/daemon_token_middleware.d.ts +7 -69
  14. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  15. package/dist/auth/daemon_token_middleware.js +8 -104
  16. package/dist/auth/deps.d.ts +9 -5
  17. package/dist/auth/deps.d.ts.map +1 -1
  18. package/dist/auth/middleware.js +1 -1
  19. package/dist/auth/request_context.d.ts +1 -3
  20. package/dist/auth/request_context.d.ts.map +1 -1
  21. package/dist/auth/request_context.js +2 -5
  22. package/dist/auth/session_cookie.d.ts +18 -36
  23. package/dist/auth/session_cookie.d.ts.map +1 -1
  24. package/dist/auth/session_cookie.js +16 -26
  25. package/dist/auth/session_middleware.d.ts +1 -1
  26. package/dist/auth/session_middleware.d.ts.map +1 -1
  27. package/dist/auth/session_middleware.js +1 -4
  28. package/dist/auth/session_queries.d.ts +14 -30
  29. package/dist/auth/session_queries.d.ts.map +1 -1
  30. package/dist/auth/session_queries.js +15 -45
  31. package/dist/auth/token_lifetime.d.ts +51 -0
  32. package/dist/auth/token_lifetime.d.ts.map +1 -0
  33. package/dist/auth/token_lifetime.js +54 -0
  34. package/dist/dev/setup.d.ts +5 -9
  35. package/dist/dev/setup.d.ts.map +1 -1
  36. package/dist/dev/setup.js +18 -21
  37. package/dist/runtime/deno.d.ts.map +1 -1
  38. package/dist/runtime/deno.js +27 -1
  39. package/dist/runtime/deps.d.ts +38 -4
  40. package/dist/runtime/deps.d.ts.map +1 -1
  41. package/dist/runtime/fs.d.ts +24 -5
  42. package/dist/runtime/fs.d.ts.map +1 -1
  43. package/dist/runtime/fs.js +38 -7
  44. package/dist/runtime/mock.d.ts +7 -0
  45. package/dist/runtime/mock.d.ts.map +1 -1
  46. package/dist/runtime/mock.js +41 -1
  47. package/dist/runtime/node.d.ts.map +1 -1
  48. package/dist/runtime/node.js +7 -1
  49. package/dist/runtime/secure_file.d.ts +71 -0
  50. package/dist/runtime/secure_file.d.ts.map +1 -0
  51. package/dist/runtime/secure_file.js +125 -0
  52. package/dist/server/app_backend.d.ts +6 -5
  53. package/dist/server/app_backend.d.ts.map +1 -1
  54. package/dist/server/app_backend.js +2 -3
  55. package/dist/testing/CLAUDE.md +15 -1
  56. package/dist/testing/admin_integration.d.ts.map +1 -1
  57. package/dist/testing/admin_integration.js +10 -2
  58. package/dist/testing/app_server.d.ts.map +1 -1
  59. package/dist/testing/app_server.js +13 -8
  60. package/dist/testing/audit_completeness.d.ts.map +1 -1
  61. package/dist/testing/audit_completeness.js +5 -1
  62. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  63. package/dist/testing/cross_backend/cell_grant_role.js +34 -1
  64. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  65. package/dist/testing/cross_backend/conformance_table.js +2 -1
  66. package/dist/testing/cross_backend/setup.js +1 -1
  67. package/dist/testing/cross_backend/token_lifetime.d.ts +15 -0
  68. package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -0
  69. package/dist/testing/cross_backend/token_lifetime.js +99 -0
  70. package/dist/testing/cross_backend/token_scope_surface.js +1 -1
  71. package/dist/testing/daemon_token_rotation.d.ts +83 -0
  72. package/dist/testing/daemon_token_rotation.d.ts.map +1 -0
  73. package/dist/testing/daemon_token_rotation.js +99 -0
  74. package/dist/testing/integration.d.ts.map +1 -1
  75. package/dist/testing/integration.js +15 -3
  76. package/dist/testing/stubs.d.ts.map +1 -1
  77. package/dist/testing/stubs.js +4 -4
  78. package/dist/ui/AccountSessions.svelte +0 -5
  79. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
  80. package/dist/ui/AdminOverview.svelte +7 -4
  81. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  82. package/dist/ui/AdminSessions.svelte +0 -5
  83. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  84. package/package.json +1 -1
@@ -0,0 +1,83 @@
1
+ import './assert_dev_env.js';
2
+ /**
3
+ * Daemon-token **producer** — rotation + file persistence, test-harness only.
4
+ *
5
+ * No production assembly mints daemon tokens: the credential's only remaining
6
+ * role is the cross-process test harness's keeper channel (`_testing_reset`
7
+ * etc.), so the producer lives here behind `assert_dev_env` where it cannot
8
+ * reach a production bundle. The *consumer* half — validating a presented
9
+ * `X-Daemon-Token` — stays in `auth/daemon_token_middleware.ts`, mirroring
10
+ * the Rust spine, whose producer is confined to `fuz_testing`.
11
+ *
12
+ * The token file is written atomically at mode `0600` via
13
+ * `write_file_atomic` (unique temp name + exclusive create), so it never
14
+ * exists group/other-readable — the old optional-`chmod` pattern let a
15
+ * default umask land it at `0644`.
16
+ *
17
+ * @module
18
+ */
19
+ import type { Logger } from '@fuzdev/fuz_util/log.ts';
20
+ import type { EnvDeps, FsWriteDeps, FsRemoveDeps } from '../runtime/deps.js';
21
+ import type { QueryDeps } from '../db/query_deps.js';
22
+ import { type DaemonTokenState } from '../auth/daemon_token.js';
23
+ /** Default rotation interval in milliseconds (30 seconds). */
24
+ export declare const DEFAULT_ROTATION_INTERVAL_MS = 30000;
25
+ /** Deps for writing the daemon token to disk. */
26
+ export type DaemonTokenWriteDeps = Pick<EnvDeps, 'env_get'> & Pick<FsWriteDeps, 'mkdir' | 'write_text_file' | 'rename'>;
27
+ /**
28
+ * Get the daemon token file path (`~/.{name}/run/daemon_token`).
29
+ *
30
+ * @param runtime - runtime with `env_get` capability
31
+ * @param name - application name
32
+ * @returns path to `daemon_token`, or `null` if `$HOME` is not set
33
+ */
34
+ export declare const get_daemon_token_path: (runtime: Pick<EnvDeps, "env_get">, name: string) => string | null;
35
+ /**
36
+ * Write the current token to disk atomically at mode `0600`.
37
+ *
38
+ * On-disk format is JSON `{"token": "..."}` — the wrapper leaves room for
39
+ * future fields (rotated_at, version) without changing every reader. Both
40
+ * the TS cross-backend harness reader (`spawn_backend.read_daemon_token`)
41
+ * and the Rust daemon-token writer match this shape.
42
+ *
43
+ * @param runtime - runtime with file write capabilities
44
+ * @param token_path - path to write the token
45
+ * @param token - the raw token string
46
+ * @mutates filesystem - writes `token_path` atomically at mode `0600`
47
+ */
48
+ export declare const write_daemon_token: (runtime: DaemonTokenWriteDeps, token_path: string, token: string) => Promise<void>;
49
+ /** Options for daemon token rotation. */
50
+ export interface DaemonTokenRotationOptions {
51
+ /**
52
+ * Absolute path the token file is written to. Caller computes from
53
+ * its own conventions — e.g. `get_daemon_token_path(runtime, app_name)`
54
+ * for the standard `~/.{name}/run/daemon_token` layout, or a path
55
+ * derived from `PUBLIC_<APP>_DIR` for cross-process test setups that
56
+ * isolate the app dir to a tmpdir.
57
+ */
58
+ token_path: string;
59
+ /** Rotation interval in ms. Default: `30000` (30s). */
60
+ rotation_interval_ms?: number;
61
+ }
62
+ /** Result of starting daemon token rotation. */
63
+ export interface DaemonTokenRotation {
64
+ /** The mutable runtime state. Pass to `create_daemon_token_middleware`. */
65
+ state: DaemonTokenState;
66
+ /** Stop rotation, clean up the interval, and delete the token file. Call on graceful shutdown. */
67
+ stop: () => Promise<void>;
68
+ }
69
+ /**
70
+ * Start daemon token rotation.
71
+ *
72
+ * Generates an initial token, writes it to disk, resolves the keeper account,
73
+ * and sets up periodic rotation. Returns the mutable state object and a stop function.
74
+ *
75
+ * @param runtime - runtime with file and remove capabilities
76
+ * @param deps - query dependencies for resolving keeper account
77
+ * @param options - rotation configuration
78
+ * @param log - the logger instance
79
+ * @returns rotation state and stop function
80
+ * @mutates filesystem - writes the token file on each rotation; `stop` removes it
81
+ */
82
+ export declare const start_daemon_token_rotation: (runtime: DaemonTokenWriteDeps & FsRemoveDeps, deps: QueryDeps, options: DaemonTokenRotationOptions, log: Logger) => Promise<DaemonTokenRotation>;
83
+ //# sourceMappingURL=daemon_token_rotation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon_token_rotation.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/daemon_token_rotation.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGvF,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,CAAC;AAE3D;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EACjC,MAAM,MAAM,KACV,MAAM,GAAG,IAGX,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAId,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"}
@@ -0,0 +1,99 @@
1
+ import './assert_dev_env.js';
2
+ import { write_file_atomic } from '../runtime/fs.js';
3
+ import { get_app_dir } from '../cli/config.js';
4
+ import { generate_daemon_token } from '../auth/daemon_token.js';
5
+ import { resolve_keeper_account_id } from '../auth/daemon_token_middleware.js';
6
+ /** Default rotation interval in milliseconds (30 seconds). */
7
+ export const DEFAULT_ROTATION_INTERVAL_MS = 30_000;
8
+ /**
9
+ * Get the daemon token file path (`~/.{name}/run/daemon_token`).
10
+ *
11
+ * @param runtime - runtime with `env_get` capability
12
+ * @param name - application name
13
+ * @returns path to `daemon_token`, or `null` if `$HOME` is not set
14
+ */
15
+ export const get_daemon_token_path = (runtime, name) => {
16
+ const app_dir = get_app_dir(runtime, name);
17
+ return app_dir ? `${app_dir}/run/daemon_token` : null;
18
+ };
19
+ /**
20
+ * Write the current token to disk atomically at mode `0600`.
21
+ *
22
+ * On-disk format is JSON `{"token": "..."}` — the wrapper leaves room for
23
+ * future fields (rotated_at, version) without changing every reader. Both
24
+ * the TS cross-backend harness reader (`spawn_backend.read_daemon_token`)
25
+ * and the Rust daemon-token writer match this shape.
26
+ *
27
+ * @param runtime - runtime with file write capabilities
28
+ * @param token_path - path to write the token
29
+ * @param token - the raw token string
30
+ * @mutates filesystem - writes `token_path` atomically at mode `0600`
31
+ */
32
+ export const write_daemon_token = async (runtime, token_path, token) => {
33
+ await write_file_atomic(runtime, token_path, JSON.stringify({ token }) + '\n', {
34
+ mode: 0o600
35
+ });
36
+ };
37
+ /**
38
+ * Start daemon token rotation.
39
+ *
40
+ * Generates an initial token, writes it to disk, resolves the keeper account,
41
+ * and sets up periodic rotation. Returns the mutable state object and a stop function.
42
+ *
43
+ * @param runtime - runtime with file and remove capabilities
44
+ * @param deps - query dependencies for resolving keeper account
45
+ * @param options - rotation configuration
46
+ * @param log - the logger instance
47
+ * @returns rotation state and stop function
48
+ * @mutates filesystem - writes the token file on each rotation; `stop` removes it
49
+ */
50
+ export const start_daemon_token_rotation = async (runtime, deps, options, log) => {
51
+ const { token_path, rotation_interval_ms = DEFAULT_ROTATION_INTERVAL_MS } = options;
52
+ // ensure parent directory exists
53
+ const last_slash = token_path.lastIndexOf('/');
54
+ if (last_slash > 0) {
55
+ await runtime.mkdir(token_path.slice(0, last_slash), { recursive: true });
56
+ }
57
+ // resolve keeper account (may be null pre-bootstrap; the middleware
58
+ // lazily refreshes on the first null hit to cover the
59
+ // rotation-starts-before-bootstrap case)
60
+ const keeper_account_id = await resolve_keeper_account_id(deps);
61
+ // generate initial token and write to disk
62
+ const initial_token = generate_daemon_token();
63
+ await write_daemon_token(runtime, token_path, initial_token);
64
+ const state = {
65
+ current_token: initial_token,
66
+ previous_token: null,
67
+ rotated_at: new Date(),
68
+ keeper_account_id
69
+ };
70
+ let writing = false;
71
+ const interval_id = setInterval(async () => {
72
+ if (writing)
73
+ return; // skip if previous rotation write still in progress
74
+ writing = true;
75
+ try {
76
+ const new_token = generate_daemon_token();
77
+ state.previous_token = state.current_token;
78
+ state.current_token = new_token;
79
+ state.rotated_at = new Date();
80
+ await write_daemon_token(runtime, token_path, new_token);
81
+ }
82
+ catch (err) {
83
+ log.error('Failed to write rotated token:', err);
84
+ }
85
+ finally {
86
+ writing = false;
87
+ }
88
+ }, rotation_interval_ms);
89
+ const stop = async () => {
90
+ clearInterval(interval_id);
91
+ try {
92
+ await runtime.remove(token_path);
93
+ }
94
+ catch {
95
+ // already removed or never written
96
+ }
97
+ };
98
+ return { state, stop };
99
+ };
@@ -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,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IA86CF,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,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IA07CF,CAAC"}
@@ -634,7 +634,11 @@ export const describe_standard_integration_tests = (options) => {
634
634
  app: { request: fixture.transport },
635
635
  path: rpc_path,
636
636
  spec: account_token_create_action_spec,
637
- params: { name: 'test-revoke', scope: { kind: 'full' } },
637
+ params: {
638
+ name: 'test-revoke',
639
+ scope: { kind: 'full' },
640
+ lifetime: { kind: 'eternal' }
641
+ },
638
642
  headers: fixture.create_session_headers()
639
643
  });
640
644
  assert.ok(create_res.ok, 'account_token_create should succeed');
@@ -943,7 +947,11 @@ export const describe_standard_integration_tests = (options) => {
943
947
  app: { request: unauthed },
944
948
  path: rpc_path,
945
949
  spec: account_token_create_action_spec,
946
- params: { name: 'unauth-probe', scope: { kind: 'full' } },
950
+ params: {
951
+ name: 'unauth-probe',
952
+ scope: { kind: 'full' },
953
+ lifetime: { kind: 'eternal' }
954
+ },
947
955
  headers: { host: 'localhost' }
948
956
  });
949
957
  assert.strictEqual(token_create.status, 401);
@@ -1039,7 +1047,11 @@ export const describe_standard_integration_tests = (options) => {
1039
1047
  app: { request: fixture.transport },
1040
1048
  path: rpc_path,
1041
1049
  spec: account_token_create_action_spec,
1042
- params: { name: 'test-token', scope: { kind: 'full' } },
1050
+ params: {
1051
+ name: 'test-token',
1052
+ scope: { kind: 'full' },
1053
+ lifetime: { kind: 'eternal' }
1054
+ },
1043
1055
  headers: fixture.create_session_headers()
1044
1056
  });
1045
1057
  assert.ok(create_res.ok, 'account_token_create should succeed');
@@ -1 +1 @@
1
- {"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAa7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAG3E,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAI9F;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,GAAG,GAAG,EAAE,OAAO,MAAM,KAAG,CAqBtD,CAAC;AAET;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,MAAM,EAAE,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,CAOxF,CAAC;AAET,iEAAiE;AACjE,eAAO,MAAM,IAAI,EAAE,GAAkC,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,QAAO,EAI/B,CAAC;AAEJ,gDAAgD;AAChD,eAAO,MAAM,YAAY,QAAO,QAAgC,CAAC;AAEjE,2CAA2C;AAC3C,eAAO,MAAM,OAAO,GAAU,IAAI,GAAG,EAAE,MAAM,GAAG,KAAG,OAAO,CAAC,IAAI,CAAW,CAAC;AAI3E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,QAAO,YAW3C,CAAC;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,QAAO,WAUxC,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,aAAa,EAAE,OAS3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,QAAO,OAStC,CAAC;AAEH,2FAA2F;AAC3F,eAAO,MAAM,0BAA0B,GAAI,UAAU;IACpD,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAC/B,KAAG,KAAK,CAAC,cAAc,CAqBvB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAC1C,iBAAiB,cAAc,CAAC,MAAM,CAAC,KACrC,gBAsBF,CAAC;AAEF,kDAAkD;AAClD,MAAM,WAAW,+BAA+B;IAC/C,6DAA6D;IAC7D,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,qFAAqF;IACrF,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,oEAAoE;IACpE,UAAU,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACzB,8CAA8C;IAC9C,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/B;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;IAC7F;;;;;;;;OAQG;IACH,YAAY,CAAC,EACZ,aAAa,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,gBAAgB,KAAK,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC;IAC5F,mFAAmF;IACnF,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,KAAK,CAAC,cAAc,CAAC,CAAC;IAC/E;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,cAmDF,CAAC"}
1
+ {"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
@@ -133,8 +133,7 @@ export const create_stub_audit_sse = () => {
133
133
  };
134
134
  /** Stub `AppDeps` for auth surface tests — throws on any method access. */
135
135
  export const stub_app_deps = {
136
- stat: create_throwing_stub('stat'),
137
- read_text_file: create_throwing_stub('read_text_file'),
136
+ read_secure_file: create_throwing_stub('read_secure_file'),
138
137
  delete_file: create_throwing_stub('delete_file'),
139
138
  keyring: create_throwing_stub('keyring'),
140
139
  password: create_throwing_stub('password'),
@@ -146,8 +145,9 @@ export const stub_app_deps = {
146
145
  * Create no-op `AppDeps` for auth surface testing.
147
146
  */
148
147
  export const create_stub_app_deps = () => ({
149
- stat: async () => null,
150
- read_text_file: async () => '',
148
+ read_secure_file: async (path) => {
149
+ throw new Error(`ENOENT: no such file or directory: ${path}`);
150
+ },
151
151
  delete_file: async (_path) => { },
152
152
  keyring: create_noop_stub('keyring'),
153
153
  password: create_noop_stub('password'),
@@ -34,7 +34,6 @@
34
34
  const columns: Array<DatatableColumn<AuthSessionJson>> = [
35
35
  { key: 'id', label: 'session', width: 140 },
36
36
  { key: 'created_at', label: 'created', width: 120 },
37
- { key: 'last_seen_at', label: 'last seen', width: 120 },
38
37
  { key: 'expires_at', label: 'expires', width: 120 },
39
38
  { key: 'account_id', label: '', width: 100 }
40
39
  ];
@@ -71,10 +70,6 @@
71
70
  <span title={format_datetime_local(row.created_at)}>
72
71
  {format_relative_time(row.created_at)}
73
72
  </span>
74
- {:else if column.key === 'last_seen_at'}
75
- <span title={format_datetime_local(row.last_seen_at)}>
76
- {format_relative_time(row.last_seen_at)}
77
- </span>
78
73
  {:else if column.key === 'expires_at'}
79
74
  <span title={format_datetime_local(row.expires_at)}>
80
75
  {format_relative_time(row.expires_at)}
@@ -1 +1 @@
1
- {"version":3,"file":"AccountSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AccountSessions.svelte"],"names":[],"mappings":"AAiHA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,eAAe;;kBAA+E,CAAC;AACnF,KAAK,eAAe,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC;AAC9D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"AccountSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AccountSessions.svelte"],"names":[],"mappings":"AA4GA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,eAAe;;kBAA+E,CAAC;AACnF,KAAK,eAAe,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC;AAC9D,eAAe,eAAe,CAAC"}
@@ -52,10 +52,13 @@
52
52
 
53
53
  // sessions
54
54
  const unique_users = $derived(new Set(sessions.sessions.map((s) => s.username)).size);
55
+ // newest session by mint time — sessions carry no activity signal
56
+ // (`last_seen_at` is decorative post-hard-cap; see `docs/security.md`
57
+ // §Session Security)
55
58
  const most_recent = $derived(
56
59
  sessions.sessions.length > 0
57
60
  ? sessions.sessions.reduce((a, b) =>
58
- new Date(a.last_seen_at) > new Date(b.last_seen_at) ? a : b
61
+ new Date(a.created_at) > new Date(b.created_at) ? a : b
59
62
  )
60
63
  : null
61
64
  );
@@ -146,10 +149,10 @@
146
149
  </div>
147
150
  {#if most_recent}
148
151
  <div class="baseline-row gap_xs font_size_sm mt_sm">
149
- <span class="text_50">last active:</span>
152
+ <span class="text_50">newest session:</span>
150
153
  <strong>{most_recent.username}</strong>
151
- <span class="text_50" title={format_datetime_local(most_recent.last_seen_at)}
152
- >{format_relative_time(most_recent.last_seen_at)}</span
154
+ <span class="text_50" title={format_datetime_local(most_recent.created_at)}
155
+ >{format_relative_time(most_recent.created_at)}</span
153
156
  >
154
157
  </div>
155
158
  {/if}
@@ -1 +1 @@
1
- {"version":3,"file":"AdminOverview.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminOverview.svelte"],"names":[],"mappings":"AA2UA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"AdminOverview.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminOverview.svelte"],"names":[],"mappings":"AA8UA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -26,7 +26,6 @@
26
26
  { key: 'username', label: 'user', width: 120 },
27
27
  { key: 'id', label: 'session', width: 130 },
28
28
  { key: 'created_at', label: 'created', width: 100 },
29
- { key: 'last_seen_at', label: 'last seen', width: 100 },
30
29
  { key: 'expires_at', label: 'expires', width: 100 },
31
30
  { key: 'account_id', label: '', width: 220 }
32
31
  ];
@@ -53,10 +52,6 @@
53
52
  <span title={format_datetime_local(row.created_at)}>
54
53
  {format_relative_time(row.created_at)}
55
54
  </span>
56
- {:else if column.key === 'last_seen_at'}
57
- <span title={format_datetime_local(row.last_seen_at)}>
58
- {format_relative_time(row.last_seen_at)}
59
- </span>
60
55
  {:else if column.key === 'expires_at'}
61
56
  <span title={format_datetime_local(row.expires_at)}>
62
57
  {format_relative_time(row.expires_at)}
@@ -1 +1 @@
1
- {"version":3,"file":"AdminSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSessions.svelte"],"names":[],"mappings":"AAkGA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,aAAa;;kBAA+E,CAAC;AACjF,KAAK,aAAa,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAC1D,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"AdminSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSessions.svelte"],"names":[],"mappings":"AA6FA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,aAAa;;kBAA+E,CAAC;AACjF,KAAK,aAAa,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAC1D,eAAe,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.109.0",
3
+ "version": "0.110.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",