@fuzdev/fuz_app 0.30.0 → 0.32.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 (222) hide show
  1. package/dist/actions/CLAUDE.md +630 -0
  2. package/dist/actions/action_rpc.d.ts +29 -0
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +42 -6
  5. package/dist/actions/action_types.d.ts +2 -2
  6. package/dist/actions/cancel.d.ts +12 -13
  7. package/dist/actions/cancel.d.ts.map +1 -1
  8. package/dist/actions/cancel.js +10 -13
  9. package/dist/actions/heartbeat.d.ts +8 -13
  10. package/dist/actions/heartbeat.d.ts.map +1 -1
  11. package/dist/actions/heartbeat.js +5 -8
  12. package/dist/actions/register_action_ws.d.ts +3 -3
  13. package/dist/actions/register_action_ws.js +2 -2
  14. package/dist/actions/register_ws_endpoint.d.ts +4 -4
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +3 -3
  17. package/dist/actions/rpc_client.d.ts +29 -0
  18. package/dist/actions/rpc_client.d.ts.map +1 -1
  19. package/dist/actions/rpc_client.js +31 -0
  20. package/dist/actions/socket.svelte.d.ts +16 -16
  21. package/dist/actions/socket.svelte.d.ts.map +1 -1
  22. package/dist/actions/socket.svelte.js +15 -15
  23. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  24. package/dist/auth/CLAUDE.md +945 -0
  25. package/dist/auth/account_action_specs.d.ts +216 -0
  26. package/dist/auth/account_action_specs.d.ts.map +1 -0
  27. package/dist/auth/account_action_specs.js +159 -0
  28. package/dist/auth/account_actions.d.ts +51 -0
  29. package/dist/auth/account_actions.d.ts.map +1 -0
  30. package/dist/auth/account_actions.js +119 -0
  31. package/dist/auth/account_queries.d.ts +6 -2
  32. package/dist/auth/account_queries.d.ts.map +1 -1
  33. package/dist/auth/account_queries.js +40 -4
  34. package/dist/auth/account_routes.d.ts +94 -16
  35. package/dist/auth/account_routes.d.ts.map +1 -1
  36. package/dist/auth/account_routes.js +108 -180
  37. package/dist/auth/account_schema.d.ts +85 -30
  38. package/dist/auth/account_schema.d.ts.map +1 -1
  39. package/dist/auth/account_schema.js +40 -8
  40. package/dist/auth/admin_action_specs.d.ts +674 -0
  41. package/dist/auth/admin_action_specs.d.ts.map +1 -0
  42. package/dist/auth/admin_action_specs.js +287 -0
  43. package/dist/auth/admin_actions.d.ts +69 -0
  44. package/dist/auth/admin_actions.d.ts.map +1 -0
  45. package/dist/auth/admin_actions.js +256 -0
  46. package/dist/auth/admin_rpc_actions.d.ts +49 -0
  47. package/dist/auth/admin_rpc_actions.d.ts.map +1 -0
  48. package/dist/auth/admin_rpc_actions.js +32 -0
  49. package/dist/auth/api_token.d.ts +10 -0
  50. package/dist/auth/api_token.d.ts.map +1 -1
  51. package/dist/auth/api_token.js +9 -0
  52. package/dist/auth/api_token_queries.d.ts +3 -3
  53. package/dist/auth/api_token_queries.js +3 -3
  54. package/dist/auth/app_settings_schema.d.ts +4 -3
  55. package/dist/auth/app_settings_schema.d.ts.map +1 -1
  56. package/dist/auth/app_settings_schema.js +2 -1
  57. package/dist/auth/audit_log_routes.d.ts +14 -6
  58. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  59. package/dist/auth/audit_log_routes.js +22 -79
  60. package/dist/auth/audit_log_schema.d.ts +100 -29
  61. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  62. package/dist/auth/audit_log_schema.js +83 -11
  63. package/dist/auth/bootstrap_routes.d.ts +14 -0
  64. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  65. package/dist/auth/bootstrap_routes.js +10 -3
  66. package/dist/auth/cleanup.d.ts +63 -0
  67. package/dist/auth/cleanup.d.ts.map +1 -0
  68. package/dist/auth/cleanup.js +80 -0
  69. package/dist/auth/invite_schema.d.ts +11 -10
  70. package/dist/auth/invite_schema.d.ts.map +1 -1
  71. package/dist/auth/invite_schema.js +4 -3
  72. package/dist/auth/migrations.d.ts +6 -0
  73. package/dist/auth/migrations.d.ts.map +1 -1
  74. package/dist/auth/migrations.js +28 -0
  75. package/dist/auth/permit_offer_action_specs.d.ts +364 -0
  76. package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
  77. package/dist/auth/permit_offer_action_specs.js +216 -0
  78. package/dist/auth/permit_offer_actions.d.ts +96 -0
  79. package/dist/auth/permit_offer_actions.d.ts.map +1 -0
  80. package/dist/auth/permit_offer_actions.js +428 -0
  81. package/dist/auth/permit_offer_notifications.d.ts +361 -0
  82. package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
  83. package/dist/auth/permit_offer_notifications.js +179 -0
  84. package/dist/auth/permit_offer_queries.d.ts +165 -0
  85. package/dist/auth/permit_offer_queries.d.ts.map +1 -0
  86. package/dist/auth/permit_offer_queries.js +390 -0
  87. package/dist/auth/permit_offer_schema.d.ts +103 -0
  88. package/dist/auth/permit_offer_schema.d.ts.map +1 -0
  89. package/dist/auth/permit_offer_schema.js +142 -0
  90. package/dist/auth/permit_queries.d.ts +77 -14
  91. package/dist/auth/permit_queries.d.ts.map +1 -1
  92. package/dist/auth/permit_queries.js +119 -24
  93. package/dist/auth/session_queries.d.ts +4 -2
  94. package/dist/auth/session_queries.d.ts.map +1 -1
  95. package/dist/auth/session_queries.js +4 -2
  96. package/dist/auth/signup_routes.d.ts +13 -0
  97. package/dist/auth/signup_routes.d.ts.map +1 -1
  98. package/dist/auth/signup_routes.js +14 -7
  99. package/dist/http/CLAUDE.md +584 -0
  100. package/dist/http/pending_effects.d.ts +29 -0
  101. package/dist/http/pending_effects.d.ts.map +1 -0
  102. package/dist/http/pending_effects.js +31 -0
  103. package/dist/http/route_spec.d.ts.map +1 -1
  104. package/dist/http/route_spec.js +4 -3
  105. package/dist/rate_limiter.d.ts +30 -0
  106. package/dist/rate_limiter.d.ts.map +1 -1
  107. package/dist/rate_limiter.js +25 -2
  108. package/dist/realtime/sse_auth_guard.d.ts +2 -0
  109. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  110. package/dist/realtime/sse_auth_guard.js +5 -3
  111. package/dist/server/app_server.d.ts +13 -2
  112. package/dist/server/app_server.d.ts.map +1 -1
  113. package/dist/server/app_server.js +12 -1
  114. package/dist/testing/CLAUDE.md +668 -1
  115. package/dist/testing/admin_integration.d.ts +10 -7
  116. package/dist/testing/admin_integration.d.ts.map +1 -1
  117. package/dist/testing/admin_integration.js +382 -482
  118. package/dist/testing/app_server.d.ts +7 -6
  119. package/dist/testing/app_server.d.ts.map +1 -1
  120. package/dist/testing/attack_surface.d.ts +9 -3
  121. package/dist/testing/attack_surface.d.ts.map +1 -1
  122. package/dist/testing/attack_surface.js +4 -4
  123. package/dist/testing/audit_completeness.d.ts +11 -0
  124. package/dist/testing/audit_completeness.d.ts.map +1 -1
  125. package/dist/testing/audit_completeness.js +169 -134
  126. package/dist/testing/auth_apps.d.ts.map +1 -1
  127. package/dist/testing/auth_apps.js +4 -33
  128. package/dist/testing/db.d.ts +1 -1
  129. package/dist/testing/db.d.ts.map +1 -1
  130. package/dist/testing/db.js +2 -0
  131. package/dist/testing/entities.d.ts +35 -13
  132. package/dist/testing/entities.d.ts.map +1 -1
  133. package/dist/testing/entities.js +17 -0
  134. package/dist/testing/integration.d.ts +10 -0
  135. package/dist/testing/integration.d.ts.map +1 -1
  136. package/dist/testing/integration.js +352 -340
  137. package/dist/testing/integration_helpers.d.ts +16 -5
  138. package/dist/testing/integration_helpers.d.ts.map +1 -1
  139. package/dist/testing/integration_helpers.js +24 -4
  140. package/dist/testing/rate_limiting.d.ts +7 -0
  141. package/dist/testing/rate_limiting.d.ts.map +1 -1
  142. package/dist/testing/rate_limiting.js +41 -10
  143. package/dist/testing/rpc_helpers.d.ts +153 -1
  144. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  145. package/dist/testing/rpc_helpers.js +184 -8
  146. package/dist/testing/sse_round_trip.d.ts +8 -0
  147. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  148. package/dist/testing/sse_round_trip.js +10 -3
  149. package/dist/testing/standard.d.ts +9 -1
  150. package/dist/testing/standard.d.ts.map +1 -1
  151. package/dist/testing/standard.js +6 -2
  152. package/dist/testing/stubs.d.ts +10 -2
  153. package/dist/testing/stubs.d.ts.map +1 -1
  154. package/dist/testing/stubs.js +17 -2
  155. package/dist/testing/surface_invariants.d.ts +7 -3
  156. package/dist/testing/surface_invariants.d.ts.map +1 -1
  157. package/dist/testing/surface_invariants.js +5 -4
  158. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  159. package/dist/testing/ws_round_trip.js +9 -38
  160. package/dist/ui/AccountSessions.svelte +8 -4
  161. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
  162. package/dist/ui/AdminAccounts.svelte +61 -33
  163. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  164. package/dist/ui/AdminAuditLog.svelte +3 -2
  165. package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
  166. package/dist/ui/AdminInvites.svelte +3 -2
  167. package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
  168. package/dist/ui/AdminOverview.svelte +14 -9
  169. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  170. package/dist/ui/AdminPermitHistory.svelte +3 -2
  171. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
  172. package/dist/ui/AdminSessions.svelte +29 -25
  173. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  174. package/dist/ui/CLAUDE.md +363 -0
  175. package/dist/ui/OpenSignupToggle.svelte +6 -3
  176. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
  177. package/dist/ui/PermitOfferForm.svelte +141 -0
  178. package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
  179. package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
  180. package/dist/ui/PermitOfferHistory.svelte +109 -0
  181. package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
  182. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
  183. package/dist/ui/PermitOfferInbox.svelte +121 -0
  184. package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
  185. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
  186. package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
  187. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  188. package/dist/ui/account_sessions_state.svelte.js +39 -16
  189. package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
  190. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  191. package/dist/ui/admin_accounts_state.svelte.js +99 -23
  192. package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
  193. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  194. package/dist/ui/admin_invites_state.svelte.js +38 -26
  195. package/dist/ui/admin_rpc_adapters.d.ts +94 -0
  196. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -0
  197. package/dist/ui/admin_rpc_adapters.js +100 -0
  198. package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
  199. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  200. package/dist/ui/admin_sessions_state.svelte.js +35 -21
  201. package/dist/ui/app_settings_state.svelte.d.ts +39 -0
  202. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  203. package/dist/ui/app_settings_state.svelte.js +34 -18
  204. package/dist/ui/audit_log_state.svelte.d.ts +40 -3
  205. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  206. package/dist/ui/audit_log_state.svelte.js +36 -42
  207. package/dist/ui/auth_state.svelte.d.ts +4 -3
  208. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  209. package/dist/ui/auth_state.svelte.js +4 -1
  210. package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
  211. package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
  212. package/dist/ui/permit_offers_state.svelte.js +197 -0
  213. package/package.json +3 -3
  214. package/dist/auth/admin_routes.d.ts +0 -29
  215. package/dist/auth/admin_routes.d.ts.map +0 -1
  216. package/dist/auth/admin_routes.js +0 -226
  217. package/dist/auth/app_settings_routes.d.ts +0 -27
  218. package/dist/auth/app_settings_routes.d.ts.map +0 -1
  219. package/dist/auth/app_settings_routes.js +0 -66
  220. package/dist/auth/invite_routes.d.ts +0 -18
  221. package/dist/auth/invite_routes.d.ts.map +0 -1
  222. package/dist/auth/invite_routes.js +0 -129
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Reactive state for the consentful-permits offer flow.
3
+ *
4
+ * Maintains one offer cache keyed by id, seeded by the RPC list/history
5
+ * actions and kept live by the six permit-offer WebSocket notifications.
6
+ * `incoming` (recipient-side pending) and `outgoing` (grantor-side pending)
7
+ * are derived views; `history` is the full cache ordered newest-first for
8
+ * the grantor/admin history view.
9
+ *
10
+ * Wiring is transport-agnostic: the ctor accepts a narrow RPC interface
11
+ * the consumer adapts from their typed client, plus an `account_id` /
12
+ * `actor_id` getter pair (typically bound to `auth_state`). Notification
13
+ * delivery is pull-only via `subscribe()` — the consumer plumbs their
14
+ * `FrontendWebsocketClient` / `ActionPeer` receiver to `apply_notification`.
15
+ *
16
+ * @module
17
+ */
18
+ import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
19
+ import { Loadable } from './loadable.svelte.js';
20
+ import { PERMIT_OFFER_ACCEPTED_NOTIFICATION_METHOD, PERMIT_OFFER_DECLINED_NOTIFICATION_METHOD, PERMIT_OFFER_RECEIVED_NOTIFICATION_METHOD, PERMIT_OFFER_RETRACTED_NOTIFICATION_METHOD, PERMIT_OFFER_SUPERSEDE_NOTIFICATION_METHOD, PERMIT_REVOKE_NOTIFICATION_METHOD, } from '../auth/permit_offer_notifications.js';
21
+ /**
22
+ * Svelte context for `PermitOffersState`.
23
+ * Use `permit_offers_state_context.set(state)` in the provider and
24
+ * `permit_offers_state_context.get()` to access.
25
+ */
26
+ export const permit_offers_state_context = create_context();
27
+ const is_terminal = (o) => o.accepted_at !== null ||
28
+ o.declined_at !== null ||
29
+ o.retracted_at !== null ||
30
+ o.superseded_at !== null;
31
+ export class PermitOffersState extends Loadable {
32
+ #rpc;
33
+ #get_account_id;
34
+ #get_actor_id;
35
+ #offers = $state.raw(new Map());
36
+ /** Pending offers for the current account, soonest-expiring first. */
37
+ incoming = $derived.by(() => {
38
+ const account_id = this.#get_account_id();
39
+ if (!account_id)
40
+ return [];
41
+ const now = Date.now();
42
+ const rows = [];
43
+ for (const o of this.#offers.values()) {
44
+ if (o.to_account_id !== account_id)
45
+ continue;
46
+ if (is_terminal(o))
47
+ continue;
48
+ if (Date.parse(o.expires_at) <= now)
49
+ continue;
50
+ rows.push(o);
51
+ }
52
+ rows.sort((a, b) => Date.parse(a.expires_at) - Date.parse(b.expires_at));
53
+ return rows;
54
+ });
55
+ /** Pending offers from the current actor, newest-created first. */
56
+ outgoing = $derived.by(() => {
57
+ const actor_id = this.#get_actor_id();
58
+ if (!actor_id)
59
+ return [];
60
+ const now = Date.now();
61
+ const rows = [];
62
+ for (const o of this.#offers.values()) {
63
+ if (o.from_actor_id !== actor_id)
64
+ continue;
65
+ if (is_terminal(o))
66
+ continue;
67
+ if (Date.parse(o.expires_at) <= now)
68
+ continue;
69
+ rows.push(o);
70
+ }
71
+ rows.sort((a, b) => Date.parse(b.created_at) - Date.parse(a.created_at));
72
+ return rows;
73
+ });
74
+ /** Every offer known to this state, newest-created first. Feeds the history view. */
75
+ history = $derived.by(() => {
76
+ const rows = Array.from(this.#offers.values());
77
+ rows.sort((a, b) => Date.parse(b.created_at) - Date.parse(a.created_at));
78
+ return rows;
79
+ });
80
+ incoming_count = $derived(this.incoming.length);
81
+ constructor(options) {
82
+ super();
83
+ this.#rpc = options.rpc;
84
+ this.#get_account_id = options.account_id;
85
+ this.#get_actor_id = options.actor_id;
86
+ }
87
+ /** Seed the cache with the recipient-side pending inbox. */
88
+ async fetch() {
89
+ await this.run(async () => {
90
+ const { offers } = await this.#rpc.list();
91
+ this.#merge_offers(offers);
92
+ });
93
+ }
94
+ /** Seed both-directions history (includes terminal rows). */
95
+ async fetch_history(options) {
96
+ await this.run(async () => {
97
+ const { offers } = await this.#rpc.history(options);
98
+ this.#merge_offers(offers);
99
+ });
100
+ }
101
+ /** Issue a new offer; merges the returned offer into the cache on success. */
102
+ async create(params) {
103
+ return this.run(async () => {
104
+ const { offer } = await this.#rpc.create(params);
105
+ this.#merge_offers([offer]);
106
+ return offer;
107
+ });
108
+ }
109
+ /** Accept an offer; stamps it terminal in the cache and drops any siblings the server superseded. */
110
+ async accept(offer_id) {
111
+ await this.run(async () => {
112
+ const result = await this.#rpc.accept(offer_id);
113
+ this.#merge_offers([result.offer]);
114
+ // siblings are authoritatively superseded server-side; the
115
+ // corresponding WS notifications will also arrive, but dropping
116
+ // them eagerly keeps the inbox accurate in the gap.
117
+ for (const sibling_id of result.superseded_offer_ids) {
118
+ this.#remove_offer(sibling_id);
119
+ }
120
+ });
121
+ }
122
+ async decline(offer_id, reason) {
123
+ await this.run(async () => {
124
+ await this.#rpc.decline(offer_id, reason);
125
+ this.#remove_offer(offer_id);
126
+ });
127
+ }
128
+ async retract(offer_id) {
129
+ await this.run(async () => {
130
+ await this.#rpc.retract(offer_id);
131
+ this.#remove_offer(offer_id);
132
+ });
133
+ }
134
+ /**
135
+ * Wire a notification subscription. The handler dispatches each matching
136
+ * notification into `apply_notification`; the returned disposer unwires.
137
+ */
138
+ subscribe(subscribe_fn) {
139
+ return subscribe_fn((notification) => {
140
+ this.apply_notification(notification);
141
+ });
142
+ }
143
+ /**
144
+ * Reduce a single WS notification into the cache. Exposed so consumers
145
+ * wiring their WS receiver directly (without `subscribe`) and tests can
146
+ * drive the reducer without allocating a subscription.
147
+ */
148
+ apply_notification(notification) {
149
+ switch (notification.method) {
150
+ case PERMIT_OFFER_RECEIVED_NOTIFICATION_METHOD:
151
+ case PERMIT_OFFER_RETRACTED_NOTIFICATION_METHOD:
152
+ case PERMIT_OFFER_ACCEPTED_NOTIFICATION_METHOD:
153
+ case PERMIT_OFFER_DECLINED_NOTIFICATION_METHOD:
154
+ case PERMIT_OFFER_SUPERSEDE_NOTIFICATION_METHOD: {
155
+ const params = notification.params;
156
+ if (!params || typeof params !== 'object' || !('offer' in params))
157
+ return;
158
+ const offer = params.offer;
159
+ if (!is_permit_offer_like(offer))
160
+ return;
161
+ this.#merge_offers([offer]);
162
+ return;
163
+ }
164
+ case PERMIT_REVOKE_NOTIFICATION_METHOD:
165
+ // permit_revoke is a permit-lifecycle event — the offer cache
166
+ // is unaffected. Consumers handle it in an auth/permits state.
167
+ return;
168
+ default:
169
+ // unrelated notifications — ignore silently.
170
+ return;
171
+ }
172
+ }
173
+ /** Clear the cache and reset loading/error state. */
174
+ reset() {
175
+ super.reset();
176
+ this.#offers = new Map();
177
+ }
178
+ #merge_offers(offers) {
179
+ const next = new Map(this.#offers);
180
+ for (const offer of offers) {
181
+ next.set(offer.id, offer);
182
+ }
183
+ this.#offers = next;
184
+ }
185
+ #remove_offer(offer_id) {
186
+ if (!this.#offers.has(offer_id))
187
+ return;
188
+ const next = new Map(this.#offers);
189
+ next.delete(offer_id);
190
+ this.#offers = next;
191
+ }
192
+ }
193
+ const is_permit_offer_like = (value) => !!value &&
194
+ typeof value === 'object' &&
195
+ typeof value.id === 'string' &&
196
+ typeof value.to_account_id === 'string' &&
197
+ typeof value.from_actor_id === 'string';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "type": "module",
21
21
  "engines": {
22
- "node": ">=22.15"
22
+ "node": ">=24.14"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "@electric-sql/pglite": ">=0.3",
@@ -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.4",
49
- "@fuzdev/fuz_util": "^0.57.0",
49
+ "@fuzdev/fuz_util": "^0.58.0",
50
50
  "@fuzdev/gro": "^0.198.0",
51
51
  "@jridgewell/trace-mapping": "^0.3.31",
52
52
  "@node-rs/argon2": "^2.0.2",
@@ -1,29 +0,0 @@
1
- /**
2
- * Generic admin route specs — account listing, permit management, session and token revocation.
3
- *
4
- * All routes require the `admin` role.
5
- *
6
- * @module
7
- */
8
- import { type RoleSchemaResult } from './role_schema.js';
9
- import { type RouteSpec } from '../http/route_spec.js';
10
- import type { RouteFactoryDeps } from './deps.js';
11
- /** Options for admin route specs. */
12
- export interface AdminRouteOptions {
13
- /**
14
- * Role schema result from `create_role_schema()`. Defaults to builtin roles only.
15
- * Pass the full result to enable extended app-defined roles in the admin UI.
16
- * Both `Role` and `role_options` come from the same call — passing them together
17
- * via this field ensures they stay in sync.
18
- */
19
- roles?: RoleSchemaResult;
20
- }
21
- /**
22
- * Create admin route specs for account listing and permit management.
23
- *
24
- * @param deps - stateless capabilities (log)
25
- * @param options - optional options with role schema for validation
26
- * @returns route specs for admin account management
27
- */
28
- export declare const create_admin_account_route_specs: (deps: Pick<RouteFactoryDeps, "log" | "on_audit_event">, options?: AdminRouteOptions) => Array<RouteSpec>;
29
- //# sourceMappingURL=admin_routes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"admin_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAA8C,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAGpG,OAAO,EAAoC,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAcxF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAShD,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,gBAAgB,CAAC,EACtD,UAAU,iBAAiB,KACzB,KAAK,CAAC,SAAS,CAoPjB,CAAC"}
@@ -1,226 +0,0 @@
1
- /**
2
- * Generic admin route specs — account listing, permit management, session and token revocation.
3
- *
4
- * All routes require the `admin` role.
5
- *
6
- * @module
7
- */
8
- import { z } from 'zod';
9
- import { BUILTIN_ROLE_OPTIONS, BuiltinRole, RoleName } from './role_schema.js';
10
- import { AdminAccountEntryJson } from './account_schema.js';
11
- import { require_request_context } from './request_context.js';
12
- import { get_route_input, get_route_params } from '../http/route_spec.js';
13
- import { query_account_by_id, query_actor_by_account, query_admin_account_list, } from './account_queries.js';
14
- import { query_grant_permit, query_permit_find_active_role_for_actor, query_revoke_permit, } from './permit_queries.js';
15
- import { query_session_revoke_all_for_account } from './session_queries.js';
16
- import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
17
- import { audit_log_fire_and_forget } from './audit_log_queries.js';
18
- import { ERROR_ACCOUNT_NOT_FOUND, ERROR_ROLE_NOT_WEB_GRANTABLE, ERROR_PERMIT_NOT_FOUND, ERROR_INSUFFICIENT_PERMISSIONS, } from '../http/error_schemas.js';
19
- import { get_client_ip } from '../http/proxy.js';
20
- /**
21
- * Create admin route specs for account listing and permit management.
22
- *
23
- * @param deps - stateless capabilities (log)
24
- * @param options - optional options with role schema for validation
25
- * @returns route specs for admin account management
26
- */
27
- export const create_admin_account_route_specs = (deps, options) => {
28
- const role = 'admin';
29
- const { on_audit_event } = deps;
30
- const role_schema = options?.roles?.Role ?? BuiltinRole;
31
- const role_options = options?.roles?.role_options ?? BUILTIN_ROLE_OPTIONS;
32
- const grantable_roles = [];
33
- for (const [name, rc] of role_options) {
34
- if (rc.web_grantable)
35
- grantable_roles.push(name);
36
- }
37
- return [
38
- {
39
- method: 'GET',
40
- path: '/accounts',
41
- auth: { type: 'role', role },
42
- description: 'List all accounts with their permits',
43
- input: z.null(),
44
- output: z.strictObject({
45
- accounts: z.array(AdminAccountEntryJson),
46
- grantable_roles: z.array(RoleName),
47
- }),
48
- handler: async (c, route) => {
49
- const accounts = await query_admin_account_list(route);
50
- return c.json({ accounts, grantable_roles });
51
- },
52
- },
53
- {
54
- method: 'POST',
55
- path: '/accounts/:account_id/permits/grant',
56
- auth: { type: 'role', role },
57
- description: 'Grant a role permit to an account',
58
- params: z.strictObject({ account_id: z.uuid() }),
59
- input: z.strictObject({ role: role_schema }),
60
- output: z.strictObject({
61
- ok: z.literal(true),
62
- permit: z.strictObject({ id: z.string(), role: z.string() }),
63
- }),
64
- errors: {
65
- 403: z.looseObject({
66
- error: z.enum([ERROR_INSUFFICIENT_PERMISSIONS, ERROR_ROLE_NOT_WEB_GRANTABLE]),
67
- }),
68
- 404: z.looseObject({ error: z.literal(ERROR_ACCOUNT_NOT_FOUND) }),
69
- },
70
- handler: async (c, route) => {
71
- const { account_id } = get_route_params(c);
72
- const { role: role_name } = get_route_input(c);
73
- const ctx = require_request_context(c);
74
- // Enforce web_grantable — direct API calls must respect the same
75
- // restrictions as the UI. Keeper role can only be granted via daemon token.
76
- const rc = role_options.get(role_name);
77
- if (!rc?.web_grantable) {
78
- void audit_log_fire_and_forget(route, {
79
- event_type: 'permit_grant',
80
- outcome: 'failure',
81
- actor_id: ctx.actor.id,
82
- account_id: ctx.account.id,
83
- target_account_id: account_id,
84
- ip: get_client_ip(c),
85
- metadata: { role: role_name },
86
- }, deps.log, on_audit_event);
87
- return c.json({ error: ERROR_ROLE_NOT_WEB_GRANTABLE }, 403);
88
- }
89
- const actor = await query_actor_by_account(route, account_id);
90
- if (!actor) {
91
- return c.json({ error: ERROR_ACCOUNT_NOT_FOUND }, 404);
92
- }
93
- const permit = await query_grant_permit(route, {
94
- actor_id: actor.id,
95
- role: role_name,
96
- granted_by: ctx.actor.id,
97
- });
98
- void audit_log_fire_and_forget(route, {
99
- event_type: 'permit_grant',
100
- actor_id: ctx.actor.id,
101
- account_id: ctx.account.id,
102
- target_account_id: account_id,
103
- ip: get_client_ip(c),
104
- metadata: { role: role_name, permit_id: permit.id },
105
- }, deps.log, on_audit_event);
106
- return c.json({ ok: true, permit: { id: permit.id, role: permit.role } });
107
- },
108
- },
109
- {
110
- method: 'POST',
111
- path: '/accounts/:account_id/sessions/revoke-all',
112
- auth: { type: 'role', role },
113
- description: 'Revoke all sessions for an account',
114
- params: z.strictObject({ account_id: z.uuid() }),
115
- input: z.null(),
116
- output: z.strictObject({ ok: z.literal(true), count: z.number() }),
117
- errors: { 404: z.looseObject({ error: z.literal(ERROR_ACCOUNT_NOT_FOUND) }) },
118
- handler: async (c, route) => {
119
- const { account_id } = get_route_params(c);
120
- const account = await query_account_by_id(route, account_id);
121
- if (!account) {
122
- return c.json({ error: ERROR_ACCOUNT_NOT_FOUND }, 404);
123
- }
124
- const ctx = require_request_context(c);
125
- const count = await query_session_revoke_all_for_account(route, account_id);
126
- void audit_log_fire_and_forget(route, {
127
- event_type: 'session_revoke_all',
128
- actor_id: ctx.actor.id,
129
- account_id: ctx.account.id,
130
- target_account_id: account_id,
131
- ip: get_client_ip(c),
132
- metadata: { count },
133
- }, deps.log, on_audit_event);
134
- return c.json({ ok: true, count });
135
- },
136
- },
137
- {
138
- method: 'POST',
139
- path: '/accounts/:account_id/tokens/revoke-all',
140
- auth: { type: 'role', role },
141
- description: 'Revoke all API tokens for an account',
142
- params: z.strictObject({ account_id: z.uuid() }),
143
- input: z.null(),
144
- output: z.strictObject({ ok: z.literal(true), count: z.number() }),
145
- errors: { 404: z.looseObject({ error: z.literal(ERROR_ACCOUNT_NOT_FOUND) }) },
146
- handler: async (c, route) => {
147
- const { account_id } = get_route_params(c);
148
- const account = await query_account_by_id(route, account_id);
149
- if (!account) {
150
- return c.json({ error: ERROR_ACCOUNT_NOT_FOUND }, 404);
151
- }
152
- const ctx = require_request_context(c);
153
- const count = await query_revoke_all_api_tokens_for_account(route, account_id);
154
- void audit_log_fire_and_forget(route, {
155
- event_type: 'token_revoke_all',
156
- actor_id: ctx.actor.id,
157
- account_id: ctx.account.id,
158
- target_account_id: account_id,
159
- ip: get_client_ip(c),
160
- metadata: { count },
161
- }, deps.log, on_audit_event);
162
- return c.json({ ok: true, count });
163
- },
164
- },
165
- {
166
- method: 'POST',
167
- path: '/accounts/:account_id/permits/:permit_id/revoke',
168
- auth: { type: 'role', role },
169
- description: 'Revoke a permit',
170
- params: z.strictObject({ account_id: z.uuid(), permit_id: z.uuid() }),
171
- input: z.null(),
172
- output: z.strictObject({ ok: z.literal(true), revoked: z.literal(true) }),
173
- errors: {
174
- 403: z.looseObject({
175
- error: z.enum([ERROR_INSUFFICIENT_PERMISSIONS, ERROR_ROLE_NOT_WEB_GRANTABLE]),
176
- }),
177
- 404: z.looseObject({
178
- error: z.enum([ERROR_ACCOUNT_NOT_FOUND, ERROR_PERMIT_NOT_FOUND]),
179
- }),
180
- },
181
- handler: async (c, route) => {
182
- const { account_id, permit_id } = get_route_params(c);
183
- const ctx = require_request_context(c);
184
- // resolve the target actor from the URL account_id to prevent IDOR
185
- const target_actor = await query_actor_by_account(route, account_id);
186
- if (!target_actor) {
187
- return c.json({ error: ERROR_ACCOUNT_NOT_FOUND }, 404);
188
- }
189
- // Look up the permit's role so we can enforce web_grantable symmetrically
190
- // with the grant route. Without this, an admin could revoke the keeper
191
- // permit via the web, breaking the "only daemon token manages keeper" invariant.
192
- // Route wraps POST handlers in a transaction, so SELECT-then-UPDATE is atomic.
193
- const permit_row = await query_permit_find_active_role_for_actor(route, permit_id, target_actor.id);
194
- if (!permit_row) {
195
- return c.json({ error: ERROR_PERMIT_NOT_FOUND }, 404);
196
- }
197
- const rc = role_options.get(permit_row.role);
198
- if (!rc?.web_grantable) {
199
- void audit_log_fire_and_forget(route, {
200
- event_type: 'permit_revoke',
201
- outcome: 'failure',
202
- actor_id: ctx.actor.id,
203
- account_id: ctx.account.id,
204
- target_account_id: account_id,
205
- ip: get_client_ip(c),
206
- metadata: { role: permit_row.role, permit_id },
207
- }, deps.log, on_audit_event);
208
- return c.json({ error: ERROR_ROLE_NOT_WEB_GRANTABLE }, 403);
209
- }
210
- const result = await query_revoke_permit(route, permit_id, target_actor.id, ctx.actor.id);
211
- if (!result) {
212
- return c.json({ error: ERROR_PERMIT_NOT_FOUND }, 404);
213
- }
214
- void audit_log_fire_and_forget(route, {
215
- event_type: 'permit_revoke',
216
- actor_id: ctx.actor.id,
217
- account_id: ctx.account.id,
218
- target_account_id: account_id,
219
- ip: get_client_ip(c),
220
- metadata: { role: result.role, permit_id },
221
- }, deps.log, on_audit_event);
222
- return c.json({ ok: true, revoked: true });
223
- },
224
- },
225
- ];
226
- };
@@ -1,27 +0,0 @@
1
- /**
2
- * Admin app settings route specs.
3
- *
4
- * GET and PATCH routes for managing global app settings (e.g. open signup toggle).
5
- * All routes require the `admin` role.
6
- *
7
- * @module
8
- */
9
- import { type RouteSpec } from '../http/route_spec.js';
10
- import { type AppSettings } from './app_settings_schema.js';
11
- import type { RouteFactoryDeps } from './deps.js';
12
- /**
13
- * Per-factory configuration for app settings route specs.
14
- */
15
- export interface AppSettingsRouteOptions {
16
- /** Mutable ref to the in-memory app settings — mutated on PATCH. */
17
- app_settings: AppSettings;
18
- }
19
- /**
20
- * Create admin app settings route specs.
21
- *
22
- * @param deps - stateless capabilities (log, on_audit_event)
23
- * @param options - per-factory configuration
24
- * @returns route specs for app settings management
25
- */
26
- export declare const create_app_settings_route_specs: (deps: Pick<RouteFactoryDeps, "log" | "on_audit_event">, options: AppSettingsRouteOptions) => Array<RouteSpec>;
27
- //# sourceMappingURL=app_settings_routes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app_settings_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/app_settings_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAQtE,OAAO,EAGN,KAAK,WAAW,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,oEAAoE;IACpE,YAAY,EAAE,WAAW,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,gBAAgB,CAAC,EACtD,SAAS,uBAAuB,KAC9B,KAAK,CAAC,SAAS,CAoDjB,CAAC"}
@@ -1,66 +0,0 @@
1
- /**
2
- * Admin app settings route specs.
3
- *
4
- * GET and PATCH routes for managing global app settings (e.g. open signup toggle).
5
- * All routes require the `admin` role.
6
- *
7
- * @module
8
- */
9
- import { z } from 'zod';
10
- import { get_route_input } from '../http/route_spec.js';
11
- import { require_request_context } from './request_context.js';
12
- import { get_client_ip } from '../http/proxy.js';
13
- import { audit_log_fire_and_forget } from './audit_log_queries.js';
14
- import { query_app_settings_load_with_username, query_app_settings_update, } from './app_settings_queries.js';
15
- import { AppSettingsWithUsernameJson, UpdateAppSettingsInput, } from './app_settings_schema.js';
16
- /**
17
- * Create admin app settings route specs.
18
- *
19
- * @param deps - stateless capabilities (log, on_audit_event)
20
- * @param options - per-factory configuration
21
- * @returns route specs for app settings management
22
- */
23
- export const create_app_settings_route_specs = (deps, options) => {
24
- const { app_settings } = options;
25
- return [
26
- {
27
- method: 'GET',
28
- path: '/settings',
29
- auth: { type: 'role', role: 'admin' },
30
- description: 'Get app settings',
31
- input: z.null(),
32
- output: z.strictObject({ settings: AppSettingsWithUsernameJson }),
33
- handler: async (c, route) => {
34
- const settings = await query_app_settings_load_with_username(route);
35
- return c.json({ settings });
36
- },
37
- },
38
- {
39
- method: 'PATCH',
40
- path: '/settings',
41
- auth: { type: 'role', role: 'admin' },
42
- description: 'Update app settings',
43
- input: UpdateAppSettingsInput,
44
- output: z.strictObject({ ok: z.literal(true), settings: AppSettingsWithUsernameJson }),
45
- handler: async (c, route) => {
46
- const ctx = require_request_context(c);
47
- const { open_signup } = get_route_input(c);
48
- const old_value = app_settings.open_signup;
49
- const updated = await query_app_settings_update(route, open_signup, ctx.actor.id);
50
- // Mutate the in-memory ref so GET reads are consistent
51
- app_settings.open_signup = updated.open_signup;
52
- app_settings.updated_at = updated.updated_at;
53
- app_settings.updated_by = updated.updated_by;
54
- void audit_log_fire_and_forget(route, {
55
- event_type: 'app_settings_update',
56
- actor_id: ctx.actor.id,
57
- account_id: ctx.account.id,
58
- ip: get_client_ip(c),
59
- metadata: { setting: 'open_signup', old_value, new_value: open_signup },
60
- }, deps.log, deps.on_audit_event);
61
- const settings_with_username = await query_app_settings_load_with_username(route);
62
- return c.json({ ok: true, settings: settings_with_username });
63
- },
64
- },
65
- ];
66
- };
@@ -1,18 +0,0 @@
1
- /**
2
- * Admin invite route specs for invite-based signup.
3
- *
4
- * All routes require the `admin` role. Provides CRUD for invites
5
- * that gate who can sign up.
6
- *
7
- * @module
8
- */
9
- import { type RouteSpec } from '../http/route_spec.js';
10
- import type { RouteFactoryDeps } from './deps.js';
11
- /**
12
- * Create admin invite route specs.
13
- *
14
- * @param deps - stateless capabilities (log)
15
- * @returns route specs for invite management
16
- */
17
- export declare const create_invite_route_specs: (deps: Pick<RouteFactoryDeps, "log" | "on_audit_event">) => Array<RouteSpec>;
18
- //# sourceMappingURL=invite_routes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"invite_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAoC,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAYxF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAUhD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,gBAAgB,CAAC,KACpD,KAAK,CAAC,SAAS,CAwHjB,CAAC"}