@objectstack/plugin-security 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.
package/dist/index.mjs CHANGED
@@ -1,3 +1,891 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/translations/en.objects.generated.ts
12
+ var enObjects;
13
+ var init_en_objects_generated = __esm({
14
+ "src/translations/en.objects.generated.ts"() {
15
+ "use strict";
16
+ enObjects = {
17
+ sys_role: {
18
+ label: "Role",
19
+ pluralLabel: "Roles",
20
+ description: "Role definitions for RBAC access control",
21
+ fields: {
22
+ label: {
23
+ label: "Display Name"
24
+ },
25
+ name: {
26
+ label: "API Name",
27
+ help: "Unique machine name for the role (e.g. admin, editor, viewer)"
28
+ },
29
+ description: {
30
+ label: "Description"
31
+ },
32
+ permissions: {
33
+ label: "Permissions",
34
+ help: "JSON-serialized array of permission strings"
35
+ },
36
+ active: {
37
+ label: "Active"
38
+ },
39
+ is_default: {
40
+ label: "Default Role",
41
+ help: "Automatically assigned to new users"
42
+ },
43
+ id: {
44
+ label: "Role ID"
45
+ },
46
+ created_at: {
47
+ label: "Created At"
48
+ },
49
+ updated_at: {
50
+ label: "Updated At"
51
+ }
52
+ },
53
+ _views: {
54
+ active: {
55
+ label: "Active"
56
+ },
57
+ default_roles: {
58
+ label: "Default"
59
+ },
60
+ custom: {
61
+ label: "Custom"
62
+ },
63
+ all_roles: {
64
+ label: "All"
65
+ }
66
+ },
67
+ _actions: {
68
+ activate_role: {
69
+ label: "Activate Role",
70
+ successMessage: "Role activated"
71
+ },
72
+ deactivate_role: {
73
+ label: "Deactivate Role",
74
+ confirmText: "Deactivate this role? Users with the role keep their assignment but the role stops granting permissions until re-activated.",
75
+ successMessage: "Role deactivated"
76
+ },
77
+ set_default_role: {
78
+ label: "Set as Default",
79
+ confirmText: "Make this the default role for new users? Existing users are unaffected.",
80
+ successMessage: "Default role updated"
81
+ },
82
+ clone_role: {
83
+ label: "Clone Role",
84
+ successMessage: "Role cloned"
85
+ }
86
+ }
87
+ },
88
+ sys_permission_set: {
89
+ label: "Permission Set",
90
+ pluralLabel: "Permission Sets",
91
+ description: "Named permission groupings for fine-grained access control",
92
+ fields: {
93
+ label: {
94
+ label: "Display Name"
95
+ },
96
+ name: {
97
+ label: "API Name",
98
+ help: "Unique machine name for the permission set"
99
+ },
100
+ description: {
101
+ label: "Description"
102
+ },
103
+ object_permissions: {
104
+ label: "Object Permissions",
105
+ help: "JSON-serialized object-level CRUD permissions"
106
+ },
107
+ field_permissions: {
108
+ label: "Field Permissions",
109
+ help: "JSON-serialized field-level read/write permissions"
110
+ },
111
+ system_permissions: {
112
+ label: "System Permissions",
113
+ help: 'JSON-serialized array of system capability names (e.g. ["setup.access","studio.access","manage_users"])'
114
+ },
115
+ row_level_security: {
116
+ label: "Row-Level Security",
117
+ help: "JSON-serialized array of row-level security policies (USING/CHECK clauses)"
118
+ },
119
+ tab_permissions: {
120
+ label: "Tab Permissions",
121
+ help: "JSON-serialized map of app tab visibility (visible | hidden | default_on | default_off)"
122
+ },
123
+ active: {
124
+ label: "Active"
125
+ },
126
+ id: {
127
+ label: "Permission Set ID"
128
+ },
129
+ created_at: {
130
+ label: "Created At"
131
+ },
132
+ updated_at: {
133
+ label: "Updated At"
134
+ }
135
+ },
136
+ _views: {
137
+ active: {
138
+ label: "Active"
139
+ },
140
+ inactive: {
141
+ label: "Inactive"
142
+ },
143
+ all_permsets: {
144
+ label: "All"
145
+ }
146
+ },
147
+ _actions: {
148
+ activate_permission_set: {
149
+ label: "Activate",
150
+ successMessage: "Permission set activated"
151
+ },
152
+ deactivate_permission_set: {
153
+ label: "Deactivate",
154
+ confirmText: "Deactivate this permission set? Existing assignments stay in place but stop granting access until re-activated.",
155
+ successMessage: "Permission set deactivated"
156
+ },
157
+ clone_permission_set: {
158
+ label: "Clone",
159
+ successMessage: "Permission set cloned"
160
+ }
161
+ }
162
+ },
163
+ sys_user_permission_set: {
164
+ label: "User Permission Set",
165
+ pluralLabel: "User Permission Sets",
166
+ description: "Direct assignment of a permission set to a user (optionally scoped to an organization).",
167
+ fields: {
168
+ id: {
169
+ label: "Assignment ID",
170
+ help: "UUID of the assignment."
171
+ },
172
+ user_id: {
173
+ label: "User",
174
+ help: "Foreign key to sys_user."
175
+ },
176
+ permission_set_id: {
177
+ label: "Permission Set",
178
+ help: "Foreign key to sys_permission_set."
179
+ },
180
+ organization_id: {
181
+ label: "Organization",
182
+ help: "Optional organization scope. NULL = applies in every org context."
183
+ },
184
+ granted_by: {
185
+ label: "Granted By",
186
+ help: "User who granted this permission set."
187
+ },
188
+ created_at: {
189
+ label: "Created At"
190
+ },
191
+ updated_at: {
192
+ label: "Updated At"
193
+ }
194
+ }
195
+ },
196
+ sys_role_permission_set: {
197
+ label: "Role Permission Set",
198
+ pluralLabel: "Role Permission Sets",
199
+ description: "Binds a permission set to a role.",
200
+ fields: {
201
+ id: {
202
+ label: "Binding ID",
203
+ help: "UUID of the role-permission-set binding."
204
+ },
205
+ role_id: {
206
+ label: "Role",
207
+ help: "Foreign key to sys_role."
208
+ },
209
+ permission_set_id: {
210
+ label: "Permission Set",
211
+ help: "Foreign key to sys_permission_set."
212
+ },
213
+ created_at: {
214
+ label: "Created At"
215
+ },
216
+ updated_at: {
217
+ label: "Updated At"
218
+ }
219
+ }
220
+ }
221
+ };
222
+ }
223
+ });
224
+
225
+ // src/translations/zh-CN.objects.generated.ts
226
+ var zhCNObjects;
227
+ var init_zh_CN_objects_generated = __esm({
228
+ "src/translations/zh-CN.objects.generated.ts"() {
229
+ "use strict";
230
+ zhCNObjects = {
231
+ sys_role: {
232
+ label: "\u89D2\u8272",
233
+ pluralLabel: "\u89D2\u8272",
234
+ description: "\u7528\u4E8E RBAC \u8BBF\u95EE\u63A7\u5236\u7684\u89D2\u8272\u5B9A\u4E49",
235
+ fields: {
236
+ label: {
237
+ label: "\u663E\u793A\u540D\u79F0"
238
+ },
239
+ name: {
240
+ label: "API \u540D\u79F0",
241
+ help: "\u89D2\u8272\u7684\u552F\u4E00\u673A\u5668\u540D\u79F0\uFF08\u4F8B\u5982 admin\u3001editor\u3001viewer\uFF09"
242
+ },
243
+ description: {
244
+ label: "\u63CF\u8FF0"
245
+ },
246
+ permissions: {
247
+ label: "\u6743\u9650",
248
+ help: "\u6743\u9650\u5B57\u7B26\u4E32\u6570\u7EC4\u7684 JSON \u5E8F\u5217\u5316\u5185\u5BB9"
249
+ },
250
+ active: {
251
+ label: "\u542F\u7528"
252
+ },
253
+ is_default: {
254
+ label: "\u9ED8\u8BA4\u89D2\u8272",
255
+ help: "\u81EA\u52A8\u5206\u914D\u7ED9\u65B0\u7528\u6237"
256
+ },
257
+ id: {
258
+ label: "\u89D2\u8272 ID"
259
+ },
260
+ created_at: {
261
+ label: "\u521B\u5EFA\u65F6\u95F4"
262
+ },
263
+ updated_at: {
264
+ label: "\u66F4\u65B0\u65F6\u95F4"
265
+ }
266
+ },
267
+ _views: {
268
+ active: {
269
+ label: "\u542F\u7528"
270
+ },
271
+ default_roles: {
272
+ label: "\u9ED8\u8BA4"
273
+ },
274
+ custom: {
275
+ label: "\u81EA\u5B9A\u4E49"
276
+ },
277
+ all_roles: {
278
+ label: "\u5168\u90E8"
279
+ }
280
+ },
281
+ _actions: {
282
+ activate_role: {
283
+ label: "\u6FC0\u6D3B\u89D2\u8272",
284
+ successMessage: "\u89D2\u8272\u5DF2\u6FC0\u6D3B"
285
+ },
286
+ deactivate_role: {
287
+ label: "\u505C\u7528\u89D2\u8272",
288
+ confirmText: "\u786E\u5B9A\u8981\u505C\u7528\u6B64\u89D2\u8272\u5417\uFF1F\u62E5\u6709\u8BE5\u89D2\u8272\u7684\u7528\u6237\u4ECD\u4FDD\u7559\u5176\u5206\u914D\uFF0C\u4F46\u5728\u91CD\u65B0\u6FC0\u6D3B\u4E4B\u524D\u8BE5\u89D2\u8272\u5C06\u4E0D\u518D\u6388\u4E88\u6743\u9650\u3002",
289
+ successMessage: "\u89D2\u8272\u5DF2\u505C\u7528"
290
+ },
291
+ set_default_role: {
292
+ label: "\u8BBE\u4E3A\u9ED8\u8BA4",
293
+ confirmText: "\u5C06\u6B64\u89D2\u8272\u8BBE\u4E3A\u65B0\u7528\u6237\u7684\u9ED8\u8BA4\u89D2\u8272\u5417\uFF1F\u73B0\u6709\u7528\u6237\u4E0D\u53D7\u5F71\u54CD\u3002",
294
+ successMessage: "\u5DF2\u66F4\u65B0\u9ED8\u8BA4\u89D2\u8272"
295
+ },
296
+ clone_role: {
297
+ label: "\u514B\u9686\u89D2\u8272",
298
+ successMessage: "\u5DF2\u514B\u9686\u89D2\u8272"
299
+ }
300
+ }
301
+ },
302
+ sys_permission_set: {
303
+ label: "\u6743\u9650\u96C6",
304
+ pluralLabel: "\u6743\u9650\u96C6",
305
+ description: "\u7528\u4E8E\u7CBE\u7EC6\u5316\u8BBF\u95EE\u63A7\u5236\u7684\u547D\u540D\u6743\u9650\u5206\u7EC4",
306
+ fields: {
307
+ label: {
308
+ label: "\u663E\u793A\u540D\u79F0"
309
+ },
310
+ name: {
311
+ label: "API \u540D\u79F0",
312
+ help: "\u6743\u9650\u96C6\u7684\u552F\u4E00\u673A\u5668\u540D\u79F0"
313
+ },
314
+ description: {
315
+ label: "\u63CF\u8FF0"
316
+ },
317
+ object_permissions: {
318
+ label: "\u5BF9\u8C61\u6743\u9650",
319
+ help: "\u5BF9\u8C61\u7EA7 CRUD \u6743\u9650\u7684 JSON \u5E8F\u5217\u5316\u5185\u5BB9"
320
+ },
321
+ field_permissions: {
322
+ label: "\u5B57\u6BB5\u6743\u9650",
323
+ help: "\u5B57\u6BB5\u7EA7\u8BFB\u5199\u6743\u9650\u7684 JSON \u5E8F\u5217\u5316\u5185\u5BB9"
324
+ },
325
+ system_permissions: {
326
+ label: "\u7CFB\u7EDF\u6743\u9650",
327
+ help: '\u7CFB\u7EDF\u80FD\u529B\u540D\u79F0\u7684 JSON \u5E8F\u5217\u5316\u6570\u7EC4\uFF08\u4F8B\u5982 ["setup.access","studio.access","manage_users"]\uFF09'
328
+ },
329
+ row_level_security: {
330
+ label: "\u884C\u7EA7\u5B89\u5168",
331
+ help: "\u884C\u7EA7\u5B89\u5168\u7B56\u7565\u7684 JSON \u5E8F\u5217\u5316\u6570\u7EC4\uFF08USING/CHECK \u5B50\u53E5\uFF09"
332
+ },
333
+ tab_permissions: {
334
+ label: "\u6807\u7B7E\u9875\u6743\u9650",
335
+ help: "\u5E94\u7528\u6807\u7B7E\u9875\u53EF\u89C1\u6027\u7684 JSON \u5E8F\u5217\u5316\u6620\u5C04\uFF08visible | hidden | default_on | default_off\uFF09"
336
+ },
337
+ active: {
338
+ label: "\u542F\u7528"
339
+ },
340
+ id: {
341
+ label: "\u6743\u9650\u96C6 ID"
342
+ },
343
+ created_at: {
344
+ label: "\u521B\u5EFA\u65F6\u95F4"
345
+ },
346
+ updated_at: {
347
+ label: "\u66F4\u65B0\u65F6\u95F4"
348
+ }
349
+ },
350
+ _views: {
351
+ active: {
352
+ label: "\u542F\u7528"
353
+ },
354
+ inactive: {
355
+ label: "\u505C\u7528"
356
+ },
357
+ all_permsets: {
358
+ label: "\u5168\u90E8"
359
+ }
360
+ },
361
+ _actions: {
362
+ activate_permission_set: {
363
+ label: "\u6FC0\u6D3B",
364
+ successMessage: "\u6743\u9650\u96C6\u5DF2\u6FC0\u6D3B"
365
+ },
366
+ deactivate_permission_set: {
367
+ label: "\u505C\u7528",
368
+ confirmText: "\u786E\u5B9A\u8981\u505C\u7528\u6B64\u6743\u9650\u96C6\u5417\uFF1F\u73B0\u6709\u5206\u914D\u4ECD\u5C06\u4FDD\u7559\uFF0C\u4F46\u5728\u91CD\u65B0\u6FC0\u6D3B\u4E4B\u524D\u5C06\u4E0D\u518D\u6388\u4E88\u8BBF\u95EE\u6743\u9650\u3002",
369
+ successMessage: "\u6743\u9650\u96C6\u5DF2\u505C\u7528"
370
+ },
371
+ clone_permission_set: {
372
+ label: "\u514B\u9686",
373
+ successMessage: "\u5DF2\u514B\u9686\u6743\u9650\u96C6"
374
+ }
375
+ }
376
+ },
377
+ sys_user_permission_set: {
378
+ label: "\u7528\u6237\u6743\u9650\u96C6",
379
+ pluralLabel: "\u7528\u6237\u6743\u9650\u96C6",
380
+ description: "\u5C06\u6743\u9650\u96C6\u76F4\u63A5\u5206\u914D\u7ED9\u7528\u6237\uFF08\u53EF\u6309\u7EC4\u7EC7\u8303\u56F4\u9650\u5B9A\uFF09\u3002",
381
+ fields: {
382
+ id: {
383
+ label: "\u5206\u914D ID",
384
+ help: "\u8BE5\u5206\u914D\u8BB0\u5F55\u7684 UUID\u3002"
385
+ },
386
+ user_id: {
387
+ label: "\u7528\u6237",
388
+ help: "\u6307\u5411 sys_user \u7684\u5916\u952E\u3002"
389
+ },
390
+ permission_set_id: {
391
+ label: "\u6743\u9650\u96C6",
392
+ help: "\u6307\u5411 sys_permission_set \u7684\u5916\u952E\u3002"
393
+ },
394
+ organization_id: {
395
+ label: "\u7EC4\u7EC7",
396
+ help: "\u53EF\u9009\u7684\u7EC4\u7EC7\u8303\u56F4\u3002NULL = \u5728\u6240\u6709\u7EC4\u7EC7\u4E0A\u4E0B\u6587\u4E2D\u90FD\u751F\u6548\u3002"
397
+ },
398
+ granted_by: {
399
+ label: "\u6388\u6743\u4EBA",
400
+ help: "\u6388\u4E88\u8BE5\u6743\u9650\u96C6\u7684\u7528\u6237\u3002"
401
+ },
402
+ created_at: {
403
+ label: "\u521B\u5EFA\u65F6\u95F4"
404
+ },
405
+ updated_at: {
406
+ label: "\u66F4\u65B0\u65F6\u95F4"
407
+ }
408
+ }
409
+ },
410
+ sys_role_permission_set: {
411
+ label: "\u89D2\u8272\u6743\u9650\u96C6",
412
+ pluralLabel: "\u89D2\u8272\u6743\u9650\u96C6",
413
+ description: "\u5C06\u6743\u9650\u96C6\u7ED1\u5B9A\u5230\u89D2\u8272\u3002",
414
+ fields: {
415
+ id: {
416
+ label: "\u7ED1\u5B9A ID",
417
+ help: "\u89D2\u8272-\u6743\u9650\u96C6\u7ED1\u5B9A\u8BB0\u5F55\u7684 UUID\u3002"
418
+ },
419
+ role_id: {
420
+ label: "\u89D2\u8272",
421
+ help: "\u6307\u5411 sys_role \u7684\u5916\u952E\u3002"
422
+ },
423
+ permission_set_id: {
424
+ label: "\u6743\u9650\u96C6",
425
+ help: "\u6307\u5411 sys_permission_set \u7684\u5916\u952E\u3002"
426
+ },
427
+ created_at: {
428
+ label: "\u521B\u5EFA\u65F6\u95F4"
429
+ },
430
+ updated_at: {
431
+ label: "\u66F4\u65B0\u65F6\u95F4"
432
+ }
433
+ }
434
+ }
435
+ };
436
+ }
437
+ });
438
+
439
+ // src/translations/ja-JP.objects.generated.ts
440
+ var jaJPObjects;
441
+ var init_ja_JP_objects_generated = __esm({
442
+ "src/translations/ja-JP.objects.generated.ts"() {
443
+ "use strict";
444
+ jaJPObjects = {
445
+ sys_role: {
446
+ label: "\u30ED\u30FC\u30EB",
447
+ pluralLabel: "\u30ED\u30FC\u30EB",
448
+ description: "RBAC \u30A2\u30AF\u30BB\u30B9\u5236\u5FA1\u306E\u305F\u3081\u306E\u30ED\u30FC\u30EB\u5B9A\u7FA9",
449
+ fields: {
450
+ label: {
451
+ label: "\u8868\u793A\u540D"
452
+ },
453
+ name: {
454
+ label: "API \u540D",
455
+ help: "\u30ED\u30FC\u30EB\u306E\u4E00\u610F\u306E\u6A5F\u68B0\u540D\uFF08\u4F8B: admin\u3001editor\u3001viewer\uFF09"
456
+ },
457
+ description: {
458
+ label: "\u8AAC\u660E"
459
+ },
460
+ permissions: {
461
+ label: "\u6A29\u9650",
462
+ help: "\u6A29\u9650\u6587\u5B57\u5217\u306E JSON \u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u914D\u5217"
463
+ },
464
+ active: {
465
+ label: "\u6709\u52B9"
466
+ },
467
+ is_default: {
468
+ label: "\u30C7\u30D5\u30A9\u30EB\u30C8\u30ED\u30FC\u30EB",
469
+ help: "\u65B0\u898F\u30E6\u30FC\u30B6\u30FC\u306B\u81EA\u52D5\u7684\u306B\u5272\u308A\u5F53\u3066\u3089\u308C\u307E\u3059"
470
+ },
471
+ id: {
472
+ label: "\u30ED\u30FC\u30EB ID"
473
+ },
474
+ created_at: {
475
+ label: "\u4F5C\u6210\u65E5\u6642"
476
+ },
477
+ updated_at: {
478
+ label: "\u66F4\u65B0\u65E5\u6642"
479
+ }
480
+ },
481
+ _views: {
482
+ active: {
483
+ label: "\u6709\u52B9"
484
+ },
485
+ default_roles: {
486
+ label: "\u30C7\u30D5\u30A9\u30EB\u30C8"
487
+ },
488
+ custom: {
489
+ label: "\u30AB\u30B9\u30BF\u30E0"
490
+ },
491
+ all_roles: {
492
+ label: "\u3059\u3079\u3066"
493
+ }
494
+ },
495
+ _actions: {
496
+ activate_role: {
497
+ label: "\u30ED\u30FC\u30EB\u3092\u6709\u52B9\u5316",
498
+ successMessage: "\u30ED\u30FC\u30EB\u304C\u6709\u52B9\u5316\u3055\u308C\u307E\u3057\u305F"
499
+ },
500
+ deactivate_role: {
501
+ label: "\u30ED\u30FC\u30EB\u3092\u7121\u52B9\u5316",
502
+ confirmText: "\u3053\u306E\u30ED\u30FC\u30EB\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u304B\uFF1F\u3053\u306E\u30ED\u30FC\u30EB\u3092\u6301\u3064\u30E6\u30FC\u30B6\u30FC\u306E\u5272\u308A\u5F53\u3066\u306F\u7DAD\u6301\u3055\u308C\u307E\u3059\u304C\u3001\u518D\u5EA6\u6709\u52B9\u5316\u3059\u308B\u307E\u3067\u6A29\u9650\u306E\u4ED8\u4E0E\u306F\u505C\u6B62\u3055\u308C\u307E\u3059\u3002",
503
+ successMessage: "\u30ED\u30FC\u30EB\u304C\u7121\u52B9\u5316\u3055\u308C\u307E\u3057\u305F"
504
+ },
505
+ set_default_role: {
506
+ label: "\u30C7\u30D5\u30A9\u30EB\u30C8\u306B\u8A2D\u5B9A",
507
+ confirmText: "\u3053\u306E\u30ED\u30FC\u30EB\u3092\u65B0\u898F\u30E6\u30FC\u30B6\u30FC\u306E\u30C7\u30D5\u30A9\u30EB\u30C8\u30ED\u30FC\u30EB\u306B\u3057\u307E\u3059\u304B\uFF1F\u65E2\u5B58\u306E\u30E6\u30FC\u30B6\u30FC\u306B\u306F\u5F71\u97FF\u3057\u307E\u305B\u3093\u3002",
508
+ successMessage: "\u30C7\u30D5\u30A9\u30EB\u30C8\u30ED\u30FC\u30EB\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F"
509
+ },
510
+ clone_role: {
511
+ label: "\u30ED\u30FC\u30EB\u3092\u8907\u88FD",
512
+ successMessage: "\u30ED\u30FC\u30EB\u3092\u8907\u88FD\u3057\u307E\u3057\u305F"
513
+ }
514
+ }
515
+ },
516
+ sys_permission_set: {
517
+ label: "\u6A29\u9650\u30BB\u30C3\u30C8",
518
+ pluralLabel: "\u6A29\u9650\u30BB\u30C3\u30C8",
519
+ description: "\u7D30\u304B\u3044\u30A2\u30AF\u30BB\u30B9\u5236\u5FA1\u306E\u305F\u3081\u306E\u6A29\u9650\u30B0\u30EB\u30FC\u30D7",
520
+ fields: {
521
+ label: {
522
+ label: "\u8868\u793A\u540D"
523
+ },
524
+ name: {
525
+ label: "API \u540D",
526
+ help: "\u6A29\u9650\u30BB\u30C3\u30C8\u306E\u4E00\u610F\u306E\u6A5F\u68B0\u540D"
527
+ },
528
+ description: {
529
+ label: "\u8AAC\u660E"
530
+ },
531
+ object_permissions: {
532
+ label: "\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u6A29\u9650",
533
+ help: "JSON \u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u3055\u308C\u305F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u30EC\u30D9\u30EB\u306E CRUD \u6A29\u9650"
534
+ },
535
+ field_permissions: {
536
+ label: "\u30D5\u30A3\u30FC\u30EB\u30C9\u6A29\u9650",
537
+ help: "JSON \u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9\u30EC\u30D9\u30EB\u306E\u8AAD\u307F\u53D6\u308A/\u66F8\u304D\u8FBC\u307F\u6A29\u9650"
538
+ },
539
+ system_permissions: {
540
+ label: "\u30B7\u30B9\u30C6\u30E0\u6A29\u9650",
541
+ help: '\u30B7\u30B9\u30C6\u30E0\u30B1\u30FC\u30D1\u30D3\u30EA\u30C6\u30A3\u540D\u306EJSON\u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u914D\u5217\uFF08\u4F8B: ["setup.access","studio.access","manage_users"]\uFF09'
542
+ },
543
+ row_level_security: {
544
+ label: "\u884C\u30EC\u30D9\u30EB\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3",
545
+ help: "\u884C\u30EC\u30D9\u30EB\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u30DD\u30EA\u30B7\u30FC\u306EJSON\u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u914D\u5217\uFF08USING/CHECK \u53E5\uFF09"
546
+ },
547
+ tab_permissions: {
548
+ label: "\u30BF\u30D6\u6A29\u9650",
549
+ help: "\u30A2\u30D7\u30EA\u306E\u30BF\u30D6\u8868\u793A\u306EJSON\u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u30DE\u30C3\u30D7\uFF08visible | hidden | default_on | default_off\uFF09"
550
+ },
551
+ active: {
552
+ label: "\u6709\u52B9"
553
+ },
554
+ id: {
555
+ label: "\u6A29\u9650\u30BB\u30C3\u30C8 ID"
556
+ },
557
+ created_at: {
558
+ label: "\u4F5C\u6210\u65E5\u6642"
559
+ },
560
+ updated_at: {
561
+ label: "\u66F4\u65B0\u65E5\u6642"
562
+ }
563
+ },
564
+ _views: {
565
+ active: {
566
+ label: "\u6709\u52B9"
567
+ },
568
+ inactive: {
569
+ label: "\u7121\u52B9"
570
+ },
571
+ all_permsets: {
572
+ label: "\u3059\u3079\u3066"
573
+ }
574
+ },
575
+ _actions: {
576
+ activate_permission_set: {
577
+ label: "\u6709\u52B9\u5316",
578
+ successMessage: "\u6A29\u9650\u30BB\u30C3\u30C8\u304C\u6709\u52B9\u5316\u3055\u308C\u307E\u3057\u305F"
579
+ },
580
+ deactivate_permission_set: {
581
+ label: "\u7121\u52B9\u5316",
582
+ confirmText: "\u3053\u306E\u6A29\u9650\u30BB\u30C3\u30C8\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u304B\uFF1F\u65E2\u5B58\u306E\u5272\u308A\u5F53\u3066\u306F\u7DAD\u6301\u3055\u308C\u307E\u3059\u304C\u3001\u518D\u5EA6\u6709\u52B9\u5316\u3059\u308B\u307E\u3067\u30A2\u30AF\u30BB\u30B9\u306E\u4ED8\u4E0E\u306F\u505C\u6B62\u3055\u308C\u307E\u3059\u3002",
583
+ successMessage: "\u6A29\u9650\u30BB\u30C3\u30C8\u304C\u7121\u52B9\u5316\u3055\u308C\u307E\u3057\u305F"
584
+ },
585
+ clone_permission_set: {
586
+ label: "\u8907\u88FD",
587
+ successMessage: "\u6A29\u9650\u30BB\u30C3\u30C8\u3092\u8907\u88FD\u3057\u307E\u3057\u305F"
588
+ }
589
+ }
590
+ },
591
+ sys_user_permission_set: {
592
+ label: "\u30E6\u30FC\u30B6\u30FC\u6A29\u9650\u30BB\u30C3\u30C8",
593
+ pluralLabel: "\u30E6\u30FC\u30B6\u30FC\u6A29\u9650\u30BB\u30C3\u30C8",
594
+ description: "\u30E6\u30FC\u30B6\u30FC\u3078\u306E\u6A29\u9650\u30BB\u30C3\u30C8\u306E\u76F4\u63A5\u5272\u308A\u5F53\u3066\uFF08\u7D44\u7E54\u30B9\u30B3\u30FC\u30D7\u53EF\u80FD\uFF09\u3002",
595
+ fields: {
596
+ id: {
597
+ label: "\u5272\u308A\u5F53\u3066 ID",
598
+ help: "\u5272\u308A\u5F53\u3066\u306E UUID\u3002"
599
+ },
600
+ user_id: {
601
+ label: "\u30E6\u30FC\u30B6\u30FC",
602
+ help: "sys_user \u3078\u306E\u5916\u90E8\u30AD\u30FC\u3002"
603
+ },
604
+ permission_set_id: {
605
+ label: "\u6A29\u9650\u30BB\u30C3\u30C8",
606
+ help: "sys_permission_set \u3078\u306E\u5916\u90E8\u30AD\u30FC\u3002"
607
+ },
608
+ organization_id: {
609
+ label: "\u7D44\u7E54",
610
+ help: "\u30AA\u30D7\u30B7\u30E7\u30F3\u306E\u7D44\u7E54\u30B9\u30B3\u30FC\u30D7\u3002NULL = \u3059\u3079\u3066\u306E\u7D44\u7E54\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3067\u9069\u7528\u3002"
611
+ },
612
+ granted_by: {
613
+ label: "\u4ED8\u4E0E\u8005",
614
+ help: "\u3053\u306E\u6A29\u9650\u30BB\u30C3\u30C8\u3092\u4ED8\u4E0E\u3057\u305F\u30E6\u30FC\u30B6\u30FC\u3002"
615
+ },
616
+ created_at: {
617
+ label: "\u4F5C\u6210\u65E5\u6642"
618
+ },
619
+ updated_at: {
620
+ label: "\u66F4\u65B0\u65E5\u6642"
621
+ }
622
+ }
623
+ },
624
+ sys_role_permission_set: {
625
+ label: "\u30ED\u30FC\u30EB\u6A29\u9650\u30BB\u30C3\u30C8",
626
+ pluralLabel: "\u30ED\u30FC\u30EB\u6A29\u9650\u30BB\u30C3\u30C8",
627
+ description: "\u6A29\u9650\u30BB\u30C3\u30C8\u3092\u30ED\u30FC\u30EB\u306B\u30D0\u30A4\u30F3\u30C9\u3057\u307E\u3059\u3002",
628
+ fields: {
629
+ id: {
630
+ label: "\u30D0\u30A4\u30F3\u30C9 ID",
631
+ help: "\u30ED\u30FC\u30EB\u6A29\u9650\u30BB\u30C3\u30C8\u30D0\u30A4\u30F3\u30C9\u306E UUID\u3002"
632
+ },
633
+ role_id: {
634
+ label: "\u30ED\u30FC\u30EB",
635
+ help: "sys_role \u3078\u306E\u5916\u90E8\u30AD\u30FC\u3002"
636
+ },
637
+ permission_set_id: {
638
+ label: "\u6A29\u9650\u30BB\u30C3\u30C8",
639
+ help: "sys_permission_set \u3078\u306E\u5916\u90E8\u30AD\u30FC\u3002"
640
+ },
641
+ created_at: {
642
+ label: "\u4F5C\u6210\u65E5\u6642"
643
+ },
644
+ updated_at: {
645
+ label: "\u66F4\u65B0\u65E5\u6642"
646
+ }
647
+ }
648
+ }
649
+ };
650
+ }
651
+ });
652
+
653
+ // src/translations/es-ES.objects.generated.ts
654
+ var esESObjects;
655
+ var init_es_ES_objects_generated = __esm({
656
+ "src/translations/es-ES.objects.generated.ts"() {
657
+ "use strict";
658
+ esESObjects = {
659
+ sys_role: {
660
+ label: "Rol",
661
+ pluralLabel: "Roles",
662
+ description: "Definiciones de rol para el control de acceso RBAC",
663
+ fields: {
664
+ label: {
665
+ label: "Nombre visible"
666
+ },
667
+ name: {
668
+ label: "Nombre de API",
669
+ help: "Nombre t\xE9cnico \xFAnico del rol (p. ej. admin, editor, viewer)."
670
+ },
671
+ description: {
672
+ label: "Descripci\xF3n"
673
+ },
674
+ permissions: {
675
+ label: "Permisos",
676
+ help: "Matriz serializada en JSON de cadenas de permisos."
677
+ },
678
+ active: {
679
+ label: "Activo"
680
+ },
681
+ is_default: {
682
+ label: "Rol predeterminado",
683
+ help: "Se asigna autom\xE1ticamente a los nuevos usuarios."
684
+ },
685
+ id: {
686
+ label: "ID de rol"
687
+ },
688
+ created_at: {
689
+ label: "Creado el"
690
+ },
691
+ updated_at: {
692
+ label: "Actualizado el"
693
+ }
694
+ },
695
+ _views: {
696
+ active: {
697
+ label: "Activo"
698
+ },
699
+ default_roles: {
700
+ label: "Predeterminado"
701
+ },
702
+ custom: {
703
+ label: "Personalizado"
704
+ },
705
+ all_roles: {
706
+ label: "Todos"
707
+ }
708
+ },
709
+ _actions: {
710
+ activate_role: {
711
+ label: "Activar rol",
712
+ successMessage: "Rol activado"
713
+ },
714
+ deactivate_role: {
715
+ label: "Desactivar rol",
716
+ confirmText: "\xBFDesactivar este rol? Los usuarios con el rol conservan su asignaci\xF3n, pero el rol deja de otorgar permisos hasta que se vuelva a activar.",
717
+ successMessage: "Rol desactivado"
718
+ },
719
+ set_default_role: {
720
+ label: "Establecer como predeterminado",
721
+ confirmText: "\xBFConvertir este en el rol predeterminado para los nuevos usuarios? Los usuarios existentes no se ven afectados.",
722
+ successMessage: "Rol predeterminado actualizado"
723
+ },
724
+ clone_role: {
725
+ label: "Clonar rol",
726
+ successMessage: "Rol clonado"
727
+ }
728
+ }
729
+ },
730
+ sys_permission_set: {
731
+ label: "Conjunto de permisos",
732
+ pluralLabel: "Conjuntos de permisos",
733
+ description: "Agrupaciones de permisos con nombre para un control de acceso detallado",
734
+ fields: {
735
+ label: {
736
+ label: "Nombre visible"
737
+ },
738
+ name: {
739
+ label: "Nombre de API",
740
+ help: "Nombre t\xE9cnico \xFAnico del conjunto de permisos."
741
+ },
742
+ description: {
743
+ label: "Descripci\xF3n"
744
+ },
745
+ object_permissions: {
746
+ label: "Permisos de objeto",
747
+ help: "Permisos CRUD a nivel de objeto serializados en JSON."
748
+ },
749
+ field_permissions: {
750
+ label: "Permisos de campo",
751
+ help: "Permisos de lectura/escritura a nivel de campo serializados en JSON."
752
+ },
753
+ system_permissions: {
754
+ label: "Permisos del sistema",
755
+ help: 'Array serializado en JSON de nombres de capacidades del sistema (p. ej. ["setup.access","studio.access","manage_users"])'
756
+ },
757
+ row_level_security: {
758
+ label: "Seguridad a nivel de fila",
759
+ help: "Array serializado en JSON de pol\xEDticas de seguridad a nivel de fila (cl\xE1usulas USING/CHECK)"
760
+ },
761
+ tab_permissions: {
762
+ label: "Permisos de pesta\xF1as",
763
+ help: "Mapa serializado en JSON de la visibilidad de las pesta\xF1as de la app (visible | hidden | default_on | default_off)"
764
+ },
765
+ active: {
766
+ label: "Activo"
767
+ },
768
+ id: {
769
+ label: "ID de conjunto de permisos"
770
+ },
771
+ created_at: {
772
+ label: "Creado el"
773
+ },
774
+ updated_at: {
775
+ label: "Actualizado el"
776
+ }
777
+ },
778
+ _views: {
779
+ active: {
780
+ label: "Activo"
781
+ },
782
+ inactive: {
783
+ label: "Inactivo"
784
+ },
785
+ all_permsets: {
786
+ label: "Todos"
787
+ }
788
+ },
789
+ _actions: {
790
+ activate_permission_set: {
791
+ label: "Activar",
792
+ successMessage: "Conjunto de permisos activado"
793
+ },
794
+ deactivate_permission_set: {
795
+ label: "Desactivar",
796
+ confirmText: "\xBFDesactivar este conjunto de permisos? Las asignaciones existentes se mantienen, pero dejan de otorgar acceso hasta que se vuelva a activar.",
797
+ successMessage: "Conjunto de permisos desactivado"
798
+ },
799
+ clone_permission_set: {
800
+ label: "Clonar",
801
+ successMessage: "Conjunto de permisos clonado"
802
+ }
803
+ }
804
+ },
805
+ sys_user_permission_set: {
806
+ label: "Conjunto de permisos de usuario",
807
+ pluralLabel: "Conjuntos de permisos de usuario",
808
+ description: "Asignaci\xF3n directa de un conjunto de permisos a un usuario (opcionalmente con \xE1mbito de organizaci\xF3n).",
809
+ fields: {
810
+ id: {
811
+ label: "ID de asignaci\xF3n",
812
+ help: "UUID de la asignaci\xF3n."
813
+ },
814
+ user_id: {
815
+ label: "Usuario",
816
+ help: "Clave for\xE1nea a sys_user."
817
+ },
818
+ permission_set_id: {
819
+ label: "Conjunto de permisos",
820
+ help: "Clave for\xE1nea a sys_permission_set."
821
+ },
822
+ organization_id: {
823
+ label: "Organizaci\xF3n",
824
+ help: "\xC1mbito de organizaci\xF3n opcional. NULL = se aplica en cualquier contexto de organizaci\xF3n."
825
+ },
826
+ granted_by: {
827
+ label: "Concedido por",
828
+ help: "Usuario que concedi\xF3 este conjunto de permisos."
829
+ },
830
+ created_at: {
831
+ label: "Creado el"
832
+ },
833
+ updated_at: {
834
+ label: "Actualizado el"
835
+ }
836
+ }
837
+ },
838
+ sys_role_permission_set: {
839
+ label: "Conjunto de permisos de rol",
840
+ pluralLabel: "Conjuntos de permisos de rol",
841
+ description: "Vincula un conjunto de permisos a un rol.",
842
+ fields: {
843
+ id: {
844
+ label: "ID de vinculaci\xF3n",
845
+ help: "UUID de la vinculaci\xF3n rol-conjunto de permisos."
846
+ },
847
+ role_id: {
848
+ label: "Rol",
849
+ help: "Clave for\xE1nea a sys_role."
850
+ },
851
+ permission_set_id: {
852
+ label: "Conjunto de permisos",
853
+ help: "Clave for\xE1nea a sys_permission_set."
854
+ },
855
+ created_at: {
856
+ label: "Creado el"
857
+ },
858
+ updated_at: {
859
+ label: "Actualizado el"
860
+ }
861
+ }
862
+ }
863
+ };
864
+ }
865
+ });
866
+
867
+ // src/translations/index.ts
868
+ var translations_exports = {};
869
+ __export(translations_exports, {
870
+ SecurityTranslations: () => SecurityTranslations
871
+ });
872
+ var SecurityTranslations;
873
+ var init_translations = __esm({
874
+ "src/translations/index.ts"() {
875
+ "use strict";
876
+ init_en_objects_generated();
877
+ init_zh_CN_objects_generated();
878
+ init_ja_JP_objects_generated();
879
+ init_es_ES_objects_generated();
880
+ SecurityTranslations = {
881
+ en: { objects: enObjects },
882
+ "zh-CN": { objects: zhCNObjects },
883
+ "ja-JP": { objects: jaJPObjects },
884
+ "es-ES": { objects: esESObjects }
885
+ };
886
+ }
887
+ });
888
+
1
889
  // src/permission-evaluator.ts
2
890
  var OPERATION_TO_PERMISSION = {
3
891
  find: "allowRead",
@@ -316,6 +1204,7 @@ function isPermissionDeniedError(e) {
316
1204
  }
317
1205
 
318
1206
  // src/bootstrap-platform-admin.ts
1207
+ import { SystemUserId } from "@objectstack/spec/system";
319
1208
  var SYSTEM_CTX = { isSystem: true };
320
1209
  async function tryFind(ql, object, where, limit = 100) {
321
1210
  try {
@@ -382,17 +1271,20 @@ async function bootstrapPlatformAdmin(ql, bootstrapPermissionSets, options = {})
382
1271
  ql,
383
1272
  "sys_user_permission_set",
384
1273
  { permission_set_id: adminPsId },
385
- 5
1274
+ 50
386
1275
  );
387
- if (existingAdminLinks.some((r) => !r.organization_id)) {
1276
+ if (existingAdminLinks.some((r) => !r.organization_id && r.user_id !== SystemUserId.SYSTEM)) {
388
1277
  return { seeded: seededCount, adminPromoted: false, reason: "already_have_admin" };
389
1278
  }
390
1279
  const allUsers = await tryFind(ql, "sys_user", {}, 50);
391
- if (allUsers.length === 0) {
392
- logger?.info?.("[security] no users yet \u2014 first sign-up will be promoted to platform admin");
1280
+ const humanUsers = allUsers.filter(
1281
+ (u) => u.id !== SystemUserId.SYSTEM && u.role !== "system"
1282
+ );
1283
+ if (humanUsers.length === 0) {
1284
+ logger?.info?.("[security] no human users yet \u2014 first sign-up will be promoted to platform admin");
393
1285
  return { seeded: seededCount, adminPromoted: false, reason: "no_users" };
394
1286
  }
395
- const sorted = [...allUsers].sort((a, b) => {
1287
+ const sorted = [...humanUsers].sort((a, b) => {
396
1288
  const ta = a.created_at ? new Date(a.created_at).getTime() : 0;
397
1289
  const tb = b.created_at ? new Date(b.created_at).getTime() : 0;
398
1290
  return ta - tb;
@@ -581,14 +1473,1015 @@ function extractMemberPairs(opCtx) {
581
1473
  return Array.from(out.values());
582
1474
  }
583
1475
 
1476
+ // src/objects/sys-role.object.ts
1477
+ import { ObjectSchema, Field } from "@objectstack/spec/data";
1478
+ var SysRole = ObjectSchema.create({
1479
+ name: "sys_role",
1480
+ label: "Role",
1481
+ pluralLabel: "Roles",
1482
+ icon: "shield",
1483
+ isSystem: true,
1484
+ managedBy: "config",
1485
+ // ADR-0010 §3.7 — RBAC primitive; tenants may add custom rows
1486
+ // (created via UI / API) but the schema itself is locked.
1487
+ protection: {
1488
+ lock: "no-overlay",
1489
+ reason: "RBAC schema is platform-defined \u2014 see ADR-0010.",
1490
+ docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
1491
+ },
1492
+ description: "Role definitions for RBAC access control",
1493
+ displayNameField: "label",
1494
+ titleFormat: "{label}",
1495
+ compactLayout: ["label", "name", "active", "is_default"],
1496
+ // Custom actions — system roles drive RBAC and are edited rarely but
1497
+ // require the four high-frequency sysadmin affordances every IdP
1498
+ // (Salesforce, ServiceNow, Okta) ships: activate/deactivate (lifecycle
1499
+ // without losing assignments), mark default (auto-assign to new users),
1500
+ // and clone (template for new roles). All operations hit the generic
1501
+ // data CRUD endpoint exposed by `apiEnabled` — no custom server route
1502
+ // required because `managedBy: 'config'` allows direct mutation.
1503
+ actions: [
1504
+ {
1505
+ name: "activate_role",
1506
+ label: "Activate Role",
1507
+ icon: "circle-check",
1508
+ variant: "secondary",
1509
+ mode: "custom",
1510
+ locations: ["list_item", "record_header"],
1511
+ type: "api",
1512
+ method: "PATCH",
1513
+ target: "/api/v1/data/sys_role/{id}",
1514
+ bodyExtra: { active: true },
1515
+ successMessage: "Role activated",
1516
+ refreshAfter: true
1517
+ },
1518
+ {
1519
+ name: "deactivate_role",
1520
+ label: "Deactivate Role",
1521
+ icon: "circle-off",
1522
+ variant: "danger",
1523
+ mode: "custom",
1524
+ locations: ["list_item", "record_header"],
1525
+ type: "api",
1526
+ method: "PATCH",
1527
+ target: "/api/v1/data/sys_role/{id}",
1528
+ bodyExtra: { active: false },
1529
+ confirmText: "Deactivate this role? Users with the role keep their assignment but the role stops granting permissions until re-activated.",
1530
+ successMessage: "Role deactivated",
1531
+ refreshAfter: true
1532
+ },
1533
+ {
1534
+ name: "set_default_role",
1535
+ label: "Set as Default",
1536
+ icon: "star",
1537
+ variant: "secondary",
1538
+ mode: "custom",
1539
+ locations: ["list_item", "record_header"],
1540
+ type: "api",
1541
+ method: "PATCH",
1542
+ target: "/api/v1/data/sys_role/{id}",
1543
+ bodyExtra: { is_default: true },
1544
+ confirmText: "Make this the default role for new users? Existing users are unaffected.",
1545
+ successMessage: "Default role updated",
1546
+ refreshAfter: true
1547
+ },
1548
+ {
1549
+ // Clone — POST a new sys_role row pre-filled from the source. The
1550
+ // dialog asks only for the new API name / label so the operator
1551
+ // can rename atomically; permissions JSON is copied wholesale via
1552
+ // defaultFromRow.
1553
+ name: "clone_role",
1554
+ label: "Clone Role",
1555
+ icon: "copy",
1556
+ variant: "secondary",
1557
+ mode: "custom",
1558
+ locations: ["list_item", "record_header"],
1559
+ type: "api",
1560
+ method: "POST",
1561
+ target: "/api/v1/data/sys_role",
1562
+ bodyExtra: { is_default: false, active: true },
1563
+ successMessage: "Role cloned",
1564
+ refreshAfter: true,
1565
+ params: [
1566
+ { name: "label", label: "New Display Name", type: "text", required: true },
1567
+ { name: "name", label: "New API Name", type: "text", required: true, helpText: "Unique snake_case machine name" },
1568
+ { field: "description", defaultFromRow: true },
1569
+ { field: "permissions", defaultFromRow: true }
1570
+ ]
1571
+ }
1572
+ ],
1573
+ listViews: {
1574
+ active: {
1575
+ type: "grid",
1576
+ name: "active",
1577
+ label: "Active",
1578
+ data: { provider: "object", object: "sys_role" },
1579
+ columns: ["label", "name", "is_default", "updated_at"],
1580
+ filter: [{ field: "active", operator: "equals", value: true }],
1581
+ sort: [{ field: "label", order: "asc" }],
1582
+ pagination: { pageSize: 50 }
1583
+ },
1584
+ default_roles: {
1585
+ type: "grid",
1586
+ name: "default_roles",
1587
+ label: "Default",
1588
+ data: { provider: "object", object: "sys_role" },
1589
+ columns: ["label", "name", "description", "active"],
1590
+ filter: [{ field: "is_default", operator: "equals", value: true }],
1591
+ sort: [{ field: "label", order: "asc" }],
1592
+ pagination: { pageSize: 50 }
1593
+ },
1594
+ custom: {
1595
+ type: "grid",
1596
+ name: "custom",
1597
+ label: "Custom",
1598
+ data: { provider: "object", object: "sys_role" },
1599
+ columns: ["label", "name", "active", "updated_at"],
1600
+ filter: [{ field: "is_default", operator: "equals", value: false }],
1601
+ sort: [{ field: "label", order: "asc" }],
1602
+ pagination: { pageSize: 50 }
1603
+ },
1604
+ all_roles: {
1605
+ type: "grid",
1606
+ name: "all_roles",
1607
+ label: "All",
1608
+ data: { provider: "object", object: "sys_role" },
1609
+ columns: ["label", "name", "active", "is_default", "updated_at"],
1610
+ sort: [{ field: "label", order: "asc" }],
1611
+ pagination: { pageSize: 50 }
1612
+ }
1613
+ },
1614
+ fields: {
1615
+ // ── Identity ─────────────────────────────────────────────────
1616
+ label: Field.text({
1617
+ label: "Display Name",
1618
+ required: true,
1619
+ searchable: true,
1620
+ maxLength: 255,
1621
+ group: "Identity"
1622
+ }),
1623
+ name: Field.text({
1624
+ label: "API Name",
1625
+ required: true,
1626
+ searchable: true,
1627
+ maxLength: 100,
1628
+ description: "Unique machine name for the role (e.g. admin, editor, viewer)",
1629
+ group: "Identity"
1630
+ }),
1631
+ description: Field.textarea({
1632
+ label: "Description",
1633
+ required: false,
1634
+ group: "Identity"
1635
+ }),
1636
+ // ── Configuration ────────────────────────────────────────────
1637
+ permissions: Field.textarea({
1638
+ label: "Permissions",
1639
+ required: false,
1640
+ description: "JSON-serialized array of permission strings",
1641
+ group: "Configuration"
1642
+ }),
1643
+ // ── Status ───────────────────────────────────────────────────
1644
+ active: Field.boolean({
1645
+ label: "Active",
1646
+ defaultValue: true,
1647
+ group: "Status"
1648
+ }),
1649
+ is_default: Field.boolean({
1650
+ label: "Default Role",
1651
+ defaultValue: false,
1652
+ description: "Automatically assigned to new users",
1653
+ group: "Status"
1654
+ }),
1655
+ // ── System ───────────────────────────────────────────────────
1656
+ id: Field.text({
1657
+ label: "Role ID",
1658
+ required: true,
1659
+ readonly: true,
1660
+ group: "System"
1661
+ }),
1662
+ created_at: Field.datetime({
1663
+ label: "Created At",
1664
+ defaultValue: "NOW()",
1665
+ readonly: true,
1666
+ group: "System"
1667
+ }),
1668
+ updated_at: Field.datetime({
1669
+ label: "Updated At",
1670
+ defaultValue: "NOW()",
1671
+ readonly: true,
1672
+ group: "System"
1673
+ })
1674
+ },
1675
+ indexes: [
1676
+ { fields: ["name"], unique: true },
1677
+ { fields: ["active"] }
1678
+ ],
1679
+ enable: {
1680
+ trackHistory: true,
1681
+ searchable: true,
1682
+ apiEnabled: true,
1683
+ apiMethods: ["get", "list", "create", "update", "delete"],
1684
+ trash: true,
1685
+ mru: true
1686
+ }
1687
+ });
1688
+
1689
+ // src/objects/sys-permission-set.object.ts
1690
+ import { ObjectSchema as ObjectSchema2, Field as Field2 } from "@objectstack/spec/data";
1691
+ var SysPermissionSet = ObjectSchema2.create({
1692
+ name: "sys_permission_set",
1693
+ label: "Permission Set",
1694
+ pluralLabel: "Permission Sets",
1695
+ icon: "lock",
1696
+ isSystem: true,
1697
+ managedBy: "config",
1698
+ // ADR-0010 §3.7 — RBAC primitive; tenants may add custom rows
1699
+ // (created via UI / API) but the schema itself is locked.
1700
+ protection: {
1701
+ lock: "no-overlay",
1702
+ reason: "RBAC schema is platform-defined \u2014 see ADR-0010.",
1703
+ docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
1704
+ },
1705
+ description: "Named permission groupings for fine-grained access control",
1706
+ displayNameField: "label",
1707
+ titleFormat: "{label}",
1708
+ compactLayout: ["label", "name", "active"],
1709
+ // Custom actions — permission sets are templates assigned to roles or
1710
+ // users (via sys_role_permission_set / sys_user_permission_set). The
1711
+ // sysadmin operations that don't live on the parent-detail tabs are
1712
+ // lifecycle (activate/deactivate without losing assignments) and
1713
+ // clone (build a new permset by tweaking an existing one). Both hit
1714
+ // the generic data CRUD endpoint — managedBy: 'config' permits it.
1715
+ actions: [
1716
+ {
1717
+ name: "activate_permission_set",
1718
+ label: "Activate",
1719
+ icon: "circle-check",
1720
+ variant: "secondary",
1721
+ mode: "custom",
1722
+ locations: ["list_item", "record_header"],
1723
+ type: "api",
1724
+ method: "PATCH",
1725
+ target: "/api/v1/data/sys_permission_set/{id}",
1726
+ bodyExtra: { active: true },
1727
+ successMessage: "Permission set activated",
1728
+ refreshAfter: true
1729
+ },
1730
+ {
1731
+ name: "deactivate_permission_set",
1732
+ label: "Deactivate",
1733
+ icon: "circle-off",
1734
+ variant: "danger",
1735
+ mode: "custom",
1736
+ locations: ["list_item", "record_header"],
1737
+ type: "api",
1738
+ method: "PATCH",
1739
+ target: "/api/v1/data/sys_permission_set/{id}",
1740
+ bodyExtra: { active: false },
1741
+ confirmText: "Deactivate this permission set? Existing assignments stay in place but stop granting access until re-activated.",
1742
+ successMessage: "Permission set deactivated",
1743
+ refreshAfter: true
1744
+ },
1745
+ {
1746
+ name: "clone_permission_set",
1747
+ label: "Clone",
1748
+ icon: "copy",
1749
+ variant: "secondary",
1750
+ mode: "custom",
1751
+ locations: ["list_item", "record_header"],
1752
+ type: "api",
1753
+ method: "POST",
1754
+ target: "/api/v1/data/sys_permission_set",
1755
+ bodyExtra: { active: true },
1756
+ successMessage: "Permission set cloned",
1757
+ refreshAfter: true,
1758
+ params: [
1759
+ { name: "label", label: "New Display Name", type: "text", required: true },
1760
+ { name: "name", label: "New API Name", type: "text", required: true, helpText: "Unique snake_case machine name" },
1761
+ { field: "description", defaultFromRow: true },
1762
+ { field: "object_permissions", defaultFromRow: true },
1763
+ { field: "field_permissions", defaultFromRow: true }
1764
+ ]
1765
+ }
1766
+ ],
1767
+ listViews: {
1768
+ active: {
1769
+ type: "grid",
1770
+ name: "active",
1771
+ label: "Active",
1772
+ data: { provider: "object", object: "sys_permission_set" },
1773
+ columns: ["label", "name", "description", "updated_at"],
1774
+ filter: [{ field: "active", operator: "equals", value: true }],
1775
+ sort: [{ field: "label", order: "asc" }],
1776
+ pagination: { pageSize: 50 }
1777
+ },
1778
+ inactive: {
1779
+ type: "grid",
1780
+ name: "inactive",
1781
+ label: "Inactive",
1782
+ data: { provider: "object", object: "sys_permission_set" },
1783
+ columns: ["label", "name", "updated_at"],
1784
+ filter: [{ field: "active", operator: "equals", value: false }],
1785
+ sort: [{ field: "label", order: "asc" }],
1786
+ pagination: { pageSize: 50 }
1787
+ },
1788
+ all_permsets: {
1789
+ type: "grid",
1790
+ name: "all_permsets",
1791
+ label: "All",
1792
+ data: { provider: "object", object: "sys_permission_set" },
1793
+ columns: ["label", "name", "active", "updated_at"],
1794
+ sort: [{ field: "label", order: "asc" }],
1795
+ pagination: { pageSize: 50 }
1796
+ }
1797
+ },
1798
+ fields: {
1799
+ // ── Identity ─────────────────────────────────────────────────
1800
+ label: Field2.text({
1801
+ label: "Display Name",
1802
+ required: true,
1803
+ searchable: true,
1804
+ maxLength: 255,
1805
+ group: "Identity"
1806
+ }),
1807
+ name: Field2.text({
1808
+ label: "API Name",
1809
+ required: true,
1810
+ searchable: true,
1811
+ maxLength: 100,
1812
+ description: "Unique machine name for the permission set",
1813
+ group: "Identity"
1814
+ }),
1815
+ description: Field2.textarea({
1816
+ label: "Description",
1817
+ required: false,
1818
+ group: "Identity"
1819
+ }),
1820
+ // ── Permissions ──────────────────────────────────────────────
1821
+ object_permissions: Field2.textarea({
1822
+ label: "Object Permissions",
1823
+ required: false,
1824
+ description: "JSON-serialized object-level CRUD permissions",
1825
+ group: "Permissions"
1826
+ }),
1827
+ field_permissions: Field2.textarea({
1828
+ label: "Field Permissions",
1829
+ required: false,
1830
+ description: "JSON-serialized field-level read/write permissions",
1831
+ group: "Permissions"
1832
+ }),
1833
+ system_permissions: Field2.textarea({
1834
+ label: "System Permissions",
1835
+ required: false,
1836
+ description: 'JSON-serialized array of system capability names (e.g. ["setup.access","studio.access","manage_users"])',
1837
+ group: "Permissions"
1838
+ }),
1839
+ row_level_security: Field2.textarea({
1840
+ label: "Row-Level Security",
1841
+ required: false,
1842
+ description: "JSON-serialized array of row-level security policies (USING/CHECK clauses)",
1843
+ group: "Permissions"
1844
+ }),
1845
+ tab_permissions: Field2.textarea({
1846
+ label: "Tab Permissions",
1847
+ required: false,
1848
+ description: "JSON-serialized map of app tab visibility (visible | hidden | default_on | default_off)",
1849
+ group: "Permissions"
1850
+ }),
1851
+ // ── Status ───────────────────────────────────────────────────
1852
+ active: Field2.boolean({
1853
+ label: "Active",
1854
+ defaultValue: true,
1855
+ group: "Status"
1856
+ }),
1857
+ // ── System ───────────────────────────────────────────────────
1858
+ id: Field2.text({
1859
+ label: "Permission Set ID",
1860
+ required: true,
1861
+ readonly: true,
1862
+ group: "System"
1863
+ }),
1864
+ created_at: Field2.datetime({
1865
+ label: "Created At",
1866
+ defaultValue: "NOW()",
1867
+ readonly: true,
1868
+ group: "System"
1869
+ }),
1870
+ updated_at: Field2.datetime({
1871
+ label: "Updated At",
1872
+ defaultValue: "NOW()",
1873
+ readonly: true,
1874
+ group: "System"
1875
+ })
1876
+ },
1877
+ indexes: [
1878
+ { fields: ["name"], unique: true },
1879
+ { fields: ["active"] }
1880
+ ],
1881
+ enable: {
1882
+ trackHistory: true,
1883
+ searchable: true,
1884
+ apiEnabled: true,
1885
+ apiMethods: ["get", "list", "create", "update", "delete"],
1886
+ trash: true,
1887
+ mru: true
1888
+ }
1889
+ });
1890
+
1891
+ // src/objects/sys-user-permission-set.object.ts
1892
+ import { ObjectSchema as ObjectSchema3, Field as Field3 } from "@objectstack/spec/data";
1893
+ var SysUserPermissionSet = ObjectSchema3.create({
1894
+ name: "sys_user_permission_set",
1895
+ label: "User Permission Set",
1896
+ pluralLabel: "User Permission Sets",
1897
+ icon: "user-check",
1898
+ isSystem: true,
1899
+ managedBy: "system",
1900
+ description: "Direct assignment of a permission set to a user (optionally scoped to an organization).",
1901
+ titleFormat: "{user_id} \u2192 {permission_set_id}",
1902
+ compactLayout: ["user_id", "permission_set_id", "organization_id"],
1903
+ fields: {
1904
+ id: Field3.text({
1905
+ label: "Assignment ID",
1906
+ required: true,
1907
+ readonly: true,
1908
+ description: "UUID of the assignment."
1909
+ }),
1910
+ user_id: Field3.lookup("sys_user", {
1911
+ label: "User",
1912
+ required: true,
1913
+ description: "Foreign key to sys_user."
1914
+ }),
1915
+ permission_set_id: Field3.lookup("sys_permission_set", {
1916
+ label: "Permission Set",
1917
+ required: true,
1918
+ description: "Foreign key to sys_permission_set."
1919
+ }),
1920
+ organization_id: Field3.lookup("sys_organization", {
1921
+ label: "Organization",
1922
+ required: false,
1923
+ description: "Optional organization scope. NULL = applies in every org context."
1924
+ }),
1925
+ granted_by: Field3.lookup("sys_user", {
1926
+ label: "Granted By",
1927
+ required: false,
1928
+ description: "User who granted this permission set."
1929
+ }),
1930
+ created_at: Field3.datetime({
1931
+ label: "Created At",
1932
+ defaultValue: "NOW()",
1933
+ readonly: true
1934
+ }),
1935
+ updated_at: Field3.datetime({
1936
+ label: "Updated At",
1937
+ defaultValue: "NOW()",
1938
+ readonly: true
1939
+ })
1940
+ },
1941
+ indexes: [
1942
+ { fields: ["user_id", "permission_set_id", "organization_id"], unique: true },
1943
+ { fields: ["user_id"] },
1944
+ { fields: ["organization_id"] },
1945
+ { fields: ["permission_set_id"] }
1946
+ ],
1947
+ enable: {
1948
+ trackHistory: true,
1949
+ searchable: true,
1950
+ apiEnabled: true,
1951
+ apiMethods: ["get", "list", "create", "update", "delete"],
1952
+ trash: true,
1953
+ mru: false
1954
+ }
1955
+ });
1956
+
1957
+ // src/objects/sys-role-permission-set.object.ts
1958
+ import { ObjectSchema as ObjectSchema4, Field as Field4 } from "@objectstack/spec/data";
1959
+ var SysRolePermissionSet = ObjectSchema4.create({
1960
+ name: "sys_role_permission_set",
1961
+ label: "Role Permission Set",
1962
+ pluralLabel: "Role Permission Sets",
1963
+ icon: "shield-plus",
1964
+ isSystem: true,
1965
+ managedBy: "system",
1966
+ description: "Binds a permission set to a role.",
1967
+ titleFormat: "{role_id} \u2192 {permission_set_id}",
1968
+ compactLayout: ["role_id", "permission_set_id"],
1969
+ fields: {
1970
+ id: Field4.text({
1971
+ label: "Binding ID",
1972
+ required: true,
1973
+ readonly: true,
1974
+ description: "UUID of the role-permission-set binding."
1975
+ }),
1976
+ role_id: Field4.lookup("sys_role", {
1977
+ label: "Role",
1978
+ required: true,
1979
+ description: "Foreign key to sys_role."
1980
+ }),
1981
+ permission_set_id: Field4.lookup("sys_permission_set", {
1982
+ label: "Permission Set",
1983
+ required: true,
1984
+ description: "Foreign key to sys_permission_set."
1985
+ }),
1986
+ created_at: Field4.datetime({
1987
+ label: "Created At",
1988
+ defaultValue: "NOW()",
1989
+ readonly: true
1990
+ }),
1991
+ updated_at: Field4.datetime({
1992
+ label: "Updated At",
1993
+ defaultValue: "NOW()",
1994
+ readonly: true
1995
+ })
1996
+ },
1997
+ indexes: [
1998
+ { fields: ["role_id", "permission_set_id"], unique: true },
1999
+ { fields: ["role_id"] },
2000
+ { fields: ["permission_set_id"] }
2001
+ ],
2002
+ enable: {
2003
+ trackHistory: true,
2004
+ searchable: true,
2005
+ apiEnabled: true,
2006
+ apiMethods: ["get", "list", "create", "update", "delete"],
2007
+ trash: true,
2008
+ mru: false
2009
+ }
2010
+ });
2011
+
2012
+ // src/objects/default-permission-sets.ts
2013
+ import { PermissionSetSchema } from "@objectstack/spec/security";
2014
+ var BETTER_AUTH_MANAGED_OBJECTS = [
2015
+ "sys_user",
2016
+ "sys_account",
2017
+ "sys_session",
2018
+ "sys_organization",
2019
+ "sys_member",
2020
+ "sys_invitation",
2021
+ "sys_team",
2022
+ "sys_team_member",
2023
+ "sys_api_key",
2024
+ "sys_two_factor",
2025
+ "sys_verification",
2026
+ "sys_jwks",
2027
+ "sys_device_code",
2028
+ "sys_oauth_application",
2029
+ "sys_oauth_access_token",
2030
+ "sys_oauth_refresh_token",
2031
+ "sys_oauth_consent"
2032
+ ];
2033
+ var denyWritesOnManagedObjects = () => Object.fromEntries(
2034
+ BETTER_AUTH_MANAGED_OBJECTS.map((name) => [
2035
+ name,
2036
+ { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false }
2037
+ ])
2038
+ );
2039
+ var defaultPermissionSets = [
2040
+ PermissionSetSchema.parse({
2041
+ name: "admin_full_access",
2042
+ label: "Administrator \u2014 Full Access",
2043
+ isProfile: true,
2044
+ objects: {
2045
+ "*": {
2046
+ allowRead: true,
2047
+ allowCreate: true,
2048
+ allowEdit: true,
2049
+ allowDelete: true,
2050
+ viewAllRecords: true,
2051
+ modifyAllRecords: true
2052
+ }
2053
+ },
2054
+ systemPermissions: [
2055
+ "manage_users",
2056
+ "manage_metadata",
2057
+ "manage_platform_settings",
2058
+ "setup.access",
2059
+ "studio.access"
2060
+ ]
2061
+ }),
2062
+ // ── Organization Administrator ──────────────────────────────────────
2063
+ //
2064
+ // Third tier between platform admin (`admin_full_access`) and rank-and-file
2065
+ // member. Lives at the *organization* scope: full CRUD on business
2066
+ // objects within their org (governed by `tenant_isolation` RLS), plus
2067
+ // `setup.access` so the Setup app shell is reachable.
2068
+ //
2069
+ // **Deliberately withheld** vs `admin_full_access`:
2070
+ // - `studio.access` — schema-design surfaces are platform-level (a
2071
+ // tenant cannot mutate the shared metadata) and Studio is hidden.
2072
+ // - `manage_metadata` — same reasoning.
2073
+ // - `manage_platform_settings` — global settings manifests
2074
+ // (mail / storage / AI / knowledge) and platform-only Setup pages
2075
+ // (sharing rules, audit logs, OAuth apps, JWKS, …) require this
2076
+ // and are hidden / 403'd for org admins. Tenant-scoped manifests
2077
+ // (`branding`, `feature_flags`) keep using `setup.access` so org
2078
+ // admins CAN configure their own org's branding.
2079
+ //
2080
+ // **Anti-escalation**: writes to the global RBAC tables
2081
+ // (`sys_role`, `sys_permission_set`, `sys_role_permission_set`,
2082
+ // `sys_user_permission_set`, `sys_user_role`) are denied. Allowing
2083
+ // them would let an org admin bind `admin_full_access` (which has no
2084
+ // RLS) to themselves and break out of tenant isolation. Reads are
2085
+ // permitted so the Roles / Permission Sets nav entries still render.
2086
+ //
2087
+ // Auto-granted to every `sys_member` whose role contains `owner` or
2088
+ // `admin` by `plugin-security/src/auto-org-admin-grant.ts`.
2089
+ PermissionSetSchema.parse({
2090
+ name: "organization_admin",
2091
+ label: "Organization Administrator",
2092
+ isProfile: true,
2093
+ objects: {
2094
+ "*": {
2095
+ allowRead: true,
2096
+ allowCreate: true,
2097
+ allowEdit: true,
2098
+ allowDelete: true,
2099
+ viewAllRecords: true,
2100
+ modifyAllRecords: true
2101
+ },
2102
+ // Identity tables — go through better-auth endpoints (invite,
2103
+ // accept, remove-member, transfer, …) rather than raw CRUD.
2104
+ ...denyWritesOnManagedObjects(),
2105
+ // RBAC tables — read-only to prevent privilege escalation.
2106
+ sys_role: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false },
2107
+ sys_permission_set: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false },
2108
+ sys_role_permission_set: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false },
2109
+ sys_user_permission_set: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false },
2110
+ sys_user_role: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false }
2111
+ },
2112
+ systemPermissions: ["manage_org_users", "setup.access"],
2113
+ rowLevelSecurity: [
2114
+ {
2115
+ name: "tenant_isolation",
2116
+ object: "*",
2117
+ operation: "all",
2118
+ using: "organization_id = current_user.organization_id"
2119
+ },
2120
+ // ── better-auth system tables that lack `organization_id` and would
2121
+ // otherwise be denied by the wildcard policy. Same self-only
2122
+ // carve-outs as `member_default` — an org admin does not get to
2123
+ // inspect cross-tenant identity rows.
2124
+ {
2125
+ name: "sys_organization_self",
2126
+ object: "sys_organization",
2127
+ operation: "all",
2128
+ using: "id = current_user.organization_id"
2129
+ },
2130
+ {
2131
+ name: "sys_user_self",
2132
+ object: "sys_user",
2133
+ operation: "select",
2134
+ using: "id = current_user.id"
2135
+ },
2136
+ {
2137
+ name: "sys_user_org_members",
2138
+ object: "sys_user",
2139
+ operation: "select",
2140
+ using: "id IN (current_user.org_user_ids)"
2141
+ },
2142
+ {
2143
+ name: "sys_session_self",
2144
+ object: "sys_session",
2145
+ operation: "all",
2146
+ using: "user_id = current_user.id"
2147
+ },
2148
+ {
2149
+ name: "sys_account_self",
2150
+ object: "sys_account",
2151
+ operation: "select",
2152
+ using: "user_id = current_user.id"
2153
+ },
2154
+ {
2155
+ name: "sys_team_member_self",
2156
+ object: "sys_team_member",
2157
+ operation: "select",
2158
+ using: "user_id = current_user.id"
2159
+ },
2160
+ {
2161
+ name: "sys_two_factor_self",
2162
+ object: "sys_two_factor",
2163
+ operation: "all",
2164
+ using: "user_id = current_user.id"
2165
+ },
2166
+ {
2167
+ name: "sys_user_preference_self",
2168
+ object: "sys_user_preference",
2169
+ operation: "all",
2170
+ using: "user_id = current_user.id"
2171
+ },
2172
+ {
2173
+ name: "sys_api_key_self",
2174
+ object: "sys_api_key",
2175
+ operation: "all",
2176
+ using: "user_id = current_user.id"
2177
+ },
2178
+ {
2179
+ name: "sys_device_code_self",
2180
+ object: "sys_device_code",
2181
+ operation: "all",
2182
+ using: "user_id = current_user.id"
2183
+ },
2184
+ {
2185
+ name: "sys_oauth_access_token_self",
2186
+ object: "sys_oauth_access_token",
2187
+ operation: "select",
2188
+ using: "user_id = current_user.id"
2189
+ },
2190
+ {
2191
+ name: "sys_oauth_refresh_token_self",
2192
+ object: "sys_oauth_refresh_token",
2193
+ operation: "select",
2194
+ using: "user_id = current_user.id"
2195
+ },
2196
+ {
2197
+ name: "sys_oauth_consent_self",
2198
+ object: "sys_oauth_consent",
2199
+ operation: "all",
2200
+ using: "user_id = current_user.id"
2201
+ },
2202
+ // OAuth applications a user has registered themselves (self-service
2203
+ // developer flow exposed in the Account app's Developer section).
2204
+ // `sys_oauth_application` has no `organization_id` so the wildcard
2205
+ // `tenant_isolation` policy would otherwise deny every row.
2206
+ {
2207
+ name: "sys_oauth_application_self",
2208
+ object: "sys_oauth_application",
2209
+ operation: "all",
2210
+ using: "user_id = current_user.id"
2211
+ },
2212
+ // Org-scoped visibility for organization-owned identity-adjacent
2213
+ // tables. Org admins may inspect their own org's invitations and
2214
+ // memberships (read; writes still flow through better-auth).
2215
+ {
2216
+ name: "sys_member_org",
2217
+ object: "sys_member",
2218
+ operation: "select",
2219
+ using: "organization_id = current_user.organization_id"
2220
+ },
2221
+ {
2222
+ name: "sys_invitation_org",
2223
+ object: "sys_invitation",
2224
+ operation: "select",
2225
+ using: "organization_id = current_user.organization_id"
2226
+ },
2227
+ {
2228
+ name: "sys_team_org",
2229
+ object: "sys_team",
2230
+ operation: "select",
2231
+ using: "organization_id = current_user.organization_id"
2232
+ }
2233
+ ]
2234
+ }),
2235
+ PermissionSetSchema.parse({
2236
+ name: "member_default",
2237
+ label: "Member \u2014 Standard Access",
2238
+ isProfile: true,
2239
+ objects: {
2240
+ "*": {
2241
+ allowRead: true,
2242
+ allowCreate: true,
2243
+ allowEdit: true,
2244
+ allowDelete: true
2245
+ },
2246
+ // Identity tables are managed by better-auth — no direct writes.
2247
+ ...denyWritesOnManagedObjects()
2248
+ },
2249
+ rowLevelSecurity: [
2250
+ {
2251
+ name: "tenant_isolation",
2252
+ object: "*",
2253
+ operation: "all",
2254
+ using: "organization_id = current_user.organization_id"
2255
+ },
2256
+ {
2257
+ name: "owner_only_writes",
2258
+ object: "*",
2259
+ operation: "update",
2260
+ using: "owner_id = current_user.id"
2261
+ },
2262
+ {
2263
+ name: "owner_only_deletes",
2264
+ object: "*",
2265
+ operation: "delete",
2266
+ using: "owner_id = current_user.id"
2267
+ },
2268
+ // ── better-auth system tables that lack `organization_id` and would
2269
+ // otherwise be left unprotected by the wildcard rule above. ────
2270
+ //
2271
+ // The security plugin's RLS injector treats wildcard policies that
2272
+ // target a missing field as `RLS_DENY_FILTER` (zero rows) unless a
2273
+ // per-object policy contributes an alternate match. Each `*_self`
2274
+ // policy below restores per-user visibility on a better-auth table
2275
+ // that has `user_id` but no `organization_id`. Tables without
2276
+ // `user_id` (`sys_verification`, `sys_jwks`, empty `sys_passkey`)
2277
+ // stay DENY for non-admins by design — only platform admins (via
2278
+ // `admin_full_access`, which has no RLS) should inspect them.
2279
+ {
2280
+ name: "sys_organization_self",
2281
+ object: "sys_organization",
2282
+ operation: "all",
2283
+ using: "id = current_user.organization_id"
2284
+ },
2285
+ {
2286
+ name: "sys_user_self",
2287
+ object: "sys_user",
2288
+ operation: "select",
2289
+ using: "id = current_user.id"
2290
+ },
2291
+ // Org collaborators: members can see other users in the same
2292
+ // organization. Without this, owner/assignee lookups, @-mention
2293
+ // suggestions, reviewer pickers and team-roster surfaces all
2294
+ // collapse to just the current user. `org_user_ids` is
2295
+ // pre-resolved by runtime/resolve-execution-context from
2296
+ // `sys_member` for the active organization. Sensitive credential
2297
+ // tables (`sys_account`, `sys_session`, `sys_api_key`, …) keep
2298
+ // their stricter self-only carve-outs above.
2299
+ {
2300
+ name: "sys_user_org_members",
2301
+ object: "sys_user",
2302
+ operation: "select",
2303
+ using: "id IN (current_user.org_user_ids)"
2304
+ },
2305
+ {
2306
+ name: "sys_session_self",
2307
+ object: "sys_session",
2308
+ operation: "all",
2309
+ using: "user_id = current_user.id"
2310
+ },
2311
+ {
2312
+ name: "sys_account_self",
2313
+ object: "sys_account",
2314
+ operation: "select",
2315
+ using: "user_id = current_user.id"
2316
+ },
2317
+ {
2318
+ name: "sys_team_member_self",
2319
+ object: "sys_team_member",
2320
+ operation: "select",
2321
+ using: "user_id = current_user.id"
2322
+ },
2323
+ {
2324
+ name: "sys_two_factor_self",
2325
+ object: "sys_two_factor",
2326
+ operation: "all",
2327
+ using: "user_id = current_user.id"
2328
+ },
2329
+ {
2330
+ name: "sys_user_preference_self",
2331
+ object: "sys_user_preference",
2332
+ operation: "all",
2333
+ using: "user_id = current_user.id"
2334
+ },
2335
+ {
2336
+ name: "sys_api_key_self",
2337
+ object: "sys_api_key",
2338
+ operation: "all",
2339
+ using: "user_id = current_user.id"
2340
+ },
2341
+ {
2342
+ name: "sys_device_code_self",
2343
+ object: "sys_device_code",
2344
+ operation: "all",
2345
+ using: "user_id = current_user.id"
2346
+ },
2347
+ {
2348
+ name: "sys_oauth_access_token_self",
2349
+ object: "sys_oauth_access_token",
2350
+ operation: "select",
2351
+ using: "user_id = current_user.id"
2352
+ },
2353
+ {
2354
+ name: "sys_oauth_refresh_token_self",
2355
+ object: "sys_oauth_refresh_token",
2356
+ operation: "select",
2357
+ using: "user_id = current_user.id"
2358
+ },
2359
+ {
2360
+ name: "sys_oauth_consent_self",
2361
+ object: "sys_oauth_consent",
2362
+ operation: "all",
2363
+ using: "user_id = current_user.id"
2364
+ },
2365
+ // OAuth applications a user has registered themselves (Account →
2366
+ // Developer → OAuth Applications). `sys_oauth_application` has no
2367
+ // `organization_id`, so without this carve-out the wildcard
2368
+ // `tenant_isolation` policy returns zero rows even for the owner.
2369
+ {
2370
+ name: "sys_oauth_application_self",
2371
+ object: "sys_oauth_application",
2372
+ operation: "all",
2373
+ using: "user_id = current_user.id"
2374
+ }
2375
+ ]
2376
+ }),
2377
+ PermissionSetSchema.parse({
2378
+ name: "viewer_readonly",
2379
+ label: "Viewer \u2014 Read-Only",
2380
+ isProfile: true,
2381
+ objects: {
2382
+ "*": {
2383
+ allowRead: true,
2384
+ allowCreate: false,
2385
+ allowEdit: false,
2386
+ allowDelete: false
2387
+ },
2388
+ // Belt-and-suspenders: explicit deny on managed objects even though
2389
+ // the wildcard already denies — keeps the policy readable when
2390
+ // future relaxations might widen the wildcard.
2391
+ ...denyWritesOnManagedObjects()
2392
+ },
2393
+ rowLevelSecurity: [
2394
+ {
2395
+ name: "tenant_isolation",
2396
+ object: "*",
2397
+ operation: "select",
2398
+ using: "organization_id = current_user.organization_id"
2399
+ },
2400
+ {
2401
+ name: "sys_organization_self",
2402
+ object: "sys_organization",
2403
+ operation: "select",
2404
+ using: "id = current_user.organization_id"
2405
+ },
2406
+ {
2407
+ name: "sys_user_self",
2408
+ object: "sys_user",
2409
+ operation: "select",
2410
+ using: "id = current_user.id"
2411
+ },
2412
+ // Org collaborators (read-only): see `sys_user_org_members` in
2413
+ // `member_default` for rationale.
2414
+ {
2415
+ name: "sys_user_org_members",
2416
+ object: "sys_user",
2417
+ operation: "select",
2418
+ using: "id IN (current_user.org_user_ids)"
2419
+ },
2420
+ {
2421
+ name: "sys_session_self",
2422
+ object: "sys_session",
2423
+ operation: "select",
2424
+ using: "user_id = current_user.id"
2425
+ },
2426
+ {
2427
+ name: "sys_account_self",
2428
+ object: "sys_account",
2429
+ operation: "select",
2430
+ using: "user_id = current_user.id"
2431
+ },
2432
+ {
2433
+ name: "sys_team_member_self",
2434
+ object: "sys_team_member",
2435
+ operation: "select",
2436
+ using: "user_id = current_user.id"
2437
+ },
2438
+ {
2439
+ name: "sys_two_factor_self",
2440
+ object: "sys_two_factor",
2441
+ operation: "select",
2442
+ using: "user_id = current_user.id"
2443
+ },
2444
+ {
2445
+ name: "sys_user_preference_self",
2446
+ object: "sys_user_preference",
2447
+ operation: "select",
2448
+ using: "user_id = current_user.id"
2449
+ },
2450
+ {
2451
+ name: "sys_api_key_self",
2452
+ object: "sys_api_key",
2453
+ operation: "select",
2454
+ using: "user_id = current_user.id"
2455
+ },
2456
+ {
2457
+ name: "sys_device_code_self",
2458
+ object: "sys_device_code",
2459
+ operation: "select",
2460
+ using: "user_id = current_user.id"
2461
+ },
2462
+ {
2463
+ name: "sys_oauth_access_token_self",
2464
+ object: "sys_oauth_access_token",
2465
+ operation: "select",
2466
+ using: "user_id = current_user.id"
2467
+ },
2468
+ {
2469
+ name: "sys_oauth_refresh_token_self",
2470
+ object: "sys_oauth_refresh_token",
2471
+ operation: "select",
2472
+ using: "user_id = current_user.id"
2473
+ },
2474
+ {
2475
+ name: "sys_oauth_consent_self",
2476
+ object: "sys_oauth_consent",
2477
+ operation: "select",
2478
+ using: "user_id = current_user.id"
2479
+ }
2480
+ ]
2481
+ })
2482
+ ];
2483
+
584
2484
  // src/manifest.ts
585
- import {
586
- SysPermissionSet,
587
- SysRole,
588
- SysUserPermissionSet,
589
- SysRolePermissionSet,
590
- defaultPermissionSets
591
- } from "@objectstack/platform-objects/security";
592
2485
  var SECURITY_PLUGIN_ID = "com.objectstack.plugin-security";
593
2486
  var SECURITY_PLUGIN_VERSION = "1.0.0";
594
2487
  var securityObjects = [
@@ -663,8 +2556,36 @@ var SecurityPlugin = class {
663
2556
  // Permission sets ride along on the manifest so the metadata service
664
2557
  // can resolve them by name when SecurityPlugin middleware queries
665
2558
  // `metadata.list('permissions')`.
666
- permissions: this.bootstrapPermissionSets
2559
+ permissions: this.bootstrapPermissionSets,
2560
+ // ADR-0029 D7 — contribute the RBAC entries into the Setup app's
2561
+ // `group_access_control` slot. This plugin owns these objects (K2), so it
2562
+ // ships their menu too; when the plugin is absent the entries don't appear.
2563
+ navigationContributions: [
2564
+ {
2565
+ app: "setup",
2566
+ group: "group_access_control",
2567
+ priority: 100,
2568
+ items: [
2569
+ { id: "nav_roles", type: "object", label: "Roles", objectName: "sys_role", icon: "shield-check" },
2570
+ { id: "nav_permission_sets", type: "object", label: "Permission Sets", objectName: "sys_permission_set", icon: "lock" }
2571
+ ]
2572
+ }
2573
+ ]
667
2574
  });
2575
+ if (typeof ctx.hook === "function") {
2576
+ ctx.hook("kernel:ready", async () => {
2577
+ try {
2578
+ const i18n = ctx.getService("i18n");
2579
+ if (i18n && typeof i18n.loadTranslations === "function") {
2580
+ const { SecurityTranslations: SecurityTranslations2 } = await Promise.resolve().then(() => (init_translations(), translations_exports));
2581
+ for (const [locale, data] of Object.entries(SecurityTranslations2)) {
2582
+ i18n.loadTranslations(locale, data);
2583
+ }
2584
+ }
2585
+ } catch {
2586
+ }
2587
+ });
2588
+ }
668
2589
  ctx.logger.info("Security Plugin initialized", {
669
2590
  defaultPermissionSets: this.bootstrapPermissionSets.map((p) => p.name)
670
2591
  });