@fuzdev/fuz_app 0.39.0 → 0.41.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/action_codegen.d.ts +0 -9
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +2 -35
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +1 -1
- package/dist/actions/action_types.d.ts +1 -1
- package/dist/actions/action_types.d.ts.map +1 -1
- package/dist/actions/register_action_ws.d.ts +1 -1
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.d.ts +1 -1
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +1 -1
- package/dist/auth/CLAUDE.md +117 -22
- package/dist/auth/account_actions.d.ts +5 -3
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +5 -6
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +7 -7
- package/dist/auth/account_schema.d.ts +1 -1
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +1 -1
- package/dist/auth/admin_action_specs.d.ts +6 -138
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +5 -4
- package/dist/auth/admin_actions.d.ts +4 -3
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +10 -10
- package/dist/auth/app_settings_schema.d.ts +1 -1
- package/dist/auth/app_settings_schema.d.ts.map +1 -1
- package/dist/auth/app_settings_schema.js +1 -1
- package/dist/auth/audit_log_queries.d.ts +16 -8
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +8 -11
- package/dist/auth/audit_log_schema.d.ts +28 -75
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +23 -5
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +3 -3
- package/dist/auth/cleanup.d.ts +9 -1
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +2 -2
- package/dist/auth/deps.d.ts +13 -1
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/invite_schema.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +1 -1
- package/dist/auth/permit_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/permit_offer_action_specs.js +1 -1
- package/dist/auth/permit_offer_actions.d.ts +16 -2
- package/dist/auth/permit_offer_actions.d.ts.map +1 -1
- package/dist/auth/permit_offer_actions.js +26 -8
- package/dist/auth/permit_offer_notifications.d.ts +11 -6
- package/dist/auth/permit_offer_notifications.d.ts.map +1 -1
- package/dist/auth/permit_offer_notifications.js +11 -8
- package/dist/auth/permit_offer_queries.d.ts +1 -1
- package/dist/auth/permit_offer_queries.d.ts.map +1 -1
- package/dist/auth/permit_offer_schema.d.ts +1 -1
- package/dist/auth/permit_offer_schema.d.ts.map +1 -1
- package/dist/auth/permit_offer_schema.js +1 -1
- package/dist/auth/permit_queries.d.ts +50 -1
- package/dist/auth/permit_queries.d.ts.map +1 -1
- package/dist/auth/permit_queries.js +55 -0
- package/dist/auth/self_service_role_action_specs.d.ts +83 -0
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -0
- package/dist/auth/self_service_role_action_specs.js +71 -0
- package/dist/auth/self_service_role_actions.d.ts +67 -0
- package/dist/auth/self_service_role_actions.d.ts.map +1 -0
- package/dist/auth/self_service_role_actions.js +139 -0
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +2 -2
- package/dist/auth/standard_rpc_actions.d.ts +1 -1
- package/dist/auth/standard_rpc_actions.js +1 -1
- package/dist/server/app_backend.d.ts +9 -1
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +12 -1
- package/dist/testing/CLAUDE.md +1 -1
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/app_server.d.ts +13 -2
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +6 -1
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.d.ts +1 -1
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +1 -1
- package/dist/ui/ui_format.d.ts +2 -3
- package/dist/ui/ui_format.d.ts.map +1 -1
- package/dist/ui/ui_format.js +1 -1
- package/package.json +4 -4
- package/dist/uuid.d.ts +0 -12
- package/dist/uuid.d.ts.map +0 -1
- package/dist/uuid.js +0 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permit_offer_notifications.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_notifications.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"permit_offer_notifications.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_notifications.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* - `permit_offer_retracted` → recipient's sockets when a grantor retracts
|
|
11
11
|
* - `permit_offer_accepted` → grantor's sockets when the recipient accepts
|
|
12
12
|
* - `permit_offer_declined` → grantor's sockets when the recipient declines
|
|
13
|
-
* - `permit_offer_supersede` → grantor's sockets when a sibling accept
|
|
14
|
-
* a revoke of the resulting permit
|
|
13
|
+
* - `permit_offer_supersede` → grantor's sockets when a sibling accept,
|
|
14
|
+
* a revoke of the resulting permit, or destruction of the parent scope
|
|
15
|
+
* row obsoletes their pending offer
|
|
15
16
|
* - `permit_revoke` → revokee's sockets when one of their active permits
|
|
16
17
|
* is revoked (companion to the `permit_revoke` audit event)
|
|
17
18
|
*
|
|
@@ -29,9 +30,9 @@
|
|
|
29
30
|
* @module
|
|
30
31
|
*/
|
|
31
32
|
import { z } from 'zod';
|
|
33
|
+
import { Uuid as UuidSchema } from '@fuzdev/fuz_util/id.js';
|
|
32
34
|
import { create_action_event_spec } from '../actions/action_bridge.js';
|
|
33
35
|
import { create_jsonrpc_notification } from '../http/jsonrpc_helpers.js';
|
|
34
|
-
import { Uuid as UuidSchema } from '../uuid.js';
|
|
35
36
|
import { RoleName } from './role_schema.js';
|
|
36
37
|
import { PermitOfferJson } from './permit_offer_schema.js';
|
|
37
38
|
import { PERMIT_REVOKED_REASON_LENGTH_MAX } from './account_schema.js';
|
|
@@ -66,13 +67,15 @@ export const PermitOfferDeclinedParams = z.strictObject({
|
|
|
66
67
|
/**
|
|
67
68
|
* Params for `permit_offer_supersede`. Fires to the grantor's sockets when
|
|
68
69
|
* their pending offer is obsoleted — either by a sibling accept
|
|
69
|
-
* (`reason: 'sibling_accepted'`)
|
|
70
|
-
* (`reason: 'permit_revoked'`)
|
|
71
|
-
*
|
|
70
|
+
* (`reason: 'sibling_accepted'`), by revoke of the resulting permit
|
|
71
|
+
* (`reason: 'permit_revoked'`), or by deletion of the parent scope row
|
|
72
|
+
* the offer was bound to (`reason: 'scope_destroyed'`). `cause_id` points
|
|
73
|
+
* at the accepted offer id, the revoked permit id, or the destroyed scope
|
|
74
|
+
* row id respectively.
|
|
72
75
|
*/
|
|
73
76
|
export const PermitOfferSupersedeParams = z.strictObject({
|
|
74
77
|
offer: PermitOfferJson,
|
|
75
|
-
reason: z.enum(['sibling_accepted', 'permit_revoked']),
|
|
78
|
+
reason: z.enum(['sibling_accepted', 'permit_revoked', 'scope_destroyed']),
|
|
76
79
|
cause_id: UuidSchema,
|
|
77
80
|
});
|
|
78
81
|
/**
|
|
@@ -142,7 +145,7 @@ export const permit_offer_supersede_notification_spec = {
|
|
|
142
145
|
input: PermitOfferSupersedeParams,
|
|
143
146
|
output: z.void(),
|
|
144
147
|
async: true,
|
|
145
|
-
description: 'A grantor’s pending permit offer was obsoleted by a sibling accept
|
|
148
|
+
description: 'A grantor’s pending permit offer was obsoleted by a sibling accept, by revoke of the resulting permit, or by destruction of the parent scope row.',
|
|
146
149
|
};
|
|
147
150
|
export const permit_revoke_notification_spec = {
|
|
148
151
|
method: PERMIT_REVOKE_NOTIFICATION_METHOD,
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
|
+
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
14
15
|
import type { QueryDeps } from '../db/query_deps.js';
|
|
15
|
-
import type { Uuid } from '../uuid.js';
|
|
16
16
|
import type { Permit } from './account_schema.js';
|
|
17
17
|
import { type CreatePermitOfferInput, type PermitOffer, type SupersededOffer } from './permit_offer_schema.js';
|
|
18
18
|
import type { AuditLogEvent } from './audit_log_schema.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permit_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"permit_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,qBAAqB,CAAC;AAEhD,OAAO,EAEN,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEzD;;;;;GAKG;AACH,qBAAa,+BAAgC,SAAQ,KAAK;gBAC7C,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;gBACtC,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;;GAMG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;;CAKpD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,SAAS,EACf,OAAO,sBAAsB,KAC3B,OAAO,CAAC,WAAW,CAyBrB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,eAAe,MAAM,EACrB,QAAQ,MAAM,GAAG,IAAI,KACnB,OAAO,CAAC,WAAW,GAAG,IAAI,CAe5B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,eAAe,MAAM,KACnB,OAAO,CAAC,WAAW,GAAG,IAAI,CAe5B,CAAC;AA8BF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,eAAe,MAAM,KACnB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAY5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sCAAsC,GAClD,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAW,EACX,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAS5B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,WAAW,GAAG,IAAI,CAY5B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAU5B,CAAC;AAEF,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,IAAI,CAAC;IACf,mGAAmG;IACnG,aAAa,EAAE,IAAI,CAAC;IACpB,gDAAgD;IAChD,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnB;AAED,yHAAyH;AACzH,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,4IAA4I;IAC5I,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,iBAAiB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAC1C,sLAAsL;IACtL,YAAY,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,SAAS,EACf,OAAO,gBAAgB,KACrB,OAAO,CAAC,iBAAiB,CAqK3B,CAAC"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
-
import { Uuid } from '
|
|
13
|
+
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
14
14
|
/** Sentinel UUID used inside the partial unique indexes to collapse `scope_id IS NULL` into a comparable value. */
|
|
15
15
|
export declare const PERMIT_OFFER_SCOPE_SENTINEL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
16
16
|
/** Maximum length of the optional message attached to an offer. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permit_offer_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"permit_offer_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAI5C,mHAAmH;AACnH,eAAO,MAAM,gCAAgC,yCAAyC,CAAC;AAEvF,mEAAmE;AACnE,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,yFAAyF;AACzF,eAAO,MAAM,2BAA2B,QAA2B,CAAC;AAEpE,eAAO,MAAM,mBAAmB,6kCA6B9B,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,8UAWhB,CAAC;AAE/B,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,0NAMP,CAAC;AAE/B,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mBAAmB,EAAE,IAAI,GAAG,IAAI,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IACnD,eAAe,EAAE,IAAI,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,IAAI,CAAC;CACjB;AAED,oDAAoD;AACpD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;kBA4CyD,CAAC;AACtF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,6DAA6D;AAC7D,eAAO,MAAM,oBAAoB,GAAI,OAAO,WAAW,KAAG,eAexD,CAAC"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
-
import { Uuid } from '
|
|
13
|
+
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
14
14
|
import { RoleName } from './role_schema.js';
|
|
15
15
|
/** Sentinel UUID used inside the partial unique indexes to collapse `scope_id IS NULL` into a comparable value. */
|
|
16
16
|
export const PERMIT_OFFER_SCOPE_SENTINEL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
+
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
9
10
|
import type { QueryDeps } from '../db/query_deps.js';
|
|
10
|
-
import type { Uuid } from '../uuid.js';
|
|
11
11
|
import type { Permit, GrantPermitInput } from './account_schema.js';
|
|
12
12
|
import { type SupersededOffer } from './permit_offer_schema.js';
|
|
13
13
|
/**
|
|
@@ -110,6 +110,55 @@ export declare const query_permit_list_for_actor: (deps: QueryDeps, actor_id: st
|
|
|
110
110
|
* @returns the account ID, or `null`
|
|
111
111
|
*/
|
|
112
112
|
export declare const query_permit_find_account_id_for_role: (deps: QueryDeps, role: string) => Promise<string | null>;
|
|
113
|
+
/** Result of `query_permit_revoke_for_scope` — every permit revoked plus every pending offer superseded by the scope-wide cascade. */
|
|
114
|
+
export interface RevokeForScopeResult {
|
|
115
|
+
/**
|
|
116
|
+
* One entry per permit revoked by this call. Carries the revokee's
|
|
117
|
+
* `account_id` so callers can fan out a `permit_revoke` notification per
|
|
118
|
+
* permit. Empty array means no active permit was bound to the scope.
|
|
119
|
+
*/
|
|
120
|
+
revoked: Array<{
|
|
121
|
+
permit_id: Uuid;
|
|
122
|
+
role: string;
|
|
123
|
+
scope_id: Uuid;
|
|
124
|
+
account_id: Uuid;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Every pending offer at the scope — tuple-matched and orphan, undifferentiated
|
|
128
|
+
* — superseded in the same cascade. Each entry carries its grantor's
|
|
129
|
+
* `from_account_id` for `permit_offer_supersede` notification fan-out.
|
|
130
|
+
*
|
|
131
|
+
* The caller is responsible for emitting `permit_offer_supersede` audit
|
|
132
|
+
* events with `reason: 'scope_destroyed'` and `cause_id: <destroyed scope row id>`
|
|
133
|
+
* per entry — the cause of every supersede here is the scope deletion,
|
|
134
|
+
* not any individual permit revoke (the revokes are themselves
|
|
135
|
+
* consequences of the scope going away).
|
|
136
|
+
*/
|
|
137
|
+
superseded_offers: Array<SupersededOffer>;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Revoke every active permit bound to a scope and supersede every pending
|
|
141
|
+
* offer at the scope, in one cascade.
|
|
142
|
+
*
|
|
143
|
+
* Use this from a consumer's parent-scope delete handler (e.g., classroom
|
|
144
|
+
* deletion) — `permit.scope_id` and `permit_offer.scope_id` are polymorphic
|
|
145
|
+
* with no FK constraint by design, so a parent row deletion would otherwise
|
|
146
|
+
* orphan permits and offers. The cascade is **role-agnostic**: anything
|
|
147
|
+
* attached to the destroyed scope is cleaned up.
|
|
148
|
+
*
|
|
149
|
+
* Both updates run as separate statements inside the caller's transaction
|
|
150
|
+
* (mirrors `query_permit_revoke_role`'s shape). The two halves are
|
|
151
|
+
* independent — orphan pending offers can exist at a scope with no active
|
|
152
|
+
* permits, so the supersede half always runs even when no permit was
|
|
153
|
+
* revoked.
|
|
154
|
+
*
|
|
155
|
+
* @param deps - query dependencies
|
|
156
|
+
* @param scope_id - the scope whose permits and offers to terminate
|
|
157
|
+
* @param revoked_by - the actor performing the cascade (audit trail)
|
|
158
|
+
* @param reason - optional free-form reason, stamped on `permit.revoked_reason`.
|
|
159
|
+
* @returns the revoked permits (with `account_id` for fan-out) and superseded offers (with `from_account_id` for fan-out)
|
|
160
|
+
*/
|
|
161
|
+
export declare const query_permit_revoke_for_scope: (deps: QueryDeps, scope_id: Uuid, revoked_by: Uuid | null, reason?: string | null) => Promise<RevokeForScopeResult>;
|
|
113
162
|
/** Result of `query_permit_revoke_role` — every permit revoked plus the pending offers superseded by the bulk revoke. */
|
|
114
163
|
export interface RevokeRoleResult {
|
|
115
164
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permit_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"permit_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -178,6 +178,61 @@ export const query_permit_find_account_id_for_role = async (deps, role) => {
|
|
|
178
178
|
LIMIT 1`, [role]);
|
|
179
179
|
return row?.account_id ?? null;
|
|
180
180
|
};
|
|
181
|
+
/**
|
|
182
|
+
* Revoke every active permit bound to a scope and supersede every pending
|
|
183
|
+
* offer at the scope, in one cascade.
|
|
184
|
+
*
|
|
185
|
+
* Use this from a consumer's parent-scope delete handler (e.g., classroom
|
|
186
|
+
* deletion) — `permit.scope_id` and `permit_offer.scope_id` are polymorphic
|
|
187
|
+
* with no FK constraint by design, so a parent row deletion would otherwise
|
|
188
|
+
* orphan permits and offers. The cascade is **role-agnostic**: anything
|
|
189
|
+
* attached to the destroyed scope is cleaned up.
|
|
190
|
+
*
|
|
191
|
+
* Both updates run as separate statements inside the caller's transaction
|
|
192
|
+
* (mirrors `query_permit_revoke_role`'s shape). The two halves are
|
|
193
|
+
* independent — orphan pending offers can exist at a scope with no active
|
|
194
|
+
* permits, so the supersede half always runs even when no permit was
|
|
195
|
+
* revoked.
|
|
196
|
+
*
|
|
197
|
+
* @param deps - query dependencies
|
|
198
|
+
* @param scope_id - the scope whose permits and offers to terminate
|
|
199
|
+
* @param revoked_by - the actor performing the cascade (audit trail)
|
|
200
|
+
* @param reason - optional free-form reason, stamped on `permit.revoked_reason`.
|
|
201
|
+
* @returns the revoked permits (with `account_id` for fan-out) and superseded offers (with `from_account_id` for fan-out)
|
|
202
|
+
*/
|
|
203
|
+
export const query_permit_revoke_for_scope = async (deps, scope_id, revoked_by, reason) => {
|
|
204
|
+
// Revoke every active permit at the scope. CTE pulls `account_id` via a
|
|
205
|
+
// join on `actor` so callers fan out `permit_revoke` notifications without
|
|
206
|
+
// an extra round-trip.
|
|
207
|
+
const revoked = await deps.db.query(`WITH updated AS (
|
|
208
|
+
UPDATE permit
|
|
209
|
+
SET revoked_at = NOW(), revoked_by = $2, revoked_reason = $3
|
|
210
|
+
WHERE scope_id = $1 AND revoked_at IS NULL
|
|
211
|
+
RETURNING id, role, scope_id, actor_id
|
|
212
|
+
)
|
|
213
|
+
SELECT u.id AS permit_id, u.role, u.scope_id, a.account_id
|
|
214
|
+
FROM updated u
|
|
215
|
+
JOIN actor a ON a.id = u.actor_id`, [scope_id, revoked_by ?? null, reason ?? null]);
|
|
216
|
+
// Supersede every pending offer at the scope — tuple-matched or orphan,
|
|
217
|
+
// no distinction. The cause of every supersede in this cascade is the
|
|
218
|
+
// scope deletion; offers tuple-matched to a revoked permit are not
|
|
219
|
+
// tagged separately because the revoke is itself a consequence of the
|
|
220
|
+
// scope going away.
|
|
221
|
+
const superseded_offers = await deps.db.query(`WITH updated AS (
|
|
222
|
+
UPDATE permit_offer o
|
|
223
|
+
SET superseded_at = NOW()
|
|
224
|
+
WHERE o.scope_id = $1
|
|
225
|
+
AND o.accepted_at IS NULL
|
|
226
|
+
AND o.declined_at IS NULL
|
|
227
|
+
AND o.retracted_at IS NULL
|
|
228
|
+
AND o.superseded_at IS NULL
|
|
229
|
+
RETURNING o.*
|
|
230
|
+
)
|
|
231
|
+
SELECT u.*, grantor.account_id AS from_account_id
|
|
232
|
+
FROM updated u
|
|
233
|
+
JOIN actor grantor ON grantor.id = u.from_actor_id`, [scope_id]);
|
|
234
|
+
return { revoked, superseded_offers };
|
|
235
|
+
};
|
|
181
236
|
/**
|
|
182
237
|
* Revoke every active permit an actor holds for a given role.
|
|
183
238
|
*
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-service role grant/revoke action specs — schemas, error reasons,
|
|
3
|
+
* and the codegen-ready registry.
|
|
4
|
+
*
|
|
5
|
+
* Client-safe: no query-layer or audit-write imports. Handler factory
|
|
6
|
+
* lives in `self_service_role_actions.ts`.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import type { RequestResponseActionSpec } from '../actions/action_spec.js';
|
|
12
|
+
/** Error reason — caller asked to self-toggle a role outside the configured allowlist. */
|
|
13
|
+
export declare const ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE: "role_not_self_service_eligible";
|
|
14
|
+
/** Input for `self_service_role_grant`. */
|
|
15
|
+
export declare const SelfServiceRoleGrantInput: z.ZodObject<{
|
|
16
|
+
role: z.ZodString;
|
|
17
|
+
}, z.core.$strict>;
|
|
18
|
+
export type SelfServiceRoleGrantInput = z.infer<typeof SelfServiceRoleGrantInput>;
|
|
19
|
+
/**
|
|
20
|
+
* Output for `self_service_role_grant`. `granted` is `false` on idempotent
|
|
21
|
+
* re-grant (caller already held the role globally); `permit_id` is set on
|
|
22
|
+
* new grants only.
|
|
23
|
+
*/
|
|
24
|
+
export declare const SelfServiceRoleGrantOutput: z.ZodObject<{
|
|
25
|
+
ok: z.ZodLiteral<true>;
|
|
26
|
+
granted: z.ZodBoolean;
|
|
27
|
+
permit_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
28
|
+
}, z.core.$strict>;
|
|
29
|
+
export type SelfServiceRoleGrantOutput = z.infer<typeof SelfServiceRoleGrantOutput>;
|
|
30
|
+
/** Input for `self_service_role_revoke`. */
|
|
31
|
+
export declare const SelfServiceRoleRevokeInput: z.ZodObject<{
|
|
32
|
+
role: z.ZodString;
|
|
33
|
+
}, z.core.$strict>;
|
|
34
|
+
export type SelfServiceRoleRevokeInput = z.infer<typeof SelfServiceRoleRevokeInput>;
|
|
35
|
+
/**
|
|
36
|
+
* Output for `self_service_role_revoke`. `revoked` is `false` when the
|
|
37
|
+
* caller held no active global permit for the role (idempotent).
|
|
38
|
+
*/
|
|
39
|
+
export declare const SelfServiceRoleRevokeOutput: z.ZodObject<{
|
|
40
|
+
ok: z.ZodLiteral<true>;
|
|
41
|
+
revoked: z.ZodBoolean;
|
|
42
|
+
}, z.core.$strict>;
|
|
43
|
+
export type SelfServiceRoleRevokeOutput = z.infer<typeof SelfServiceRoleRevokeOutput>;
|
|
44
|
+
export declare const self_service_role_grant_action_spec: {
|
|
45
|
+
method: string;
|
|
46
|
+
kind: "request_response";
|
|
47
|
+
initiator: "frontend";
|
|
48
|
+
auth: "authenticated";
|
|
49
|
+
side_effects: true;
|
|
50
|
+
input: z.ZodObject<{
|
|
51
|
+
role: z.ZodString;
|
|
52
|
+
}, z.core.$strict>;
|
|
53
|
+
output: z.ZodObject<{
|
|
54
|
+
ok: z.ZodLiteral<true>;
|
|
55
|
+
granted: z.ZodBoolean;
|
|
56
|
+
permit_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
57
|
+
}, z.core.$strict>;
|
|
58
|
+
async: true;
|
|
59
|
+
description: string;
|
|
60
|
+
};
|
|
61
|
+
export declare const self_service_role_revoke_action_spec: {
|
|
62
|
+
method: string;
|
|
63
|
+
kind: "request_response";
|
|
64
|
+
initiator: "frontend";
|
|
65
|
+
auth: "authenticated";
|
|
66
|
+
side_effects: true;
|
|
67
|
+
input: z.ZodObject<{
|
|
68
|
+
role: z.ZodString;
|
|
69
|
+
}, z.core.$strict>;
|
|
70
|
+
output: z.ZodObject<{
|
|
71
|
+
ok: z.ZodLiteral<true>;
|
|
72
|
+
revoked: z.ZodBoolean;
|
|
73
|
+
}, z.core.$strict>;
|
|
74
|
+
async: true;
|
|
75
|
+
description: string;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* All self-service role action specs — a codegen-ready registry. Method
|
|
79
|
+
* names are static, so consumer typed-client codegen picks them up the
|
|
80
|
+
* same way it picks up `account_*_action_specs`.
|
|
81
|
+
*/
|
|
82
|
+
export declare const all_self_service_role_action_specs: Array<RequestResponseActionSpec>;
|
|
83
|
+
//# sourceMappingURL=self_service_role_action_specs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self_service_role_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AAGzE,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,EAAG,gCAAyC,CAAC;AAE9F,2CAA2C;AAC3C,eAAO,MAAM,yBAAyB;;kBAEpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;kBAIrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF,4CAA4C;AAC5C,eAAO,MAAM,0BAA0B;;kBAErC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;GAGG;AACH,eAAO,MAAM,2BAA2B;;;kBAGtC,CAAC;AACH,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEtF,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;CAWX,CAAC;AAEtC,eAAO,MAAM,oCAAoC;;;;;;;;;;;;;;;CAWZ,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,EAAE,KAAK,CAAC,yBAAyB,CAG/E,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-service role grant/revoke action specs — schemas, error reasons,
|
|
3
|
+
* and the codegen-ready registry.
|
|
4
|
+
*
|
|
5
|
+
* Client-safe: no query-layer or audit-write imports. Handler factory
|
|
6
|
+
* lives in `self_service_role_actions.ts`.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
12
|
+
import { RoleName } from './role_schema.js';
|
|
13
|
+
/** Error reason — caller asked to self-toggle a role outside the configured allowlist. */
|
|
14
|
+
export const ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE = 'role_not_self_service_eligible';
|
|
15
|
+
/** Input for `self_service_role_grant`. */
|
|
16
|
+
export const SelfServiceRoleGrantInput = z.strictObject({
|
|
17
|
+
role: RoleName.meta({ description: 'Role to self-grant. Must be in the configured allowlist.' }),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Output for `self_service_role_grant`. `granted` is `false` on idempotent
|
|
21
|
+
* re-grant (caller already held the role globally); `permit_id` is set on
|
|
22
|
+
* new grants only.
|
|
23
|
+
*/
|
|
24
|
+
export const SelfServiceRoleGrantOutput = z.strictObject({
|
|
25
|
+
ok: z.literal(true),
|
|
26
|
+
granted: z.boolean(),
|
|
27
|
+
permit_id: Uuid.optional(),
|
|
28
|
+
});
|
|
29
|
+
/** Input for `self_service_role_revoke`. */
|
|
30
|
+
export const SelfServiceRoleRevokeInput = z.strictObject({
|
|
31
|
+
role: RoleName.meta({ description: 'Role to self-revoke. Must be in the configured allowlist.' }),
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Output for `self_service_role_revoke`. `revoked` is `false` when the
|
|
35
|
+
* caller held no active global permit for the role (idempotent).
|
|
36
|
+
*/
|
|
37
|
+
export const SelfServiceRoleRevokeOutput = z.strictObject({
|
|
38
|
+
ok: z.literal(true),
|
|
39
|
+
revoked: z.boolean(),
|
|
40
|
+
});
|
|
41
|
+
export const self_service_role_grant_action_spec = {
|
|
42
|
+
method: 'self_service_role_grant',
|
|
43
|
+
kind: 'request_response',
|
|
44
|
+
initiator: 'frontend',
|
|
45
|
+
auth: 'authenticated',
|
|
46
|
+
side_effects: true,
|
|
47
|
+
input: SelfServiceRoleGrantInput,
|
|
48
|
+
output: SelfServiceRoleGrantOutput,
|
|
49
|
+
async: true,
|
|
50
|
+
description: 'Self-grant an active permit for an allowlisted role. Idempotent — already-granted callers receive `granted: false`.',
|
|
51
|
+
};
|
|
52
|
+
export const self_service_role_revoke_action_spec = {
|
|
53
|
+
method: 'self_service_role_revoke',
|
|
54
|
+
kind: 'request_response',
|
|
55
|
+
initiator: 'frontend',
|
|
56
|
+
auth: 'authenticated',
|
|
57
|
+
side_effects: true,
|
|
58
|
+
input: SelfServiceRoleRevokeInput,
|
|
59
|
+
output: SelfServiceRoleRevokeOutput,
|
|
60
|
+
async: true,
|
|
61
|
+
description: 'Self-revoke an active global permit for an allowlisted role. Idempotent — callers without an active permit receive `revoked: false`.',
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* All self-service role action specs — a codegen-ready registry. Method
|
|
65
|
+
* names are static, so consumer typed-client codegen picks them up the
|
|
66
|
+
* same way it picks up `account_*_action_specs`.
|
|
67
|
+
*/
|
|
68
|
+
export const all_self_service_role_action_specs = [
|
|
69
|
+
self_service_role_grant_action_spec,
|
|
70
|
+
self_service_role_revoke_action_spec,
|
|
71
|
+
];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-service role grant/revoke RPC actions.
|
|
3
|
+
*
|
|
4
|
+
* Two static `request_response` actions — `self_service_role_grant` and
|
|
5
|
+
* `self_service_role_revoke` — that take `{role}` as input and toggle a
|
|
6
|
+
* permit on the caller for an allowlisted role. Idempotent in both
|
|
7
|
+
* directions: re-granting an already-held role returns `granted: false`;
|
|
8
|
+
* revoking a role the caller doesn't hold returns `revoked: false`.
|
|
9
|
+
*
|
|
10
|
+
* The factory takes an `eligible_roles` allowlist (validated against the
|
|
11
|
+
* supplied `roles.role_options` at factory time so typos surface at startup
|
|
12
|
+
* instead of at first call). Roles outside the allowlist are rejected
|
|
13
|
+
* with `forbidden` + reason `role_not_self_service_eligible`.
|
|
14
|
+
*
|
|
15
|
+
* Audit metadata carries `self_service: true` so admin reviewers can
|
|
16
|
+
* distinguish self-toggled permits from admin grants/offers. The
|
|
17
|
+
* `permit_grant` / `permit_revoke` metadata schemas declare
|
|
18
|
+
* `self_service: z.boolean().optional()` explicitly, so the field is
|
|
19
|
+
* part of the documented schema surface and is round-trip-validated by
|
|
20
|
+
* `query_audit_log`.
|
|
21
|
+
*
|
|
22
|
+
* Static method names — `role` lives in the input, not the method name —
|
|
23
|
+
* so specs are codegen-compatible (`satisfies RequestResponseActionSpec`)
|
|
24
|
+
* and the surface stays constant as consumers add eligible roles. Mirrors
|
|
25
|
+
* the existing `permit_offer_create({role})` precedent rather than
|
|
26
|
+
* generating per-role methods.
|
|
27
|
+
*
|
|
28
|
+
* Specs and schemas live in `self_service_role_action_specs.ts` so
|
|
29
|
+
* client-side codegen can import the surface without dragging in the
|
|
30
|
+
* query layer.
|
|
31
|
+
*
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
import { type RpcAction } from '../actions/action_rpc.js';
|
|
35
|
+
import type { RoleSchemaResult } from './role_schema.js';
|
|
36
|
+
import type { RouteFactoryDeps } from './deps.js';
|
|
37
|
+
/** Options for `create_self_service_role_actions`. */
|
|
38
|
+
export interface SelfServiceRoleActionsOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Allowlist of role strings eligible for self-service. Empty array
|
|
41
|
+
* effectively disables the surface — every call comes back as
|
|
42
|
+
* `forbidden` with reason `role_not_self_service_eligible`.
|
|
43
|
+
*/
|
|
44
|
+
eligible_roles: ReadonlyArray<string>;
|
|
45
|
+
/**
|
|
46
|
+
* Optional role schema. When supplied, `eligible_roles` entries are
|
|
47
|
+
* checked against `roles.role_options` at factory time so typos throw
|
|
48
|
+
* at startup instead of at first call.
|
|
49
|
+
*/
|
|
50
|
+
roles?: RoleSchemaResult;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Dependencies for `create_self_service_role_actions`. Same shape as the
|
|
54
|
+
* peer factories so consumers thread one deps object through all three.
|
|
55
|
+
* `audit_log_config` flows from `AppDeps` and is consumed by
|
|
56
|
+
* `audit_log_fire_and_forget`.
|
|
57
|
+
*/
|
|
58
|
+
export type SelfServiceRoleActionDeps = Pick<RouteFactoryDeps, 'log' | 'on_audit_event' | 'audit_log_config'>;
|
|
59
|
+
/**
|
|
60
|
+
* Build the self-service role grant/revoke RPC actions.
|
|
61
|
+
*
|
|
62
|
+
* @param deps - `SelfServiceRoleActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
|
|
63
|
+
* @param options - eligible-role allowlist plus optional role schema for typo-checking
|
|
64
|
+
* @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
|
|
65
|
+
*/
|
|
66
|
+
export declare const create_self_service_role_actions: (deps: SelfServiceRoleActionDeps, options: SelfServiceRoleActionsOptions) => Array<RpcAction>;
|
|
67
|
+
//# sourceMappingURL=self_service_role_actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self_service_role_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAmBhD,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC7C;;;;OAIG;IACH,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC3C,gBAAgB,EAChB,KAAK,GAAG,gBAAgB,GAAG,kBAAkB,CAC7C,CAAC;AAOF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,yBAAyB,EAC/B,SAAS,6BAA6B,KACpC,KAAK,CAAC,SAAS,CAqHjB,CAAC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-service role grant/revoke RPC actions.
|
|
3
|
+
*
|
|
4
|
+
* Two static `request_response` actions — `self_service_role_grant` and
|
|
5
|
+
* `self_service_role_revoke` — that take `{role}` as input and toggle a
|
|
6
|
+
* permit on the caller for an allowlisted role. Idempotent in both
|
|
7
|
+
* directions: re-granting an already-held role returns `granted: false`;
|
|
8
|
+
* revoking a role the caller doesn't hold returns `revoked: false`.
|
|
9
|
+
*
|
|
10
|
+
* The factory takes an `eligible_roles` allowlist (validated against the
|
|
11
|
+
* supplied `roles.role_options` at factory time so typos surface at startup
|
|
12
|
+
* instead of at first call). Roles outside the allowlist are rejected
|
|
13
|
+
* with `forbidden` + reason `role_not_self_service_eligible`.
|
|
14
|
+
*
|
|
15
|
+
* Audit metadata carries `self_service: true` so admin reviewers can
|
|
16
|
+
* distinguish self-toggled permits from admin grants/offers. The
|
|
17
|
+
* `permit_grant` / `permit_revoke` metadata schemas declare
|
|
18
|
+
* `self_service: z.boolean().optional()` explicitly, so the field is
|
|
19
|
+
* part of the documented schema surface and is round-trip-validated by
|
|
20
|
+
* `query_audit_log`.
|
|
21
|
+
*
|
|
22
|
+
* Static method names — `role` lives in the input, not the method name —
|
|
23
|
+
* so specs are codegen-compatible (`satisfies RequestResponseActionSpec`)
|
|
24
|
+
* and the surface stays constant as consumers add eligible roles. Mirrors
|
|
25
|
+
* the existing `permit_offer_create({role})` precedent rather than
|
|
26
|
+
* generating per-role methods.
|
|
27
|
+
*
|
|
28
|
+
* Specs and schemas live in `self_service_role_action_specs.ts` so
|
|
29
|
+
* client-side codegen can import the surface without dragging in the
|
|
30
|
+
* query layer.
|
|
31
|
+
*
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
import { rpc_action } from '../actions/action_rpc.js';
|
|
35
|
+
import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
|
|
36
|
+
import { query_grant_permit, query_permit_find_active_for_actor, query_permit_has_role, query_revoke_permit, } from './permit_queries.js';
|
|
37
|
+
import { audit_log_fire_and_forget } from './audit_log_queries.js';
|
|
38
|
+
import { ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE, self_service_role_grant_action_spec, self_service_role_revoke_action_spec, } from './self_service_role_action_specs.js';
|
|
39
|
+
const require_request_auth = (auth) => {
|
|
40
|
+
if (!auth)
|
|
41
|
+
throw new Error('unreachable: action auth guard did not enforce authentication');
|
|
42
|
+
return auth;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Build the self-service role grant/revoke RPC actions.
|
|
46
|
+
*
|
|
47
|
+
* @param deps - `SelfServiceRoleActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
|
|
48
|
+
* @param options - eligible-role allowlist plus optional role schema for typo-checking
|
|
49
|
+
* @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
|
|
50
|
+
*/
|
|
51
|
+
export const create_self_service_role_actions = (deps, options) => {
|
|
52
|
+
const eligible = new Set(options.eligible_roles);
|
|
53
|
+
if (options.roles) {
|
|
54
|
+
const role_options = options.roles.role_options;
|
|
55
|
+
for (const r of eligible) {
|
|
56
|
+
if (!role_options.has(r)) {
|
|
57
|
+
throw new Error(`create_self_service_role_actions: eligible_roles entry "${r}" is not registered in roles.role_options — typo or missing call to create_role_schema`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const reject_if_ineligible = (role) => {
|
|
62
|
+
if (!eligible.has(role)) {
|
|
63
|
+
throw jsonrpc_errors.forbidden('role not eligible for self-service', {
|
|
64
|
+
reason: ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const grant_handler = async (input, ctx) => {
|
|
69
|
+
const auth = require_request_auth(ctx.auth);
|
|
70
|
+
reject_if_ineligible(input.role);
|
|
71
|
+
// Pre-check for idempotent re-grant. `query_grant_permit` is itself
|
|
72
|
+
// idempotent (returns the existing permit instead of inserting), but
|
|
73
|
+
// it doesn't signal "already existed" vs "newly inserted" — so we
|
|
74
|
+
// peek first. The TOCTOU window is benign for self-service: two
|
|
75
|
+
// concurrent grants both observe "no permit", both call
|
|
76
|
+
// `query_grant_permit`, and one collapses onto the other inside the
|
|
77
|
+
// query's `ON CONFLICT DO NOTHING`. Worst case both responses report
|
|
78
|
+
// `granted: true`; the DB still ends up with exactly one permit.
|
|
79
|
+
const already = await query_permit_has_role(ctx, auth.actor.id, input.role);
|
|
80
|
+
if (already) {
|
|
81
|
+
return { ok: true, granted: false };
|
|
82
|
+
}
|
|
83
|
+
const permit = await query_grant_permit(ctx, {
|
|
84
|
+
actor_id: auth.actor.id,
|
|
85
|
+
role: input.role,
|
|
86
|
+
scope_id: null,
|
|
87
|
+
expires_at: null,
|
|
88
|
+
granted_by: auth.actor.id,
|
|
89
|
+
});
|
|
90
|
+
void audit_log_fire_and_forget(ctx, {
|
|
91
|
+
event_type: 'permit_grant',
|
|
92
|
+
actor_id: auth.actor.id,
|
|
93
|
+
account_id: auth.account.id,
|
|
94
|
+
ip: ctx.client_ip,
|
|
95
|
+
metadata: {
|
|
96
|
+
role: permit.role,
|
|
97
|
+
permit_id: permit.id,
|
|
98
|
+
scope_id: permit.scope_id,
|
|
99
|
+
self_service: true,
|
|
100
|
+
},
|
|
101
|
+
}, deps);
|
|
102
|
+
return { ok: true, granted: true, permit_id: permit.id };
|
|
103
|
+
};
|
|
104
|
+
const revoke_handler = async (input, ctx) => {
|
|
105
|
+
const auth = require_request_auth(ctx.auth);
|
|
106
|
+
reject_if_ineligible(input.role);
|
|
107
|
+
// Find an active global permit for this (actor, role). No dedicated
|
|
108
|
+
// query exists, but `query_permit_find_active_for_actor` returns the
|
|
109
|
+
// short list of every active permit and we filter in JS — fewer
|
|
110
|
+
// round-trips than a new helper for a one-call-per-revoke path.
|
|
111
|
+
const active = await query_permit_find_active_for_actor(ctx, auth.actor.id);
|
|
112
|
+
const target = active.find((p) => p.role === input.role && p.scope_id === null);
|
|
113
|
+
if (!target) {
|
|
114
|
+
return { ok: true, revoked: false };
|
|
115
|
+
}
|
|
116
|
+
const result = await query_revoke_permit(ctx, target.id, auth.actor.id, auth.actor.id);
|
|
117
|
+
if (!result) {
|
|
118
|
+
// Raced with another revoker — treat as already revoked.
|
|
119
|
+
return { ok: true, revoked: false };
|
|
120
|
+
}
|
|
121
|
+
void audit_log_fire_and_forget(ctx, {
|
|
122
|
+
event_type: 'permit_revoke',
|
|
123
|
+
actor_id: auth.actor.id,
|
|
124
|
+
account_id: auth.account.id,
|
|
125
|
+
ip: ctx.client_ip,
|
|
126
|
+
metadata: {
|
|
127
|
+
role: result.role,
|
|
128
|
+
permit_id: result.id,
|
|
129
|
+
scope_id: result.scope_id,
|
|
130
|
+
self_service: true,
|
|
131
|
+
},
|
|
132
|
+
}, deps);
|
|
133
|
+
return { ok: true, revoked: true };
|
|
134
|
+
};
|
|
135
|
+
return [
|
|
136
|
+
rpc_action(self_service_role_grant_action_spec, grant_handler),
|
|
137
|
+
rpc_action(self_service_role_revoke_action_spec, revoke_handler),
|
|
138
|
+
];
|
|
139
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signup_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAStB,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAQhD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,qBAAqB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAClE,6FAA6F;IAC7F,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,yFAAyF;IACzF,YAAY,EAAE,WAAW,CAAC;CAC1B;AAID,0FAA0F;AAC1F,eAAO,MAAM,WAAW;;;;kBAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,YAAY;;kBAEvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,gBAAgB,EACtB,SAAS,kBAAkB,KACzB,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"signup_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAStB,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAQhD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,qBAAqB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAClE,6FAA6F;IAC7F,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,yFAAyF;IACzF,YAAY,EAAE,WAAW,CAAC;CAC1B;AAID,0FAA0F;AAC1F,eAAO,MAAM,WAAW;;;;kBAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,YAAY;;kBAEvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,gBAAgB,EACtB,SAAS,kBAAkB,KACzB,KAAK,CAAC,SAAS,CA2HjB,CAAC"}
|