@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.
- package/dist/actions/CLAUDE.md +630 -0
- package/dist/actions/action_rpc.d.ts +29 -0
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +42 -6
- package/dist/actions/action_types.d.ts +2 -2
- package/dist/actions/cancel.d.ts +12 -13
- package/dist/actions/cancel.d.ts.map +1 -1
- package/dist/actions/cancel.js +10 -13
- package/dist/actions/heartbeat.d.ts +8 -13
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -8
- package/dist/actions/register_action_ws.d.ts +3 -3
- package/dist/actions/register_action_ws.js +2 -2
- package/dist/actions/register_ws_endpoint.d.ts +4 -4
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +3 -3
- package/dist/actions/rpc_client.d.ts +29 -0
- package/dist/actions/rpc_client.d.ts.map +1 -1
- package/dist/actions/rpc_client.js +31 -0
- package/dist/actions/socket.svelte.d.ts +16 -16
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +15 -15
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/auth/CLAUDE.md +945 -0
- package/dist/auth/account_action_specs.d.ts +216 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -0
- package/dist/auth/account_action_specs.js +159 -0
- package/dist/auth/account_actions.d.ts +51 -0
- package/dist/auth/account_actions.d.ts.map +1 -0
- package/dist/auth/account_actions.js +119 -0
- package/dist/auth/account_queries.d.ts +6 -2
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +40 -4
- package/dist/auth/account_routes.d.ts +94 -16
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +108 -180
- package/dist/auth/account_schema.d.ts +85 -30
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +40 -8
- package/dist/auth/admin_action_specs.d.ts +674 -0
- package/dist/auth/admin_action_specs.d.ts.map +1 -0
- package/dist/auth/admin_action_specs.js +287 -0
- package/dist/auth/admin_actions.d.ts +69 -0
- package/dist/auth/admin_actions.d.ts.map +1 -0
- package/dist/auth/admin_actions.js +256 -0
- package/dist/auth/admin_rpc_actions.d.ts +49 -0
- package/dist/auth/admin_rpc_actions.d.ts.map +1 -0
- package/dist/auth/admin_rpc_actions.js +32 -0
- package/dist/auth/api_token.d.ts +10 -0
- package/dist/auth/api_token.d.ts.map +1 -1
- package/dist/auth/api_token.js +9 -0
- package/dist/auth/api_token_queries.d.ts +3 -3
- package/dist/auth/api_token_queries.js +3 -3
- package/dist/auth/app_settings_schema.d.ts +4 -3
- package/dist/auth/app_settings_schema.d.ts.map +1 -1
- package/dist/auth/app_settings_schema.js +2 -1
- package/dist/auth/audit_log_routes.d.ts +14 -6
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +22 -79
- package/dist/auth/audit_log_schema.d.ts +100 -29
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +83 -11
- package/dist/auth/bootstrap_routes.d.ts +14 -0
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +10 -3
- package/dist/auth/cleanup.d.ts +63 -0
- package/dist/auth/cleanup.d.ts.map +1 -0
- package/dist/auth/cleanup.js +80 -0
- package/dist/auth/invite_schema.d.ts +11 -10
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +4 -3
- package/dist/auth/migrations.d.ts +6 -0
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +28 -0
- package/dist/auth/permit_offer_action_specs.d.ts +364 -0
- package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/permit_offer_action_specs.js +216 -0
- package/dist/auth/permit_offer_actions.d.ts +96 -0
- package/dist/auth/permit_offer_actions.d.ts.map +1 -0
- package/dist/auth/permit_offer_actions.js +428 -0
- package/dist/auth/permit_offer_notifications.d.ts +361 -0
- package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
- package/dist/auth/permit_offer_notifications.js +179 -0
- package/dist/auth/permit_offer_queries.d.ts +165 -0
- package/dist/auth/permit_offer_queries.d.ts.map +1 -0
- package/dist/auth/permit_offer_queries.js +390 -0
- package/dist/auth/permit_offer_schema.d.ts +103 -0
- package/dist/auth/permit_offer_schema.d.ts.map +1 -0
- package/dist/auth/permit_offer_schema.js +142 -0
- package/dist/auth/permit_queries.d.ts +77 -14
- package/dist/auth/permit_queries.d.ts.map +1 -1
- package/dist/auth/permit_queries.js +119 -24
- package/dist/auth/session_queries.d.ts +4 -2
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +4 -2
- package/dist/auth/signup_routes.d.ts +13 -0
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +14 -7
- package/dist/http/CLAUDE.md +584 -0
- package/dist/http/pending_effects.d.ts +29 -0
- package/dist/http/pending_effects.d.ts.map +1 -0
- package/dist/http/pending_effects.js +31 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +4 -3
- package/dist/rate_limiter.d.ts +30 -0
- package/dist/rate_limiter.d.ts.map +1 -1
- package/dist/rate_limiter.js +25 -2
- package/dist/realtime/sse_auth_guard.d.ts +2 -0
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +5 -3
- package/dist/server/app_server.d.ts +13 -2
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +12 -1
- package/dist/testing/CLAUDE.md +668 -1
- package/dist/testing/admin_integration.d.ts +10 -7
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +382 -482
- package/dist/testing/app_server.d.ts +7 -6
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/attack_surface.d.ts +9 -3
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +4 -4
- package/dist/testing/audit_completeness.d.ts +11 -0
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +169 -134
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +4 -33
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +2 -0
- package/dist/testing/entities.d.ts +35 -13
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +17 -0
- package/dist/testing/integration.d.ts +10 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +352 -340
- package/dist/testing/integration_helpers.d.ts +16 -5
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +24 -4
- package/dist/testing/rate_limiting.d.ts +7 -0
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +41 -10
- package/dist/testing/rpc_helpers.d.ts +153 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +184 -8
- package/dist/testing/sse_round_trip.d.ts +8 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +10 -3
- package/dist/testing/standard.d.ts +9 -1
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +6 -2
- package/dist/testing/stubs.d.ts +10 -2
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +17 -2
- package/dist/testing/surface_invariants.d.ts +7 -3
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +5 -4
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +9 -38
- package/dist/ui/AccountSessions.svelte +8 -4
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +61 -33
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -2
- package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
- package/dist/ui/AdminInvites.svelte +3 -2
- package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +14 -9
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminPermitHistory.svelte +3 -2
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +29 -25
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +363 -0
- package/dist/ui/OpenSignupToggle.svelte +6 -3
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/PermitOfferForm.svelte +141 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferHistory.svelte +109 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferInbox.svelte +121 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +39 -16
- package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +99 -23
- package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +38 -26
- package/dist/ui/admin_rpc_adapters.d.ts +94 -0
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -0
- package/dist/ui/admin_rpc_adapters.js +100 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +35 -21
- package/dist/ui/app_settings_state.svelte.d.ts +39 -0
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +34 -18
- package/dist/ui/audit_log_state.svelte.d.ts +40 -3
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +36 -42
- package/dist/ui/auth_state.svelte.d.ts +4 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +4 -1
- package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
- package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/permit_offers_state.svelte.js +197 -0
- package/package.json +3 -3
- package/dist/auth/admin_routes.d.ts +0 -29
- package/dist/auth/admin_routes.d.ts.map +0 -1
- package/dist/auth/admin_routes.js +0 -226
- package/dist/auth/app_settings_routes.d.ts +0 -27
- package/dist/auth/app_settings_routes.d.ts.map +0 -1
- package/dist/auth/app_settings_routes.js +0 -66
- package/dist/auth/invite_routes.d.ts +0 -18
- package/dist/auth/invite_routes.d.ts.map +0 -1
- 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.
|
|
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
|
+
"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.
|
|
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"}
|