@usehercules/convex 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +478 -0
  3. package/dist/_generated/component.d.ts +184 -0
  4. package/dist/_generated/component.d.ts.map +1 -0
  5. package/dist/_generated/component.js +11 -0
  6. package/dist/_generated/component.js.map +1 -0
  7. package/dist/checker/cli.d.ts +3 -0
  8. package/dist/checker/cli.d.ts.map +1 -0
  9. package/dist/checker/cli.js +71 -0
  10. package/dist/checker/cli.js.map +1 -0
  11. package/dist/checker/index.d.ts +28 -0
  12. package/dist/checker/index.d.ts.map +1 -0
  13. package/dist/checker/index.js +1928 -0
  14. package/dist/checker/index.js.map +1 -0
  15. package/dist/client/access-admin.d.ts +818 -0
  16. package/dist/client/access-admin.d.ts.map +1 -0
  17. package/dist/client/access-admin.js +1830 -0
  18. package/dist/client/access-admin.js.map +1 -0
  19. package/dist/client/http.d.ts +19 -0
  20. package/dist/client/http.d.ts.map +1 -0
  21. package/dist/client/http.js +76 -0
  22. package/dist/client/http.js.map +1 -0
  23. package/dist/client/index.d.ts +440 -0
  24. package/dist/client/index.d.ts.map +1 -0
  25. package/dist/client/index.js +654 -0
  26. package/dist/client/index.js.map +1 -0
  27. package/dist/component/authz.d.ts +114 -0
  28. package/dist/component/authz.d.ts.map +1 -0
  29. package/dist/component/authz.js +168 -0
  30. package/dist/component/authz.js.map +1 -0
  31. package/dist/component/checks.d.ts +86 -0
  32. package/dist/component/checks.d.ts.map +1 -0
  33. package/dist/component/checks.js +184 -0
  34. package/dist/component/checks.js.map +1 -0
  35. package/dist/component/convex.config.d.ts +3 -0
  36. package/dist/component/convex.config.d.ts.map +1 -0
  37. package/dist/component/convex.config.js +3 -0
  38. package/dist/component/convex.config.js.map +1 -0
  39. package/dist/component/effective.d.ts +82 -0
  40. package/dist/component/effective.d.ts.map +1 -0
  41. package/dist/component/effective.js +757 -0
  42. package/dist/component/effective.js.map +1 -0
  43. package/dist/component/queries.d.ts +170 -0
  44. package/dist/component/queries.d.ts.map +1 -0
  45. package/dist/component/queries.js +633 -0
  46. package/dist/component/queries.js.map +1 -0
  47. package/dist/component/schema.d.ts +258 -0
  48. package/dist/component/schema.d.ts.map +1 -0
  49. package/dist/component/schema.js +222 -0
  50. package/dist/component/schema.js.map +1 -0
  51. package/dist/component/sync.d.ts +85 -0
  52. package/dist/component/sync.d.ts.map +1 -0
  53. package/dist/component/sync.js +851 -0
  54. package/dist/component/sync.js.map +1 -0
  55. package/dist/shared/projection-protocol.d.ts +1624 -0
  56. package/dist/shared/projection-protocol.d.ts.map +1 -0
  57. package/dist/shared/projection-protocol.js +561 -0
  58. package/dist/shared/projection-protocol.js.map +1 -0
  59. package/dist/shared/sync.d.ts +24 -0
  60. package/dist/shared/sync.d.ts.map +1 -0
  61. package/dist/shared/sync.js +18 -0
  62. package/dist/shared/sync.js.map +1 -0
  63. package/dist/shared/token.d.ts +5 -0
  64. package/dist/shared/token.d.ts.map +1 -0
  65. package/dist/shared/token.js +19 -0
  66. package/dist/shared/token.js.map +1 -0
  67. package/package.json +89 -0
@@ -0,0 +1,1830 @@
1
+ "use node";
2
+ import { Hercules } from "@usehercules/sdk";
3
+ import { ConvexError, v } from "convex/values";
4
+ const DEFAULT_API_VERSION = "2025-12-09";
5
+ const DEFAULT_ACCESS_ADMIN_API_KEY_ENV_VAR = "HERCULES_API_KEY";
6
+ const serviceActor = { actor_mode: "service" };
7
+ const optionalPrincipalRef = {
8
+ principalId: v.optional(v.string()),
9
+ herculesAuthUserId: v.optional(v.string()),
10
+ };
11
+ const optionalRoleRef = {
12
+ roleId: v.optional(v.string()),
13
+ roleKey: v.optional(v.string()),
14
+ };
15
+ const accountEntryModeValidator = v.union(v.literal("open"), v.literal("allowlisted_only"), v.literal("invite_only"), v.literal("approval_required"));
16
+ const bindingAppliesToValidator = v.union(v.literal("self"), v.literal("self_and_descendants"));
17
+ const grantableRoleTargetValidator = v.union(v.object({ type: v.literal("scope") }), v.object({
18
+ type: v.literal("resource"),
19
+ resourceType: v.string(),
20
+ resourceId: v.string(),
21
+ appliesTo: v.optional(bindingAppliesToValidator),
22
+ }));
23
+ const MAX_MEMBER_ROLE_REPLACEMENT_ENTRIES = 500;
24
+ const MIN_RESOURCE_GRANT_REPLACEMENT_SUBJECTS = 1;
25
+ const MAX_RESOURCE_GRANT_REPLACEMENT_SUBJECTS = 100;
26
+ const MAX_RESOURCE_GRANT_REPLACEMENT_ENTRIES = 500;
27
+ const resourceGrantReplacementValidator = v.object({
28
+ roleKey: v.optional(v.string()),
29
+ permissionKey: v.optional(v.string()),
30
+ appliesTo: v.optional(bindingAppliesToValidator),
31
+ expiresAt: v.optional(v.union(v.string(), v.null())),
32
+ });
33
+ const resourceGrantSubjectReplacementValidator = v.object({
34
+ ...optionalPrincipalRef,
35
+ grants: v.array(resourceGrantReplacementValidator),
36
+ });
37
+ const resourceRuleSubjectValidator = v.union(v.object({ type: v.literal("principal"), principalId: v.string() }), v.object({ type: v.literal("role"), roleKey: v.string() }));
38
+ const resourceRuleTargetValidator = v.union(v.object({ mode: v.literal("all") }), v.object({ mode: v.literal("specific"), resourceId: v.string() }));
39
+ const resourceRuleEffectValidator = v.union(v.literal("allow"), v.literal("deny"));
40
+ const resourceRuleReplacementEffectValidator = v.union(v.literal("allow"), v.literal("deny"), v.literal("clear"));
41
+ /**
42
+ * Builds the managed Access Control write actions (assign/remove roles,
43
+ * invite, create org custom roles, resource grants, overrides, expiries,
44
+ * member lifecycle, admission rules, entry mode, and groups) plus the raw
45
+ * reads backing them (group/resource-invitation lists, role overrides, user
46
+ * exceptions). Each one calls the Hercules control plane, so it needs the
47
+ * `HERCULES_API_KEY` secret. Wire it once in `convex/accessAdmin.ts` and
48
+ * re-export the actions you use.
49
+ *
50
+ * These are internal service-authority actions. Do not re-export them as public
51
+ * Convex actions. Use {@link createAccessUserActions} for public resource
52
+ * management by signed-in app users.
53
+ */
54
+ export function createAccessAdminActions(options) {
55
+ const callAccessControlApi = makeAccessControlApiCaller(options);
56
+ const { internalAction } = options;
57
+ return {
58
+ archiveScope: internalAction({
59
+ args: { scopeId: v.string() },
60
+ handler: async (_ctx, args) => {
61
+ const body = { scope_id: args.scopeId };
62
+ return await callAccessControlApi("/v1/access-control/scopes/archive", body);
63
+ },
64
+ }),
65
+ setDefaultRole: internalAction({
66
+ args: { scopeId: v.string(), ...optionalRoleRef },
67
+ handler: async (_ctx, args) => {
68
+ const body = {
69
+ scope_id: args.scopeId,
70
+ ...roleRef(args),
71
+ ...serviceActor,
72
+ };
73
+ return await callAccessControlApi("/v1/access-control/scopes/set-default-role", body);
74
+ },
75
+ }),
76
+ createInvitation: internalAction({
77
+ args: {
78
+ scopeId: v.string(),
79
+ email: v.string(),
80
+ roleIds: v.optional(v.array(v.string())),
81
+ roleKeys: v.optional(v.array(v.string())),
82
+ expiresInDays: v.optional(v.number()),
83
+ },
84
+ handler: async (_ctx, args) => {
85
+ const result = await createAccessInvitation(args, options);
86
+ return result;
87
+ },
88
+ }),
89
+ revokeInvitation: internalAction({
90
+ args: { scopeId: v.string(), invitationId: v.string() },
91
+ handler: async (_ctx, args) => {
92
+ const body = {
93
+ scope_id: args.scopeId,
94
+ invitation_id: args.invitationId,
95
+ ...serviceActor,
96
+ };
97
+ return await callAccessControlApi("/v1/access-control/invitations/revoke", body);
98
+ },
99
+ }),
100
+ assignRole: internalAction({
101
+ args: {
102
+ scopeId: v.string(),
103
+ ...optionalPrincipalRef,
104
+ ...optionalRoleRef,
105
+ },
106
+ handler: async (_ctx, args) => {
107
+ const body = {
108
+ scope_id: args.scopeId,
109
+ ...principalRef(args),
110
+ ...roleRef(args),
111
+ ...serviceActor,
112
+ };
113
+ return await callAccessControlApi("/v1/access-control/roles/assign", body);
114
+ },
115
+ }),
116
+ removeRole: internalAction({
117
+ args: {
118
+ scopeId: v.string(),
119
+ ...optionalPrincipalRef,
120
+ ...optionalRoleRef,
121
+ },
122
+ handler: async (_ctx, args) => {
123
+ const body = {
124
+ scope_id: args.scopeId,
125
+ ...principalRef(args),
126
+ ...roleRef(args),
127
+ ...serviceActor,
128
+ };
129
+ return await callAccessControlApi("/v1/access-control/roles/remove", body);
130
+ },
131
+ }),
132
+ createOrgCustomRole: internalAction({
133
+ args: {
134
+ scopeId: v.string(),
135
+ key: v.optional(v.string()),
136
+ name: v.string(),
137
+ description: v.optional(v.string()),
138
+ permissionKeys: v.array(v.string()),
139
+ },
140
+ handler: async (_ctx, args) => {
141
+ const body = {
142
+ scope_id: args.scopeId,
143
+ key: args.key,
144
+ name: args.name,
145
+ description: args.description,
146
+ permission_keys: args.permissionKeys,
147
+ ...serviceActor,
148
+ };
149
+ return await callAccessControlApi("/v1/access-control/roles/create-org-custom", body);
150
+ },
151
+ }),
152
+ updateRolePermissions: internalAction({
153
+ args: {
154
+ scopeId: v.string(),
155
+ ...optionalRoleRef,
156
+ permissionKeys: v.array(v.string()),
157
+ },
158
+ handler: async (_ctx, args) => {
159
+ const body = {
160
+ scope_id: args.scopeId,
161
+ ...roleRef(args),
162
+ permission_keys: args.permissionKeys,
163
+ ...serviceActor,
164
+ };
165
+ return await callAccessControlApi("/v1/access-control/roles/update-permissions", body);
166
+ },
167
+ }),
168
+ setUserExceptions: internalAction({
169
+ args: {
170
+ scopeId: v.string(),
171
+ ...optionalPrincipalRef,
172
+ allow: v.array(v.string()),
173
+ deny: v.array(v.string()),
174
+ },
175
+ handler: async (_ctx, args) => {
176
+ const body = {
177
+ scope_id: args.scopeId,
178
+ ...principalRef(args),
179
+ allow: args.allow,
180
+ deny: args.deny,
181
+ ...serviceActor,
182
+ };
183
+ return await callAccessControlApi("/v1/access-control/user-exceptions/set", body);
184
+ },
185
+ }),
186
+ createResourceGrant: internalAction({
187
+ args: {
188
+ scopeId: v.string(),
189
+ ...optionalPrincipalRef,
190
+ resourceType: v.string(),
191
+ resourceId: v.string(),
192
+ roleKey: v.optional(v.string()),
193
+ permissionKey: v.optional(v.string()),
194
+ appliesTo: v.optional(bindingAppliesToValidator),
195
+ expiresAt: v.optional(v.union(v.string(), v.null())),
196
+ },
197
+ handler: async (_ctx, args) => {
198
+ requireExactResource(args);
199
+ requireExactResourceForDescendants(args);
200
+ const body = {
201
+ scope_id: args.scopeId,
202
+ ...principalRef(args),
203
+ resource_type: args.resourceType,
204
+ resource_id: args.resourceId,
205
+ role_key: args.roleKey,
206
+ permission_key: args.permissionKey,
207
+ ...(args.appliesTo ? { applies_to: args.appliesTo } : {}),
208
+ expires_at: args.expiresAt,
209
+ ...serviceActor,
210
+ };
211
+ const result = await callAccessControlApi("/v1/access-control/resource-grants/create", body);
212
+ return normalizeAccessResourceGrantWriteResult(result);
213
+ },
214
+ }),
215
+ replaceResourceGrants: internalAction({
216
+ args: {
217
+ scopeId: v.string(),
218
+ resourceType: v.string(),
219
+ resourceId: v.string(),
220
+ subjects: v.array(resourceGrantSubjectReplacementValidator),
221
+ },
222
+ handler: async (_ctx, args) => {
223
+ requireExactResource(args);
224
+ const body = {
225
+ scope_id: args.scopeId,
226
+ resource_type: args.resourceType,
227
+ resource_id: args.resourceId,
228
+ subjects: resourceGrantReplacementSubjectsBody(args.subjects),
229
+ ...serviceActor,
230
+ };
231
+ const result = await callAccessControlApi("/v1/access-control/resource-grants/replace", body);
232
+ return normalizeAccessResourceGrantsReplaceResult(result);
233
+ },
234
+ }),
235
+ replaceMemberRoles: internalAction({
236
+ args: {
237
+ scopeId: v.string(),
238
+ ...optionalPrincipalRef,
239
+ roleKeys: v.array(v.string()),
240
+ },
241
+ handler: async (_ctx, args) => {
242
+ const body = {
243
+ scope_id: args.scopeId,
244
+ ...principalRef(args),
245
+ role_keys: memberRoleReplacementKeysBody(args.roleKeys),
246
+ ...serviceActor,
247
+ };
248
+ const result = await callAccessControlApi("/v1/access-control/roles/replace", body);
249
+ return normalizeAccessMemberRolesReplaceResult(result);
250
+ },
251
+ }),
252
+ createResourceInvitation: internalAction({
253
+ args: {
254
+ scopeId: v.string(),
255
+ email: v.string(),
256
+ resourceType: v.string(),
257
+ resourceId: v.string(),
258
+ roleKey: v.optional(v.string()),
259
+ permissionKey: v.optional(v.string()),
260
+ appliesTo: v.optional(bindingAppliesToValidator),
261
+ expiresInDays: v.optional(v.number()),
262
+ },
263
+ handler: async (_ctx, args) => {
264
+ return await createResourceInvitation(args, options);
265
+ },
266
+ }),
267
+ setResourcePermissionRule: internalAction({
268
+ args: {
269
+ scopeId: v.string(),
270
+ subject: resourceRuleSubjectValidator,
271
+ resourceType: v.string(),
272
+ target: resourceRuleTargetValidator,
273
+ permissionKey: v.string(),
274
+ effect: resourceRuleEffectValidator,
275
+ appliesTo: v.optional(bindingAppliesToValidator),
276
+ expiresAt: v.optional(v.union(v.string(), v.null())),
277
+ },
278
+ handler: async (_ctx, args) => {
279
+ requireSpecificTargetForDescendants(args);
280
+ const body = {
281
+ scope_id: args.scopeId,
282
+ subject: resourceRuleSubjectBody(args.subject),
283
+ resource_type: args.resourceType,
284
+ target: resourceRuleTargetBody(args.target),
285
+ permission_key: args.permissionKey,
286
+ effect: args.effect,
287
+ ...(args.appliesTo ? { applies_to: args.appliesTo } : {}),
288
+ expires_at: args.expiresAt,
289
+ ...serviceActor,
290
+ };
291
+ return await callAccessControlApi("/v1/access-control/resource-rules/set", body);
292
+ },
293
+ }),
294
+ setResourcePermissionRules: internalAction({
295
+ args: {
296
+ scopeId: v.string(),
297
+ subject: resourceRuleSubjectValidator,
298
+ resourceType: v.string(),
299
+ target: resourceRuleTargetValidator,
300
+ appliesTo: v.optional(bindingAppliesToValidator),
301
+ rules: v.array(v.object({
302
+ permissionKey: v.string(),
303
+ effect: resourceRuleReplacementEffectValidator,
304
+ expiresAt: v.optional(v.union(v.string(), v.null())),
305
+ })),
306
+ },
307
+ handler: async (_ctx, args) => {
308
+ requireSpecificTargetForDescendants(args);
309
+ const body = {
310
+ scope_id: args.scopeId,
311
+ subject: resourceRuleSubjectBody(args.subject),
312
+ resource_type: args.resourceType,
313
+ target: resourceRuleTargetBody(args.target),
314
+ ...(args.appliesTo ? { applies_to: args.appliesTo } : {}),
315
+ rules: args.rules.map((rule) => ({
316
+ permission_key: rule.permissionKey,
317
+ effect: rule.effect,
318
+ expires_at: rule.expiresAt,
319
+ })),
320
+ ...serviceActor,
321
+ };
322
+ return await callAccessControlApi("/v1/access-control/resource-rules/replace", body);
323
+ },
324
+ }),
325
+ revokeResourceGrant: internalAction({
326
+ args: { scopeId: v.string(), grantId: v.string() },
327
+ handler: async (_ctx, args) => {
328
+ const body = {
329
+ scope_id: args.scopeId,
330
+ grant_id: args.grantId,
331
+ ...serviceActor,
332
+ };
333
+ const result = await callAccessControlApi("/v1/access-control/resource-grants/revoke", body);
334
+ return normalizeAccessResourceGrantWriteResult(result);
335
+ },
336
+ }),
337
+ setGrantExpiry: internalAction({
338
+ args: {
339
+ scopeId: v.string(),
340
+ grantId: v.string(),
341
+ expiresAt: v.union(v.string(), v.null()),
342
+ },
343
+ handler: async (_ctx, args) => {
344
+ const body = {
345
+ scope_id: args.scopeId,
346
+ grant_id: args.grantId,
347
+ expires_at: args.expiresAt,
348
+ ...serviceActor,
349
+ };
350
+ const result = await callAccessControlApi("/v1/access-control/expiries/set", body);
351
+ return normalizeAccessResourceGrantWriteResult(result);
352
+ },
353
+ }),
354
+ setRoleOverride: internalAction({
355
+ args: {
356
+ scopeId: v.string(),
357
+ roleKey: v.string(),
358
+ allow: v.array(v.string()),
359
+ deny: v.array(v.string()),
360
+ },
361
+ handler: async (_ctx, args) => {
362
+ const body = {
363
+ scope_id: args.scopeId,
364
+ role_key: args.roleKey,
365
+ allow: args.allow,
366
+ deny: args.deny,
367
+ ...serviceActor,
368
+ };
369
+ return await callAccessControlApi("/v1/access-control/role-overrides/set", body);
370
+ },
371
+ }),
372
+ // Adds a member to an organization scope, identified by their Hercules
373
+ // Auth user id, with an optional role (the scope default role when
374
+ // omitted). It also restores a previously removed or suspended member to
375
+ // active.
376
+ addMember: internalAction({
377
+ args: {
378
+ scopeId: v.string(),
379
+ herculesAuthUserId: v.string(),
380
+ ...optionalRoleRef,
381
+ },
382
+ handler: async (_ctx, args) => {
383
+ const body = {
384
+ scope_id: args.scopeId,
385
+ hercules_auth_user_id: args.herculesAuthUserId,
386
+ ...roleRef(args),
387
+ ...serviceActor,
388
+ };
389
+ return await callAccessControlApi("/v1/access-control/members/add", body);
390
+ },
391
+ }),
392
+ setMemberStatus: internalAction({
393
+ args: {
394
+ scopeId: v.string(),
395
+ principalId: v.string(),
396
+ status: v.union(v.literal("active"), v.literal("suspended")),
397
+ },
398
+ handler: async (_ctx, args) => {
399
+ const body = {
400
+ scope_id: args.scopeId,
401
+ principal_id: args.principalId,
402
+ status: args.status,
403
+ ...serviceActor,
404
+ };
405
+ return await callAccessControlApi("/v1/access-control/members/status", body);
406
+ },
407
+ }),
408
+ removeMember: internalAction({
409
+ args: { scopeId: v.string(), principalId: v.string() },
410
+ handler: async (_ctx, args) => {
411
+ const body = {
412
+ scope_id: args.scopeId,
413
+ principal_id: args.principalId,
414
+ ...serviceActor,
415
+ };
416
+ return await callAccessControlApi("/v1/access-control/members/remove", body);
417
+ },
418
+ }),
419
+ approveMember: internalAction({
420
+ args: { scopeId: v.string(), principalId: v.string() },
421
+ handler: async (_ctx, args) => {
422
+ const body = {
423
+ scope_id: args.scopeId,
424
+ principal_id: args.principalId,
425
+ ...serviceActor,
426
+ };
427
+ return await callAccessControlApi("/v1/access-control/members/approve", body);
428
+ },
429
+ }),
430
+ upsertAdmissionRule: internalAction({
431
+ args: {
432
+ scopeId: v.string(),
433
+ effect: v.union(v.literal("allow"), v.literal("deny")),
434
+ subjectType: v.union(v.literal("email"), v.literal("domain")),
435
+ subjectValue: v.string(),
436
+ reason: v.optional(v.union(v.string(), v.null())),
437
+ },
438
+ handler: async (_ctx, args) => {
439
+ const body = {
440
+ scope_id: args.scopeId,
441
+ effect: args.effect,
442
+ subject_type: args.subjectType,
443
+ subject_value: args.subjectValue,
444
+ reason: args.reason,
445
+ ...serviceActor,
446
+ };
447
+ return await callAccessControlApi("/v1/access-control/admission-rules/upsert", body);
448
+ },
449
+ }),
450
+ archiveAdmissionRule: internalAction({
451
+ args: { scopeId: v.string(), ruleId: v.string() },
452
+ handler: async (_ctx, args) => {
453
+ const body = {
454
+ scope_id: args.scopeId,
455
+ rule_id: args.ruleId,
456
+ ...serviceActor,
457
+ };
458
+ return await callAccessControlApi("/v1/access-control/admission-rules/archive", body);
459
+ },
460
+ }),
461
+ setAccountEntryMode: internalAction({
462
+ args: {
463
+ scopeId: v.string(),
464
+ accountEntryMode: accountEntryModeValidator,
465
+ },
466
+ handler: async (_ctx, args) => {
467
+ const body = {
468
+ scope_id: args.scopeId,
469
+ account_entry_mode: args.accountEntryMode,
470
+ ...serviceActor,
471
+ };
472
+ return await callAccessControlApi("/v1/access-control/entry-mode/set", body);
473
+ },
474
+ }),
475
+ createGroup: internalAction({
476
+ args: { scopeId: v.string(), name: v.string() },
477
+ handler: async (_ctx, args) => {
478
+ const body = {
479
+ scope_id: args.scopeId,
480
+ name: args.name,
481
+ ...serviceActor,
482
+ };
483
+ const result = await callAccessControlApi("/v1/access-control/groups/create", body);
484
+ return normalizeAccessGroupWriteResult(result);
485
+ },
486
+ }),
487
+ renameGroup: internalAction({
488
+ args: {
489
+ scopeId: v.string(),
490
+ groupPrincipalId: v.string(),
491
+ name: v.string(),
492
+ },
493
+ handler: async (_ctx, args) => {
494
+ const body = {
495
+ scope_id: args.scopeId,
496
+ group_principal_id: args.groupPrincipalId,
497
+ name: args.name,
498
+ ...serviceActor,
499
+ };
500
+ const result = await callAccessControlApi("/v1/access-control/groups/rename", body);
501
+ return normalizeAccessGroupWriteResult(result);
502
+ },
503
+ }),
504
+ // Archive is the group's terminal state and only removal path (no hard delete).
505
+ archiveGroup: internalAction({
506
+ args: { scopeId: v.string(), groupPrincipalId: v.string() },
507
+ handler: async (_ctx, args) => {
508
+ const body = {
509
+ scope_id: args.scopeId,
510
+ group_principal_id: args.groupPrincipalId,
511
+ ...serviceActor,
512
+ };
513
+ const result = await callAccessControlApi("/v1/access-control/groups/archive", body);
514
+ return normalizeAccessGroupWriteResult(result);
515
+ },
516
+ }),
517
+ listGroups: internalAction({
518
+ args: { scopeId: v.string(), includeArchived: v.optional(v.boolean()) },
519
+ handler: async (_ctx, args) => {
520
+ const body = {
521
+ scope_id: args.scopeId,
522
+ include_archived: args.includeArchived,
523
+ ...serviceActor,
524
+ };
525
+ const result = await callAccessControlApi("/v1/access-control/groups/list", body);
526
+ return normalizeAccessGroupListResult(result);
527
+ },
528
+ }),
529
+ addGroupMember: internalAction({
530
+ args: {
531
+ scopeId: v.string(),
532
+ groupPrincipalId: v.string(),
533
+ memberPrincipalId: v.string(),
534
+ },
535
+ handler: async (_ctx, args) => {
536
+ const body = {
537
+ scope_id: args.scopeId,
538
+ group_principal_id: args.groupPrincipalId,
539
+ member_principal_id: args.memberPrincipalId,
540
+ ...serviceActor,
541
+ };
542
+ const result = await callAccessControlApi("/v1/access-control/groups/members/add", body);
543
+ return normalizeAccessGroupMemberWriteResult(result);
544
+ },
545
+ }),
546
+ removeGroupMember: internalAction({
547
+ args: {
548
+ scopeId: v.string(),
549
+ groupPrincipalId: v.string(),
550
+ memberPrincipalId: v.string(),
551
+ },
552
+ handler: async (_ctx, args) => {
553
+ const body = {
554
+ scope_id: args.scopeId,
555
+ group_principal_id: args.groupPrincipalId,
556
+ member_principal_id: args.memberPrincipalId,
557
+ ...serviceActor,
558
+ };
559
+ const result = await callAccessControlApi("/v1/access-control/groups/members/remove", body);
560
+ return normalizeAccessGroupMemberWriteResult(result);
561
+ },
562
+ }),
563
+ listResourceInvitations: internalAction({
564
+ args: { scopeId: v.string() },
565
+ handler: async (_ctx, args) => {
566
+ const body = { scope_id: args.scopeId, ...serviceActor };
567
+ const result = await callAccessControlApi("/v1/access-control/invitations/list-resource", body);
568
+ return normalizeAccessResourceInvitationListResult(result);
569
+ },
570
+ }),
571
+ getRoleOverrides: internalAction({
572
+ args: { scopeId: v.string(), ...optionalRoleRef },
573
+ handler: async (_ctx, args) => {
574
+ const body = {
575
+ scope_id: args.scopeId,
576
+ ...roleRef(args),
577
+ ...serviceActor,
578
+ };
579
+ const result = await callAccessControlApi("/v1/access-control/role-overrides/get", body);
580
+ return normalizeAccessRoleOverridesResult(result);
581
+ },
582
+ }),
583
+ getUserExceptions: internalAction({
584
+ args: { scopeId: v.string(), ...optionalPrincipalRef },
585
+ handler: async (_ctx, args) => {
586
+ const body = {
587
+ scope_id: args.scopeId,
588
+ ...principalRef(args),
589
+ ...serviceActor,
590
+ };
591
+ const result = await callAccessControlApi("/v1/access-control/user-exceptions/get", body);
592
+ return normalizeAccessUserExceptionsResult(result);
593
+ },
594
+ }),
595
+ };
596
+ }
597
+ /**
598
+ * Builds authenticated public actions for end-user access management. The
599
+ * control plane verifies the supplied ID token and applies the operation's
600
+ * scope, Owner, or resource-level RBAC gate.
601
+ *
602
+ * Every action's `idToken` argument must be the signed-in user's OIDC ID token
603
+ * (`user.id_token`): a JWT with three dot-separated segments. Never pass a user
604
+ * or subject id (for example `user.profile.sub`); the SDK rejects values that
605
+ * are not JWT-shaped before calling the API.
606
+ */
607
+ export function createAccessUserActions(options) {
608
+ const callAccessControlApi = makeAccessControlApiCaller(options);
609
+ const { authenticatedAction } = options;
610
+ return {
611
+ enterDeployment: authenticatedAction({
612
+ args: { idToken: v.string() },
613
+ handler: async (ctx, args) => {
614
+ const idToken = normalizeIdToken(args.idToken);
615
+ if (options.getDeploymentEntryStatus) {
616
+ const mirror = await options.getDeploymentEntryStatus(ctx);
617
+ if (mirror.kind === "principal" && mirror.status === "active") {
618
+ return activeDeploymentEntryResultFromMirror(mirror);
619
+ }
620
+ }
621
+ const result = await callAccessControlApi("/v1/access-control/entry", {
622
+ id_token: idToken,
623
+ });
624
+ return normalizeAccessDeploymentEntryResult(result);
625
+ },
626
+ }),
627
+ setDefaultRole: authenticatedAction({
628
+ args: { scopeId: v.string(), ...optionalRoleRef, idToken: v.string() },
629
+ handler: async (_ctx, args) => {
630
+ const body = {
631
+ scope_id: args.scopeId,
632
+ ...roleRef(args),
633
+ ...appUserActor(args.idToken),
634
+ };
635
+ return await callAccessControlApi("/v1/access-control/scopes/set-default-role", body);
636
+ },
637
+ }),
638
+ createInvitation: authenticatedAction({
639
+ args: {
640
+ scopeId: v.string(),
641
+ email: v.string(),
642
+ roleIds: v.optional(v.array(v.string())),
643
+ roleKeys: v.optional(v.array(v.string())),
644
+ expiresInDays: v.optional(v.number()),
645
+ idToken: v.string(),
646
+ },
647
+ handler: async (_ctx, args) => {
648
+ const body = {
649
+ scope_id: args.scopeId,
650
+ email: args.email,
651
+ role_ids: args.roleIds,
652
+ role_keys: args.roleKeys,
653
+ expires_in_days: args.expiresInDays,
654
+ ...appUserActor(args.idToken),
655
+ };
656
+ const result = await callAccessControlApi("/v1/access-control/invitations/create", body);
657
+ return normalizeAccessInvitationCreateResult(result);
658
+ },
659
+ }),
660
+ revokeInvitation: authenticatedAction({
661
+ args: {
662
+ scopeId: v.string(),
663
+ invitationId: v.string(),
664
+ idToken: v.string(),
665
+ },
666
+ handler: async (_ctx, args) => {
667
+ const body = {
668
+ scope_id: args.scopeId,
669
+ invitation_id: args.invitationId,
670
+ ...appUserActor(args.idToken),
671
+ };
672
+ return await callAccessControlApi("/v1/access-control/invitations/revoke", body);
673
+ },
674
+ }),
675
+ /**
676
+ * Lists only roles the signed-in actor may assign at the exact target.
677
+ * Use this for role pickers; `listScopeRoles` is the complete mirrored
678
+ * catalog and can include roles the actor is not authorized to confer.
679
+ * `subjectType` must match the intended user or group recipient.
680
+ */
681
+ listGrantableRoles: authenticatedAction({
682
+ args: {
683
+ scopeId: v.string(),
684
+ subjectType: v.union(v.literal("user"), v.literal("group")),
685
+ target: grantableRoleTargetValidator,
686
+ idToken: v.string(),
687
+ },
688
+ handler: async (_ctx, args) => {
689
+ const target = args.target.type === "scope"
690
+ ? { type: "scope" }
691
+ : {
692
+ type: "resource",
693
+ resource_type: args.target.resourceType,
694
+ resource_id: args.target.resourceId,
695
+ applies_to: args.target.appliesTo ?? "self",
696
+ };
697
+ const result = await callAccessControlApi("/v1/access-control/roles/list-grantable", {
698
+ scope_id: args.scopeId,
699
+ subject_type: args.subjectType,
700
+ target,
701
+ ...appUserActor(args.idToken),
702
+ });
703
+ return normalizeAccessGrantableRoleListResult(result);
704
+ },
705
+ }),
706
+ assignRole: authenticatedAction({
707
+ args: {
708
+ scopeId: v.string(),
709
+ ...optionalPrincipalRef,
710
+ ...optionalRoleRef,
711
+ idToken: v.string(),
712
+ },
713
+ handler: async (_ctx, args) => {
714
+ const body = {
715
+ scope_id: args.scopeId,
716
+ ...principalRef(args),
717
+ ...roleRef(args),
718
+ ...appUserActor(args.idToken),
719
+ };
720
+ return await callAccessControlApi("/v1/access-control/roles/assign", body);
721
+ },
722
+ }),
723
+ removeRole: authenticatedAction({
724
+ args: {
725
+ scopeId: v.string(),
726
+ ...optionalPrincipalRef,
727
+ ...optionalRoleRef,
728
+ idToken: v.string(),
729
+ },
730
+ handler: async (_ctx, args) => {
731
+ const body = {
732
+ scope_id: args.scopeId,
733
+ ...principalRef(args),
734
+ ...roleRef(args),
735
+ ...appUserActor(args.idToken),
736
+ };
737
+ return await callAccessControlApi("/v1/access-control/roles/remove", body);
738
+ },
739
+ }),
740
+ createOrgCustomRole: authenticatedAction({
741
+ args: {
742
+ scopeId: v.string(),
743
+ key: v.optional(v.string()),
744
+ name: v.string(),
745
+ description: v.optional(v.string()),
746
+ permissionKeys: v.array(v.string()),
747
+ idToken: v.string(),
748
+ },
749
+ handler: async (_ctx, args) => {
750
+ const body = {
751
+ scope_id: args.scopeId,
752
+ key: args.key,
753
+ name: args.name,
754
+ description: args.description,
755
+ permission_keys: args.permissionKeys,
756
+ ...appUserActor(args.idToken),
757
+ };
758
+ return await callAccessControlApi("/v1/access-control/roles/create-org-custom", body);
759
+ },
760
+ }),
761
+ updateRolePermissions: authenticatedAction({
762
+ args: {
763
+ scopeId: v.string(),
764
+ ...optionalRoleRef,
765
+ permissionKeys: v.array(v.string()),
766
+ idToken: v.string(),
767
+ },
768
+ handler: async (_ctx, args) => {
769
+ const body = {
770
+ scope_id: args.scopeId,
771
+ ...roleRef(args),
772
+ permission_keys: args.permissionKeys,
773
+ ...appUserActor(args.idToken),
774
+ };
775
+ return await callAccessControlApi("/v1/access-control/roles/update-permissions", body);
776
+ },
777
+ }),
778
+ setUserExceptions: authenticatedAction({
779
+ args: {
780
+ scopeId: v.string(),
781
+ ...optionalPrincipalRef,
782
+ allow: v.array(v.string()),
783
+ deny: v.array(v.string()),
784
+ idToken: v.string(),
785
+ },
786
+ handler: async (_ctx, args) => {
787
+ const body = {
788
+ scope_id: args.scopeId,
789
+ ...principalRef(args),
790
+ allow: args.allow,
791
+ deny: args.deny,
792
+ ...appUserActor(args.idToken),
793
+ };
794
+ return await callAccessControlApi("/v1/access-control/user-exceptions/set", body);
795
+ },
796
+ }),
797
+ createResourceGrant: authenticatedAction({
798
+ args: {
799
+ scopeId: v.string(),
800
+ ...optionalPrincipalRef,
801
+ resourceType: v.string(),
802
+ resourceId: v.string(),
803
+ roleKey: v.optional(v.string()),
804
+ permissionKey: v.optional(v.string()),
805
+ appliesTo: v.optional(bindingAppliesToValidator),
806
+ expiresAt: v.optional(v.union(v.string(), v.null())),
807
+ idToken: v.string(),
808
+ },
809
+ handler: async (_ctx, args) => {
810
+ requireExactResource(args);
811
+ requireExactResourceForDescendants(args);
812
+ const body = {
813
+ scope_id: args.scopeId,
814
+ ...principalRef(args),
815
+ resource_type: args.resourceType,
816
+ resource_id: args.resourceId,
817
+ role_key: args.roleKey,
818
+ permission_key: args.permissionKey,
819
+ ...(args.appliesTo ? { applies_to: args.appliesTo } : {}),
820
+ expires_at: args.expiresAt,
821
+ ...appUserActor(args.idToken),
822
+ };
823
+ const result = await callAccessControlApi("/v1/access-control/resource-grants/create", body);
824
+ return normalizeAccessResourceGrantWriteResult(result);
825
+ },
826
+ }),
827
+ replaceResourceGrants: authenticatedAction({
828
+ args: {
829
+ scopeId: v.string(),
830
+ resourceType: v.string(),
831
+ resourceId: v.string(),
832
+ subjects: v.array(resourceGrantSubjectReplacementValidator),
833
+ idToken: v.string(),
834
+ },
835
+ handler: async (_ctx, args) => {
836
+ requireExactResource(args);
837
+ const body = {
838
+ scope_id: args.scopeId,
839
+ resource_type: args.resourceType,
840
+ resource_id: args.resourceId,
841
+ subjects: resourceGrantReplacementSubjectsBody(args.subjects),
842
+ ...appUserActor(args.idToken),
843
+ };
844
+ const result = await callAccessControlApi("/v1/access-control/resource-grants/replace", body);
845
+ return normalizeAccessResourceGrantsReplaceResult(result);
846
+ },
847
+ }),
848
+ replaceMemberRoles: authenticatedAction({
849
+ args: {
850
+ scopeId: v.string(),
851
+ ...optionalPrincipalRef,
852
+ roleKeys: v.array(v.string()),
853
+ idToken: v.string(),
854
+ },
855
+ handler: async (_ctx, args) => {
856
+ const body = {
857
+ scope_id: args.scopeId,
858
+ ...principalRef(args),
859
+ role_keys: memberRoleReplacementKeysBody(args.roleKeys),
860
+ ...appUserActor(args.idToken),
861
+ };
862
+ const result = await callAccessControlApi("/v1/access-control/roles/replace", body);
863
+ return normalizeAccessMemberRolesReplaceResult(result);
864
+ },
865
+ }),
866
+ createResourceInvitation: authenticatedAction({
867
+ args: {
868
+ scopeId: v.string(),
869
+ email: v.string(),
870
+ resourceType: v.string(),
871
+ resourceId: v.string(),
872
+ roleKey: v.optional(v.string()),
873
+ permissionKey: v.optional(v.string()),
874
+ appliesTo: v.optional(bindingAppliesToValidator),
875
+ expiresInDays: v.optional(v.number()),
876
+ idToken: v.string(),
877
+ },
878
+ handler: async (_ctx, args) => {
879
+ requireExactResourceForDescendants(args);
880
+ const body = {
881
+ scope_id: args.scopeId,
882
+ email: args.email,
883
+ resource_type: args.resourceType,
884
+ resource_id: args.resourceId,
885
+ role_key: args.roleKey,
886
+ permission_key: args.permissionKey,
887
+ ...(args.appliesTo ? { applies_to: args.appliesTo } : {}),
888
+ expires_in_days: args.expiresInDays,
889
+ ...appUserActor(args.idToken),
890
+ };
891
+ const result = await callAccessControlApi("/v1/access-control/invitations/create-resource", body);
892
+ return normalizeAccessInvitationCreateResult(result);
893
+ },
894
+ }),
895
+ setResourcePermissionRule: authenticatedAction({
896
+ args: {
897
+ scopeId: v.string(),
898
+ subject: resourceRuleSubjectValidator,
899
+ resourceType: v.string(),
900
+ target: resourceRuleTargetValidator,
901
+ permissionKey: v.string(),
902
+ effect: resourceRuleEffectValidator,
903
+ appliesTo: v.optional(bindingAppliesToValidator),
904
+ expiresAt: v.optional(v.union(v.string(), v.null())),
905
+ idToken: v.string(),
906
+ },
907
+ handler: async (_ctx, args) => {
908
+ requireSpecificTargetForDescendants(args);
909
+ const body = {
910
+ scope_id: args.scopeId,
911
+ subject: resourceRuleSubjectBody(args.subject),
912
+ resource_type: args.resourceType,
913
+ target: resourceRuleTargetBody(args.target),
914
+ permission_key: args.permissionKey,
915
+ effect: args.effect,
916
+ ...(args.appliesTo ? { applies_to: args.appliesTo } : {}),
917
+ expires_at: args.expiresAt,
918
+ ...appUserActor(args.idToken),
919
+ };
920
+ return await callAccessControlApi("/v1/access-control/resource-rules/set", body);
921
+ },
922
+ }),
923
+ setResourcePermissionRules: authenticatedAction({
924
+ args: {
925
+ scopeId: v.string(),
926
+ subject: resourceRuleSubjectValidator,
927
+ resourceType: v.string(),
928
+ target: resourceRuleTargetValidator,
929
+ appliesTo: v.optional(bindingAppliesToValidator),
930
+ rules: v.array(v.object({
931
+ permissionKey: v.string(),
932
+ effect: resourceRuleReplacementEffectValidator,
933
+ expiresAt: v.optional(v.union(v.string(), v.null())),
934
+ })),
935
+ idToken: v.string(),
936
+ },
937
+ handler: async (_ctx, args) => {
938
+ requireSpecificTargetForDescendants(args);
939
+ const body = {
940
+ scope_id: args.scopeId,
941
+ subject: resourceRuleSubjectBody(args.subject),
942
+ resource_type: args.resourceType,
943
+ target: resourceRuleTargetBody(args.target),
944
+ ...(args.appliesTo ? { applies_to: args.appliesTo } : {}),
945
+ rules: args.rules.map((rule) => ({
946
+ permission_key: rule.permissionKey,
947
+ effect: rule.effect,
948
+ expires_at: rule.expiresAt,
949
+ })),
950
+ ...appUserActor(args.idToken),
951
+ };
952
+ return await callAccessControlApi("/v1/access-control/resource-rules/replace", body);
953
+ },
954
+ }),
955
+ revokeResourceGrant: authenticatedAction({
956
+ args: { scopeId: v.string(), grantId: v.string(), idToken: v.string() },
957
+ handler: async (_ctx, args) => {
958
+ const body = {
959
+ scope_id: args.scopeId,
960
+ grant_id: args.grantId,
961
+ ...appUserActor(args.idToken),
962
+ };
963
+ const result = await callAccessControlApi("/v1/access-control/resource-grants/revoke", body);
964
+ return normalizeAccessResourceGrantWriteResult(result);
965
+ },
966
+ }),
967
+ setGrantExpiry: authenticatedAction({
968
+ args: {
969
+ scopeId: v.string(),
970
+ grantId: v.string(),
971
+ expiresAt: v.union(v.string(), v.null()),
972
+ idToken: v.string(),
973
+ },
974
+ handler: async (_ctx, args) => {
975
+ const body = {
976
+ scope_id: args.scopeId,
977
+ grant_id: args.grantId,
978
+ expires_at: args.expiresAt,
979
+ ...appUserActor(args.idToken),
980
+ };
981
+ const result = await callAccessControlApi("/v1/access-control/expiries/set", body);
982
+ return normalizeAccessResourceGrantWriteResult(result);
983
+ },
984
+ }),
985
+ setRoleOverride: authenticatedAction({
986
+ args: {
987
+ scopeId: v.string(),
988
+ roleKey: v.string(),
989
+ allow: v.array(v.string()),
990
+ deny: v.array(v.string()),
991
+ idToken: v.string(),
992
+ },
993
+ handler: async (_ctx, args) => {
994
+ const body = {
995
+ scope_id: args.scopeId,
996
+ role_key: args.roleKey,
997
+ allow: args.allow,
998
+ deny: args.deny,
999
+ ...appUserActor(args.idToken),
1000
+ };
1001
+ return await callAccessControlApi("/v1/access-control/role-overrides/set", body);
1002
+ },
1003
+ }),
1004
+ // Adds a member to an organization scope, identified by their Hercules
1005
+ // Auth user id, with an optional role (the scope default role when
1006
+ // omitted). It also restores a previously removed or suspended member to
1007
+ // active.
1008
+ addMember: authenticatedAction({
1009
+ args: {
1010
+ scopeId: v.string(),
1011
+ herculesAuthUserId: v.string(),
1012
+ ...optionalRoleRef,
1013
+ idToken: v.string(),
1014
+ },
1015
+ handler: async (_ctx, args) => {
1016
+ const body = {
1017
+ scope_id: args.scopeId,
1018
+ hercules_auth_user_id: args.herculesAuthUserId,
1019
+ ...roleRef(args),
1020
+ ...appUserActor(args.idToken),
1021
+ };
1022
+ return await callAccessControlApi("/v1/access-control/members/add", body);
1023
+ },
1024
+ }),
1025
+ setMemberStatus: authenticatedAction({
1026
+ args: {
1027
+ scopeId: v.string(),
1028
+ principalId: v.string(),
1029
+ status: v.union(v.literal("active"), v.literal("suspended")),
1030
+ idToken: v.string(),
1031
+ },
1032
+ handler: async (_ctx, args) => {
1033
+ const body = {
1034
+ scope_id: args.scopeId,
1035
+ principal_id: args.principalId,
1036
+ status: args.status,
1037
+ ...appUserActor(args.idToken),
1038
+ };
1039
+ return await callAccessControlApi("/v1/access-control/members/status", body);
1040
+ },
1041
+ }),
1042
+ removeMember: authenticatedAction({
1043
+ args: {
1044
+ scopeId: v.string(),
1045
+ principalId: v.string(),
1046
+ idToken: v.string(),
1047
+ },
1048
+ handler: async (_ctx, args) => {
1049
+ const body = {
1050
+ scope_id: args.scopeId,
1051
+ principal_id: args.principalId,
1052
+ ...appUserActor(args.idToken),
1053
+ };
1054
+ return await callAccessControlApi("/v1/access-control/members/remove", body);
1055
+ },
1056
+ }),
1057
+ approveMember: authenticatedAction({
1058
+ args: {
1059
+ scopeId: v.string(),
1060
+ principalId: v.string(),
1061
+ idToken: v.string(),
1062
+ },
1063
+ handler: async (_ctx, args) => {
1064
+ const body = {
1065
+ scope_id: args.scopeId,
1066
+ principal_id: args.principalId,
1067
+ ...appUserActor(args.idToken),
1068
+ };
1069
+ return await callAccessControlApi("/v1/access-control/members/approve", body);
1070
+ },
1071
+ }),
1072
+ upsertAdmissionRule: authenticatedAction({
1073
+ args: {
1074
+ scopeId: v.string(),
1075
+ effect: v.union(v.literal("allow"), v.literal("deny")),
1076
+ subjectType: v.union(v.literal("email"), v.literal("domain")),
1077
+ subjectValue: v.string(),
1078
+ reason: v.optional(v.union(v.string(), v.null())),
1079
+ idToken: v.string(),
1080
+ },
1081
+ handler: async (_ctx, args) => {
1082
+ const body = {
1083
+ scope_id: args.scopeId,
1084
+ effect: args.effect,
1085
+ subject_type: args.subjectType,
1086
+ subject_value: args.subjectValue,
1087
+ reason: args.reason,
1088
+ ...appUserActor(args.idToken),
1089
+ };
1090
+ return await callAccessControlApi("/v1/access-control/admission-rules/upsert", body);
1091
+ },
1092
+ }),
1093
+ archiveAdmissionRule: authenticatedAction({
1094
+ args: { scopeId: v.string(), ruleId: v.string(), idToken: v.string() },
1095
+ handler: async (_ctx, args) => {
1096
+ const body = {
1097
+ scope_id: args.scopeId,
1098
+ rule_id: args.ruleId,
1099
+ ...appUserActor(args.idToken),
1100
+ };
1101
+ return await callAccessControlApi("/v1/access-control/admission-rules/archive", body);
1102
+ },
1103
+ }),
1104
+ setAccountEntryMode: authenticatedAction({
1105
+ args: {
1106
+ scopeId: v.string(),
1107
+ accountEntryMode: accountEntryModeValidator,
1108
+ idToken: v.string(),
1109
+ },
1110
+ handler: async (_ctx, args) => {
1111
+ const body = {
1112
+ scope_id: args.scopeId,
1113
+ account_entry_mode: args.accountEntryMode,
1114
+ ...appUserActor(args.idToken),
1115
+ };
1116
+ return await callAccessControlApi("/v1/access-control/entry-mode/set", body);
1117
+ },
1118
+ }),
1119
+ createGroup: authenticatedAction({
1120
+ args: { scopeId: v.string(), name: v.string(), idToken: v.string() },
1121
+ handler: async (_ctx, args) => {
1122
+ const body = {
1123
+ scope_id: args.scopeId,
1124
+ name: args.name,
1125
+ ...appUserActor(args.idToken),
1126
+ };
1127
+ const result = await callAccessControlApi("/v1/access-control/groups/create", body);
1128
+ return normalizeAccessGroupWriteResult(result);
1129
+ },
1130
+ }),
1131
+ renameGroup: authenticatedAction({
1132
+ args: {
1133
+ scopeId: v.string(),
1134
+ groupPrincipalId: v.string(),
1135
+ name: v.string(),
1136
+ idToken: v.string(),
1137
+ },
1138
+ handler: async (_ctx, args) => {
1139
+ const body = {
1140
+ scope_id: args.scopeId,
1141
+ group_principal_id: args.groupPrincipalId,
1142
+ name: args.name,
1143
+ ...appUserActor(args.idToken),
1144
+ };
1145
+ const result = await callAccessControlApi("/v1/access-control/groups/rename", body);
1146
+ return normalizeAccessGroupWriteResult(result);
1147
+ },
1148
+ }),
1149
+ // Archive is the group's terminal state and only removal path (no hard delete).
1150
+ archiveGroup: authenticatedAction({
1151
+ args: {
1152
+ scopeId: v.string(),
1153
+ groupPrincipalId: v.string(),
1154
+ idToken: v.string(),
1155
+ },
1156
+ handler: async (_ctx, args) => {
1157
+ const body = {
1158
+ scope_id: args.scopeId,
1159
+ group_principal_id: args.groupPrincipalId,
1160
+ ...appUserActor(args.idToken),
1161
+ };
1162
+ const result = await callAccessControlApi("/v1/access-control/groups/archive", body);
1163
+ return normalizeAccessGroupWriteResult(result);
1164
+ },
1165
+ }),
1166
+ listGroups: authenticatedAction({
1167
+ args: {
1168
+ scopeId: v.string(),
1169
+ includeArchived: v.optional(v.boolean()),
1170
+ idToken: v.string(),
1171
+ },
1172
+ handler: async (_ctx, args) => {
1173
+ const body = {
1174
+ scope_id: args.scopeId,
1175
+ include_archived: args.includeArchived,
1176
+ ...appUserActor(args.idToken),
1177
+ };
1178
+ const result = await callAccessControlApi("/v1/access-control/groups/list", body);
1179
+ return normalizeAccessGroupListResult(result);
1180
+ },
1181
+ }),
1182
+ addGroupMember: authenticatedAction({
1183
+ args: {
1184
+ scopeId: v.string(),
1185
+ groupPrincipalId: v.string(),
1186
+ memberPrincipalId: v.string(),
1187
+ idToken: v.string(),
1188
+ },
1189
+ handler: async (_ctx, args) => {
1190
+ const body = {
1191
+ scope_id: args.scopeId,
1192
+ group_principal_id: args.groupPrincipalId,
1193
+ member_principal_id: args.memberPrincipalId,
1194
+ ...appUserActor(args.idToken),
1195
+ };
1196
+ const result = await callAccessControlApi("/v1/access-control/groups/members/add", body);
1197
+ return normalizeAccessGroupMemberWriteResult(result);
1198
+ },
1199
+ }),
1200
+ removeGroupMember: authenticatedAction({
1201
+ args: {
1202
+ scopeId: v.string(),
1203
+ groupPrincipalId: v.string(),
1204
+ memberPrincipalId: v.string(),
1205
+ idToken: v.string(),
1206
+ },
1207
+ handler: async (_ctx, args) => {
1208
+ const body = {
1209
+ scope_id: args.scopeId,
1210
+ group_principal_id: args.groupPrincipalId,
1211
+ member_principal_id: args.memberPrincipalId,
1212
+ ...appUserActor(args.idToken),
1213
+ };
1214
+ const result = await callAccessControlApi("/v1/access-control/groups/members/remove", body);
1215
+ return normalizeAccessGroupMemberWriteResult(result);
1216
+ },
1217
+ }),
1218
+ listResourceInvitations: authenticatedAction({
1219
+ args: { scopeId: v.string(), idToken: v.string() },
1220
+ handler: async (_ctx, args) => {
1221
+ const body = { scope_id: args.scopeId, ...appUserActor(args.idToken) };
1222
+ const result = await callAccessControlApi("/v1/access-control/invitations/list-resource", body);
1223
+ return normalizeAccessResourceInvitationListResult(result);
1224
+ },
1225
+ }),
1226
+ getRoleOverrides: authenticatedAction({
1227
+ args: { scopeId: v.string(), ...optionalRoleRef, idToken: v.string() },
1228
+ handler: async (_ctx, args) => {
1229
+ const body = {
1230
+ scope_id: args.scopeId,
1231
+ ...roleRef(args),
1232
+ ...appUserActor(args.idToken),
1233
+ };
1234
+ const result = await callAccessControlApi("/v1/access-control/role-overrides/get", body);
1235
+ return normalizeAccessRoleOverridesResult(result);
1236
+ },
1237
+ }),
1238
+ getUserExceptions: authenticatedAction({
1239
+ args: {
1240
+ scopeId: v.string(),
1241
+ ...optionalPrincipalRef,
1242
+ idToken: v.string(),
1243
+ },
1244
+ handler: async (_ctx, args) => {
1245
+ const body = {
1246
+ scope_id: args.scopeId,
1247
+ ...principalRef(args),
1248
+ ...appUserActor(args.idToken),
1249
+ };
1250
+ const result = await callAccessControlApi("/v1/access-control/user-exceptions/get", body);
1251
+ return normalizeAccessUserExceptionsResult(result);
1252
+ },
1253
+ }),
1254
+ };
1255
+ }
1256
+ /**
1257
+ * Builds a public authenticated action for creating an organization scope.
1258
+ * `canCreateScope` is the app's product-policy gate. The authenticated caller
1259
+ * becomes the new scope's Owner automatically; do not add a separate self
1260
+ * role or resource grant.
1261
+ */
1262
+ export function createAccessScopeAction(options) {
1263
+ return options.authenticatedAction({
1264
+ args: {
1265
+ name: v.string(),
1266
+ defaultRoleKey: v.optional(v.string()),
1267
+ accountEntryMode: v.optional(accountEntryModeValidator),
1268
+ },
1269
+ handler: async (ctx, args) => {
1270
+ const allowed = await options.canCreateScope(ctx, args);
1271
+ if (!allowed) {
1272
+ throw new ConvexError({
1273
+ code: "ACCESS_DENIED",
1274
+ message: "Access denied",
1275
+ });
1276
+ }
1277
+ return await createAccessScope(ctx, args, options);
1278
+ },
1279
+ });
1280
+ }
1281
+ /**
1282
+ * Builds a public action that gives a newly created app resource's trusted
1283
+ * creator one fixed manager role, then marks the app row active.
1284
+ *
1285
+ * The browser supplies only `resourceId`. App-owned callbacks must load the
1286
+ * trusted creator and scope from the database and activate the same
1287
+ * provisioning row. The resource type, role, and descendant behavior are
1288
+ * static factory configuration, so callers cannot turn this into arbitrary
1289
+ * self-grant.
1290
+ *
1291
+ * Keep the resource unavailable while it is `provisioning`. If activation
1292
+ * fails after the grant, retrying is safe because the control-plane grant
1293
+ * write is idempotent. Once active, this action never recreates a removed
1294
+ * manager grant.
1295
+ */
1296
+ export function createResourceCreatorBootstrapAction(options) {
1297
+ const callAccessControlApi = makeAccessControlApiCaller(options);
1298
+ return options.authenticatedAction({
1299
+ args: { resourceId: v.string() },
1300
+ handler: async (ctx, args) => {
1301
+ const identity = await ctx.auth.getUserIdentity();
1302
+ const actorHerculesAuthUserId = parseTokenIdentifierSubject(identity?.tokenIdentifier);
1303
+ const target = await options.getBootstrapTarget(ctx, args);
1304
+ if (!target ||
1305
+ target.resourceId !== args.resourceId ||
1306
+ target.creatorHerculesAuthUserId !== actorHerculesAuthUserId) {
1307
+ throwAccessDenied();
1308
+ }
1309
+ const memberships = await options.listMyMemberships(ctx);
1310
+ const activeInTargetScope = memberships.some((membership) => membership.scopeId === target.scopeId &&
1311
+ membership.status === "active");
1312
+ if (!activeInTargetScope) {
1313
+ throwAccessDenied();
1314
+ }
1315
+ if (target.state === "active") {
1316
+ return {
1317
+ resourceId: target.resourceId,
1318
+ state: "active",
1319
+ bootstrapped: false,
1320
+ };
1321
+ }
1322
+ const result = await callAccessControlApi("/v1/access-control/resource-grants/create", {
1323
+ scope_id: target.scopeId,
1324
+ principal_id: undefined,
1325
+ hercules_auth_user_id: actorHerculesAuthUserId,
1326
+ resource_type: options.resourceType,
1327
+ resource_id: target.resourceId,
1328
+ role_key: options.managerRoleKey,
1329
+ permission_key: undefined,
1330
+ applies_to: options.appliesTo,
1331
+ expires_at: undefined,
1332
+ ...serviceActor,
1333
+ });
1334
+ const grant = normalizeAccessResourceGrantWriteResult(result);
1335
+ await options.activateResource(ctx, {
1336
+ resourceId: target.resourceId,
1337
+ creatorHerculesAuthUserId: actorHerculesAuthUserId,
1338
+ grant,
1339
+ });
1340
+ return {
1341
+ resourceId: target.resourceId,
1342
+ state: "active",
1343
+ bootstrapped: true,
1344
+ grant,
1345
+ };
1346
+ },
1347
+ });
1348
+ }
1349
+ /**
1350
+ * Creates an organization scope for the authenticated caller. Hercules derives
1351
+ * the caller from the Convex identity and makes that user Owner of the new
1352
+ * scope. The app should persist the returned `accessScopeId` on its
1353
+ * organization metadata row.
1354
+ */
1355
+ export async function createAccessScope(ctx, args, options = {}) {
1356
+ const callAccessControlApi = makeAccessControlApiCaller(options);
1357
+ const identity = await ctx.auth.getUserIdentity();
1358
+ const actorHerculesAuthUserId = parseTokenIdentifierSubject(identity?.tokenIdentifier);
1359
+ const body = {
1360
+ name: args.name,
1361
+ default_role_key: args.defaultRoleKey,
1362
+ account_entry_mode: args.accountEntryMode,
1363
+ owner_hercules_auth_user_id: actorHerculesAuthUserId,
1364
+ };
1365
+ const result = await callAccessControlApi("/v1/access-control/scopes/create", body);
1366
+ return normalizeAccessScopeCreateResult(result);
1367
+ }
1368
+ export async function createAccessInvitation(args, options = {}) {
1369
+ const callAccessControlApi = makeAccessControlApiCaller(options);
1370
+ const body = {
1371
+ scope_id: args.scopeId,
1372
+ email: args.email,
1373
+ role_ids: args.roleIds,
1374
+ role_keys: args.roleKeys,
1375
+ expires_in_days: args.expiresInDays,
1376
+ ...serviceActor,
1377
+ };
1378
+ const result = await callAccessControlApi("/v1/access-control/invitations/create", body);
1379
+ return normalizeAccessInvitationCreateResult(result);
1380
+ }
1381
+ /**
1382
+ * Invite an email to a single resource, conferring a custom role or a single
1383
+ * permission scoped to that resource (not the whole scope). Pass exactly one of
1384
+ * `roleKey` / `permissionKey`. This helper always acts as the internal service.
1385
+ * Public app-user invitations are exposed by {@link createAccessUserActions}.
1386
+ */
1387
+ export async function createResourceInvitation(args, options = {}) {
1388
+ requireExactResourceForDescendants(args);
1389
+ const callAccessControlApi = makeAccessControlApiCaller(options);
1390
+ const body = {
1391
+ scope_id: args.scopeId,
1392
+ email: args.email,
1393
+ resource_type: args.resourceType,
1394
+ resource_id: args.resourceId,
1395
+ role_key: args.roleKey,
1396
+ permission_key: args.permissionKey,
1397
+ ...(args.appliesTo ? { applies_to: args.appliesTo } : {}),
1398
+ expires_in_days: args.expiresInDays,
1399
+ ...serviceActor,
1400
+ };
1401
+ const result = await callAccessControlApi("/v1/access-control/invitations/create-resource", body);
1402
+ return normalizeAccessInvitationCreateResult(result);
1403
+ }
1404
+ function requireExactResourceForDescendants(args) {
1405
+ if (args.appliesTo === "self_and_descendants" &&
1406
+ (typeof args.resourceId !== "string" || args.resourceId.length === 0)) {
1407
+ throw new Error('appliesTo "self_and_descendants" requires an exact resourceId.');
1408
+ }
1409
+ }
1410
+ function requireSpecificTargetForDescendants(args) {
1411
+ if (args.appliesTo === "self_and_descendants" &&
1412
+ args.target.mode !== "specific") {
1413
+ throw new Error('appliesTo "self_and_descendants" requires a specific resource target.');
1414
+ }
1415
+ }
1416
+ export async function acceptAccessInvitation(ctx, args, options = {}) {
1417
+ const callAccessControlApi = makeAccessControlApiCaller(options);
1418
+ const identity = await ctx.auth.getUserIdentity();
1419
+ requireTokenIdentifier(identity?.tokenIdentifier);
1420
+ const body = { token: args.token, id_token: normalizeIdToken(args.idToken) };
1421
+ const result = await callAccessControlApi("/v1/access-control/invitations/accept", body);
1422
+ return normalizeAccessInvitationAcceptResult(result);
1423
+ }
1424
+ function makeAccessControlApiCaller(options) {
1425
+ let client = options.client;
1426
+ return async (path, body) => {
1427
+ client ??= createSdkClient(options);
1428
+ return await client.post(path, { body });
1429
+ };
1430
+ }
1431
+ function createSdkClient(options) {
1432
+ const envVarName = options.apiKeyEnvVar ?? DEFAULT_ACCESS_ADMIN_API_KEY_ENV_VAR;
1433
+ const apiKey = options.apiKey ?? process.env[envVarName];
1434
+ if (!apiKey) {
1435
+ throw new Error(`${envVarName} is required for Access Control admin actions.`);
1436
+ }
1437
+ return new Hercules({
1438
+ apiKey,
1439
+ apiVersion: options.apiVersion ?? DEFAULT_API_VERSION,
1440
+ });
1441
+ }
1442
+ function appUserActor(idToken) {
1443
+ return {
1444
+ actor_mode: "app_user",
1445
+ id_token: normalizeIdToken(idToken),
1446
+ };
1447
+ }
1448
+ // An OIDC ID token is a JWT: three dot-separated base64url segments. A bare
1449
+ // user or subject id (for example user.profile.sub) has no dots, so a shape
1450
+ // check here turns the most common token mix-up into an immediate developer
1451
+ // error instead of a confusing control-plane 403.
1452
+ const jwtShapePattern = /^[\w-]+\.[\w-]+\.[\w-]+$/;
1453
+ function normalizeIdToken(idToken) {
1454
+ const normalizedIdToken = idToken.trim();
1455
+ if (!normalizedIdToken) {
1456
+ throw new ConvexError({
1457
+ code: "INVALID_ID_TOKEN",
1458
+ message: "idToken is required",
1459
+ });
1460
+ }
1461
+ if (!jwtShapePattern.test(normalizedIdToken)) {
1462
+ throw new ConvexError({
1463
+ code: "INVALID_ID_TOKEN",
1464
+ message: "idToken does not look like an OIDC ID token (a JWT with three dot-separated segments). " +
1465
+ "Pass the signed-in user's ID token (user.id_token), not a user or subject id such as user.profile.sub.",
1466
+ });
1467
+ }
1468
+ return normalizedIdToken;
1469
+ }
1470
+ function principalRef(args) {
1471
+ return {
1472
+ principal_id: args.principalId,
1473
+ hercules_auth_user_id: args.herculesAuthUserId,
1474
+ };
1475
+ }
1476
+ function memberRoleReplacementKeysBody(roleKeys) {
1477
+ if (roleKeys.length > MAX_MEMBER_ROLE_REPLACEMENT_ENTRIES) {
1478
+ throw new Error(`At most ${MAX_MEMBER_ROLE_REPLACEMENT_ENTRIES} member roles can be replaced at once.`);
1479
+ }
1480
+ return roleKeys;
1481
+ }
1482
+ function resourceGrantReplacementSubjectsBody(subjects) {
1483
+ if (subjects.length < MIN_RESOURCE_GRANT_REPLACEMENT_SUBJECTS) {
1484
+ throw new Error(`At least ${MIN_RESOURCE_GRANT_REPLACEMENT_SUBJECTS} resource grant subject is required.`);
1485
+ }
1486
+ if (subjects.length > MAX_RESOURCE_GRANT_REPLACEMENT_SUBJECTS) {
1487
+ throw new Error(`At most ${MAX_RESOURCE_GRANT_REPLACEMENT_SUBJECTS} resource grant subjects can be replaced at once.`);
1488
+ }
1489
+ const grantCount = subjects.reduce((count, subject) => count + subject.grants.length, 0);
1490
+ if (grantCount > MAX_RESOURCE_GRANT_REPLACEMENT_ENTRIES) {
1491
+ throw new Error(`At most ${MAX_RESOURCE_GRANT_REPLACEMENT_ENTRIES} resource grants can be replaced at once. Split larger edits by subjects.`);
1492
+ }
1493
+ return subjects.map((subject) => ({
1494
+ ...principalRef(subject),
1495
+ grants: subject.grants.map((grant) => ({
1496
+ role_key: grant.roleKey,
1497
+ permission_key: grant.permissionKey,
1498
+ applies_to: grant.appliesTo,
1499
+ expires_at: grant.expiresAt,
1500
+ })),
1501
+ }));
1502
+ }
1503
+ function requireExactResource(args) {
1504
+ if (typeof args.resourceId !== "string" ||
1505
+ args.resourceId.trim().length === 0) {
1506
+ throw new Error("resourceId must identify one exact resource.");
1507
+ }
1508
+ }
1509
+ function roleRef(args) {
1510
+ return { role_id: args.roleId, role_key: args.roleKey };
1511
+ }
1512
+ function resourceRuleSubjectBody(subject) {
1513
+ return subject.type === "role"
1514
+ ? { type: "role", role_key: subject.roleKey }
1515
+ : { type: "principal", principal_id: subject.principalId };
1516
+ }
1517
+ function resourceRuleTargetBody(target) {
1518
+ return target.mode === "all"
1519
+ ? { mode: "all" }
1520
+ : { mode: "specific", resource_id: target.resourceId };
1521
+ }
1522
+ function parseTokenIdentifierSubject(tokenIdentifier) {
1523
+ const value = requireTokenIdentifier(tokenIdentifier);
1524
+ const separatorIndex = value.lastIndexOf("|");
1525
+ return value.slice(separatorIndex + 1);
1526
+ }
1527
+ function requireTokenIdentifier(tokenIdentifier) {
1528
+ if (!tokenIdentifier) {
1529
+ throw new ConvexError({
1530
+ code: "UNAUTHENTICATED",
1531
+ message: "Authentication required",
1532
+ });
1533
+ }
1534
+ const separatorIndex = tokenIdentifier.lastIndexOf("|");
1535
+ if (separatorIndex <= 0 || separatorIndex === tokenIdentifier.length - 1) {
1536
+ throw new ConvexError({
1537
+ code: "UNAUTHENTICATED",
1538
+ message: "Authentication required",
1539
+ });
1540
+ }
1541
+ return tokenIdentifier;
1542
+ }
1543
+ function throwAccessDenied() {
1544
+ throw new ConvexError({
1545
+ code: "ACCESS_DENIED",
1546
+ message: "Access denied",
1547
+ });
1548
+ }
1549
+ function normalizeAccessScopeCreateResult(result) {
1550
+ return {
1551
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1552
+ created: optionalBoolean(result, "created", "created"),
1553
+ sourceVersion: requiredNumber(result, "source_version", "sourceVersion"),
1554
+ projectionIds: requiredStringArray(result, "projection_ids", "projectionIds"),
1555
+ };
1556
+ }
1557
+ function normalizeAccessResourceGrantWriteResult(result) {
1558
+ return {
1559
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1560
+ grantId: requiredString(result, "grant_id", "grantId"),
1561
+ changed: requiredBoolean(result, "changed", "changed"),
1562
+ sourceVersion: requiredNumber(result, "source_version", "sourceVersion"),
1563
+ projectionIds: requiredStringArray(result, "projection_ids", "projectionIds"),
1564
+ };
1565
+ }
1566
+ function normalizeAccessResourceGrantsReplaceResult(result) {
1567
+ return {
1568
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1569
+ resourceType: requiredString(result, "resource_type", "resourceType"),
1570
+ resourceId: requiredString(result, "resource_id", "resourceId"),
1571
+ subjects: requiredRecordArray(result, "subjects", "subjects").map((subject) => ({
1572
+ principalId: requiredString(subject, "principal_id", "subjects[].principalId"),
1573
+ grants: requiredRecordArray(subject, "grants", "subjects[].grants").map((grant) => ({
1574
+ grantId: requiredString(grant, "grant_id", "subjects[].grants[].grantId"),
1575
+ roleId: nullableString(grant, "role_id", "subjects[].grants[].roleId"),
1576
+ permissionId: nullableString(grant, "permission_id", "subjects[].grants[].permissionId"),
1577
+ appliesTo: optionalBindingAppliesTo(grant),
1578
+ expiresAt: nullableString(grant, "expires_at", "subjects[].grants[].expiresAt"),
1579
+ })),
1580
+ })),
1581
+ changed: requiredBoolean(result, "changed", "changed"),
1582
+ sourceVersion: requiredNumber(result, "source_version", "sourceVersion"),
1583
+ projectionIds: requiredStringArray(result, "projection_ids", "projectionIds"),
1584
+ };
1585
+ }
1586
+ function normalizeAccessMemberRolesReplaceResult(result) {
1587
+ return {
1588
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1589
+ principalId: requiredString(result, "principal_id", "principalId"),
1590
+ roleIds: requiredStringArray(result, "role_ids", "roleIds"),
1591
+ changed: requiredBoolean(result, "changed", "changed"),
1592
+ sourceVersion: requiredNumber(result, "source_version", "sourceVersion"),
1593
+ projectionIds: requiredStringArray(result, "projection_ids", "projectionIds"),
1594
+ };
1595
+ }
1596
+ function normalizeAccessInvitationCreateResult(result) {
1597
+ return {
1598
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1599
+ invitationId: requiredString(result, "invitation_id", "invitationId"),
1600
+ email: requiredString(result, "email", "email"),
1601
+ roleIds: requiredStringArray(result, "role_ids", "roleIds"),
1602
+ token: requiredString(result, "token", "token"),
1603
+ acceptUrl: requiredString(result, "accept_url", "acceptUrl"),
1604
+ expiresAt: requiredString(result, "expires_at", "expiresAt"),
1605
+ sourceVersion: requiredNumber(result, "source_version", "sourceVersion"),
1606
+ projectionIds: requiredStringArray(result, "projection_ids", "projectionIds"),
1607
+ };
1608
+ }
1609
+ function normalizeAccessInvitationAcceptResult(result) {
1610
+ return {
1611
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1612
+ invitationId: requiredString(result, "invitation_id", "invitationId"),
1613
+ principalId: requiredString(result, "principal_id", "principalId"),
1614
+ roleIds: requiredStringArray(result, "role_ids", "roleIds"),
1615
+ changed: optionalBoolean(result, "changed", "changed"),
1616
+ sourceVersion: requiredNumber(result, "source_version", "sourceVersion"),
1617
+ projectionIds: requiredStringArray(result, "projection_ids", "projectionIds"),
1618
+ };
1619
+ }
1620
+ function normalizeAccessGroupListResult(result) {
1621
+ return {
1622
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1623
+ groups: requiredRecordArray(result, "groups", "groups").map((group) => ({
1624
+ groupPrincipalId: requiredString(group, "group_principal_id", "groups[].groupPrincipalId"),
1625
+ name: nullableString(group, "name", "groups[].name"),
1626
+ memberCount: requiredNumber(group, "member_count", "groups[].memberCount"),
1627
+ archived: requiredBoolean(group, "archived", "groups[].archived"),
1628
+ archivedAt: nullableString(group, "archived_at", "groups[].archivedAt"),
1629
+ createdAt: requiredString(group, "created_at", "groups[].createdAt"),
1630
+ updatedAt: requiredString(group, "updated_at", "groups[].updatedAt"),
1631
+ })),
1632
+ };
1633
+ }
1634
+ function normalizeAccessGrantableRoleListResult(result) {
1635
+ return {
1636
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1637
+ roles: requiredRecordArray(result, "roles", "roles").map((role) => {
1638
+ const roleKind = requiredString(role, "role_kind", "roles[].roleKind");
1639
+ if (roleKind !== "system" && roleKind !== "custom") {
1640
+ throw new Error("Access Control API response has invalid roles[].roleKind.");
1641
+ }
1642
+ return {
1643
+ roleId: requiredString(role, "role_id", "roles[].roleId"),
1644
+ roleKey: requiredString(role, "role_key", "roles[].roleKey"),
1645
+ roleName: requiredString(role, "role_name", "roles[].roleName"),
1646
+ roleKind,
1647
+ shared: requiredBoolean(role, "shared", "roles[].shared"),
1648
+ };
1649
+ }),
1650
+ };
1651
+ }
1652
+ function normalizeAccessGroupWriteResult(result) {
1653
+ return {
1654
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1655
+ groupPrincipalId: requiredString(result, "group_principal_id", "groupPrincipalId"),
1656
+ changed: optionalBoolean(result, "changed", "changed"),
1657
+ sourceVersion: requiredNumber(result, "source_version", "sourceVersion"),
1658
+ projectionIds: requiredStringArray(result, "projection_ids", "projectionIds"),
1659
+ };
1660
+ }
1661
+ function normalizeAccessGroupMemberWriteResult(result) {
1662
+ return {
1663
+ ...normalizeAccessGroupWriteResult(result),
1664
+ memberPrincipalId: requiredString(result, "member_principal_id", "memberPrincipalId"),
1665
+ membershipId: optionalString(result, "membership_id", "membershipId"),
1666
+ };
1667
+ }
1668
+ function normalizeAccessResourceInvitationListResult(result) {
1669
+ return {
1670
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1671
+ invitations: requiredRecordArray(result, "invitations", "invitations").map((invitation) => ({
1672
+ invitationId: requiredString(invitation, "invitation_id", "invitations[].invitationId"),
1673
+ email: requiredString(invitation, "email", "invitations[].email"),
1674
+ resourceType: requiredString(invitation, "resource_type", "invitations[].resourceType"),
1675
+ resourceId: requiredString(invitation, "resource_id", "invitations[].resourceId"),
1676
+ conferralType: nullableConferralType(invitation),
1677
+ roleId: nullableString(invitation, "role_id", "invitations[].roleId"),
1678
+ permissionId: nullableString(invitation, "permission_id", "invitations[].permissionId"),
1679
+ appliesTo: optionalBindingAppliesTo(invitation),
1680
+ expiresAt: requiredString(invitation, "expires_at", "invitations[].expiresAt"),
1681
+ createdAt: requiredString(invitation, "created_at", "invitations[].createdAt"),
1682
+ updatedAt: requiredString(invitation, "updated_at", "invitations[].updatedAt"),
1683
+ })),
1684
+ };
1685
+ }
1686
+ function normalizeAccessRoleOverridesResult(result) {
1687
+ return {
1688
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1689
+ roleId: requiredString(result, "role_id", "roleId"),
1690
+ overrides: requiredRecordArray(result, "overrides", "overrides").map((override) => ({
1691
+ permissionId: requiredString(override, "permission_id", "overrides[].permissionId"),
1692
+ permissionKey: requiredString(override, "permission_key", "overrides[].permissionKey"),
1693
+ effect: requiredEffect(override, "effect", "overrides[].effect"),
1694
+ })),
1695
+ };
1696
+ }
1697
+ function normalizeAccessUserExceptionsResult(result) {
1698
+ return {
1699
+ accessScopeId: requiredString(result, "access_scope_id", "accessScopeId"),
1700
+ principalId: requiredString(result, "principal_id", "principalId"),
1701
+ exceptions: requiredRecordArray(result, "exceptions", "exceptions").map((exception) => ({
1702
+ permissionId: requiredString(exception, "permission_id", "exceptions[].permissionId"),
1703
+ permissionKey: requiredString(exception, "permission_key", "exceptions[].permissionKey"),
1704
+ effect: requiredEffect(exception, "effect", "exceptions[].effect"),
1705
+ expiresAt: nullableString(exception, "expires_at", "exceptions[].expiresAt"),
1706
+ })),
1707
+ };
1708
+ }
1709
+ function normalizeAccessDeploymentEntryResult(result) {
1710
+ return {
1711
+ allowed: requiredBoolean(result, "allowed", "allowed"),
1712
+ reason: requiredString(result, "reason", "reason"),
1713
+ principalId: optionalString(result, "principal_id", "principalId"),
1714
+ status: optionalAccessEntryStatus(result),
1715
+ stateVersion: requiredNumber(result, "state_version", "stateVersion"),
1716
+ changed: requiredBoolean(result, "changed", "changed"),
1717
+ };
1718
+ }
1719
+ function activeDeploymentEntryResultFromMirror(result) {
1720
+ return {
1721
+ allowed: true,
1722
+ reason: "existing_active",
1723
+ principalId: result.principalId,
1724
+ status: "active",
1725
+ stateVersion: result.stateVersion,
1726
+ changed: false,
1727
+ };
1728
+ }
1729
+ function requiredString(result, apiKey, resultName) {
1730
+ const value = result[apiKey];
1731
+ if (typeof value !== "string" || value.length === 0) {
1732
+ throw new Error(`Access Control API response missing ${resultName}.`);
1733
+ }
1734
+ return value;
1735
+ }
1736
+ function optionalString(result, apiKey, resultName) {
1737
+ const value = result[apiKey];
1738
+ if (value === undefined || value === null)
1739
+ return undefined;
1740
+ if (typeof value !== "string" || value.length === 0) {
1741
+ throw new Error(`Access Control API response has invalid ${resultName}.`);
1742
+ }
1743
+ return value;
1744
+ }
1745
+ function requiredBoolean(result, apiKey, resultName) {
1746
+ const value = result[apiKey];
1747
+ if (typeof value !== "boolean") {
1748
+ throw new Error(`Access Control API response missing ${resultName}.`);
1749
+ }
1750
+ return value;
1751
+ }
1752
+ function optionalBoolean(result, apiKey, resultName) {
1753
+ const value = result[apiKey];
1754
+ if (value === undefined || value === null)
1755
+ return undefined;
1756
+ if (typeof value !== "boolean") {
1757
+ throw new Error(`Access Control API response has invalid ${resultName}.`);
1758
+ }
1759
+ return value;
1760
+ }
1761
+ function optionalAccessEntryStatus(result) {
1762
+ const value = result["status"];
1763
+ if (value === undefined || value === null)
1764
+ return undefined;
1765
+ if (value !== "active" &&
1766
+ value !== "blocked" &&
1767
+ value !== "suspended" &&
1768
+ value !== "pending_approval" &&
1769
+ value !== "removed") {
1770
+ throw new Error("Access Control API response has invalid status.");
1771
+ }
1772
+ return value;
1773
+ }
1774
+ function requiredNumber(result, apiKey, resultName) {
1775
+ const value = result[apiKey];
1776
+ if (typeof value !== "number") {
1777
+ throw new Error(`Access Control API response missing ${resultName}.`);
1778
+ }
1779
+ return value;
1780
+ }
1781
+ function requiredStringArray(result, apiKey, resultName) {
1782
+ const value = result[apiKey];
1783
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
1784
+ throw new Error(`Access Control API response missing ${resultName}.`);
1785
+ }
1786
+ return value;
1787
+ }
1788
+ function requiredRecordArray(result, apiKey, resultName) {
1789
+ const value = result[apiKey];
1790
+ if (!Array.isArray(value) ||
1791
+ value.some((item) => typeof item !== "object" || item === null || Array.isArray(item))) {
1792
+ throw new Error(`Access Control API response missing ${resultName}.`);
1793
+ }
1794
+ return value;
1795
+ }
1796
+ function nullableString(result, apiKey, resultName) {
1797
+ const value = result[apiKey];
1798
+ if (value === undefined || value === null)
1799
+ return null;
1800
+ if (typeof value !== "string") {
1801
+ throw new Error(`Access Control API response has invalid ${resultName}.`);
1802
+ }
1803
+ return value;
1804
+ }
1805
+ function requiredEffect(result, apiKey, resultName) {
1806
+ const value = result[apiKey];
1807
+ if (value !== "allow" && value !== "deny") {
1808
+ throw new Error(`Access Control API response has invalid ${resultName}.`);
1809
+ }
1810
+ return value;
1811
+ }
1812
+ function optionalBindingAppliesTo(result) {
1813
+ const value = result["applies_to"];
1814
+ if (value === undefined)
1815
+ return "self";
1816
+ if (value !== "self" && value !== "self_and_descendants") {
1817
+ throw new Error("Access Control API response has invalid invitations[].appliesTo.");
1818
+ }
1819
+ return value;
1820
+ }
1821
+ function nullableConferralType(result) {
1822
+ const value = result["conferral_type"];
1823
+ if (value === undefined || value === null)
1824
+ return null;
1825
+ if (value !== "role" && value !== "permission") {
1826
+ throw new Error("Access Control API response has invalid invitations[].conferralType.");
1827
+ }
1828
+ return value;
1829
+ }
1830
+ //# sourceMappingURL=access-admin.js.map