@objectstack/platform-objects 7.3.0 → 7.4.1

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 (51) hide show
  1. package/dist/apps/index.d.mts +45 -19
  2. package/dist/apps/index.d.ts +45 -19
  3. package/dist/apps/index.js +1434 -3406
  4. package/dist/apps/index.js.map +1 -1
  5. package/dist/apps/index.mjs +1434 -3407
  6. package/dist/apps/index.mjs.map +1 -1
  7. package/dist/audit/index.d.mts +4595 -26176
  8. package/dist/audit/index.d.ts +4595 -26176
  9. package/dist/audit/index.js +63 -1063
  10. package/dist/audit/index.js.map +1 -1
  11. package/dist/audit/index.mjs +64 -1057
  12. package/dist/audit/index.mjs.map +1 -1
  13. package/dist/identity/index.d.mts +839 -1281
  14. package/dist/identity/index.d.ts +839 -1281
  15. package/dist/index.d.mts +3 -8
  16. package/dist/index.d.ts +3 -8
  17. package/dist/index.js +2344 -6782
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +2343 -6767
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/integration/index.d.mts +1 -2947
  22. package/dist/integration/index.d.ts +1 -2947
  23. package/dist/integration/index.js +0 -136
  24. package/dist/integration/index.js.map +1 -1
  25. package/dist/integration/index.mjs +0 -135
  26. package/dist/integration/index.mjs.map +1 -1
  27. package/dist/metadata/index.d.mts +3924 -261
  28. package/dist/metadata/index.d.ts +3924 -261
  29. package/dist/metadata/index.js +101 -0
  30. package/dist/metadata/index.js.map +1 -1
  31. package/dist/metadata/index.mjs +101 -1
  32. package/dist/metadata/index.mjs.map +1 -1
  33. package/dist/metadata-translations/index.js +597 -505
  34. package/dist/metadata-translations/index.js.map +1 -1
  35. package/dist/metadata-translations/index.mjs +597 -505
  36. package/dist/metadata-translations/index.mjs.map +1 -1
  37. package/dist/plugin.js +1790 -3614
  38. package/dist/plugin.js.map +1 -1
  39. package/dist/plugin.mjs +1790 -3614
  40. package/dist/plugin.mjs.map +1 -1
  41. package/dist/security/index.d.mts +1 -18850
  42. package/dist/security/index.d.ts +1 -18850
  43. package/dist/security/index.js +0 -1531
  44. package/dist/security/index.js.map +1 -1
  45. package/dist/security/index.mjs +0 -1523
  46. package/dist/security/index.mjs.map +1 -1
  47. package/dist/system/index.d.mts +144 -212
  48. package/dist/system/index.d.ts +144 -212
  49. package/package.json +2 -2
  50. package/dist/state-machine.zod-BNanU03M.d-Ek3_yo9P.d.mts +0 -41
  51. package/dist/state-machine.zod-BNanU03M.d-Ek3_yo9P.d.ts +0 -41
@@ -1,1535 +1,4 @@
1
1
  'use strict';
2
2
 
3
- var data = require('@objectstack/spec/data');
4
- var security = require('@objectstack/spec/security');
5
-
6
- // src/security/sys-role.object.ts
7
- var SysRole = data.ObjectSchema.create({
8
- name: "sys_role",
9
- label: "Role",
10
- pluralLabel: "Roles",
11
- icon: "shield",
12
- isSystem: true,
13
- managedBy: "config",
14
- // ADR-0010 §3.7 — RBAC primitive; tenants may add custom rows
15
- // (created via UI / API) but the schema itself is locked.
16
- protection: {
17
- lock: "no-overlay",
18
- reason: "RBAC schema is platform-defined \u2014 see ADR-0010.",
19
- docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
20
- },
21
- description: "Role definitions for RBAC access control",
22
- displayNameField: "label",
23
- titleFormat: "{label}",
24
- compactLayout: ["label", "name", "active", "is_default"],
25
- // Custom actions — system roles drive RBAC and are edited rarely but
26
- // require the four high-frequency sysadmin affordances every IdP
27
- // (Salesforce, ServiceNow, Okta) ships: activate/deactivate (lifecycle
28
- // without losing assignments), mark default (auto-assign to new users),
29
- // and clone (template for new roles). All operations hit the generic
30
- // data CRUD endpoint exposed by `apiEnabled` — no custom server route
31
- // required because `managedBy: 'config'` allows direct mutation.
32
- actions: [
33
- {
34
- name: "activate_role",
35
- label: "Activate Role",
36
- icon: "circle-check",
37
- variant: "secondary",
38
- mode: "custom",
39
- locations: ["list_item", "record_header"],
40
- type: "api",
41
- method: "PATCH",
42
- target: "/api/v1/data/sys_role/{id}",
43
- bodyExtra: { active: true },
44
- successMessage: "Role activated",
45
- refreshAfter: true
46
- },
47
- {
48
- name: "deactivate_role",
49
- label: "Deactivate Role",
50
- icon: "circle-off",
51
- variant: "danger",
52
- mode: "custom",
53
- locations: ["list_item", "record_header"],
54
- type: "api",
55
- method: "PATCH",
56
- target: "/api/v1/data/sys_role/{id}",
57
- bodyExtra: { active: false },
58
- confirmText: "Deactivate this role? Users with the role keep their assignment but the role stops granting permissions until re-activated.",
59
- successMessage: "Role deactivated",
60
- refreshAfter: true
61
- },
62
- {
63
- name: "set_default_role",
64
- label: "Set as Default",
65
- icon: "star",
66
- variant: "secondary",
67
- mode: "custom",
68
- locations: ["list_item", "record_header"],
69
- type: "api",
70
- method: "PATCH",
71
- target: "/api/v1/data/sys_role/{id}",
72
- bodyExtra: { is_default: true },
73
- confirmText: "Make this the default role for new users? Existing users are unaffected.",
74
- successMessage: "Default role updated",
75
- refreshAfter: true
76
- },
77
- {
78
- // Clone — POST a new sys_role row pre-filled from the source. The
79
- // dialog asks only for the new API name / label so the operator
80
- // can rename atomically; permissions JSON is copied wholesale via
81
- // defaultFromRow.
82
- name: "clone_role",
83
- label: "Clone Role",
84
- icon: "copy",
85
- variant: "secondary",
86
- mode: "custom",
87
- locations: ["list_item", "record_header"],
88
- type: "api",
89
- method: "POST",
90
- target: "/api/v1/data/sys_role",
91
- bodyExtra: { is_default: false, active: true },
92
- successMessage: "Role cloned",
93
- refreshAfter: true,
94
- params: [
95
- { name: "label", label: "New Display Name", type: "text", required: true },
96
- { name: "name", label: "New API Name", type: "text", required: true, helpText: "Unique snake_case machine name" },
97
- { field: "description", defaultFromRow: true },
98
- { field: "permissions", defaultFromRow: true }
99
- ]
100
- }
101
- ],
102
- listViews: {
103
- active: {
104
- type: "grid",
105
- name: "active",
106
- label: "Active",
107
- data: { provider: "object", object: "sys_role" },
108
- columns: ["label", "name", "is_default", "updated_at"],
109
- filter: [{ field: "active", operator: "equals", value: true }],
110
- sort: [{ field: "label", order: "asc" }],
111
- pagination: { pageSize: 50 }
112
- },
113
- default_roles: {
114
- type: "grid",
115
- name: "default_roles",
116
- label: "Default",
117
- data: { provider: "object", object: "sys_role" },
118
- columns: ["label", "name", "description", "active"],
119
- filter: [{ field: "is_default", operator: "equals", value: true }],
120
- sort: [{ field: "label", order: "asc" }],
121
- pagination: { pageSize: 50 }
122
- },
123
- custom: {
124
- type: "grid",
125
- name: "custom",
126
- label: "Custom",
127
- data: { provider: "object", object: "sys_role" },
128
- columns: ["label", "name", "active", "updated_at"],
129
- filter: [{ field: "is_default", operator: "equals", value: false }],
130
- sort: [{ field: "label", order: "asc" }],
131
- pagination: { pageSize: 50 }
132
- },
133
- all_roles: {
134
- type: "grid",
135
- name: "all_roles",
136
- label: "All",
137
- data: { provider: "object", object: "sys_role" },
138
- columns: ["label", "name", "active", "is_default", "updated_at"],
139
- sort: [{ field: "label", order: "asc" }],
140
- pagination: { pageSize: 50 }
141
- }
142
- },
143
- fields: {
144
- // ── Identity ─────────────────────────────────────────────────
145
- label: data.Field.text({
146
- label: "Display Name",
147
- required: true,
148
- searchable: true,
149
- maxLength: 255,
150
- group: "Identity"
151
- }),
152
- name: data.Field.text({
153
- label: "API Name",
154
- required: true,
155
- searchable: true,
156
- maxLength: 100,
157
- description: "Unique machine name for the role (e.g. admin, editor, viewer)",
158
- group: "Identity"
159
- }),
160
- description: data.Field.textarea({
161
- label: "Description",
162
- required: false,
163
- group: "Identity"
164
- }),
165
- // ── Configuration ────────────────────────────────────────────
166
- permissions: data.Field.textarea({
167
- label: "Permissions",
168
- required: false,
169
- description: "JSON-serialized array of permission strings",
170
- group: "Configuration"
171
- }),
172
- // ── Status ───────────────────────────────────────────────────
173
- active: data.Field.boolean({
174
- label: "Active",
175
- defaultValue: true,
176
- group: "Status"
177
- }),
178
- is_default: data.Field.boolean({
179
- label: "Default Role",
180
- defaultValue: false,
181
- description: "Automatically assigned to new users",
182
- group: "Status"
183
- }),
184
- // ── System ───────────────────────────────────────────────────
185
- id: data.Field.text({
186
- label: "Role ID",
187
- required: true,
188
- readonly: true,
189
- group: "System"
190
- }),
191
- created_at: data.Field.datetime({
192
- label: "Created At",
193
- defaultValue: "NOW()",
194
- readonly: true,
195
- group: "System"
196
- }),
197
- updated_at: data.Field.datetime({
198
- label: "Updated At",
199
- defaultValue: "NOW()",
200
- readonly: true,
201
- group: "System"
202
- })
203
- },
204
- indexes: [
205
- { fields: ["name"], unique: true },
206
- { fields: ["active"] }
207
- ],
208
- enable: {
209
- trackHistory: true,
210
- searchable: true,
211
- apiEnabled: true,
212
- apiMethods: ["get", "list", "create", "update", "delete"],
213
- trash: true,
214
- mru: true
215
- }
216
- });
217
- var SysPermissionSet = data.ObjectSchema.create({
218
- name: "sys_permission_set",
219
- label: "Permission Set",
220
- pluralLabel: "Permission Sets",
221
- icon: "lock",
222
- isSystem: true,
223
- managedBy: "config",
224
- // ADR-0010 §3.7 — RBAC primitive; tenants may add custom rows
225
- // (created via UI / API) but the schema itself is locked.
226
- protection: {
227
- lock: "no-overlay",
228
- reason: "RBAC schema is platform-defined \u2014 see ADR-0010.",
229
- docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
230
- },
231
- description: "Named permission groupings for fine-grained access control",
232
- displayNameField: "label",
233
- titleFormat: "{label}",
234
- compactLayout: ["label", "name", "active"],
235
- // Custom actions — permission sets are templates assigned to roles or
236
- // users (via sys_role_permission_set / sys_user_permission_set). The
237
- // sysadmin operations that don't live on the parent-detail tabs are
238
- // lifecycle (activate/deactivate without losing assignments) and
239
- // clone (build a new permset by tweaking an existing one). Both hit
240
- // the generic data CRUD endpoint — managedBy: 'config' permits it.
241
- actions: [
242
- {
243
- name: "activate_permission_set",
244
- label: "Activate",
245
- icon: "circle-check",
246
- variant: "secondary",
247
- mode: "custom",
248
- locations: ["list_item", "record_header"],
249
- type: "api",
250
- method: "PATCH",
251
- target: "/api/v1/data/sys_permission_set/{id}",
252
- bodyExtra: { active: true },
253
- successMessage: "Permission set activated",
254
- refreshAfter: true
255
- },
256
- {
257
- name: "deactivate_permission_set",
258
- label: "Deactivate",
259
- icon: "circle-off",
260
- variant: "danger",
261
- mode: "custom",
262
- locations: ["list_item", "record_header"],
263
- type: "api",
264
- method: "PATCH",
265
- target: "/api/v1/data/sys_permission_set/{id}",
266
- bodyExtra: { active: false },
267
- confirmText: "Deactivate this permission set? Existing assignments stay in place but stop granting access until re-activated.",
268
- successMessage: "Permission set deactivated",
269
- refreshAfter: true
270
- },
271
- {
272
- name: "clone_permission_set",
273
- label: "Clone",
274
- icon: "copy",
275
- variant: "secondary",
276
- mode: "custom",
277
- locations: ["list_item", "record_header"],
278
- type: "api",
279
- method: "POST",
280
- target: "/api/v1/data/sys_permission_set",
281
- bodyExtra: { active: true },
282
- successMessage: "Permission set cloned",
283
- refreshAfter: true,
284
- params: [
285
- { name: "label", label: "New Display Name", type: "text", required: true },
286
- { name: "name", label: "New API Name", type: "text", required: true, helpText: "Unique snake_case machine name" },
287
- { field: "description", defaultFromRow: true },
288
- { field: "object_permissions", defaultFromRow: true },
289
- { field: "field_permissions", defaultFromRow: true }
290
- ]
291
- }
292
- ],
293
- listViews: {
294
- active: {
295
- type: "grid",
296
- name: "active",
297
- label: "Active",
298
- data: { provider: "object", object: "sys_permission_set" },
299
- columns: ["label", "name", "description", "updated_at"],
300
- filter: [{ field: "active", operator: "equals", value: true }],
301
- sort: [{ field: "label", order: "asc" }],
302
- pagination: { pageSize: 50 }
303
- },
304
- inactive: {
305
- type: "grid",
306
- name: "inactive",
307
- label: "Inactive",
308
- data: { provider: "object", object: "sys_permission_set" },
309
- columns: ["label", "name", "updated_at"],
310
- filter: [{ field: "active", operator: "equals", value: false }],
311
- sort: [{ field: "label", order: "asc" }],
312
- pagination: { pageSize: 50 }
313
- },
314
- all_permsets: {
315
- type: "grid",
316
- name: "all_permsets",
317
- label: "All",
318
- data: { provider: "object", object: "sys_permission_set" },
319
- columns: ["label", "name", "active", "updated_at"],
320
- sort: [{ field: "label", order: "asc" }],
321
- pagination: { pageSize: 50 }
322
- }
323
- },
324
- fields: {
325
- // ── Identity ─────────────────────────────────────────────────
326
- label: data.Field.text({
327
- label: "Display Name",
328
- required: true,
329
- searchable: true,
330
- maxLength: 255,
331
- group: "Identity"
332
- }),
333
- name: data.Field.text({
334
- label: "API Name",
335
- required: true,
336
- searchable: true,
337
- maxLength: 100,
338
- description: "Unique machine name for the permission set",
339
- group: "Identity"
340
- }),
341
- description: data.Field.textarea({
342
- label: "Description",
343
- required: false,
344
- group: "Identity"
345
- }),
346
- // ── Permissions ──────────────────────────────────────────────
347
- object_permissions: data.Field.textarea({
348
- label: "Object Permissions",
349
- required: false,
350
- description: "JSON-serialized object-level CRUD permissions",
351
- group: "Permissions"
352
- }),
353
- field_permissions: data.Field.textarea({
354
- label: "Field Permissions",
355
- required: false,
356
- description: "JSON-serialized field-level read/write permissions",
357
- group: "Permissions"
358
- }),
359
- system_permissions: data.Field.textarea({
360
- label: "System Permissions",
361
- required: false,
362
- description: 'JSON-serialized array of system capability names (e.g. ["setup.access","studio.access","manage_users"])',
363
- group: "Permissions"
364
- }),
365
- row_level_security: data.Field.textarea({
366
- label: "Row-Level Security",
367
- required: false,
368
- description: "JSON-serialized array of row-level security policies (USING/CHECK clauses)",
369
- group: "Permissions"
370
- }),
371
- tab_permissions: data.Field.textarea({
372
- label: "Tab Permissions",
373
- required: false,
374
- description: "JSON-serialized map of app tab visibility (visible | hidden | default_on | default_off)",
375
- group: "Permissions"
376
- }),
377
- // ── Status ───────────────────────────────────────────────────
378
- active: data.Field.boolean({
379
- label: "Active",
380
- defaultValue: true,
381
- group: "Status"
382
- }),
383
- // ── System ───────────────────────────────────────────────────
384
- id: data.Field.text({
385
- label: "Permission Set ID",
386
- required: true,
387
- readonly: true,
388
- group: "System"
389
- }),
390
- created_at: data.Field.datetime({
391
- label: "Created At",
392
- defaultValue: "NOW()",
393
- readonly: true,
394
- group: "System"
395
- }),
396
- updated_at: data.Field.datetime({
397
- label: "Updated At",
398
- defaultValue: "NOW()",
399
- readonly: true,
400
- group: "System"
401
- })
402
- },
403
- indexes: [
404
- { fields: ["name"], unique: true },
405
- { fields: ["active"] }
406
- ],
407
- enable: {
408
- trackHistory: true,
409
- searchable: true,
410
- apiEnabled: true,
411
- apiMethods: ["get", "list", "create", "update", "delete"],
412
- trash: true,
413
- mru: true
414
- }
415
- });
416
- var SysUserPermissionSet = data.ObjectSchema.create({
417
- name: "sys_user_permission_set",
418
- label: "User Permission Set",
419
- pluralLabel: "User Permission Sets",
420
- icon: "user-check",
421
- isSystem: true,
422
- managedBy: "system",
423
- description: "Direct assignment of a permission set to a user (optionally scoped to an organization).",
424
- titleFormat: "{user_id} \u2192 {permission_set_id}",
425
- compactLayout: ["user_id", "permission_set_id", "organization_id"],
426
- fields: {
427
- id: data.Field.text({
428
- label: "Assignment ID",
429
- required: true,
430
- readonly: true,
431
- description: "UUID of the assignment."
432
- }),
433
- user_id: data.Field.lookup("sys_user", {
434
- label: "User",
435
- required: true,
436
- description: "Foreign key to sys_user."
437
- }),
438
- permission_set_id: data.Field.lookup("sys_permission_set", {
439
- label: "Permission Set",
440
- required: true,
441
- description: "Foreign key to sys_permission_set."
442
- }),
443
- organization_id: data.Field.lookup("sys_organization", {
444
- label: "Organization",
445
- required: false,
446
- description: "Optional organization scope. NULL = applies in every org context."
447
- }),
448
- granted_by: data.Field.lookup("sys_user", {
449
- label: "Granted By",
450
- required: false,
451
- description: "User who granted this permission set."
452
- }),
453
- created_at: data.Field.datetime({
454
- label: "Created At",
455
- defaultValue: "NOW()",
456
- readonly: true
457
- }),
458
- updated_at: data.Field.datetime({
459
- label: "Updated At",
460
- defaultValue: "NOW()",
461
- readonly: true
462
- })
463
- },
464
- indexes: [
465
- { fields: ["user_id", "permission_set_id", "organization_id"], unique: true },
466
- { fields: ["user_id"] },
467
- { fields: ["organization_id"] },
468
- { fields: ["permission_set_id"] }
469
- ],
470
- enable: {
471
- trackHistory: true,
472
- searchable: true,
473
- apiEnabled: true,
474
- apiMethods: ["get", "list", "create", "update", "delete"],
475
- trash: true,
476
- mru: false
477
- }
478
- });
479
- var SysRolePermissionSet = data.ObjectSchema.create({
480
- name: "sys_role_permission_set",
481
- label: "Role Permission Set",
482
- pluralLabel: "Role Permission Sets",
483
- icon: "shield-plus",
484
- isSystem: true,
485
- managedBy: "system",
486
- description: "Binds a permission set to a role.",
487
- titleFormat: "{role_id} \u2192 {permission_set_id}",
488
- compactLayout: ["role_id", "permission_set_id"],
489
- fields: {
490
- id: data.Field.text({
491
- label: "Binding ID",
492
- required: true,
493
- readonly: true,
494
- description: "UUID of the role-permission-set binding."
495
- }),
496
- role_id: data.Field.lookup("sys_role", {
497
- label: "Role",
498
- required: true,
499
- description: "Foreign key to sys_role."
500
- }),
501
- permission_set_id: data.Field.lookup("sys_permission_set", {
502
- label: "Permission Set",
503
- required: true,
504
- description: "Foreign key to sys_permission_set."
505
- }),
506
- created_at: data.Field.datetime({
507
- label: "Created At",
508
- defaultValue: "NOW()",
509
- readonly: true
510
- }),
511
- updated_at: data.Field.datetime({
512
- label: "Updated At",
513
- defaultValue: "NOW()",
514
- readonly: true
515
- })
516
- },
517
- indexes: [
518
- { fields: ["role_id", "permission_set_id"], unique: true },
519
- { fields: ["role_id"] },
520
- { fields: ["permission_set_id"] }
521
- ],
522
- enable: {
523
- trackHistory: true,
524
- searchable: true,
525
- apiEnabled: true,
526
- apiMethods: ["get", "list", "create", "update", "delete"],
527
- trash: true,
528
- mru: false
529
- }
530
- });
531
- var SysRecordShare = data.ObjectSchema.create({
532
- name: "sys_record_share",
533
- label: "Record Share",
534
- pluralLabel: "Record Shares",
535
- icon: "share",
536
- isSystem: true,
537
- managedBy: "system",
538
- description: "Per-record sharing grant \u2014 extends OWD with explicit access",
539
- titleFormat: "{object_name}/{record_id} \u2192 {recipient_id} ({access_level})",
540
- compactLayout: ["object_name", "record_id", "recipient_id", "access_level", "source"],
541
- listViews: {
542
- granted_to_me: {
543
- type: "grid",
544
- name: "granted_to_me",
545
- label: "Granted to Me",
546
- data: { provider: "object", object: "sys_record_share" },
547
- columns: ["object_name", "record_id", "access_level", "source", "granted_by", "created_at"],
548
- filter: [
549
- { field: "recipient_type", operator: "equals", value: "user" },
550
- { field: "recipient_id", operator: "equals", value: "{current_user_id}" }
551
- ],
552
- sort: [{ field: "created_at", order: "desc" }],
553
- pagination: { pageSize: 50 }
554
- },
555
- granted_by_me: {
556
- type: "grid",
557
- name: "granted_by_me",
558
- label: "Granted by Me",
559
- data: { provider: "object", object: "sys_record_share" },
560
- columns: ["object_name", "record_id", "recipient_id", "access_level", "source", "created_at"],
561
- filter: [
562
- { field: "granted_by", operator: "equals", value: "{current_user_id}" }
563
- ],
564
- sort: [{ field: "created_at", order: "desc" }],
565
- pagination: { pageSize: 50 }
566
- },
567
- by_object: {
568
- type: "grid",
569
- name: "by_object",
570
- label: "By Object",
571
- data: { provider: "object", object: "sys_record_share" },
572
- columns: ["object_name", "record_id", "recipient_id", "access_level", "source", "created_at"],
573
- sort: [{ field: "object_name", order: "asc" }, { field: "created_at", order: "desc" }],
574
- grouping: { fields: [{ field: "object_name", order: "asc", collapsed: false }] },
575
- pagination: { pageSize: 100 }
576
- },
577
- manual_grants: {
578
- type: "grid",
579
- name: "manual_grants",
580
- label: "Manual Grants",
581
- data: { provider: "object", object: "sys_record_share" },
582
- columns: ["object_name", "record_id", "recipient_id", "access_level", "granted_by", "reason", "created_at"],
583
- filter: [{ field: "source", operator: "equals", value: "manual" }],
584
- sort: [{ field: "created_at", order: "desc" }],
585
- pagination: { pageSize: 50 }
586
- },
587
- rule_grants: {
588
- type: "grid",
589
- name: "rule_grants",
590
- label: "Rule Grants",
591
- data: { provider: "object", object: "sys_record_share" },
592
- columns: ["object_name", "record_id", "recipient_id", "access_level", "source_id", "created_at"],
593
- filter: [{ field: "source", operator: "in", value: ["rule", "team", "inherited"] }],
594
- sort: [{ field: "source_id", order: "asc" }, { field: "created_at", order: "desc" }],
595
- pagination: { pageSize: 50 }
596
- },
597
- all_shares: {
598
- type: "grid",
599
- name: "all_shares",
600
- label: "All",
601
- data: { provider: "object", object: "sys_record_share" },
602
- columns: ["object_name", "record_id", "recipient_type", "recipient_id", "access_level", "source", "created_at"],
603
- sort: [{ field: "created_at", order: "desc" }],
604
- pagination: { pageSize: 100 }
605
- }
606
- },
607
- fields: {
608
- id: data.Field.text({
609
- label: "Share ID",
610
- required: true,
611
- readonly: true,
612
- group: "System"
613
- }),
614
- // ── Target (which record is being shared) ────────────────────
615
- object_name: data.Field.text({
616
- label: "Object",
617
- required: true,
618
- maxLength: 100,
619
- description: "Short object name of the shared record",
620
- group: "Target"
621
- }),
622
- record_id: data.Field.text({
623
- label: "Record",
624
- required: true,
625
- maxLength: 100,
626
- description: "Primary key of the shared record within object_name",
627
- group: "Target"
628
- }),
629
- // ── Recipient (who receives access) ──────────────────────────
630
- recipient_type: data.Field.select(
631
- ["user", "group", "role", "role_and_subordinates", "guest"],
632
- {
633
- label: "Recipient Type",
634
- required: true,
635
- defaultValue: "user",
636
- description: "Kind of principal that holds the grant",
637
- group: "Recipient"
638
- }
639
- ),
640
- recipient_id: data.Field.text({
641
- label: "Recipient",
642
- required: true,
643
- maxLength: 100,
644
- description: "ID of the user/group/role that receives access",
645
- group: "Recipient"
646
- }),
647
- access_level: data.Field.select(
648
- ["read", "edit", "full"],
649
- {
650
- label: "Access Level",
651
- required: true,
652
- defaultValue: "read",
653
- description: "What the recipient can do \u2014 read | edit | full (transfer/share/delete)",
654
- group: "Recipient"
655
- }
656
- ),
657
- // ── Provenance ───────────────────────────────────────────────
658
- source: data.Field.select(
659
- ["manual", "rule", "team", "inherited"],
660
- {
661
- label: "Source",
662
- required: true,
663
- defaultValue: "manual",
664
- description: "Why this grant exists \u2014 used by the rule evaluator to reconcile",
665
- group: "Provenance"
666
- }
667
- ),
668
- source_id: data.Field.text({
669
- label: "Source ID",
670
- required: false,
671
- maxLength: 200,
672
- description: "Rule name / team id when source != manual",
673
- group: "Provenance"
674
- }),
675
- granted_by: data.Field.lookup("sys_user", {
676
- label: "Granted By",
677
- required: false,
678
- description: "User that created the grant (manual only)",
679
- group: "Provenance"
680
- }),
681
- reason: data.Field.text({
682
- label: "Reason",
683
- required: false,
684
- maxLength: 500,
685
- description: "Optional free-text explanation surfaced to the recipient",
686
- group: "Provenance"
687
- }),
688
- // ── Lifecycle ────────────────────────────────────────────────
689
- created_at: data.Field.datetime({
690
- label: "Created At",
691
- required: true,
692
- defaultValue: "NOW()",
693
- readonly: true,
694
- group: "System"
695
- }),
696
- updated_at: data.Field.datetime({
697
- label: "Updated At",
698
- required: false,
699
- group: "System"
700
- })
701
- },
702
- indexes: [
703
- // Hot path: "all records visible to user U on object O" — the
704
- // middleware reads (object_name, recipient_type, recipient_id) to
705
- // build the `id IN (...)` predicate on every find.
706
- { fields: ["object_name", "recipient_type", "recipient_id"] },
707
- // "all grants on this record" — used by the share-management UI
708
- // and by canEdit() to look up explicit grants.
709
- { fields: ["object_name", "record_id"] },
710
- // Reconciliation key for rule-driven shares.
711
- { fields: ["source", "source_id"] }
712
- ]
713
- });
714
- var SysSharingRule = data.ObjectSchema.create({
715
- name: "sys_sharing_rule",
716
- label: "Sharing Rule",
717
- pluralLabel: "Sharing Rules",
718
- icon: "shield-check",
719
- isSystem: true,
720
- managedBy: "config",
721
- // Sharing rules can now be authored visually via the Studio criteria
722
- // builder (apps/studio/src/components/SharingCriteriaBuilder.tsx).
723
- // We still recommend `defineSharingRule({...})` for repo-controlled
724
- // baselines, but admins can safely create/edit/delete from the UI.
725
- userActions: { create: true, edit: true, delete: true, import: false },
726
- description: "Declarative sharing rule that auto-materialises sys_record_share grants. Authored via defineSharingRule() in code or the Studio criteria builder.",
727
- displayNameField: "name",
728
- titleFormat: "{label}",
729
- compactLayout: ["name", "object_name", "recipient_type", "recipient_id", "access_level", "active"],
730
- listViews: {
731
- active: {
732
- type: "grid",
733
- name: "active",
734
- label: "Active",
735
- data: { provider: "object", object: "sys_sharing_rule" },
736
- columns: ["label", "object_name", "recipient_type", "recipient_id", "access_level", "updated_at"],
737
- filter: [{ field: "active", operator: "equals", value: true }],
738
- sort: [{ field: "object_name", order: "asc" }, { field: "label", order: "asc" }],
739
- pagination: { pageSize: 50 }
740
- },
741
- inactive: {
742
- type: "grid",
743
- name: "inactive",
744
- label: "Inactive",
745
- data: { provider: "object", object: "sys_sharing_rule" },
746
- columns: ["label", "object_name", "recipient_type", "recipient_id", "updated_at"],
747
- filter: [{ field: "active", operator: "equals", value: false }],
748
- sort: [{ field: "label", order: "asc" }],
749
- pagination: { pageSize: 50 }
750
- },
751
- by_object: {
752
- type: "grid",
753
- name: "by_object",
754
- label: "By Object",
755
- data: { provider: "object", object: "sys_sharing_rule" },
756
- columns: ["object_name", "label", "recipient_type", "access_level", "active"],
757
- sort: [{ field: "object_name", order: "asc" }, { field: "label", order: "asc" }],
758
- grouping: { fields: [{ field: "object_name", order: "asc", collapsed: false }] },
759
- pagination: { pageSize: 100 }
760
- },
761
- all_rules: {
762
- type: "grid",
763
- name: "all_rules",
764
- label: "All",
765
- data: { provider: "object", object: "sys_sharing_rule" },
766
- columns: ["label", "object_name", "recipient_type", "recipient_id", "access_level", "active", "updated_at"],
767
- sort: [{ field: "label", order: "asc" }],
768
- pagination: { pageSize: 50 }
769
- }
770
- },
771
- fields: {
772
- id: data.Field.text({ label: "Rule ID", required: true, readonly: true, group: "System" }),
773
- organization_id: data.Field.lookup("sys_organization", {
774
- label: "Organization",
775
- required: false,
776
- group: "System",
777
- description: "Tenant that owns this rule; null = global"
778
- }),
779
- name: data.Field.text({
780
- label: "Name",
781
- required: true,
782
- maxLength: 100,
783
- description: "Unique snake_case rule name",
784
- group: "Identity"
785
- }),
786
- label: data.Field.text({
787
- label: "Display Label",
788
- required: true,
789
- maxLength: 200,
790
- group: "Identity"
791
- }),
792
- description: data.Field.textarea({
793
- label: "Description",
794
- required: false,
795
- group: "Identity"
796
- }),
797
- object_name: data.Field.text({
798
- label: "Object",
799
- required: true,
800
- maxLength: 100,
801
- description: "Short object name (e.g. opportunity, account)",
802
- group: "Target"
803
- }),
804
- criteria_json: data.Field.textarea({
805
- label: "Criteria (FilterCondition JSON)",
806
- required: false,
807
- description: "JSON FilterCondition matched against records of object_name. Empty = match all.",
808
- group: "Target"
809
- }),
810
- recipient_type: data.Field.select(
811
- ["user", "team", "department", "role", "queue"],
812
- {
813
- label: "Recipient Type",
814
- required: true,
815
- defaultValue: "department",
816
- description: "Kind of principal that receives access \u2014 expanded to user grants at evaluation time. `department` walks the parent_department_id tree; `team` is flat (better-auth).",
817
- group: "Recipient"
818
- }
819
- ),
820
- recipient_id: data.Field.text({
821
- label: "Recipient",
822
- required: true,
823
- maxLength: 200,
824
- description: "department id / team id / role name / queue name / user id depending on recipient_type",
825
- group: "Recipient"
826
- }),
827
- access_level: data.Field.select(
828
- ["read", "edit", "full"],
829
- {
830
- label: "Access Level",
831
- required: true,
832
- defaultValue: "read",
833
- group: "Recipient"
834
- }
835
- ),
836
- active: data.Field.boolean({
837
- label: "Active",
838
- required: false,
839
- defaultValue: true,
840
- description: "Only active rules participate in lifecycle evaluation",
841
- group: "Lifecycle"
842
- }),
843
- created_at: data.Field.datetime({
844
- label: "Created At",
845
- required: true,
846
- defaultValue: "NOW()",
847
- readonly: true,
848
- group: "System"
849
- }),
850
- updated_at: data.Field.datetime({
851
- label: "Updated At",
852
- required: false,
853
- group: "System"
854
- })
855
- },
856
- indexes: [
857
- { fields: ["object_name", "active"] },
858
- { fields: ["name"], unique: true },
859
- { fields: ["organization_id"] }
860
- ]
861
- });
862
- var SysShareLink = data.ObjectSchema.create({
863
- name: "sys_share_link",
864
- label: "Share Link",
865
- pluralLabel: "Share Links",
866
- icon: "link-2",
867
- isSystem: true,
868
- managedBy: "system",
869
- description: "Opaque capability token granting access to a single record. Notion/Figma-style public link sharing.",
870
- titleFormat: "{object_name}/{record_id} ({permission})",
871
- compactLayout: ["object_name", "record_id", "permission", "audience", "expires_at", "revoked_at"],
872
- listViews: {
873
- active_links: {
874
- type: "grid",
875
- name: "active_links",
876
- label: "Active",
877
- data: { provider: "object", object: "sys_share_link" },
878
- columns: ["object_name", "record_id", "permission", "audience", "expires_at", "use_count", "last_used_at"],
879
- filter: [{ field: "revoked_at", operator: "isNull" }],
880
- sort: [{ field: "created_at", order: "desc" }],
881
- pagination: { pageSize: 100 }
882
- },
883
- by_me: {
884
- type: "grid",
885
- name: "by_me",
886
- label: "Created by Me",
887
- data: { provider: "object", object: "sys_share_link" },
888
- columns: ["object_name", "record_id", "permission", "audience", "expires_at", "revoked_at"],
889
- filter: [{ field: "created_by", operator: "equals", value: "{current_user_id}" }],
890
- sort: [{ field: "created_at", order: "desc" }],
891
- pagination: { pageSize: 100 }
892
- },
893
- revoked: {
894
- type: "grid",
895
- name: "revoked",
896
- label: "Revoked",
897
- data: { provider: "object", object: "sys_share_link" },
898
- columns: ["object_name", "record_id", "revoked_at", "created_by"],
899
- filter: [{ field: "revoked_at", operator: "isNotNull" }],
900
- sort: [{ field: "revoked_at", order: "desc" }],
901
- pagination: { pageSize: 50 }
902
- },
903
- all_links: {
904
- type: "grid",
905
- name: "all_links",
906
- label: "All",
907
- data: { provider: "object", object: "sys_share_link" },
908
- columns: ["object_name", "record_id", "permission", "audience", "expires_at", "revoked_at", "created_at"],
909
- sort: [{ field: "created_at", order: "desc" }],
910
- pagination: { pageSize: 200 }
911
- }
912
- },
913
- fields: {
914
- id: data.Field.text({
915
- label: "Link ID",
916
- required: true,
917
- readonly: true,
918
- group: "System"
919
- }),
920
- // ── Token (the secret) ───────────────────────────────────────
921
- token: data.Field.text({
922
- label: "Token",
923
- required: true,
924
- maxLength: 64,
925
- description: "Opaque URL-safe random token (\u2265 22 chars). The only secret in this row.",
926
- group: "Token"
927
- }),
928
- // ── Target ───────────────────────────────────────────────────
929
- object_name: data.Field.text({
930
- label: "Object",
931
- required: true,
932
- maxLength: 100,
933
- description: "Short object name of the shared record (e.g. ai_conversation, contracts_contract)",
934
- group: "Target"
935
- }),
936
- record_id: data.Field.text({
937
- label: "Record",
938
- required: true,
939
- maxLength: 100,
940
- description: "Primary key of the shared record within object_name",
941
- group: "Target"
942
- }),
943
- // ── Access Policy ────────────────────────────────────────────
944
- permission: data.Field.select(
945
- [
946
- { label: "View", value: "view" },
947
- { label: "Comment", value: "comment" },
948
- { label: "Edit", value: "edit" }
949
- ],
950
- {
951
- label: "Permission",
952
- required: true,
953
- defaultValue: "view",
954
- description: "What the link holder can do with the record",
955
- group: "Access Policy"
956
- }
957
- ),
958
- audience: data.Field.select(
959
- [
960
- { label: "Public (indexable)", value: "public" },
961
- { label: "Anyone with the link", value: "link_only" },
962
- { label: "Signed-in users", value: "signed_in" },
963
- { label: "Specific emails", value: "email" }
964
- ],
965
- {
966
- label: "Audience",
967
- required: true,
968
- defaultValue: "link_only",
969
- description: "Gating layer applied on top of the token check",
970
- group: "Access Policy"
971
- }
972
- ),
973
- expires_at: data.Field.datetime({
974
- label: "Expires At",
975
- description: "When set, resolveToken returns null after this timestamp",
976
- group: "Access Policy"
977
- }),
978
- email_allowlist: data.Field.json({
979
- label: "Email Allowlist",
980
- description: "Lowercased addresses checked when audience=email",
981
- group: "Access Policy"
982
- }),
983
- password_hash: data.Field.text({
984
- label: "Password Hash",
985
- maxLength: 256,
986
- description: "Argon2/bcrypt hash. When set, the UI prompts for a password before rendering.",
987
- group: "Access Policy"
988
- }),
989
- redact_fields: data.Field.json({
990
- label: "Per-Link Redactions",
991
- description: "Extra fields stripped from the response, on top of the object-default set",
992
- group: "Access Policy"
993
- }),
994
- label: data.Field.text({
995
- label: "Label",
996
- maxLength: 200,
997
- description: 'Free-text shown in the share dialog (e.g. "ACME Q3 contract")',
998
- group: "Metadata"
999
- }),
1000
- // ── Lifecycle ────────────────────────────────────────────────
1001
- revoked_at: data.Field.datetime({
1002
- label: "Revoked At",
1003
- readonly: true,
1004
- description: "When set, the link is permanently disabled",
1005
- group: "Lifecycle"
1006
- }),
1007
- created_by: data.Field.lookup("sys_user", {
1008
- label: "Created By",
1009
- readonly: true,
1010
- description: "Issuer of the link",
1011
- group: "Lifecycle"
1012
- }),
1013
- created_at: data.Field.datetime({
1014
- label: "Created At",
1015
- required: true,
1016
- defaultValue: "NOW()",
1017
- readonly: true,
1018
- group: "Lifecycle"
1019
- }),
1020
- last_used_at: data.Field.datetime({
1021
- label: "Last Used At",
1022
- readonly: true,
1023
- description: "Stamped by resolveToken; used by the dashboard to highlight active links",
1024
- group: "Lifecycle"
1025
- }),
1026
- use_count: data.Field.number({
1027
- label: "Use Count",
1028
- defaultValue: 0,
1029
- readonly: true,
1030
- description: "Incremented by resolveToken on every successful resolution",
1031
- group: "Lifecycle"
1032
- })
1033
- },
1034
- indexes: [
1035
- // Hot path: resolveToken — one row lookup per public request.
1036
- { fields: ["token"], unique: true },
1037
- // Management UI: "all links for this record".
1038
- { fields: ["object_name", "record_id"] },
1039
- // "Active links I issued".
1040
- { fields: ["created_by", "revoked_at"] },
1041
- // Reaper for expired rows (background sweep).
1042
- { fields: ["expires_at"] }
1043
- ],
1044
- enable: {
1045
- trackHistory: false,
1046
- searchable: false,
1047
- apiEnabled: true,
1048
- // The /api/v1/share-links endpoints are the authoritative surface;
1049
- // the generic data API is exposed read-only for the admin grid.
1050
- apiMethods: ["get", "list"],
1051
- trash: false,
1052
- mru: false,
1053
- clone: false
1054
- }
1055
- });
1056
- var BETTER_AUTH_MANAGED_OBJECTS = [
1057
- "sys_user",
1058
- "sys_account",
1059
- "sys_session",
1060
- "sys_organization",
1061
- "sys_member",
1062
- "sys_invitation",
1063
- "sys_team",
1064
- "sys_team_member",
1065
- "sys_api_key",
1066
- "sys_two_factor",
1067
- "sys_verification",
1068
- "sys_jwks",
1069
- "sys_device_code",
1070
- "sys_oauth_application",
1071
- "sys_oauth_access_token",
1072
- "sys_oauth_refresh_token",
1073
- "sys_oauth_consent"
1074
- ];
1075
- var denyWritesOnManagedObjects = () => Object.fromEntries(
1076
- BETTER_AUTH_MANAGED_OBJECTS.map((name) => [
1077
- name,
1078
- { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false }
1079
- ])
1080
- );
1081
- var defaultPermissionSets = [
1082
- security.PermissionSetSchema.parse({
1083
- name: "admin_full_access",
1084
- label: "Administrator \u2014 Full Access",
1085
- isProfile: true,
1086
- objects: {
1087
- "*": {
1088
- allowRead: true,
1089
- allowCreate: true,
1090
- allowEdit: true,
1091
- allowDelete: true,
1092
- viewAllRecords: true,
1093
- modifyAllRecords: true
1094
- }
1095
- },
1096
- systemPermissions: [
1097
- "manage_users",
1098
- "manage_metadata",
1099
- "manage_platform_settings",
1100
- "setup.access",
1101
- "studio.access"
1102
- ]
1103
- }),
1104
- // ── Organization Administrator ──────────────────────────────────────
1105
- //
1106
- // Third tier between platform admin (`admin_full_access`) and rank-and-file
1107
- // member. Lives at the *organization* scope: full CRUD on business
1108
- // objects within their org (governed by `tenant_isolation` RLS), plus
1109
- // `setup.access` so the Setup app shell is reachable.
1110
- //
1111
- // **Deliberately withheld** vs `admin_full_access`:
1112
- // - `studio.access` — schema-design surfaces are platform-level (a
1113
- // tenant cannot mutate the shared metadata) and Studio is hidden.
1114
- // - `manage_metadata` — same reasoning.
1115
- // - `manage_platform_settings` — global settings manifests
1116
- // (mail / storage / AI / knowledge) and platform-only Setup pages
1117
- // (sharing rules, audit logs, OAuth apps, JWKS, …) require this
1118
- // and are hidden / 403'd for org admins. Tenant-scoped manifests
1119
- // (`branding`, `feature_flags`) keep using `setup.access` so org
1120
- // admins CAN configure their own org's branding.
1121
- //
1122
- // **Anti-escalation**: writes to the global RBAC tables
1123
- // (`sys_role`, `sys_permission_set`, `sys_role_permission_set`,
1124
- // `sys_user_permission_set`, `sys_user_role`) are denied. Allowing
1125
- // them would let an org admin bind `admin_full_access` (which has no
1126
- // RLS) to themselves and break out of tenant isolation. Reads are
1127
- // permitted so the Roles / Permission Sets nav entries still render.
1128
- //
1129
- // Auto-granted to every `sys_member` whose role contains `owner` or
1130
- // `admin` by `plugin-security/src/auto-org-admin-grant.ts`.
1131
- security.PermissionSetSchema.parse({
1132
- name: "organization_admin",
1133
- label: "Organization Administrator",
1134
- isProfile: true,
1135
- objects: {
1136
- "*": {
1137
- allowRead: true,
1138
- allowCreate: true,
1139
- allowEdit: true,
1140
- allowDelete: true,
1141
- viewAllRecords: true,
1142
- modifyAllRecords: true
1143
- },
1144
- // Identity tables — go through better-auth endpoints (invite,
1145
- // accept, remove-member, transfer, …) rather than raw CRUD.
1146
- ...denyWritesOnManagedObjects(),
1147
- // RBAC tables — read-only to prevent privilege escalation.
1148
- sys_role: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false },
1149
- sys_permission_set: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false },
1150
- sys_role_permission_set: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false },
1151
- sys_user_permission_set: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false },
1152
- sys_user_role: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false }
1153
- },
1154
- systemPermissions: ["manage_org_users", "setup.access"],
1155
- rowLevelSecurity: [
1156
- {
1157
- name: "tenant_isolation",
1158
- object: "*",
1159
- operation: "all",
1160
- using: "organization_id = current_user.organization_id"
1161
- },
1162
- // ── better-auth system tables that lack `organization_id` and would
1163
- // otherwise be denied by the wildcard policy. Same self-only
1164
- // carve-outs as `member_default` — an org admin does not get to
1165
- // inspect cross-tenant identity rows.
1166
- {
1167
- name: "sys_organization_self",
1168
- object: "sys_organization",
1169
- operation: "all",
1170
- using: "id = current_user.organization_id"
1171
- },
1172
- {
1173
- name: "sys_user_self",
1174
- object: "sys_user",
1175
- operation: "select",
1176
- using: "id = current_user.id"
1177
- },
1178
- {
1179
- name: "sys_user_org_members",
1180
- object: "sys_user",
1181
- operation: "select",
1182
- using: "id IN (current_user.org_user_ids)"
1183
- },
1184
- {
1185
- name: "sys_session_self",
1186
- object: "sys_session",
1187
- operation: "all",
1188
- using: "user_id = current_user.id"
1189
- },
1190
- {
1191
- name: "sys_account_self",
1192
- object: "sys_account",
1193
- operation: "select",
1194
- using: "user_id = current_user.id"
1195
- },
1196
- {
1197
- name: "sys_team_member_self",
1198
- object: "sys_team_member",
1199
- operation: "select",
1200
- using: "user_id = current_user.id"
1201
- },
1202
- {
1203
- name: "sys_two_factor_self",
1204
- object: "sys_two_factor",
1205
- operation: "all",
1206
- using: "user_id = current_user.id"
1207
- },
1208
- {
1209
- name: "sys_user_preference_self",
1210
- object: "sys_user_preference",
1211
- operation: "all",
1212
- using: "user_id = current_user.id"
1213
- },
1214
- {
1215
- name: "sys_api_key_self",
1216
- object: "sys_api_key",
1217
- operation: "all",
1218
- using: "user_id = current_user.id"
1219
- },
1220
- {
1221
- name: "sys_device_code_self",
1222
- object: "sys_device_code",
1223
- operation: "all",
1224
- using: "user_id = current_user.id"
1225
- },
1226
- {
1227
- name: "sys_oauth_access_token_self",
1228
- object: "sys_oauth_access_token",
1229
- operation: "select",
1230
- using: "user_id = current_user.id"
1231
- },
1232
- {
1233
- name: "sys_oauth_refresh_token_self",
1234
- object: "sys_oauth_refresh_token",
1235
- operation: "select",
1236
- using: "user_id = current_user.id"
1237
- },
1238
- {
1239
- name: "sys_oauth_consent_self",
1240
- object: "sys_oauth_consent",
1241
- operation: "all",
1242
- using: "user_id = current_user.id"
1243
- },
1244
- // OAuth applications a user has registered themselves (self-service
1245
- // developer flow exposed in the Account app's Developer section).
1246
- // `sys_oauth_application` has no `organization_id` so the wildcard
1247
- // `tenant_isolation` policy would otherwise deny every row.
1248
- {
1249
- name: "sys_oauth_application_self",
1250
- object: "sys_oauth_application",
1251
- operation: "all",
1252
- using: "user_id = current_user.id"
1253
- },
1254
- // Org-scoped visibility for organization-owned identity-adjacent
1255
- // tables. Org admins may inspect their own org's invitations and
1256
- // memberships (read; writes still flow through better-auth).
1257
- {
1258
- name: "sys_member_org",
1259
- object: "sys_member",
1260
- operation: "select",
1261
- using: "organization_id = current_user.organization_id"
1262
- },
1263
- {
1264
- name: "sys_invitation_org",
1265
- object: "sys_invitation",
1266
- operation: "select",
1267
- using: "organization_id = current_user.organization_id"
1268
- },
1269
- {
1270
- name: "sys_team_org",
1271
- object: "sys_team",
1272
- operation: "select",
1273
- using: "organization_id = current_user.organization_id"
1274
- }
1275
- ]
1276
- }),
1277
- security.PermissionSetSchema.parse({
1278
- name: "member_default",
1279
- label: "Member \u2014 Standard Access",
1280
- isProfile: true,
1281
- objects: {
1282
- "*": {
1283
- allowRead: true,
1284
- allowCreate: true,
1285
- allowEdit: true,
1286
- allowDelete: true
1287
- },
1288
- // Identity tables are managed by better-auth — no direct writes.
1289
- ...denyWritesOnManagedObjects()
1290
- },
1291
- rowLevelSecurity: [
1292
- {
1293
- name: "tenant_isolation",
1294
- object: "*",
1295
- operation: "all",
1296
- using: "organization_id = current_user.organization_id"
1297
- },
1298
- {
1299
- name: "owner_only_writes",
1300
- object: "*",
1301
- operation: "update",
1302
- using: "owner_id = current_user.id"
1303
- },
1304
- {
1305
- name: "owner_only_deletes",
1306
- object: "*",
1307
- operation: "delete",
1308
- using: "owner_id = current_user.id"
1309
- },
1310
- // ── better-auth system tables that lack `organization_id` and would
1311
- // otherwise be left unprotected by the wildcard rule above. ────
1312
- //
1313
- // The security plugin's RLS injector treats wildcard policies that
1314
- // target a missing field as `RLS_DENY_FILTER` (zero rows) unless a
1315
- // per-object policy contributes an alternate match. Each `*_self`
1316
- // policy below restores per-user visibility on a better-auth table
1317
- // that has `user_id` but no `organization_id`. Tables without
1318
- // `user_id` (`sys_verification`, `sys_jwks`, empty `sys_passkey`)
1319
- // stay DENY for non-admins by design — only platform admins (via
1320
- // `admin_full_access`, which has no RLS) should inspect them.
1321
- {
1322
- name: "sys_organization_self",
1323
- object: "sys_organization",
1324
- operation: "all",
1325
- using: "id = current_user.organization_id"
1326
- },
1327
- {
1328
- name: "sys_user_self",
1329
- object: "sys_user",
1330
- operation: "select",
1331
- using: "id = current_user.id"
1332
- },
1333
- // Org collaborators: members can see other users in the same
1334
- // organization. Without this, owner/assignee lookups, @-mention
1335
- // suggestions, reviewer pickers and team-roster surfaces all
1336
- // collapse to just the current user. `org_user_ids` is
1337
- // pre-resolved by runtime/resolve-execution-context from
1338
- // `sys_member` for the active organization. Sensitive credential
1339
- // tables (`sys_account`, `sys_session`, `sys_api_key`, …) keep
1340
- // their stricter self-only carve-outs above.
1341
- {
1342
- name: "sys_user_org_members",
1343
- object: "sys_user",
1344
- operation: "select",
1345
- using: "id IN (current_user.org_user_ids)"
1346
- },
1347
- {
1348
- name: "sys_session_self",
1349
- object: "sys_session",
1350
- operation: "all",
1351
- using: "user_id = current_user.id"
1352
- },
1353
- {
1354
- name: "sys_account_self",
1355
- object: "sys_account",
1356
- operation: "select",
1357
- using: "user_id = current_user.id"
1358
- },
1359
- {
1360
- name: "sys_team_member_self",
1361
- object: "sys_team_member",
1362
- operation: "select",
1363
- using: "user_id = current_user.id"
1364
- },
1365
- {
1366
- name: "sys_two_factor_self",
1367
- object: "sys_two_factor",
1368
- operation: "all",
1369
- using: "user_id = current_user.id"
1370
- },
1371
- {
1372
- name: "sys_user_preference_self",
1373
- object: "sys_user_preference",
1374
- operation: "all",
1375
- using: "user_id = current_user.id"
1376
- },
1377
- {
1378
- name: "sys_api_key_self",
1379
- object: "sys_api_key",
1380
- operation: "all",
1381
- using: "user_id = current_user.id"
1382
- },
1383
- {
1384
- name: "sys_device_code_self",
1385
- object: "sys_device_code",
1386
- operation: "all",
1387
- using: "user_id = current_user.id"
1388
- },
1389
- {
1390
- name: "sys_oauth_access_token_self",
1391
- object: "sys_oauth_access_token",
1392
- operation: "select",
1393
- using: "user_id = current_user.id"
1394
- },
1395
- {
1396
- name: "sys_oauth_refresh_token_self",
1397
- object: "sys_oauth_refresh_token",
1398
- operation: "select",
1399
- using: "user_id = current_user.id"
1400
- },
1401
- {
1402
- name: "sys_oauth_consent_self",
1403
- object: "sys_oauth_consent",
1404
- operation: "all",
1405
- using: "user_id = current_user.id"
1406
- },
1407
- // OAuth applications a user has registered themselves (Account →
1408
- // Developer → OAuth Applications). `sys_oauth_application` has no
1409
- // `organization_id`, so without this carve-out the wildcard
1410
- // `tenant_isolation` policy returns zero rows even for the owner.
1411
- {
1412
- name: "sys_oauth_application_self",
1413
- object: "sys_oauth_application",
1414
- operation: "all",
1415
- using: "user_id = current_user.id"
1416
- }
1417
- ]
1418
- }),
1419
- security.PermissionSetSchema.parse({
1420
- name: "viewer_readonly",
1421
- label: "Viewer \u2014 Read-Only",
1422
- isProfile: true,
1423
- objects: {
1424
- "*": {
1425
- allowRead: true,
1426
- allowCreate: false,
1427
- allowEdit: false,
1428
- allowDelete: false
1429
- },
1430
- // Belt-and-suspenders: explicit deny on managed objects even though
1431
- // the wildcard already denies — keeps the policy readable when
1432
- // future relaxations might widen the wildcard.
1433
- ...denyWritesOnManagedObjects()
1434
- },
1435
- rowLevelSecurity: [
1436
- {
1437
- name: "tenant_isolation",
1438
- object: "*",
1439
- operation: "select",
1440
- using: "organization_id = current_user.organization_id"
1441
- },
1442
- {
1443
- name: "sys_organization_self",
1444
- object: "sys_organization",
1445
- operation: "select",
1446
- using: "id = current_user.organization_id"
1447
- },
1448
- {
1449
- name: "sys_user_self",
1450
- object: "sys_user",
1451
- operation: "select",
1452
- using: "id = current_user.id"
1453
- },
1454
- // Org collaborators (read-only): see `sys_user_org_members` in
1455
- // `member_default` for rationale.
1456
- {
1457
- name: "sys_user_org_members",
1458
- object: "sys_user",
1459
- operation: "select",
1460
- using: "id IN (current_user.org_user_ids)"
1461
- },
1462
- {
1463
- name: "sys_session_self",
1464
- object: "sys_session",
1465
- operation: "select",
1466
- using: "user_id = current_user.id"
1467
- },
1468
- {
1469
- name: "sys_account_self",
1470
- object: "sys_account",
1471
- operation: "select",
1472
- using: "user_id = current_user.id"
1473
- },
1474
- {
1475
- name: "sys_team_member_self",
1476
- object: "sys_team_member",
1477
- operation: "select",
1478
- using: "user_id = current_user.id"
1479
- },
1480
- {
1481
- name: "sys_two_factor_self",
1482
- object: "sys_two_factor",
1483
- operation: "select",
1484
- using: "user_id = current_user.id"
1485
- },
1486
- {
1487
- name: "sys_user_preference_self",
1488
- object: "sys_user_preference",
1489
- operation: "select",
1490
- using: "user_id = current_user.id"
1491
- },
1492
- {
1493
- name: "sys_api_key_self",
1494
- object: "sys_api_key",
1495
- operation: "select",
1496
- using: "user_id = current_user.id"
1497
- },
1498
- {
1499
- name: "sys_device_code_self",
1500
- object: "sys_device_code",
1501
- operation: "select",
1502
- using: "user_id = current_user.id"
1503
- },
1504
- {
1505
- name: "sys_oauth_access_token_self",
1506
- object: "sys_oauth_access_token",
1507
- operation: "select",
1508
- using: "user_id = current_user.id"
1509
- },
1510
- {
1511
- name: "sys_oauth_refresh_token_self",
1512
- object: "sys_oauth_refresh_token",
1513
- operation: "select",
1514
- using: "user_id = current_user.id"
1515
- },
1516
- {
1517
- name: "sys_oauth_consent_self",
1518
- object: "sys_oauth_consent",
1519
- operation: "select",
1520
- using: "user_id = current_user.id"
1521
- }
1522
- ]
1523
- })
1524
- ];
1525
-
1526
- exports.SysPermissionSet = SysPermissionSet;
1527
- exports.SysRecordShare = SysRecordShare;
1528
- exports.SysRole = SysRole;
1529
- exports.SysRolePermissionSet = SysRolePermissionSet;
1530
- exports.SysShareLink = SysShareLink;
1531
- exports.SysSharingRule = SysSharingRule;
1532
- exports.SysUserPermissionSet = SysUserPermissionSet;
1533
- exports.defaultPermissionSets = defaultPermissionSets;
1534
3
  //# sourceMappingURL=index.js.map
1535
4
  //# sourceMappingURL=index.js.map