@fuzdev/fuz_app 0.29.0 → 0.31.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/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/actions/transports_ws_backend.d.ts +15 -0
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +17 -0
- package/dist/auth/CLAUDE.md +923 -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/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/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 +6 -0
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +158 -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/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 +351 -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_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,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin RPC action specs — declarative contract for admin-only operations.
|
|
3
|
+
*
|
|
4
|
+
* Import this module for the specs, Input/Output schemas, and the
|
|
5
|
+
* `all_admin_action_specs` registry. Handlers live in `./admin_actions.js`.
|
|
6
|
+
*
|
|
7
|
+
* Authorization is declared at the spec level (`auth: {role: ROLE_ADMIN}`)
|
|
8
|
+
* so the RPC dispatcher enforces admin before the handler runs and the
|
|
9
|
+
* generated surface accurately reports the requirement.
|
|
10
|
+
*
|
|
11
|
+
* The registry always includes `app_settings_get` / `app_settings_update` —
|
|
12
|
+
* the runtime factory only wires their handlers when
|
|
13
|
+
* `AdminActionOptions.app_settings` is provided; dispatch falls back to
|
|
14
|
+
* `method_not_found` when absent.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
import { ROLE_ADMIN, RoleName } from './role_schema.js';
|
|
20
|
+
import { AdminAccountEntryJson, Email, Username } from './account_schema.js';
|
|
21
|
+
import { AdminSessionJson, AuditEventType, AuditLogEventWithUsernamesJson, AuditOutcome, PermitHistoryEventJson, } from './audit_log_schema.js';
|
|
22
|
+
import { InviteJson, InviteWithUsernamesJson } from './invite_schema.js';
|
|
23
|
+
import { AppSettingsWithUsernameJson } from './app_settings_schema.js';
|
|
24
|
+
import { AUDIT_LOG_DEFAULT_LIMIT } from './audit_log_queries.js';
|
|
25
|
+
import { Uuid } from '../uuid.js';
|
|
26
|
+
/** Max audit-log page size. Mirrors the former REST route's clamp. */
|
|
27
|
+
export const AUDIT_LOG_LIST_LIMIT_MAX = 200;
|
|
28
|
+
// -- Input/output schemas ---------------------------------------------------
|
|
29
|
+
/** Input for `admin_account_list`. No parameters — the caller is the subject. */
|
|
30
|
+
export const AdminAccountListInput = z.null();
|
|
31
|
+
/** Output for `admin_account_list`. */
|
|
32
|
+
export const AdminAccountListOutput = z.strictObject({
|
|
33
|
+
accounts: z.array(AdminAccountEntryJson),
|
|
34
|
+
grantable_roles: z.array(RoleName),
|
|
35
|
+
});
|
|
36
|
+
/** Input for `admin_session_list`. No parameters — reads every active session. */
|
|
37
|
+
export const AdminSessionListInput = z.null();
|
|
38
|
+
/** Output for `admin_session_list`. Cross-account listing; fan-out already scoped by role auth. */
|
|
39
|
+
export const AdminSessionListOutput = z.strictObject({
|
|
40
|
+
sessions: z.array(AdminSessionJson),
|
|
41
|
+
});
|
|
42
|
+
/** Input for `admin_session_revoke_all`. */
|
|
43
|
+
export const AdminSessionRevokeAllInput = z.strictObject({
|
|
44
|
+
account_id: Uuid.meta({ description: 'Account whose sessions to revoke.' }),
|
|
45
|
+
});
|
|
46
|
+
/** Output for `admin_session_revoke_all`. */
|
|
47
|
+
export const AdminSessionRevokeAllOutput = z.strictObject({
|
|
48
|
+
ok: z.literal(true),
|
|
49
|
+
count: z.number(),
|
|
50
|
+
});
|
|
51
|
+
/** Input for `admin_token_revoke_all`. */
|
|
52
|
+
export const AdminTokenRevokeAllInput = z.strictObject({
|
|
53
|
+
account_id: Uuid.meta({ description: 'Account whose API tokens to revoke.' }),
|
|
54
|
+
});
|
|
55
|
+
/** Output for `admin_token_revoke_all`. */
|
|
56
|
+
export const AdminTokenRevokeAllOutput = z.strictObject({
|
|
57
|
+
ok: z.literal(true),
|
|
58
|
+
count: z.number(),
|
|
59
|
+
});
|
|
60
|
+
/**
|
|
61
|
+
* Input for `audit_log_list`. All filter fields are optional — omit for the
|
|
62
|
+
* default newest-first page. `since_seq` exists for SSE reconnection gap
|
|
63
|
+
* fill (caller supplies the highest seq seen; server returns everything
|
|
64
|
+
* after).
|
|
65
|
+
*/
|
|
66
|
+
export const AuditLogListInput = z.strictObject({
|
|
67
|
+
event_type: AuditEventType.nullish().meta({ description: 'Filter by event type.' }),
|
|
68
|
+
outcome: AuditOutcome.nullish().meta({
|
|
69
|
+
description: 'Filter by outcome (`success` or `failure`).',
|
|
70
|
+
}),
|
|
71
|
+
account_id: Uuid.nullish().meta({ description: 'Filter by actor account id.' }),
|
|
72
|
+
limit: z
|
|
73
|
+
.number()
|
|
74
|
+
.int()
|
|
75
|
+
.min(1)
|
|
76
|
+
.max(AUDIT_LOG_LIST_LIMIT_MAX)
|
|
77
|
+
.nullish()
|
|
78
|
+
.meta({
|
|
79
|
+
description: `Max rows to return (default ${AUDIT_LOG_DEFAULT_LIMIT}, max ${AUDIT_LOG_LIST_LIMIT_MAX}).`,
|
|
80
|
+
}),
|
|
81
|
+
offset: z.number().int().min(0).nullish().meta({ description: 'Pagination offset.' }),
|
|
82
|
+
since_seq: z.number().int().min(0).nullish().meta({
|
|
83
|
+
description: 'Gap-fill from this seq forward. Used for SSE reconnection.',
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
/** Output for `audit_log_list`. */
|
|
87
|
+
export const AuditLogListOutput = z.strictObject({
|
|
88
|
+
events: z.array(AuditLogEventWithUsernamesJson),
|
|
89
|
+
});
|
|
90
|
+
/** Input for `audit_log_permit_history`. */
|
|
91
|
+
export const AuditLogPermitHistoryInput = z.strictObject({
|
|
92
|
+
limit: z
|
|
93
|
+
.number()
|
|
94
|
+
.int()
|
|
95
|
+
.min(1)
|
|
96
|
+
.max(AUDIT_LOG_LIST_LIMIT_MAX)
|
|
97
|
+
.nullish()
|
|
98
|
+
.meta({
|
|
99
|
+
description: `Max rows to return (default ${AUDIT_LOG_DEFAULT_LIMIT}, max ${AUDIT_LOG_LIST_LIMIT_MAX}).`,
|
|
100
|
+
}),
|
|
101
|
+
offset: z.number().int().min(0).nullish().meta({ description: 'Pagination offset.' }),
|
|
102
|
+
});
|
|
103
|
+
/** Output for `audit_log_permit_history`. */
|
|
104
|
+
export const AuditLogPermitHistoryOutput = z.strictObject({
|
|
105
|
+
events: z.array(PermitHistoryEventJson),
|
|
106
|
+
});
|
|
107
|
+
/** Input for `invite_create`. At least one of `email` / `username` must be provided. */
|
|
108
|
+
export const InviteCreateInput = z.strictObject({
|
|
109
|
+
email: Email.nullish().meta({ description: 'Invitee email.' }),
|
|
110
|
+
username: Username.nullish().meta({ description: 'Invitee username.' }),
|
|
111
|
+
});
|
|
112
|
+
/** Output for `invite_create`. */
|
|
113
|
+
export const InviteCreateOutput = z.strictObject({
|
|
114
|
+
ok: z.literal(true),
|
|
115
|
+
invite: InviteJson,
|
|
116
|
+
});
|
|
117
|
+
/** Input for `invite_list`. */
|
|
118
|
+
export const InviteListInput = z.null();
|
|
119
|
+
/** Output for `invite_list`. Uses the enriched row including creator/claimer usernames. */
|
|
120
|
+
export const InviteListOutput = z.strictObject({
|
|
121
|
+
invites: z.array(InviteWithUsernamesJson),
|
|
122
|
+
});
|
|
123
|
+
/** Input for `invite_delete`. */
|
|
124
|
+
export const InviteDeleteInput = z.strictObject({
|
|
125
|
+
invite_id: Uuid.meta({ description: 'Invite to delete. Must be unclaimed.' }),
|
|
126
|
+
});
|
|
127
|
+
/** Output for `invite_delete`. */
|
|
128
|
+
export const InviteDeleteOutput = z.strictObject({
|
|
129
|
+
ok: z.literal(true),
|
|
130
|
+
});
|
|
131
|
+
/** Input for `app_settings_get`. No parameters. */
|
|
132
|
+
export const AppSettingsGetInput = z.null();
|
|
133
|
+
/** Output for `app_settings_get`. */
|
|
134
|
+
export const AppSettingsGetOutput = z.strictObject({
|
|
135
|
+
settings: AppSettingsWithUsernameJson,
|
|
136
|
+
});
|
|
137
|
+
/** Input for `app_settings_update`. */
|
|
138
|
+
export const AppSettingsUpdateInput = z.strictObject({
|
|
139
|
+
open_signup: z.boolean().meta({ description: 'New value for the open signup toggle.' }),
|
|
140
|
+
});
|
|
141
|
+
/** Output for `app_settings_update`. */
|
|
142
|
+
export const AppSettingsUpdateOutput = z.strictObject({
|
|
143
|
+
ok: z.literal(true),
|
|
144
|
+
settings: AppSettingsWithUsernameJson,
|
|
145
|
+
});
|
|
146
|
+
// -- Action specs -----------------------------------------------------------
|
|
147
|
+
export const admin_account_list_action_spec = {
|
|
148
|
+
method: 'admin_account_list',
|
|
149
|
+
kind: 'request_response',
|
|
150
|
+
initiator: 'frontend',
|
|
151
|
+
auth: { role: ROLE_ADMIN },
|
|
152
|
+
side_effects: false,
|
|
153
|
+
input: AdminAccountListInput,
|
|
154
|
+
output: AdminAccountListOutput,
|
|
155
|
+
async: true,
|
|
156
|
+
description: 'List all accounts with their actors, permits, and pending offers. Admin-only.',
|
|
157
|
+
};
|
|
158
|
+
export const admin_session_list_action_spec = {
|
|
159
|
+
method: 'admin_session_list',
|
|
160
|
+
kind: 'request_response',
|
|
161
|
+
initiator: 'frontend',
|
|
162
|
+
auth: { role: ROLE_ADMIN },
|
|
163
|
+
side_effects: false,
|
|
164
|
+
input: AdminSessionListInput,
|
|
165
|
+
output: AdminSessionListOutput,
|
|
166
|
+
async: true,
|
|
167
|
+
description: 'List every active auth session across all accounts. Admin-only.',
|
|
168
|
+
};
|
|
169
|
+
export const admin_session_revoke_all_action_spec = {
|
|
170
|
+
method: 'admin_session_revoke_all',
|
|
171
|
+
kind: 'request_response',
|
|
172
|
+
initiator: 'frontend',
|
|
173
|
+
auth: { role: ROLE_ADMIN },
|
|
174
|
+
side_effects: true,
|
|
175
|
+
input: AdminSessionRevokeAllInput,
|
|
176
|
+
output: AdminSessionRevokeAllOutput,
|
|
177
|
+
async: true,
|
|
178
|
+
description: 'Revoke all sessions for an account. Admin-only.',
|
|
179
|
+
};
|
|
180
|
+
export const admin_token_revoke_all_action_spec = {
|
|
181
|
+
method: 'admin_token_revoke_all',
|
|
182
|
+
kind: 'request_response',
|
|
183
|
+
initiator: 'frontend',
|
|
184
|
+
auth: { role: ROLE_ADMIN },
|
|
185
|
+
side_effects: true,
|
|
186
|
+
input: AdminTokenRevokeAllInput,
|
|
187
|
+
output: AdminTokenRevokeAllOutput,
|
|
188
|
+
async: true,
|
|
189
|
+
description: 'Revoke all API tokens for an account. Admin-only.',
|
|
190
|
+
};
|
|
191
|
+
export const audit_log_list_action_spec = {
|
|
192
|
+
method: 'audit_log_list',
|
|
193
|
+
kind: 'request_response',
|
|
194
|
+
initiator: 'frontend',
|
|
195
|
+
auth: { role: ROLE_ADMIN },
|
|
196
|
+
side_effects: false,
|
|
197
|
+
input: AuditLogListInput,
|
|
198
|
+
output: AuditLogListOutput,
|
|
199
|
+
async: true,
|
|
200
|
+
description: 'List audit log events with optional filters. Admin-only.',
|
|
201
|
+
};
|
|
202
|
+
export const audit_log_permit_history_action_spec = {
|
|
203
|
+
method: 'audit_log_permit_history',
|
|
204
|
+
kind: 'request_response',
|
|
205
|
+
initiator: 'frontend',
|
|
206
|
+
auth: { role: ROLE_ADMIN },
|
|
207
|
+
side_effects: false,
|
|
208
|
+
input: AuditLogPermitHistoryInput,
|
|
209
|
+
output: AuditLogPermitHistoryOutput,
|
|
210
|
+
async: true,
|
|
211
|
+
description: 'List permit grant and revoke events with usernames. Admin-only.',
|
|
212
|
+
};
|
|
213
|
+
export const invite_create_action_spec = {
|
|
214
|
+
method: 'invite_create',
|
|
215
|
+
kind: 'request_response',
|
|
216
|
+
initiator: 'frontend',
|
|
217
|
+
auth: { role: ROLE_ADMIN },
|
|
218
|
+
side_effects: true,
|
|
219
|
+
input: InviteCreateInput,
|
|
220
|
+
output: InviteCreateOutput,
|
|
221
|
+
async: true,
|
|
222
|
+
description: 'Create an invite addressed to an email, username, or both. Admin-only.',
|
|
223
|
+
};
|
|
224
|
+
export const invite_list_action_spec = {
|
|
225
|
+
method: 'invite_list',
|
|
226
|
+
kind: 'request_response',
|
|
227
|
+
initiator: 'frontend',
|
|
228
|
+
auth: { role: ROLE_ADMIN },
|
|
229
|
+
side_effects: false,
|
|
230
|
+
input: InviteListInput,
|
|
231
|
+
output: InviteListOutput,
|
|
232
|
+
async: true,
|
|
233
|
+
description: 'List all invites with creator and claimer usernames. Admin-only.',
|
|
234
|
+
};
|
|
235
|
+
export const invite_delete_action_spec = {
|
|
236
|
+
method: 'invite_delete',
|
|
237
|
+
kind: 'request_response',
|
|
238
|
+
initiator: 'frontend',
|
|
239
|
+
auth: { role: ROLE_ADMIN },
|
|
240
|
+
side_effects: true,
|
|
241
|
+
input: InviteDeleteInput,
|
|
242
|
+
output: InviteDeleteOutput,
|
|
243
|
+
async: true,
|
|
244
|
+
description: 'Delete an unclaimed invite. Admin-only.',
|
|
245
|
+
};
|
|
246
|
+
export const app_settings_get_action_spec = {
|
|
247
|
+
method: 'app_settings_get',
|
|
248
|
+
kind: 'request_response',
|
|
249
|
+
initiator: 'frontend',
|
|
250
|
+
auth: { role: ROLE_ADMIN },
|
|
251
|
+
side_effects: false,
|
|
252
|
+
input: AppSettingsGetInput,
|
|
253
|
+
output: AppSettingsGetOutput,
|
|
254
|
+
async: true,
|
|
255
|
+
description: 'Read global app settings. Admin-only.',
|
|
256
|
+
};
|
|
257
|
+
export const app_settings_update_action_spec = {
|
|
258
|
+
method: 'app_settings_update',
|
|
259
|
+
kind: 'request_response',
|
|
260
|
+
initiator: 'frontend',
|
|
261
|
+
auth: { role: ROLE_ADMIN },
|
|
262
|
+
side_effects: true,
|
|
263
|
+
input: AppSettingsUpdateInput,
|
|
264
|
+
output: AppSettingsUpdateOutput,
|
|
265
|
+
async: true,
|
|
266
|
+
description: 'Update global app settings (currently just the open signup toggle). Admin-only.',
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* All admin action specs — a codegen-ready registry. Consumers spread this
|
|
270
|
+
* into their own action-spec array to include admin methods in a typed
|
|
271
|
+
* client surface. Always includes the two app-settings specs; the runtime
|
|
272
|
+
* factory only wires their handlers when `AdminActionOptions.app_settings`
|
|
273
|
+
* is provided.
|
|
274
|
+
*/
|
|
275
|
+
export const all_admin_action_specs = [
|
|
276
|
+
admin_account_list_action_spec,
|
|
277
|
+
admin_session_list_action_spec,
|
|
278
|
+
admin_session_revoke_all_action_spec,
|
|
279
|
+
admin_token_revoke_all_action_spec,
|
|
280
|
+
audit_log_list_action_spec,
|
|
281
|
+
audit_log_permit_history_action_spec,
|
|
282
|
+
invite_create_action_spec,
|
|
283
|
+
invite_list_action_spec,
|
|
284
|
+
invite_delete_action_spec,
|
|
285
|
+
app_settings_get_action_spec,
|
|
286
|
+
app_settings_update_action_spec,
|
|
287
|
+
];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin RPC action handlers — admin-only operations exposed on the JSON-RPC surface.
|
|
3
|
+
*
|
|
4
|
+
* Four action categories:
|
|
5
|
+
*
|
|
6
|
+
* - Account management: `admin_account_list`, `admin_session_list`,
|
|
7
|
+
* `admin_session_revoke_all`, `admin_token_revoke_all`.
|
|
8
|
+
* - Audit log reads: `audit_log_list`, `audit_log_permit_history`.
|
|
9
|
+
* - Invite CRUD: `invite_create`, `invite_list`, `invite_delete`.
|
|
10
|
+
* - App settings: `app_settings_get`, `app_settings_update` (registered only
|
|
11
|
+
* when `AdminActionOptions.app_settings` is provided — the mutable ref is
|
|
12
|
+
* owned by the server context and shared with signup middleware).
|
|
13
|
+
*
|
|
14
|
+
* The action specs themselves live in `./admin_action_specs.js`. Mutations
|
|
15
|
+
* emit matching audit events via `audit_log_fire_and_forget`.
|
|
16
|
+
*
|
|
17
|
+
* Authorization is declared at the spec level (`auth: {role: 'admin'}`) so
|
|
18
|
+
* the RPC dispatcher enforces it before the handler runs and the generated
|
|
19
|
+
* surface accurately reports the requirement. `permit_revoke` in
|
|
20
|
+
* `permit_offer_actions.ts` uses the same spec-level pattern even though its
|
|
21
|
+
* sibling methods are authenticated-but-not-admin — the dispatcher checks
|
|
22
|
+
* auth per-spec, so mixed-auth endpoints compose cleanly. Handler-level
|
|
23
|
+
* gates are reserved for input-dependent elevation (e.g.
|
|
24
|
+
* `permit_offer_list`/`_history` elevate to admin only when the caller
|
|
25
|
+
* passes an `account_id` other than their own — an input-dependent check
|
|
26
|
+
* the spec can't express).
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import { type RpcAction } from '../actions/action_rpc.js';
|
|
31
|
+
import { type RoleSchemaResult } from './role_schema.js';
|
|
32
|
+
import { type AppSettings } from './app_settings_schema.js';
|
|
33
|
+
import type { RouteFactoryDeps } from './deps.js';
|
|
34
|
+
/** Options for `create_admin_actions`. */
|
|
35
|
+
export interface AdminActionOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Role schema result from `create_role_schema()`. Defaults to builtin
|
|
38
|
+
* roles only. Used to derive `grantable_roles` (the `web_grantable`
|
|
39
|
+
* subset) returned by `admin_account_list`.
|
|
40
|
+
*/
|
|
41
|
+
roles?: RoleSchemaResult;
|
|
42
|
+
/**
|
|
43
|
+
* Mutable in-memory app settings ref — typically `ctx.app_settings` from
|
|
44
|
+
* `AppServerContext`. When provided, the factory wires the
|
|
45
|
+
* `app_settings_get` and `app_settings_update` handlers; the update
|
|
46
|
+
* handler mutates this ref so signup middleware reads the new value
|
|
47
|
+
* without a DB round trip. When omitted, those two methods have no
|
|
48
|
+
* handler and RPC dispatch returns `method_not_found`.
|
|
49
|
+
*/
|
|
50
|
+
app_settings?: AppSettings;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Dependencies for `create_admin_actions`.
|
|
54
|
+
*
|
|
55
|
+
* Shares shape with `PermitOfferActionDeps` so consumers can pass the same
|
|
56
|
+
* deps to both factories. `log` drives RPC-internal error logging;
|
|
57
|
+
* `on_audit_event` is wired by the two revoke-all mutations so SSE fan-out
|
|
58
|
+
* mirrors the former REST-route behavior.
|
|
59
|
+
*/
|
|
60
|
+
export type AdminActionDeps = Pick<RouteFactoryDeps, 'log' | 'on_audit_event'>;
|
|
61
|
+
/**
|
|
62
|
+
* Create the admin-only RPC actions.
|
|
63
|
+
*
|
|
64
|
+
* @param deps - stateless capabilities (log, on_audit_event)
|
|
65
|
+
* @param options - role schema for `grantable_roles` derivation
|
|
66
|
+
* @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
|
|
67
|
+
*/
|
|
68
|
+
export declare const create_admin_actions: (deps: AdminActionDeps, options?: AdminActionOptions) => Array<RpcAction>;
|
|
69
|
+
//# sourceMappingURL=admin_actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExF,OAAO,EAAuB,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAuB7E,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAK1D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AA8ChD,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,gBAAgB,CAAC,CAAC;AAE/E;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,eAAe,EACrB,UAAS,kBAAuB,KAC9B,KAAK,CAAC,SAAS,CA2SjB,CAAC"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin RPC action handlers — admin-only operations exposed on the JSON-RPC surface.
|
|
3
|
+
*
|
|
4
|
+
* Four action categories:
|
|
5
|
+
*
|
|
6
|
+
* - Account management: `admin_account_list`, `admin_session_list`,
|
|
7
|
+
* `admin_session_revoke_all`, `admin_token_revoke_all`.
|
|
8
|
+
* - Audit log reads: `audit_log_list`, `audit_log_permit_history`.
|
|
9
|
+
* - Invite CRUD: `invite_create`, `invite_list`, `invite_delete`.
|
|
10
|
+
* - App settings: `app_settings_get`, `app_settings_update` (registered only
|
|
11
|
+
* when `AdminActionOptions.app_settings` is provided — the mutable ref is
|
|
12
|
+
* owned by the server context and shared with signup middleware).
|
|
13
|
+
*
|
|
14
|
+
* The action specs themselves live in `./admin_action_specs.js`. Mutations
|
|
15
|
+
* emit matching audit events via `audit_log_fire_and_forget`.
|
|
16
|
+
*
|
|
17
|
+
* Authorization is declared at the spec level (`auth: {role: 'admin'}`) so
|
|
18
|
+
* the RPC dispatcher enforces it before the handler runs and the generated
|
|
19
|
+
* surface accurately reports the requirement. `permit_revoke` in
|
|
20
|
+
* `permit_offer_actions.ts` uses the same spec-level pattern even though its
|
|
21
|
+
* sibling methods are authenticated-but-not-admin — the dispatcher checks
|
|
22
|
+
* auth per-spec, so mixed-auth endpoints compose cleanly. Handler-level
|
|
23
|
+
* gates are reserved for input-dependent elevation (e.g.
|
|
24
|
+
* `permit_offer_list`/`_history` elevate to admin only when the caller
|
|
25
|
+
* passes an `account_id` other than their own — an input-dependent check
|
|
26
|
+
* the spec can't express).
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import { rpc_action } from '../actions/action_rpc.js';
|
|
31
|
+
import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
|
|
32
|
+
import { BUILTIN_ROLE_OPTIONS } from './role_schema.js';
|
|
33
|
+
import { query_account_by_email, query_account_by_id, query_account_by_username, query_admin_account_list, } from './account_queries.js';
|
|
34
|
+
import { query_session_list_all_active, query_session_revoke_all_for_account, } from './session_queries.js';
|
|
35
|
+
import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
|
|
36
|
+
import { AUDIT_LOG_DEFAULT_LIMIT, audit_log_fire_and_forget, query_audit_log_list_permit_history, query_audit_log_list_with_usernames, } from './audit_log_queries.js';
|
|
37
|
+
import { query_create_invite, query_invite_delete_unclaimed, query_invite_list_all_with_usernames, } from './invite_queries.js';
|
|
38
|
+
import {} from './app_settings_schema.js';
|
|
39
|
+
import { query_app_settings_load_with_username, query_app_settings_update, } from './app_settings_queries.js';
|
|
40
|
+
import { is_pg_unique_violation } from '../db/pg_error.js';
|
|
41
|
+
import { ERROR_ACCOUNT_NOT_FOUND, ERROR_INVITE_ACCOUNT_EXISTS_EMAIL, ERROR_INVITE_ACCOUNT_EXISTS_USERNAME, ERROR_INVITE_DUPLICATE, ERROR_INVITE_MISSING_IDENTIFIER, ERROR_INVITE_NOT_FOUND, } from '../http/error_schemas.js';
|
|
42
|
+
import { admin_account_list_action_spec, admin_session_list_action_spec, admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, audit_log_list_action_spec, audit_log_permit_history_action_spec, invite_create_action_spec, invite_list_action_spec, invite_delete_action_spec, app_settings_get_action_spec, app_settings_update_action_spec, } from './admin_action_specs.js';
|
|
43
|
+
/**
|
|
44
|
+
* Create the admin-only RPC actions.
|
|
45
|
+
*
|
|
46
|
+
* @param deps - stateless capabilities (log, on_audit_event)
|
|
47
|
+
* @param options - role schema for `grantable_roles` derivation
|
|
48
|
+
* @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
|
|
49
|
+
*/
|
|
50
|
+
export const create_admin_actions = (deps, options = {}) => {
|
|
51
|
+
const { log, on_audit_event } = deps;
|
|
52
|
+
const role_options = options.roles?.role_options ?? BUILTIN_ROLE_OPTIONS;
|
|
53
|
+
const grantable_roles = [];
|
|
54
|
+
for (const [name, rc] of role_options) {
|
|
55
|
+
if (rc.web_grantable)
|
|
56
|
+
grantable_roles.push(name);
|
|
57
|
+
}
|
|
58
|
+
const account_list_handler = async (_input, ctx) => {
|
|
59
|
+
const accounts = await query_admin_account_list(ctx);
|
|
60
|
+
return { accounts, grantable_roles };
|
|
61
|
+
};
|
|
62
|
+
const session_list_handler = async (_input, ctx) => {
|
|
63
|
+
const sessions = await query_session_list_all_active(ctx);
|
|
64
|
+
return { sessions };
|
|
65
|
+
};
|
|
66
|
+
const session_revoke_all_handler = async (input, ctx) => {
|
|
67
|
+
const auth = ctx.auth;
|
|
68
|
+
const account = await query_account_by_id(ctx, input.account_id);
|
|
69
|
+
if (!account) {
|
|
70
|
+
void audit_log_fire_and_forget(ctx, {
|
|
71
|
+
event_type: 'session_revoke_all',
|
|
72
|
+
outcome: 'failure',
|
|
73
|
+
actor_id: auth.actor.id,
|
|
74
|
+
account_id: auth.account.id,
|
|
75
|
+
// `target_account_id` is null: the FK to `account` would reject
|
|
76
|
+
// a probe for a non-existent id. The probed value is preserved
|
|
77
|
+
// under `metadata.attempted_account_id` for forensics.
|
|
78
|
+
target_account_id: null,
|
|
79
|
+
ip: ctx.client_ip,
|
|
80
|
+
metadata: {
|
|
81
|
+
reason: ERROR_ACCOUNT_NOT_FOUND,
|
|
82
|
+
attempted_account_id: input.account_id,
|
|
83
|
+
},
|
|
84
|
+
}, log, on_audit_event);
|
|
85
|
+
throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
|
|
86
|
+
}
|
|
87
|
+
const count = await query_session_revoke_all_for_account(ctx, input.account_id);
|
|
88
|
+
void audit_log_fire_and_forget(ctx, {
|
|
89
|
+
event_type: 'session_revoke_all',
|
|
90
|
+
actor_id: auth.actor.id,
|
|
91
|
+
account_id: auth.account.id,
|
|
92
|
+
target_account_id: input.account_id,
|
|
93
|
+
ip: ctx.client_ip,
|
|
94
|
+
metadata: { count },
|
|
95
|
+
}, log, on_audit_event);
|
|
96
|
+
return { ok: true, count };
|
|
97
|
+
};
|
|
98
|
+
const token_revoke_all_handler = async (input, ctx) => {
|
|
99
|
+
const auth = ctx.auth;
|
|
100
|
+
const account = await query_account_by_id(ctx, input.account_id);
|
|
101
|
+
if (!account) {
|
|
102
|
+
void audit_log_fire_and_forget(ctx, {
|
|
103
|
+
event_type: 'token_revoke_all',
|
|
104
|
+
outcome: 'failure',
|
|
105
|
+
actor_id: auth.actor.id,
|
|
106
|
+
account_id: auth.account.id,
|
|
107
|
+
// See `session_revoke_all_handler` — FK forces null here; the
|
|
108
|
+
// probed id lives under `metadata.attempted_account_id`.
|
|
109
|
+
target_account_id: null,
|
|
110
|
+
ip: ctx.client_ip,
|
|
111
|
+
metadata: {
|
|
112
|
+
reason: ERROR_ACCOUNT_NOT_FOUND,
|
|
113
|
+
attempted_account_id: input.account_id,
|
|
114
|
+
},
|
|
115
|
+
}, log, on_audit_event);
|
|
116
|
+
throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
|
|
117
|
+
}
|
|
118
|
+
const count = await query_revoke_all_api_tokens_for_account(ctx, input.account_id);
|
|
119
|
+
void audit_log_fire_and_forget(ctx, {
|
|
120
|
+
event_type: 'token_revoke_all',
|
|
121
|
+
actor_id: auth.actor.id,
|
|
122
|
+
account_id: auth.account.id,
|
|
123
|
+
target_account_id: input.account_id,
|
|
124
|
+
ip: ctx.client_ip,
|
|
125
|
+
metadata: { count },
|
|
126
|
+
}, log, on_audit_event);
|
|
127
|
+
return { ok: true, count };
|
|
128
|
+
};
|
|
129
|
+
const audit_log_list_handler = async (input, ctx) => {
|
|
130
|
+
const events = await query_audit_log_list_with_usernames(ctx, {
|
|
131
|
+
event_type: input.event_type ?? undefined,
|
|
132
|
+
outcome: input.outcome ?? undefined,
|
|
133
|
+
account_id: input.account_id ?? undefined,
|
|
134
|
+
limit: input.limit ?? AUDIT_LOG_DEFAULT_LIMIT,
|
|
135
|
+
offset: input.offset ?? 0,
|
|
136
|
+
since_seq: input.since_seq ?? undefined,
|
|
137
|
+
});
|
|
138
|
+
return { events };
|
|
139
|
+
};
|
|
140
|
+
const audit_log_permit_history_handler = async (input, ctx) => {
|
|
141
|
+
const events = await query_audit_log_list_permit_history(ctx, input.limit ?? AUDIT_LOG_DEFAULT_LIMIT, input.offset ?? 0);
|
|
142
|
+
return { events };
|
|
143
|
+
};
|
|
144
|
+
const invite_create_handler = async (input, ctx) => {
|
|
145
|
+
const auth = ctx.auth;
|
|
146
|
+
const email = input.email ?? null;
|
|
147
|
+
const username = input.username ?? null;
|
|
148
|
+
if (!email && !username) {
|
|
149
|
+
throw jsonrpc_errors.invalid_params('invite must specify email or username', {
|
|
150
|
+
reason: ERROR_INVITE_MISSING_IDENTIFIER,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (username) {
|
|
154
|
+
const existing = await query_account_by_username(ctx, username);
|
|
155
|
+
if (existing) {
|
|
156
|
+
throw jsonrpc_errors.conflict('an account already exists with that username', {
|
|
157
|
+
reason: ERROR_INVITE_ACCOUNT_EXISTS_USERNAME,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (email) {
|
|
162
|
+
const existing = await query_account_by_email(ctx, email);
|
|
163
|
+
if (existing) {
|
|
164
|
+
throw jsonrpc_errors.conflict('an account already exists with that email', {
|
|
165
|
+
reason: ERROR_INVITE_ACCOUNT_EXISTS_EMAIL,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
let invite;
|
|
170
|
+
try {
|
|
171
|
+
invite = await query_create_invite(ctx, {
|
|
172
|
+
email,
|
|
173
|
+
username,
|
|
174
|
+
created_by: auth.actor.id,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
if (is_pg_unique_violation(err)) {
|
|
179
|
+
throw jsonrpc_errors.conflict('an unclaimed invite already exists', {
|
|
180
|
+
reason: ERROR_INVITE_DUPLICATE,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
throw err;
|
|
184
|
+
}
|
|
185
|
+
void audit_log_fire_and_forget(ctx, {
|
|
186
|
+
event_type: 'invite_create',
|
|
187
|
+
actor_id: auth.actor.id,
|
|
188
|
+
account_id: auth.account.id,
|
|
189
|
+
ip: ctx.client_ip,
|
|
190
|
+
metadata: { invite_id: invite.id, email, username },
|
|
191
|
+
}, log, on_audit_event);
|
|
192
|
+
return { ok: true, invite };
|
|
193
|
+
};
|
|
194
|
+
const invite_list_handler = async (_input, ctx) => {
|
|
195
|
+
const invites = await query_invite_list_all_with_usernames(ctx);
|
|
196
|
+
return { invites };
|
|
197
|
+
};
|
|
198
|
+
const invite_delete_handler = async (input, ctx) => {
|
|
199
|
+
const auth = ctx.auth;
|
|
200
|
+
const deleted = await query_invite_delete_unclaimed(ctx, input.invite_id);
|
|
201
|
+
if (!deleted) {
|
|
202
|
+
throw jsonrpc_errors.not_found('invite', { reason: ERROR_INVITE_NOT_FOUND });
|
|
203
|
+
}
|
|
204
|
+
void audit_log_fire_and_forget(ctx, {
|
|
205
|
+
event_type: 'invite_delete',
|
|
206
|
+
actor_id: auth.actor.id,
|
|
207
|
+
account_id: auth.account.id,
|
|
208
|
+
ip: ctx.client_ip,
|
|
209
|
+
metadata: { invite_id: input.invite_id },
|
|
210
|
+
}, log, on_audit_event);
|
|
211
|
+
return { ok: true };
|
|
212
|
+
};
|
|
213
|
+
const actions = [
|
|
214
|
+
rpc_action(admin_account_list_action_spec, account_list_handler),
|
|
215
|
+
rpc_action(admin_session_list_action_spec, session_list_handler),
|
|
216
|
+
rpc_action(admin_session_revoke_all_action_spec, session_revoke_all_handler),
|
|
217
|
+
rpc_action(admin_token_revoke_all_action_spec, token_revoke_all_handler),
|
|
218
|
+
rpc_action(audit_log_list_action_spec, audit_log_list_handler),
|
|
219
|
+
rpc_action(audit_log_permit_history_action_spec, audit_log_permit_history_handler),
|
|
220
|
+
rpc_action(invite_create_action_spec, invite_create_handler),
|
|
221
|
+
rpc_action(invite_list_action_spec, invite_list_handler),
|
|
222
|
+
rpc_action(invite_delete_action_spec, invite_delete_handler),
|
|
223
|
+
];
|
|
224
|
+
const { app_settings } = options;
|
|
225
|
+
if (app_settings) {
|
|
226
|
+
const app_settings_get_handler = async (_input, ctx) => {
|
|
227
|
+
const settings = await query_app_settings_load_with_username(ctx);
|
|
228
|
+
return { settings };
|
|
229
|
+
};
|
|
230
|
+
const app_settings_update_handler = async (input, ctx) => {
|
|
231
|
+
const auth = ctx.auth;
|
|
232
|
+
const old_value = app_settings.open_signup;
|
|
233
|
+
const updated = await query_app_settings_update(ctx, input.open_signup, auth.actor.id);
|
|
234
|
+
// Mutate the in-memory ref so signup middleware reads the new value
|
|
235
|
+
// without a DB round trip.
|
|
236
|
+
app_settings.open_signup = updated.open_signup;
|
|
237
|
+
app_settings.updated_at = updated.updated_at;
|
|
238
|
+
app_settings.updated_by = updated.updated_by;
|
|
239
|
+
void audit_log_fire_and_forget(ctx, {
|
|
240
|
+
event_type: 'app_settings_update',
|
|
241
|
+
actor_id: auth.actor.id,
|
|
242
|
+
account_id: auth.account.id,
|
|
243
|
+
ip: ctx.client_ip,
|
|
244
|
+
metadata: {
|
|
245
|
+
setting: 'open_signup',
|
|
246
|
+
old_value,
|
|
247
|
+
new_value: input.open_signup,
|
|
248
|
+
},
|
|
249
|
+
}, log, on_audit_event);
|
|
250
|
+
const settings = await query_app_settings_load_with_username(ctx);
|
|
251
|
+
return { ok: true, settings };
|
|
252
|
+
};
|
|
253
|
+
actions.push(rpc_action(app_settings_get_action_spec, app_settings_get_handler), rpc_action(app_settings_update_action_spec, app_settings_update_handler));
|
|
254
|
+
}
|
|
255
|
+
return actions;
|
|
256
|
+
};
|
package/dist/auth/api_token.d.ts
CHANGED
|
@@ -8,8 +8,18 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
|
+
import { z } from 'zod';
|
|
11
12
|
/** Prefix for all fuz API tokens (enables secret scanning). */
|
|
12
13
|
export declare const API_TOKEN_PREFIX = "secret_fuz_token_";
|
|
14
|
+
/**
|
|
15
|
+
* Regex for the public API token id (e.g. `tok_abC0_d-3xyzA`). Twelve
|
|
16
|
+
* base64url characters after the `tok_` prefix. Matches the format produced
|
|
17
|
+
* by `generate_api_token`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const API_TOKEN_ID_REGEX: RegExp;
|
|
20
|
+
/** Zod schema for the public API token id. */
|
|
21
|
+
export declare const ApiTokenId: z.ZodString;
|
|
22
|
+
export type ApiTokenId = z.infer<typeof ApiTokenId>;
|
|
13
23
|
/**
|
|
14
24
|
* Hash an API token for storage using blake3.
|
|
15
25
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api_token.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/api_token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"api_token.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/api_token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAKtB,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAA4B,CAAC;AAE5D,8CAA8C;AAC9C,eAAO,MAAM,UAAU,aAAuC,CAAC;AAC/D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,KAAG,MAA4B,CAAC;AAE5E;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,QAAO;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAMnF,CAAC"}
|
package/dist/auth/api_token.js
CHANGED
|
@@ -8,10 +8,19 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
|
+
import { z } from 'zod';
|
|
11
12
|
import { hash_blake3 } from '@fuzdev/fuz_util/hash_blake3.js';
|
|
12
13
|
import { generate_random_base64url } from '../crypto.js';
|
|
13
14
|
/** Prefix for all fuz API tokens (enables secret scanning). */
|
|
14
15
|
export const API_TOKEN_PREFIX = 'secret_fuz_token_';
|
|
16
|
+
/**
|
|
17
|
+
* Regex for the public API token id (e.g. `tok_abC0_d-3xyzA`). Twelve
|
|
18
|
+
* base64url characters after the `tok_` prefix. Matches the format produced
|
|
19
|
+
* by `generate_api_token`.
|
|
20
|
+
*/
|
|
21
|
+
export const API_TOKEN_ID_REGEX = /^tok_[A-Za-z0-9_-]{12}$/;
|
|
22
|
+
/** Zod schema for the public API token id. */
|
|
23
|
+
export const ApiTokenId = z.string().regex(API_TOKEN_ID_REGEX);
|
|
15
24
|
/**
|
|
16
25
|
* Hash an API token for storage using blake3.
|
|
17
26
|
*
|
|
@@ -66,9 +66,9 @@ export declare const query_api_token_list_for_account: (deps: QueryDeps, account
|
|
|
66
66
|
* Enforce a per-account token limit by evicting the oldest tokens.
|
|
67
67
|
*
|
|
68
68
|
* Race safety: this function must run inside a transaction alongside the
|
|
69
|
-
* INSERT that created the new token. The caller (
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* INSERT that created the new token. The caller (the `account_token_create`
|
|
70
|
+
* RPC handler) runs under the dispatcher's transaction path because the
|
|
71
|
+
* spec declares `side_effects: true`, ensuring the INSERT + enforce_limit
|
|
72
72
|
* pair is atomic — concurrent token creation cannot interleave.
|
|
73
73
|
*
|
|
74
74
|
* @param deps - query dependencies (must be transaction-scoped)
|