@objectstack/platform-objects 14.3.0 → 14.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,15 @@ var SysUser = ObjectSchema.create({
8
8
  icon: "user",
9
9
  isSystem: true,
10
10
  managedBy: "better-auth",
11
+ // ADR-0092 D4 — the ONE generic affordance opened on an identity table:
12
+ // standard row editing. Safe because the plugin-auth identity write guard
13
+ // (ADR-0092 D2) enforces the profile whitelist server-side — a user-context
14
+ // update may only touch SYS_USER_PROFILE_EDIT_FIELDS ({name, image});
15
+ // everything else is stripped/rejected regardless of what a form submits.
16
+ // The permission layer still decides WHO may edit (platform admins only by
17
+ // default; member/org-admin sets keep allowEdit: false). create / import /
18
+ // delete stay bucket-default (off).
19
+ userActions: { edit: true },
11
20
  // ADR-0010 §3.7 — identity table is managed by better-auth; schema must not drift.
12
21
  protection: {
13
22
  lock: "full",
@@ -55,15 +64,17 @@ var SysUser = ObjectSchema.create({
55
64
  // These actions hit /api/v1/auth/admin/* endpoints that are only
56
65
  // wired when `auth.plugins.admin` is enabled. When the plugin is
57
66
  // disabled the actions still render (schema is static) but server
58
- // returns 404. UI surfaces them under the row menu so platform
59
- // admins can manage accounts without dropping to SQL or
67
+ // returns 404. UI surfaces them under the row menu AND the
68
+ // record-detail header (`record_header`, overflowing into the
69
+ // "More" menu) so platform admins can manage an account from either
70
+ // the Users list or an open user record — without dropping to SQL or
60
71
  // a custom Setup wizard.
61
72
  {
62
73
  name: "ban_user",
63
74
  label: "Ban User",
64
75
  icon: "ban",
65
76
  variant: "danger",
66
- locations: ["list_item"],
77
+ locations: ["list_item", "record_header"],
67
78
  type: "api",
68
79
  target: "/api/v1/auth/admin/ban-user",
69
80
  recordIdParam: "userId",
@@ -79,7 +90,7 @@ var SysUser = ObjectSchema.create({
79
90
  label: "Unban User",
80
91
  icon: "check-circle-2",
81
92
  variant: "secondary",
82
- locations: ["list_item"],
93
+ locations: ["list_item", "record_header"],
83
94
  type: "api",
84
95
  target: "/api/v1/auth/admin/unban-user",
85
96
  recordIdParam: "userId",
@@ -94,7 +105,7 @@ var SysUser = ObjectSchema.create({
94
105
  label: "Unlock Account",
95
106
  icon: "lock-open",
96
107
  variant: "secondary",
97
- locations: ["list_item"],
108
+ locations: ["list_item", "record_header"],
98
109
  type: "api",
99
110
  target: "/api/v1/auth/admin/unlock-user",
100
111
  recordIdParam: "userId",
@@ -128,7 +139,12 @@ var SysUser = ObjectSchema.create({
128
139
  label: "Phone Number",
129
140
  type: "text",
130
141
  required: false,
131
- helpText: "Sign-in phone number (E.164, e.g. +8613800000000). Required when no email is given."
142
+ helpText: "Sign-in phone number (E.164, e.g. +8613800000000). Required when no email is given.",
143
+ // Only offer phone when the opt-in phoneNumber auth plugin is loaded —
144
+ // otherwise the create-user endpoint rejects a phone with
145
+ // "Phone numbers require the phoneNumber auth plugin". `features.phoneNumber`
146
+ // is served in /api/v1/auth/config (getPublicConfig).
147
+ visible: "features.phoneNumber == true"
132
148
  },
133
149
  { field: "name", required: false },
134
150
  {
@@ -162,7 +178,7 @@ var SysUser = ObjectSchema.create({
162
178
  label: "Set Password",
163
179
  icon: "key-round",
164
180
  variant: "secondary",
165
- locations: ["list_item"],
181
+ locations: ["list_item", "record_header"],
166
182
  type: "api",
167
183
  // #2766 V1 — same path as better-auth's stock route, but served by the
168
184
  // plugin-auth wrapper registered ahead of the catch-all: it accepts the
@@ -204,7 +220,7 @@ var SysUser = ObjectSchema.create({
204
220
  label: "Set Platform Role",
205
221
  icon: "shield-check",
206
222
  variant: "secondary",
207
- locations: ["list_item"],
223
+ locations: ["list_item", "record_header"],
208
224
  type: "api",
209
225
  target: "/api/v1/auth/admin/set-role",
210
226
  recordIdParam: "userId",
@@ -219,7 +235,7 @@ var SysUser = ObjectSchema.create({
219
235
  label: "Impersonate User",
220
236
  icon: "user-cog",
221
237
  variant: "secondary",
222
- locations: ["list_item"],
238
+ locations: ["list_item", "record_header"],
223
239
  type: "api",
224
240
  target: "/api/v1/auth/admin/impersonate-user",
225
241
  recordIdParam: "userId",
@@ -443,20 +459,28 @@ var SysUser = ObjectSchema.create({
443
459
  maxLength: 255,
444
460
  group: "Identity"
445
461
  }),
462
+ // ADR-0092 D4 — with the generic edit affordance open, every non-profile
463
+ // field is readonly so the standard edit form renders it non-editable.
464
+ // This is UX only; the server boundary is the plugin-auth identity write
465
+ // guard (ADR-0092 D2), which strips/rejects these regardless.
446
466
  email: Field.email({
447
467
  label: "Email",
448
468
  required: true,
469
+ readonly: true,
470
+ // login identity — change flows through better-auth change-email verification
449
471
  searchable: true,
450
472
  group: "Identity"
451
473
  }),
452
474
  email_verified: Field.boolean({
453
475
  label: "Email Verified",
454
476
  defaultValue: false,
477
+ readonly: true,
455
478
  group: "Identity"
456
479
  }),
457
480
  two_factor_enabled: Field.boolean({
458
481
  label: "Two-Factor Enabled",
459
482
  defaultValue: false,
483
+ readonly: true,
460
484
  group: "Identity",
461
485
  description: "Whether two-factor authentication is enabled for this user. Maintained by the better-auth `twoFactor` plugin."
462
486
  }),
@@ -464,6 +488,8 @@ var SysUser = ObjectSchema.create({
464
488
  role: Field.text({
465
489
  label: "Platform Role",
466
490
  required: false,
491
+ readonly: true,
492
+ // ADR-0092 — set via the Set Platform Role action, never the edit form
467
493
  maxLength: 64,
468
494
  group: "Admin",
469
495
  description: "Platform-level role (admin, user, \u2026). Set via the Set Platform Role action."
@@ -471,18 +497,24 @@ var SysUser = ObjectSchema.create({
471
497
  banned: Field.boolean({
472
498
  label: "Banned",
473
499
  defaultValue: false,
500
+ readonly: true,
501
+ // ADR-0092 — toggled via Ban/Unban actions (session side effects)
474
502
  group: "Admin",
475
503
  description: "When true, the user cannot sign in. Toggle via Ban User / Unban User actions."
476
504
  }),
477
505
  ban_reason: Field.text({
478
506
  label: "Ban Reason",
479
507
  required: false,
508
+ readonly: true,
509
+ // ADR-0092 — written by the Ban User action
480
510
  maxLength: 255,
481
511
  group: "Admin"
482
512
  }),
483
513
  ban_expires: Field.datetime({
484
514
  label: "Ban Expires",
485
515
  required: false,
516
+ readonly: true,
517
+ // ADR-0092 — written by the Ban User action
486
518
  group: "Admin",
487
519
  description: "When set, the ban auto-clears at this time."
488
520
  }),
@@ -578,6 +610,8 @@ var SysUser = ObjectSchema.create({
578
610
  ai_access: Field.boolean({
579
611
  label: "AI Access",
580
612
  defaultValue: false,
613
+ readonly: true,
614
+ // ADR-0092 — a licensed-seat grant; flows through the AiSeatPlugin enforcement path
581
615
  group: "Admin",
582
616
  description: "Whether this user holds an AI seat \u2014 grants access to the in-UI AI agents (build / ask). The framework synthesizes the `ai_seat` capability from this flag (plugin-hono-server resolveCtx). Assignment is capped by the licensed / purchased seat count (enforced by @objectstack/security-enterprise AiSeatPlugin). Owned by objectql (better-auth is oblivious to this column)."
583
617
  }),
@@ -591,12 +625,16 @@ var SysUser = ObjectSchema.create({
591
625
  manager_id: Field.lookup("sys_user", {
592
626
  label: "Manager",
593
627
  required: false,
628
+ readonly: true,
629
+ // ADR-0092 — drives own_and_reports RLS scope; org-structure maintenance is its own surface
594
630
  group: "Organization",
595
631
  description: "This user's direct manager. Forms the reporting chain the `own_and_reports` hierarchy scope walks (ADR-0057 / @objectstack/security-enterprise)."
596
632
  }),
597
633
  primary_business_unit_id: Field.lookup("sys_business_unit", {
598
634
  label: "Primary Business Unit",
599
635
  required: false,
636
+ readonly: true,
637
+ // ADR-0092 — denormalised projection maintained by plugin-sharing; never hand-edited
600
638
  group: "Organization",
601
639
  description: "The user's primary business unit \u2014 a denormalised projection of sys_business_unit_member.is_primary, maintained by plugin-sharing (ADR-0057 addendum D12). Lets a user-lookup filter candidates by business unit without traversing the membership junction. Do not edit directly; set it via business-unit membership."
602
640
  }),
@@ -2616,6 +2654,12 @@ var SysDeviceCode = ObjectSchema.create({
2616
2654
  icon: "key-round",
2617
2655
  isSystem: true,
2618
2656
  managedBy: "better-auth",
2657
+ // ADR-0057: device codes are dead the moment `expires_at` passes — keep a
2658
+ // 1d grace for post-mortem, then reap.
2659
+ lifecycle: {
2660
+ class: "transient",
2661
+ ttl: { field: "expires_at", expireAfter: "1d" }
2662
+ },
2619
2663
  // [ADR-0066 D2/④] Secure-by-default: rows are LIVE pending device-grant
2620
2664
  // codes — reading `user_code`/`device_code` lets an attacker hijack a
2621
2665
  // pending CLI login. Not covered by the wildcard `'*'` grant; admins retain
@@ -3634,9 +3678,13 @@ var SysSsoProvider = ObjectSchema.create({
3634
3678
  // for a one-time DNS-TXT challenge and reveals the ready-to-paste record
3635
3679
  // ONCE via `resultDialog`. Routed through the env bridge (plugin-auth /
3636
3680
  // cloud AuthProxyPlugin) which reshapes the `{domainVerificationToken}`
3637
- // response into the `{ data: { dnsRecordName, dnsRecordValue } }` envelope
3638
- // the dialog reads. When the feature is OFF the bridge returns a clear
3639
- // "not enabled for this environment" error instead of a bare 404.
3681
+ // response into a `{ success, data: { dnsRecordName, dnsRecordValue } }`
3682
+ // envelope. The console action runtime unwraps that envelope, so the
3683
+ // `resultDialog` field paths are relative to the inner `data` payload
3684
+ // (`dnsRecordName`, not `data.dnsRecordName`) — consistent with every
3685
+ // other object's resultDialog (create_user, two-factor, OAuth). When the
3686
+ // feature is OFF the bridge returns a clear "not enabled for this
3687
+ // environment" error instead of a bare 404.
3640
3688
  target: "/api/v1/auth/admin/sso/request-domain-verification",
3641
3689
  params: [
3642
3690
  { name: "providerId", field: "provider_id", defaultFromRow: true, required: true },
@@ -3647,9 +3695,9 @@ var SysSsoProvider = ObjectSchema.create({
3647
3695
  description: "Add the DNS TXT record below at your domain\u2019s DNS provider, then run \u201CVerify Domain\u201D. The token is shown once.",
3648
3696
  acknowledge: "Done",
3649
3697
  fields: [
3650
- { path: "data.dnsRecordType", label: "Record type", format: "text" },
3651
- { path: "data.dnsRecordName", label: "Name / Host", format: "secret" },
3652
- { path: "data.dnsRecordValue", label: "Value", format: "secret" }
3698
+ { path: "dnsRecordType", label: "Record type", format: "text" },
3699
+ { path: "dnsRecordName", label: "Name / Host", format: "secret" },
3700
+ { path: "dnsRecordValue", label: "Value", format: "secret" }
3653
3701
  ]
3654
3702
  }
3655
3703
  },