@objectstack/platform-objects 10.3.0 → 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps/index.d.mts +8 -8
- package/dist/apps/index.d.ts +8 -8
- package/dist/apps/index.js +35 -6
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/index.mjs +35 -6
- package/dist/apps/index.mjs.map +1 -1
- package/dist/audit/index.d.mts +173 -364
- package/dist/audit/index.d.ts +173 -364
- package/dist/identity/index.d.mts +23108 -16447
- package/dist/identity/index.d.ts +23108 -16447
- package/dist/identity/index.js +534 -8
- package/dist/identity/index.js.map +1 -1
- package/dist/identity/index.mjs +533 -9
- package/dist/identity/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +569 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +568 -15
- package/dist/index.mjs.map +1 -1
- package/dist/plugin.js +5 -2
- package/dist/plugin.js.map +1 -1
- package/dist/plugin.mjs +5 -2
- package/dist/plugin.mjs.map +1 -1
- package/dist/system/index.d.mts +50 -106
- package/dist/system/index.d.ts +50 -106
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -35,6 +35,13 @@ var SysUser = ObjectSchema.create({
|
|
|
35
35
|
locations: ["list_toolbar"],
|
|
36
36
|
type: "api",
|
|
37
37
|
target: "/api/v1/auth/organization/invite-member",
|
|
38
|
+
// Org invitations are a multi-org-only flow (the endpoint resolves
|
|
39
|
+
// an active org that does not exist in single-org mode). Hide the
|
|
40
|
+
// affordance unless multi-org is enabled — matching the
|
|
41
|
+
// `create_organization` gate on sys_organization. This action is the
|
|
42
|
+
// most exposed of the set because the Users list is always reachable
|
|
43
|
+
// in single-org, unlike the org/membership lists.
|
|
44
|
+
visible: "features.multiOrgEnabled != false",
|
|
38
45
|
successMessage: "Invitation sent",
|
|
39
46
|
refreshAfter: true,
|
|
40
47
|
params: [
|
|
@@ -78,6 +85,21 @@ var SysUser = ObjectSchema.create({
|
|
|
78
85
|
successMessage: "User unbanned",
|
|
79
86
|
refreshAfter: true
|
|
80
87
|
},
|
|
88
|
+
{
|
|
89
|
+
// ADR-0069 D2 — clear a brute-force lockout early (locked_until auto-
|
|
90
|
+
// expires, but an admin can release a user immediately). Hits the
|
|
91
|
+
// plugin-auth custom route, which is admin-guarded server-side.
|
|
92
|
+
name: "unlock_user",
|
|
93
|
+
label: "Unlock Account",
|
|
94
|
+
icon: "lock-open",
|
|
95
|
+
variant: "secondary",
|
|
96
|
+
locations: ["list_item"],
|
|
97
|
+
type: "api",
|
|
98
|
+
target: "/api/v1/auth/admin/unlock-user",
|
|
99
|
+
recordIdParam: "userId",
|
|
100
|
+
successMessage: "Account unlocked",
|
|
101
|
+
refreshAfter: true
|
|
102
|
+
},
|
|
81
103
|
{
|
|
82
104
|
name: "set_user_password",
|
|
83
105
|
label: "Set Password",
|
|
@@ -154,7 +176,11 @@ var SysUser = ObjectSchema.create({
|
|
|
154
176
|
locations: ["record_header", "record_more", "record_section"],
|
|
155
177
|
type: "api",
|
|
156
178
|
target: "/api/v1/auth/change-password",
|
|
157
|
-
|
|
179
|
+
// Managed (IdP-provisioned) users hold no local credential — hide the
|
|
180
|
+
// password form so they can't self-mint a password that bypasses
|
|
181
|
+
// enforced SSO. The break-glass owner (env-native, or flipped back when
|
|
182
|
+
// their break-glass password is set) keeps it. ADR-0024 D4/D5.2.
|
|
183
|
+
visible: 'record.id == ctx.user.id && record.source != "idp_provisioned"',
|
|
158
184
|
successMessage: "Password changed",
|
|
159
185
|
refreshAfter: false,
|
|
160
186
|
params: [
|
|
@@ -171,7 +197,9 @@ var SysUser = ObjectSchema.create({
|
|
|
171
197
|
locations: ["record_header", "record_more", "record_section"],
|
|
172
198
|
type: "api",
|
|
173
199
|
target: "/api/v1/auth/change-email",
|
|
174
|
-
|
|
200
|
+
// A managed user's email is owned by the IdP — a local change would
|
|
201
|
+
// desync. Hide for IdP-provisioned; env-native users keep it.
|
|
202
|
+
visible: 'record.id == ctx.user.id && record.source != "idp_provisioned"',
|
|
175
203
|
successMessage: "Verification email sent \u2014 check the new address to confirm.",
|
|
176
204
|
refreshAfter: false,
|
|
177
205
|
params: [
|
|
@@ -202,7 +230,9 @@ var SysUser = ObjectSchema.create({
|
|
|
202
230
|
locations: ["record_more", "record_section"],
|
|
203
231
|
type: "api",
|
|
204
232
|
target: "/api/v1/auth/delete-user",
|
|
205
|
-
|
|
233
|
+
// Self-delete needs a local password; managed users are deprovisioned
|
|
234
|
+
// via the IdP (org-removal / SCIM), not local self-service. Hide for them.
|
|
235
|
+
visible: 'record.id == ctx.user.id && record.source != "idp_provisioned"',
|
|
206
236
|
confirmText: "Permanently delete your account? This cannot be undone \u2014 all your sessions will be terminated and all data you own will be removed per the configured retention policy.",
|
|
207
237
|
successMessage: "Account deleted",
|
|
208
238
|
refreshAfter: false,
|
|
@@ -285,7 +315,7 @@ var SysUser = ObjectSchema.create({
|
|
|
285
315
|
name: "all_users",
|
|
286
316
|
label: "All Users",
|
|
287
317
|
data: { provider: "object", object: "sys_user" },
|
|
288
|
-
columns: ["name", "email", "email_verified", "two_factor_enabled", "created_at"],
|
|
318
|
+
columns: ["name", "email", "email_verified", "source", "two_factor_enabled", "created_at"],
|
|
289
319
|
sort: [{ field: "name", order: "asc" }],
|
|
290
320
|
pagination: { pageSize: 50 }
|
|
291
321
|
},
|
|
@@ -372,6 +402,51 @@ var SysUser = ObjectSchema.create({
|
|
|
372
402
|
group: "Admin",
|
|
373
403
|
description: "When set, the ban auto-clears at this time."
|
|
374
404
|
}),
|
|
405
|
+
// ── Anti-brute-force (ADR-0069 D2) — owned by objectql, better-auth is
|
|
406
|
+
// oblivious. The auth manager's sign-in hooks maintain these: failures
|
|
407
|
+
// increment the counter; crossing `lockout_threshold` stamps
|
|
408
|
+
// `locked_until`; a successful sign-in resets both. Admins can clear
|
|
409
|
+
// them early via the Unlock action.
|
|
410
|
+
failed_login_count: Field.number({
|
|
411
|
+
label: "Failed Login Count",
|
|
412
|
+
required: false,
|
|
413
|
+
defaultValue: 0,
|
|
414
|
+
readonly: true,
|
|
415
|
+
group: "Admin",
|
|
416
|
+
description: "Consecutive failed sign-in attempts; reset to 0 on success. Maintained by the auth manager."
|
|
417
|
+
}),
|
|
418
|
+
locked_until: Field.datetime({
|
|
419
|
+
label: "Locked Until",
|
|
420
|
+
required: false,
|
|
421
|
+
readonly: true,
|
|
422
|
+
group: "Admin",
|
|
423
|
+
description: "When set and in the future, sign-in is rejected (brute-force lockout). Auto-clears past this time; an admin can clear it early via Unlock."
|
|
424
|
+
}),
|
|
425
|
+
// ADR-0069 D1 — last password change; drives password-expiry enforcement.
|
|
426
|
+
// Stamped on sign-up / change-password / reset-password. Null = never
|
|
427
|
+
// expires (until the user next changes their password).
|
|
428
|
+
password_changed_at: Field.datetime({
|
|
429
|
+
label: "Password Changed At",
|
|
430
|
+
required: false,
|
|
431
|
+
readonly: true,
|
|
432
|
+
group: "Admin",
|
|
433
|
+
description: "Timestamp of the last password change. Backs password_expiry_days; system-managed."
|
|
434
|
+
}),
|
|
435
|
+
// ADR-0069 D3 — when enforced MFA first applied to this user; starts the
|
|
436
|
+
// grace clock. Stamped lazily at session validation; system-managed.
|
|
437
|
+
mfa_required_at: Field.datetime({
|
|
438
|
+
label: "MFA Required At",
|
|
439
|
+
required: false,
|
|
440
|
+
readonly: true,
|
|
441
|
+
group: "Admin",
|
|
442
|
+
description: "When enforced MFA first applied to this user (grace-period clock). Backs mfa_required; system-managed."
|
|
443
|
+
}),
|
|
444
|
+
ai_access: Field.boolean({
|
|
445
|
+
label: "AI Access",
|
|
446
|
+
defaultValue: false,
|
|
447
|
+
group: "Admin",
|
|
448
|
+
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)."
|
|
449
|
+
}),
|
|
375
450
|
// ── Profile ──────────────────────────────────────────────────
|
|
376
451
|
image: Field.url({
|
|
377
452
|
label: "Profile Image",
|
|
@@ -392,6 +467,28 @@ var SysUser = ObjectSchema.create({
|
|
|
392
467
|
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."
|
|
393
468
|
}),
|
|
394
469
|
// ── System (auto-managed, hidden from create/edit forms) ─────
|
|
470
|
+
// Identity provenance (ADR-0024 D4). `idp_provisioned` users were
|
|
471
|
+
// JIT-created on first federated login (a `sys_account` exists for an
|
|
472
|
+
// external/OIDC provider — e.g. the cloud-as-IdP `objectstack-cloud`
|
|
473
|
+
// provider, or a customer's own IdP); `env_native` users registered
|
|
474
|
+
// locally (email/password) or are app end-users. Stamped automatically by
|
|
475
|
+
// the AuthManager `account.create.after` hook — never edited by hand.
|
|
476
|
+
// Drives the managed-vs-native user-mgmt UI gating (the password /
|
|
477
|
+
// identity-edit actions hide for managed users, who hold no local
|
|
478
|
+
// credential) and is the marker SCIM lifecycle keys off. Owned by objectql
|
|
479
|
+
// (better-auth is oblivious to this column — like `ai_access`).
|
|
480
|
+
source: Field.select({
|
|
481
|
+
label: "Identity Source",
|
|
482
|
+
required: false,
|
|
483
|
+
readonly: true,
|
|
484
|
+
group: "System",
|
|
485
|
+
defaultValue: "env_native",
|
|
486
|
+
options: [
|
|
487
|
+
{ label: "IdP-Provisioned", value: "idp_provisioned" },
|
|
488
|
+
{ label: "Env-Native", value: "env_native" }
|
|
489
|
+
],
|
|
490
|
+
description: "How this identity was created \u2014 idp_provisioned (federated SSO JIT) or env_native (local signup / app end-user). System-managed; do not edit."
|
|
491
|
+
}),
|
|
395
492
|
id: Field.text({
|
|
396
493
|
label: "User ID",
|
|
397
494
|
required: true,
|
|
@@ -516,6 +613,29 @@ var SysSession = ObjectSchema.create({
|
|
|
516
613
|
required: true,
|
|
517
614
|
group: "Session"
|
|
518
615
|
}),
|
|
616
|
+
// ── ADR-0069 D4 — session controls (idle / absolute / revoke) ──
|
|
617
|
+
last_activity_at: Field.datetime({
|
|
618
|
+
label: "Last Activity At",
|
|
619
|
+
required: false,
|
|
620
|
+
readonly: true,
|
|
621
|
+
group: "Session",
|
|
622
|
+
description: "Timestamp of the last request on this session; drives idle-timeout. System-managed."
|
|
623
|
+
}),
|
|
624
|
+
revoked_at: Field.datetime({
|
|
625
|
+
label: "Revoked At",
|
|
626
|
+
required: false,
|
|
627
|
+
readonly: true,
|
|
628
|
+
group: "Session",
|
|
629
|
+
description: "When set, this session was revoked (idle / absolute-max / concurrent-cap / admin). System-managed."
|
|
630
|
+
}),
|
|
631
|
+
revoke_reason: Field.text({
|
|
632
|
+
label: "Revoke Reason",
|
|
633
|
+
required: false,
|
|
634
|
+
maxLength: 64,
|
|
635
|
+
readonly: true,
|
|
636
|
+
group: "Session",
|
|
637
|
+
description: "Why the session was revoked (idle_timeout, absolute_max, concurrent_cap, \u2026)."
|
|
638
|
+
}),
|
|
519
639
|
// ── Active context (multi-org/team) ──────────────────────────
|
|
520
640
|
active_organization_id: Field.lookup("sys_organization", {
|
|
521
641
|
label: "Active Organization",
|
|
@@ -757,6 +877,16 @@ var SysAccount = ObjectSchema.create({
|
|
|
757
877
|
label: "Password Hash",
|
|
758
878
|
required: false,
|
|
759
879
|
description: "Hashed password for email/password provider"
|
|
880
|
+
}),
|
|
881
|
+
// ADR-0069 D1 — bounded ring of previous password hashes (JSON array of
|
|
882
|
+
// strings), used to reject password reuse on change/reset. Maintained by
|
|
883
|
+
// the auth manager; never exposed in UI.
|
|
884
|
+
previous_password_hashes: Field.textarea({
|
|
885
|
+
label: "Previous Password Hashes",
|
|
886
|
+
required: false,
|
|
887
|
+
readonly: true,
|
|
888
|
+
hidden: true,
|
|
889
|
+
description: "JSON array of prior password hashes (bounded by password_history_count); reuse-prevention only. System-managed."
|
|
760
890
|
})
|
|
761
891
|
},
|
|
762
892
|
indexes: [
|
|
@@ -822,7 +952,13 @@ var SysVerification = ObjectSchema.create({
|
|
|
822
952
|
})
|
|
823
953
|
},
|
|
824
954
|
indexes: [
|
|
825
|
-
|
|
955
|
+
// `value` must NOT be unique. better-auth's oauth-provider stores OIDC
|
|
956
|
+
// authorization codes in this table with `value` = a JSON blob keyed by
|
|
957
|
+
// user+client+state, which can legitimately repeat. A UNIQUE constraint
|
|
958
|
+
// makes `/api/v1/auth/oauth2/authorize` fail (`UNIQUE constraint failed:
|
|
959
|
+
// sys_verification.value`) → 503, breaking cloud-as-IdP SSO entirely.
|
|
960
|
+
// better-auth keys verification lookups on `identifier`, not `value`.
|
|
961
|
+
{ fields: ["value"], unique: false },
|
|
826
962
|
{ fields: ["identifier"], unique: false },
|
|
827
963
|
{ fields: ["expires_at"], unique: false }
|
|
828
964
|
],
|
|
@@ -890,6 +1026,10 @@ var SysOrganization = ObjectSchema.create({
|
|
|
890
1026
|
recordIdParam: "organizationId",
|
|
891
1027
|
// better-auth `organization/update` nests editable fields under `data`.
|
|
892
1028
|
bodyShape: { wrap: "data" },
|
|
1029
|
+
// Org-admin actions are multi-org-only; hide them in single-org for
|
|
1030
|
+
// consistency with `create_organization` (the org list is empty there,
|
|
1031
|
+
// but this also guards direct record-URL access).
|
|
1032
|
+
visible: "features.multiOrgEnabled != false",
|
|
893
1033
|
successMessage: "Organization updated",
|
|
894
1034
|
refreshAfter: true,
|
|
895
1035
|
params: [
|
|
@@ -908,6 +1048,7 @@ var SysOrganization = ObjectSchema.create({
|
|
|
908
1048
|
type: "api",
|
|
909
1049
|
target: "/api/v1/auth/organization/delete",
|
|
910
1050
|
recordIdParam: "organizationId",
|
|
1051
|
+
visible: "features.multiOrgEnabled != false",
|
|
911
1052
|
confirmText: "Delete this organization? All members will lose access immediately. This cannot be undone.",
|
|
912
1053
|
successMessage: "Organization deleted",
|
|
913
1054
|
refreshAfter: true
|
|
@@ -926,6 +1067,7 @@ var SysOrganization = ObjectSchema.create({
|
|
|
926
1067
|
type: "api",
|
|
927
1068
|
target: "/api/v1/auth/organization/set-active",
|
|
928
1069
|
recordIdParam: "organizationId",
|
|
1070
|
+
visible: "features.multiOrgEnabled != false",
|
|
929
1071
|
successMessage: "Active organization switched",
|
|
930
1072
|
refreshAfter: true
|
|
931
1073
|
},
|
|
@@ -942,6 +1084,7 @@ var SysOrganization = ObjectSchema.create({
|
|
|
942
1084
|
type: "api",
|
|
943
1085
|
target: "/api/v1/auth/organization/leave",
|
|
944
1086
|
recordIdParam: "organizationId",
|
|
1087
|
+
visible: "features.multiOrgEnabled != false",
|
|
945
1088
|
confirmText: "Leave this organization? You will lose access to all of its resources.",
|
|
946
1089
|
successMessage: "You have left the organization",
|
|
947
1090
|
refreshAfter: true
|
|
@@ -963,6 +1106,7 @@ var SysOrganization = ObjectSchema.create({
|
|
|
963
1106
|
type: "api",
|
|
964
1107
|
target: "/api/v1/cloud/organizations/{id}/change-slug",
|
|
965
1108
|
method: "POST",
|
|
1109
|
+
visible: "features.multiOrgEnabled != false",
|
|
966
1110
|
confirmText: "Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue?",
|
|
967
1111
|
successMessage: "Organization slug changed",
|
|
968
1112
|
refreshAfter: true,
|
|
@@ -1012,6 +1156,17 @@ var SysOrganization = ObjectSchema.create({
|
|
|
1012
1156
|
description: "JSON-serialized organization metadata",
|
|
1013
1157
|
group: "Configuration"
|
|
1014
1158
|
}),
|
|
1159
|
+
// ADR-0069 D3 — per-org MFA tightening above the global floor. When true,
|
|
1160
|
+
// members of this org must enrol TOTP to access data (enforced at the
|
|
1161
|
+
// session-validation gate). An org can only tighten, never loosen, the
|
|
1162
|
+
// global `mfa_required` setting.
|
|
1163
|
+
require_mfa: Field.boolean({
|
|
1164
|
+
label: "Require Multi-Factor Auth",
|
|
1165
|
+
required: false,
|
|
1166
|
+
defaultValue: false,
|
|
1167
|
+
group: "Configuration",
|
|
1168
|
+
description: "When true, every member of this organization must enroll an authenticator app to access data."
|
|
1169
|
+
}),
|
|
1015
1170
|
// ── System ───────────────────────────────────────────────────
|
|
1016
1171
|
id: Field.text({
|
|
1017
1172
|
label: "Organization ID",
|
|
@@ -1061,7 +1216,10 @@ var SysMember = ObjectSchema.create({
|
|
|
1061
1216
|
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
1062
1217
|
},
|
|
1063
1218
|
description: "Organization membership records",
|
|
1064
|
-
|
|
1219
|
+
// Org-independent title: organization_id is null in single-org mode, so a
|
|
1220
|
+
// '{user_id} in {organization_id}' format renders "… in null". User + role
|
|
1221
|
+
// identifies the membership in both single- and multi-org deployments.
|
|
1222
|
+
titleFormat: "{user_id} ({role})",
|
|
1065
1223
|
compactLayout: ["user_id", "organization_id", "role"],
|
|
1066
1224
|
// Row-level actions: better-auth `organization/update-member-role` and
|
|
1067
1225
|
// `organization/remove-member`. Generic CRUD is suppressed on better-auth
|
|
@@ -1082,6 +1240,11 @@ var SysMember = ObjectSchema.create({
|
|
|
1082
1240
|
locations: ["list_toolbar"],
|
|
1083
1241
|
type: "api",
|
|
1084
1242
|
target: "/api/v1/auth/organization/add-member",
|
|
1243
|
+
// Org-membership mutations are multi-org-only: the better-auth
|
|
1244
|
+
// endpoints resolve an active org that does not exist in single-org
|
|
1245
|
+
// mode, so these actions would fail at the API. Gate every one on
|
|
1246
|
+
// the multi-org flag (mirrors sys_organization.create_organization).
|
|
1247
|
+
visible: "features.multiOrgEnabled != false",
|
|
1085
1248
|
successMessage: "Member added",
|
|
1086
1249
|
refreshAfter: true,
|
|
1087
1250
|
params: [
|
|
@@ -1099,6 +1262,7 @@ var SysMember = ObjectSchema.create({
|
|
|
1099
1262
|
type: "api",
|
|
1100
1263
|
target: "/api/v1/auth/organization/update-member-role",
|
|
1101
1264
|
recordIdParam: "memberId",
|
|
1265
|
+
visible: "features.multiOrgEnabled != false",
|
|
1102
1266
|
successMessage: "Member role updated",
|
|
1103
1267
|
refreshAfter: true,
|
|
1104
1268
|
params: [
|
|
@@ -1115,6 +1279,7 @@ var SysMember = ObjectSchema.create({
|
|
|
1115
1279
|
type: "api",
|
|
1116
1280
|
target: "/api/v1/auth/organization/remove-member",
|
|
1117
1281
|
recordIdParam: "memberIdOrEmail",
|
|
1282
|
+
visible: "features.multiOrgEnabled != false",
|
|
1118
1283
|
confirmText: "Remove this member from the organization? They will lose access to all org resources.",
|
|
1119
1284
|
successMessage: "Member removed",
|
|
1120
1285
|
refreshAfter: true
|
|
@@ -1136,7 +1301,7 @@ var SysMember = ObjectSchema.create({
|
|
|
1136
1301
|
target: "/api/v1/auth/organization/update-member-role",
|
|
1137
1302
|
recordIdParam: "memberId",
|
|
1138
1303
|
bodyExtra: { role: "owner" },
|
|
1139
|
-
visible: "record.role != 'owner'",
|
|
1304
|
+
visible: "record.role != 'owner' && features.multiOrgEnabled != false",
|
|
1140
1305
|
confirmText: "Transfer ownership of this organization to the selected member? You will be demoted to admin and lose owner-only privileges.",
|
|
1141
1306
|
successMessage: "Ownership transferred",
|
|
1142
1307
|
refreshAfter: true
|
|
@@ -1221,7 +1386,10 @@ var SysInvitation = ObjectSchema.create({
|
|
|
1221
1386
|
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
1222
1387
|
},
|
|
1223
1388
|
description: "Organization invitations for user onboarding",
|
|
1224
|
-
|
|
1389
|
+
// Title by invitee email rather than organization_id: the latter is null in
|
|
1390
|
+
// single-org mode (renders "Invitation to null"), and the recipient email is
|
|
1391
|
+
// the more useful identifier in both modes anyway.
|
|
1392
|
+
titleFormat: "Invitation for {email}",
|
|
1225
1393
|
compactLayout: ["email", "organization_id", "status"],
|
|
1226
1394
|
// Custom actions — generic CRUD is suppressed (better-auth-managed).
|
|
1227
1395
|
// Mirror the `invite_user` toolbar action from sys_user here so admins
|
|
@@ -1235,6 +1403,13 @@ var SysInvitation = ObjectSchema.create({
|
|
|
1235
1403
|
locations: ["list_toolbar"],
|
|
1236
1404
|
type: "api",
|
|
1237
1405
|
target: "/api/v1/auth/organization/invite-member",
|
|
1406
|
+
// Inviting/managing invitations is a multi-org-only flow (the
|
|
1407
|
+
// endpoint resolves an active org absent in single-org mode). Gate
|
|
1408
|
+
// the admin-side actions on the multi-org flag (mirrors
|
|
1409
|
+
// sys_organization.create_organization). The recipient-side
|
|
1410
|
+
// accept/reject actions below stay record-gated — they are
|
|
1411
|
+
// unreachable in single-org anyway (no invitation rows exist).
|
|
1412
|
+
visible: "features.multiOrgEnabled != false",
|
|
1238
1413
|
successMessage: "Invitation sent",
|
|
1239
1414
|
refreshAfter: true,
|
|
1240
1415
|
params: [
|
|
@@ -1252,6 +1427,7 @@ var SysInvitation = ObjectSchema.create({
|
|
|
1252
1427
|
type: "api",
|
|
1253
1428
|
target: "/api/v1/auth/organization/cancel-invitation",
|
|
1254
1429
|
recordIdParam: "invitationId",
|
|
1430
|
+
visible: "features.multiOrgEnabled != false",
|
|
1255
1431
|
confirmText: "Cancel this invitation? The recipient will no longer be able to accept it.",
|
|
1256
1432
|
successMessage: "Invitation canceled",
|
|
1257
1433
|
refreshAfter: true
|
|
@@ -1265,6 +1441,7 @@ var SysInvitation = ObjectSchema.create({
|
|
|
1265
1441
|
type: "api",
|
|
1266
1442
|
target: "/api/v1/auth/organization/invite-member",
|
|
1267
1443
|
bodyExtra: { resend: true },
|
|
1444
|
+
visible: "features.multiOrgEnabled != false",
|
|
1268
1445
|
successMessage: "Invitation resent",
|
|
1269
1446
|
refreshAfter: true,
|
|
1270
1447
|
params: [
|
|
@@ -1449,6 +1626,10 @@ var SysTeam = ObjectSchema.create({
|
|
|
1449
1626
|
locations: ["list_toolbar"],
|
|
1450
1627
|
type: "api",
|
|
1451
1628
|
target: "/api/v1/auth/organization/create-team",
|
|
1629
|
+
// Teams are nested inside organizations — a multi-org-only concept.
|
|
1630
|
+
// Gate every team mutation on the multi-org flag so the affordances
|
|
1631
|
+
// disappear in single-org (mirrors sys_organization.create_organization).
|
|
1632
|
+
visible: "features.multiOrgEnabled != false",
|
|
1452
1633
|
successMessage: "Team created",
|
|
1453
1634
|
refreshAfter: true,
|
|
1454
1635
|
params: [
|
|
@@ -1469,6 +1650,7 @@ var SysTeam = ObjectSchema.create({
|
|
|
1469
1650
|
target: "/api/v1/auth/organization/update-team",
|
|
1470
1651
|
recordIdParam: "teamId",
|
|
1471
1652
|
bodyShape: { wrap: "data" },
|
|
1653
|
+
visible: "features.multiOrgEnabled != false",
|
|
1472
1654
|
successMessage: "Team updated",
|
|
1473
1655
|
refreshAfter: true,
|
|
1474
1656
|
params: [
|
|
@@ -1487,6 +1669,7 @@ var SysTeam = ObjectSchema.create({
|
|
|
1487
1669
|
type: "api",
|
|
1488
1670
|
target: "/api/v1/auth/organization/remove-team",
|
|
1489
1671
|
recordIdParam: "teamId",
|
|
1672
|
+
visible: "features.multiOrgEnabled != false",
|
|
1490
1673
|
confirmText: "Delete this team? Members will lose any team-scoped access. This cannot be undone.",
|
|
1491
1674
|
successMessage: "Team deleted",
|
|
1492
1675
|
refreshAfter: true
|
|
@@ -1597,6 +1780,10 @@ var SysTeamMember = ObjectSchema.create({
|
|
|
1597
1780
|
locations: ["list_toolbar"],
|
|
1598
1781
|
type: "api",
|
|
1599
1782
|
target: "/api/v1/auth/organization/add-team-member",
|
|
1783
|
+
// Team membership lives under organizations — multi-org-only. Gate
|
|
1784
|
+
// both mutations so they vanish in single-org (mirrors
|
|
1785
|
+
// sys_organization.create_organization).
|
|
1786
|
+
visible: "features.multiOrgEnabled != false",
|
|
1600
1787
|
successMessage: "Team member added",
|
|
1601
1788
|
refreshAfter: true,
|
|
1602
1789
|
params: [
|
|
@@ -1617,6 +1804,7 @@ var SysTeamMember = ObjectSchema.create({
|
|
|
1617
1804
|
locations: ["list_item"],
|
|
1618
1805
|
type: "api",
|
|
1619
1806
|
target: "/api/v1/auth/organization/remove-team-member",
|
|
1807
|
+
visible: "features.multiOrgEnabled != false",
|
|
1620
1808
|
confirmText: "Remove this user from the team? They will lose any team-scoped access.",
|
|
1621
1809
|
successMessage: "Team member removed",
|
|
1622
1810
|
refreshAfter: true,
|
|
@@ -3156,6 +3344,342 @@ var SysJwks = ObjectSchema.create({
|
|
|
3156
3344
|
mru: false
|
|
3157
3345
|
}
|
|
3158
3346
|
});
|
|
3347
|
+
var SysSsoProvider = ObjectSchema.create({
|
|
3348
|
+
name: "sys_sso_provider",
|
|
3349
|
+
label: "SSO Provider",
|
|
3350
|
+
pluralLabel: "SSO Providers",
|
|
3351
|
+
icon: "shield-check",
|
|
3352
|
+
isSystem: true,
|
|
3353
|
+
managedBy: "better-auth",
|
|
3354
|
+
// ADR-0024 — env-global, ADMIN-ONLY identity config. Two orthogonal controls:
|
|
3355
|
+
// • `tenancy.enabled: false` — the env IS the tenant; providers are env-wide,
|
|
3356
|
+
// not org-partitioned. Opting out of multi-tenancy lets a platform admin's
|
|
3357
|
+
// `viewAllRecords` superuser bypass see every provider (without it, the
|
|
3358
|
+
// `member_default` wildcard `tenant_isolation` RLS denies every row, since
|
|
3359
|
+
// better-auth writes via its adapter with no tenantId → `organization_id`
|
|
3360
|
+
// is never stamped).
|
|
3361
|
+
// • `requiredPermissions: ['manage_platform_settings']` — object-level
|
|
3362
|
+
// capability gate (ADR-0066 D3) so ordinary members are denied entirely
|
|
3363
|
+
// (without it, tenancy-disabled + `member_default`'s `'*': allowRead` would
|
|
3364
|
+
// leak providers to every authenticated user).
|
|
3365
|
+
// Together: admins see all env providers; non-admins get 403. better-auth's
|
|
3366
|
+
// own endpoints already read via a system context. (Env-only object — no
|
|
3367
|
+
// control-plane cross-tenant risk.)
|
|
3368
|
+
tenancy: { enabled: false, strategy: "shared" },
|
|
3369
|
+
requiredPermissions: ["manage_platform_settings"],
|
|
3370
|
+
// ADR-0010 §3.7 — managed by better-auth; tenants may not edit schema.
|
|
3371
|
+
protection: {
|
|
3372
|
+
lock: "full",
|
|
3373
|
+
reason: "Identity table managed by better-auth (@better-auth/sso) \u2014 see ADR-0024.",
|
|
3374
|
+
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
3375
|
+
},
|
|
3376
|
+
description: "External SSO identity providers (OIDC / SAML) this environment federates login to",
|
|
3377
|
+
displayNameField: "provider_id",
|
|
3378
|
+
titleFormat: "{provider_id}",
|
|
3379
|
+
compactLayout: ["provider_id", "issuer", "domain"],
|
|
3380
|
+
// All mutations go through @better-auth/sso's endpoints under
|
|
3381
|
+
// /api/v1/auth/sso/* (register / delete-provider) rather than the generic
|
|
3382
|
+
// data layer, so server-side config validation + secret handling run.
|
|
3383
|
+
actions: [
|
|
3384
|
+
{
|
|
3385
|
+
name: "register_sso_provider",
|
|
3386
|
+
label: "Register SSO Provider",
|
|
3387
|
+
icon: "plus-circle",
|
|
3388
|
+
variant: "primary",
|
|
3389
|
+
mode: "create",
|
|
3390
|
+
locations: ["list_toolbar"],
|
|
3391
|
+
type: "api",
|
|
3392
|
+
method: "POST",
|
|
3393
|
+
// Routed through the env-side bridge (plugin-auth `auth-plugin.ts`), which
|
|
3394
|
+
// reshapes these FLAT form fields into the nested `oidcConfig` body that
|
|
3395
|
+
// `@better-auth/sso`'s /sso/register requires, then re-dispatches to it
|
|
3396
|
+
// (so the admin gate + discovery hydration run). Posting straight to
|
|
3397
|
+
// /sso/register would drop clientId/clientSecret (top-level → Zod-stripped)
|
|
3398
|
+
// and persist an unusable `oidc_config = null` provider.
|
|
3399
|
+
target: "/api/v1/auth/admin/sso/register",
|
|
3400
|
+
refreshAfter: true,
|
|
3401
|
+
params: [
|
|
3402
|
+
{ name: "providerId", label: "Provider ID", type: "text", required: true, helpText: 'Stable identifier, e.g. "okta" or "acme-entra".' },
|
|
3403
|
+
{ name: "issuer", label: "Issuer URL", type: "text", required: true, helpText: "IdP issuer, e.g. https://acme.okta.com. Discovery is fetched from here unless an explicit URL is given below." },
|
|
3404
|
+
{ name: "domain", label: "Email Domain", type: "text", required: true, helpText: "Users with this email domain are routed to this IdP, e.g. acme.com." },
|
|
3405
|
+
{ name: "clientId", label: "Client ID", type: "text", required: true, helpText: "OAuth client ID issued by the IdP for this environment." },
|
|
3406
|
+
{ name: "clientSecret", label: "Client Secret", type: "text", required: true, helpText: "OAuth client secret (stored encrypted by better-auth)." },
|
|
3407
|
+
{ name: "discoveryEndpoint", label: "Discovery URL", type: "text", required: false, helpText: "Optional. OIDC discovery document URL. Leave blank to derive `<issuer>/.well-known/openid-configuration`." },
|
|
3408
|
+
{ name: "scopes", label: "Scopes", type: "text", required: false, placeholder: "openid email profile", helpText: 'Optional. Space- or comma-separated OAuth scopes. Defaults to "openid email profile".' },
|
|
3409
|
+
{ name: "mapId", label: "Map: User ID claim", type: "text", required: false, placeholder: "sub", helpText: 'Optional. ID-token claim mapped to the user ID. Defaults to "sub".' },
|
|
3410
|
+
{ name: "mapEmail", label: "Map: Email claim", type: "text", required: false, placeholder: "email", helpText: 'Optional. Claim mapped to email. Defaults to "email".' },
|
|
3411
|
+
{ name: "mapName", label: "Map: Name claim", type: "text", required: false, placeholder: "name", helpText: 'Optional. Claim mapped to display name. Defaults to "name".' }
|
|
3412
|
+
]
|
|
3413
|
+
},
|
|
3414
|
+
{
|
|
3415
|
+
name: "register_saml_provider",
|
|
3416
|
+
label: "Register SAML Provider",
|
|
3417
|
+
icon: "shield",
|
|
3418
|
+
variant: "primary",
|
|
3419
|
+
mode: "create",
|
|
3420
|
+
locations: ["list_toolbar"],
|
|
3421
|
+
type: "api",
|
|
3422
|
+
method: "POST",
|
|
3423
|
+
// SAML 2.0 via @better-auth/sso (samlify-backed). Routed through the
|
|
3424
|
+
// env-side bridge (plugin-auth `auth-plugin.ts` → register-saml), which
|
|
3425
|
+
// reshapes these FLAT IdP fields into the nested `samlConfig` body that
|
|
3426
|
+
// @better-auth/sso's /sso/register requires (entryPoint/cert/callbackUrl/
|
|
3427
|
+
// spMetadata/identifierFormat), derives the per-provider ACS URL, and
|
|
3428
|
+
// re-dispatches to /sso/register (admin gate runs). The response returns
|
|
3429
|
+
// the SP ACS + metadata URLs to configure on the IdP.
|
|
3430
|
+
target: "/api/v1/auth/admin/sso/register-saml",
|
|
3431
|
+
refreshAfter: true,
|
|
3432
|
+
params: [
|
|
3433
|
+
{ name: "providerId", label: "Provider ID", type: "text", required: true, helpText: 'Stable identifier, e.g. "acme-saml".' },
|
|
3434
|
+
{ name: "issuer", label: "IdP Entity ID", type: "text", required: true, helpText: "The IdP\u2019s SAML EntityID (issuer), e.g. https://saml.acme.com/entityid." },
|
|
3435
|
+
{ name: "domain", label: "Email Domain", type: "text", required: true, helpText: "Users with this email domain are routed to this IdP, e.g. acme.com." },
|
|
3436
|
+
{ name: "entryPoint", label: "IdP SSO URL", type: "text", required: true, helpText: "The IdP\u2019s SAML single sign-on (redirect) endpoint that receives the SAMLRequest." },
|
|
3437
|
+
{ name: "cert", label: "IdP Signing Certificate", type: "textarea", required: true, helpText: "The IdP\u2019s X.509 signing certificate (PEM body). Used to verify assertion signatures." },
|
|
3438
|
+
{ name: "identifierFormat", label: "NameID Format", type: "text", required: false, placeholder: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", helpText: "Optional. Requested SAML NameID format. Defaults to the IdP\u2019s configured format." }
|
|
3439
|
+
]
|
|
3440
|
+
},
|
|
3441
|
+
{
|
|
3442
|
+
name: "request_domain_verification",
|
|
3443
|
+
label: "Request Domain Verification",
|
|
3444
|
+
icon: "globe",
|
|
3445
|
+
variant: "secondary",
|
|
3446
|
+
locations: ["list_item", "record_header"],
|
|
3447
|
+
type: "api",
|
|
3448
|
+
method: "POST",
|
|
3449
|
+
// ADR-0024 ② (opt-in OS_SSO_DOMAIN_VERIFICATION). Asks @better-auth/sso
|
|
3450
|
+
// for a one-time DNS-TXT challenge and reveals the ready-to-paste record
|
|
3451
|
+
// ONCE via `resultDialog`. Routed through the env bridge (plugin-auth /
|
|
3452
|
+
// cloud AuthProxyPlugin) which reshapes the `{domainVerificationToken}`
|
|
3453
|
+
// response into the `{ data: { dnsRecordName, dnsRecordValue } }` envelope
|
|
3454
|
+
// the dialog reads. When the feature is OFF the bridge returns a clear
|
|
3455
|
+
// "not enabled for this environment" error instead of a bare 404.
|
|
3456
|
+
target: "/api/v1/auth/admin/sso/request-domain-verification",
|
|
3457
|
+
params: [
|
|
3458
|
+
{ name: "providerId", field: "provider_id", defaultFromRow: true, required: true },
|
|
3459
|
+
{ name: "domain", field: "domain", defaultFromRow: true, required: false }
|
|
3460
|
+
],
|
|
3461
|
+
resultDialog: {
|
|
3462
|
+
title: "Verify your domain",
|
|
3463
|
+
description: "Add the DNS TXT record below at your domain\u2019s DNS provider, then run \u201CVerify Domain\u201D. The token is shown once.",
|
|
3464
|
+
acknowledge: "Done",
|
|
3465
|
+
fields: [
|
|
3466
|
+
{ path: "data.dnsRecordType", label: "Record type", format: "text" },
|
|
3467
|
+
{ path: "data.dnsRecordName", label: "Name / Host", format: "secret" },
|
|
3468
|
+
{ path: "data.dnsRecordValue", label: "Value", format: "secret" }
|
|
3469
|
+
]
|
|
3470
|
+
}
|
|
3471
|
+
},
|
|
3472
|
+
{
|
|
3473
|
+
name: "verify_domain",
|
|
3474
|
+
label: "Verify Domain",
|
|
3475
|
+
icon: "shield-check",
|
|
3476
|
+
variant: "secondary",
|
|
3477
|
+
locations: ["list_item", "record_header"],
|
|
3478
|
+
type: "api",
|
|
3479
|
+
method: "POST",
|
|
3480
|
+
// ADR-0024 ②. Re-checks the DNS-TXT record and flips `domain_verified`
|
|
3481
|
+
// on success. Routed through the env bridge, which maps @better-auth/sso's
|
|
3482
|
+
// empty 204 / 502 into a clear success/error toast.
|
|
3483
|
+
target: "/api/v1/auth/admin/sso/verify-domain",
|
|
3484
|
+
successMessage: "Domain ownership verified",
|
|
3485
|
+
refreshAfter: true,
|
|
3486
|
+
params: [
|
|
3487
|
+
{ name: "providerId", field: "provider_id", defaultFromRow: true, required: true }
|
|
3488
|
+
]
|
|
3489
|
+
},
|
|
3490
|
+
{
|
|
3491
|
+
name: "delete_sso_provider",
|
|
3492
|
+
label: "Delete SSO Provider",
|
|
3493
|
+
icon: "trash-2",
|
|
3494
|
+
variant: "danger",
|
|
3495
|
+
mode: "delete",
|
|
3496
|
+
locations: ["list_item", "record_header"],
|
|
3497
|
+
type: "api",
|
|
3498
|
+
method: "POST",
|
|
3499
|
+
target: "/api/v1/auth/sso/delete-provider",
|
|
3500
|
+
confirmText: "Delete this SSO provider? Users from its domain will no longer be able to sign in through it.",
|
|
3501
|
+
successMessage: "SSO provider deleted",
|
|
3502
|
+
refreshAfter: true,
|
|
3503
|
+
params: [
|
|
3504
|
+
{ name: "providerId", field: "provider_id", defaultFromRow: true, required: true }
|
|
3505
|
+
]
|
|
3506
|
+
}
|
|
3507
|
+
],
|
|
3508
|
+
listViews: {
|
|
3509
|
+
all: {
|
|
3510
|
+
type: "grid",
|
|
3511
|
+
name: "all",
|
|
3512
|
+
label: "All",
|
|
3513
|
+
data: { provider: "object", object: "sys_sso_provider" },
|
|
3514
|
+
columns: ["provider_id", "issuer", "domain", "domain_verified", "created_at"],
|
|
3515
|
+
sort: [{ field: "provider_id", order: "asc" }],
|
|
3516
|
+
pagination: { pageSize: 50 },
|
|
3517
|
+
// Per-object empty state — the shared identity-object copy ("created
|
|
3518
|
+
// automatically … cannot be added here") is wrong for this object, which
|
|
3519
|
+
// HAS a "Register SSO Provider" action. Point admins at it instead.
|
|
3520
|
+
emptyState: {
|
|
3521
|
+
title: "No SSO providers yet",
|
|
3522
|
+
message: "Register your organization\u2019s external IdP \u2014 OIDC (Okta, Entra, Auth0, \u2026) with \u201CRegister SSO Provider\u201D, or SAML 2.0 with \u201CRegister SAML Provider\u201D. Members whose email domain matches can then sign in through it.",
|
|
3523
|
+
icon: "log-in"
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
},
|
|
3527
|
+
fields: {
|
|
3528
|
+
id: Field.text({ label: "ID", required: true, readonly: true, group: "System" }),
|
|
3529
|
+
provider_id: Field.text({
|
|
3530
|
+
label: "Provider ID",
|
|
3531
|
+
required: true,
|
|
3532
|
+
searchable: true,
|
|
3533
|
+
maxLength: 255,
|
|
3534
|
+
description: "Stable provider identifier (unique within the environment)",
|
|
3535
|
+
group: "Identity"
|
|
3536
|
+
}),
|
|
3537
|
+
issuer: Field.text({
|
|
3538
|
+
label: "Issuer",
|
|
3539
|
+
required: true,
|
|
3540
|
+
maxLength: 2048,
|
|
3541
|
+
description: "IdP issuer URL",
|
|
3542
|
+
group: "Identity"
|
|
3543
|
+
}),
|
|
3544
|
+
domain: Field.text({
|
|
3545
|
+
label: "Email Domain",
|
|
3546
|
+
required: true,
|
|
3547
|
+
maxLength: 255,
|
|
3548
|
+
description: "Email domain routed to this IdP (e.g. acme.com)",
|
|
3549
|
+
group: "Identity"
|
|
3550
|
+
}),
|
|
3551
|
+
domain_verified: Field.boolean({
|
|
3552
|
+
label: "Domain Verified",
|
|
3553
|
+
defaultValue: false,
|
|
3554
|
+
readonly: true,
|
|
3555
|
+
description: "Whether DNS ownership of the email domain has been proven (ADR-0024 \u2461). Set by \u201CVerify Domain\u201D after the DNS TXT record resolves. Managed by better-auth \u2014 not directly editable. Only enforced when domain verification is enabled for the environment.",
|
|
3556
|
+
group: "Identity"
|
|
3557
|
+
}),
|
|
3558
|
+
oidc_config: Field.textarea({
|
|
3559
|
+
label: "OIDC Config",
|
|
3560
|
+
required: false,
|
|
3561
|
+
description: "JSON: clientId, clientSecret, endpoints, scopes, mapping, pkce (managed by better-auth)",
|
|
3562
|
+
group: "Protocol"
|
|
3563
|
+
}),
|
|
3564
|
+
saml_config: Field.textarea({
|
|
3565
|
+
label: "SAML Config",
|
|
3566
|
+
required: false,
|
|
3567
|
+
description: "JSON: entryPoint, cert, identifierFormat, mapping (managed by better-auth)",
|
|
3568
|
+
group: "Protocol"
|
|
3569
|
+
}),
|
|
3570
|
+
user_id: Field.lookup("sys_user", {
|
|
3571
|
+
label: "Registered By",
|
|
3572
|
+
required: false,
|
|
3573
|
+
description: "User who registered this provider",
|
|
3574
|
+
group: "System"
|
|
3575
|
+
}),
|
|
3576
|
+
organization_id: Field.text({
|
|
3577
|
+
label: "Organization",
|
|
3578
|
+
required: false,
|
|
3579
|
+
maxLength: 255,
|
|
3580
|
+
description: "Organization scope (when org-scoped SSO is used)",
|
|
3581
|
+
group: "System"
|
|
3582
|
+
}),
|
|
3583
|
+
created_at: Field.datetime({ label: "Created At", defaultValue: "NOW()", readonly: true, group: "System" }),
|
|
3584
|
+
updated_at: Field.datetime({ label: "Updated At", defaultValue: "NOW()", readonly: true, group: "System" })
|
|
3585
|
+
},
|
|
3586
|
+
indexes: [
|
|
3587
|
+
{ fields: ["provider_id"], unique: true },
|
|
3588
|
+
{ fields: ["domain"] },
|
|
3589
|
+
{ fields: ["user_id"] }
|
|
3590
|
+
],
|
|
3591
|
+
enable: {
|
|
3592
|
+
trackHistory: true,
|
|
3593
|
+
searchable: true,
|
|
3594
|
+
apiEnabled: true,
|
|
3595
|
+
// Mutations go through /api/v1/auth/sso/* (register / delete-provider);
|
|
3596
|
+
// the generic data layer is read-only so sysadmins cannot bypass
|
|
3597
|
+
// server-side validation / secret handling.
|
|
3598
|
+
apiMethods: ["get", "list"],
|
|
3599
|
+
trash: false,
|
|
3600
|
+
mru: false
|
|
3601
|
+
}
|
|
3602
|
+
});
|
|
3603
|
+
var SysScimProvider = ObjectSchema.create({
|
|
3604
|
+
name: "sys_scim_provider",
|
|
3605
|
+
label: "SCIM Provider",
|
|
3606
|
+
pluralLabel: "SCIM Providers",
|
|
3607
|
+
icon: "users",
|
|
3608
|
+
isSystem: true,
|
|
3609
|
+
managedBy: "better-auth",
|
|
3610
|
+
// ADR-0010 §3.7 — managed by better-auth; tenants may not edit schema.
|
|
3611
|
+
protection: {
|
|
3612
|
+
lock: "full",
|
|
3613
|
+
reason: "Identity table managed by better-auth (@better-auth/scim) \u2014 see ADR-0071.",
|
|
3614
|
+
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
3615
|
+
},
|
|
3616
|
+
description: "SCIM 2.0 connections (bearer tokens) external IdPs use to provision/deprovision this environment's users",
|
|
3617
|
+
displayNameField: "provider_id",
|
|
3618
|
+
titleFormat: "{provider_id}",
|
|
3619
|
+
compactLayout: ["provider_id", "organization_id"],
|
|
3620
|
+
listViews: {
|
|
3621
|
+
all: {
|
|
3622
|
+
type: "grid",
|
|
3623
|
+
name: "all",
|
|
3624
|
+
label: "All",
|
|
3625
|
+
data: { provider: "object", object: "sys_scim_provider" },
|
|
3626
|
+
// scim_token is intentionally excluded — never surface the credential.
|
|
3627
|
+
columns: ["provider_id", "organization_id", "created_at"],
|
|
3628
|
+
sort: [{ field: "provider_id", order: "asc" }],
|
|
3629
|
+
pagination: { pageSize: 50 }
|
|
3630
|
+
}
|
|
3631
|
+
},
|
|
3632
|
+
fields: {
|
|
3633
|
+
id: Field.text({ label: "ID", required: true, readonly: true, group: "System" }),
|
|
3634
|
+
provider_id: Field.text({
|
|
3635
|
+
label: "Provider ID",
|
|
3636
|
+
required: true,
|
|
3637
|
+
searchable: true,
|
|
3638
|
+
maxLength: 255,
|
|
3639
|
+
description: 'Stable SCIM provider identifier (e.g. "okta-scim")',
|
|
3640
|
+
group: "Identity"
|
|
3641
|
+
}),
|
|
3642
|
+
scim_token: Field.text({
|
|
3643
|
+
label: "SCIM Token (hash)",
|
|
3644
|
+
required: false,
|
|
3645
|
+
readonly: true,
|
|
3646
|
+
maxLength: 1024,
|
|
3647
|
+
description: "Hashed bearer credential for this SCIM connection \u2014 the plaintext is shown once at generate-token. Sensitive; do not expose.",
|
|
3648
|
+
group: "Secret"
|
|
3649
|
+
}),
|
|
3650
|
+
organization_id: Field.text({
|
|
3651
|
+
label: "Organization",
|
|
3652
|
+
required: false,
|
|
3653
|
+
maxLength: 255,
|
|
3654
|
+
description: "Organization scope of this token (org-scoped tokens restrict provisioning to that org)",
|
|
3655
|
+
group: "System"
|
|
3656
|
+
}),
|
|
3657
|
+
user_id: Field.lookup("sys_user", {
|
|
3658
|
+
label: "Owned By",
|
|
3659
|
+
required: false,
|
|
3660
|
+
description: "User who generated this token (when provider-ownership is enabled)",
|
|
3661
|
+
group: "System"
|
|
3662
|
+
}),
|
|
3663
|
+
created_at: Field.datetime({ label: "Created At", defaultValue: "NOW()", readonly: true, group: "System" }),
|
|
3664
|
+
updated_at: Field.datetime({ label: "Updated At", defaultValue: "NOW()", readonly: true, group: "System" })
|
|
3665
|
+
},
|
|
3666
|
+
indexes: [
|
|
3667
|
+
{ fields: ["provider_id"], unique: true },
|
|
3668
|
+
{ fields: ["organization_id"] },
|
|
3669
|
+
{ fields: ["user_id"] }
|
|
3670
|
+
],
|
|
3671
|
+
enable: {
|
|
3672
|
+
trackHistory: true,
|
|
3673
|
+
searchable: false,
|
|
3674
|
+
apiEnabled: true,
|
|
3675
|
+
// Mutations + token issuance go through @better-auth/scim's endpoints
|
|
3676
|
+
// under /api/v1/auth/scim/*; the generic data layer is read-only so the
|
|
3677
|
+
// credential cannot be written/bypassed through it.
|
|
3678
|
+
apiMethods: ["list"],
|
|
3679
|
+
trash: false,
|
|
3680
|
+
mru: false
|
|
3681
|
+
}
|
|
3682
|
+
});
|
|
3159
3683
|
var SysNotification = ObjectSchema.create({
|
|
3160
3684
|
name: "sys_notification",
|
|
3161
3685
|
label: "Notification Event",
|
|
@@ -4611,7 +5135,7 @@ var SETUP_NAV_CONTRIBUTIONS = [
|
|
|
4611
5135
|
items: [
|
|
4612
5136
|
{ id: "nav_users", type: "object", label: "Users", objectName: "sys_user", icon: "user" },
|
|
4613
5137
|
{ id: "nav_business_units", type: "object", label: "Business Units", objectName: "sys_business_unit", icon: "building", requiresObject: "sys_business_unit" },
|
|
4614
|
-
{ id: "nav_teams", type: "object", label: "Teams", objectName: "sys_team", icon: "users-round" },
|
|
5138
|
+
{ id: "nav_teams", type: "object", label: "Teams", objectName: "sys_team", icon: "users-round", requiresService: "org-scoping" },
|
|
4615
5139
|
{ id: "nav_organizations", type: "object", label: "Organizations", objectName: "sys_organization", icon: "building-2", requiresService: "org-scoping" },
|
|
4616
5140
|
{ id: "nav_invitations", type: "object", label: "Invitations", objectName: "sys_invitation", icon: "mail", requiresService: "org-scoping" }
|
|
4617
5141
|
]
|
|
@@ -4639,6 +5163,16 @@ var SETUP_NAV_CONTRIBUTIONS = [
|
|
|
4639
5163
|
priority: BASE_PRIORITY,
|
|
4640
5164
|
items: [
|
|
4641
5165
|
{ id: "nav_settings_hub", type: "url", label: "All Settings", url: "/apps/setup/system/settings", icon: "settings-2", requiredPermissions: ["manage_platform_settings"] },
|
|
5166
|
+
// Workspace identity first — Localization (order 2) and Company (order 3)
|
|
5167
|
+
// are the lowest-`order` settings manifests and the first thing a new
|
|
5168
|
+
// company admin configures. They ship as `service-settings` manifests
|
|
5169
|
+
// (tenant scope, read=`setup.access`) but were never pinned here, so they
|
|
5170
|
+
// were reachable only by drilling into the "All Settings" hub. Mainstream
|
|
5171
|
+
// admin consoles (Salesforce "Company Information", ServiceNow) surface
|
|
5172
|
+
// both directly. No `requiredPermissions` — matches Branding (read perm is
|
|
5173
|
+
// the app's base `setup.access`).
|
|
5174
|
+
{ id: "nav_settings_localization", type: "url", label: "Localization", url: "/apps/setup/system/settings/localization", icon: "globe" },
|
|
5175
|
+
{ id: "nav_settings_company", type: "url", label: "Company", url: "/apps/setup/system/settings/company", icon: "building-2" },
|
|
4642
5176
|
{ id: "nav_settings_branding", type: "url", label: "Branding", url: "/apps/setup/system/settings/branding", icon: "palette" },
|
|
4643
5177
|
{ id: "nav_settings_auth", type: "url", label: "Authentication", url: "/apps/setup/system/settings/auth", icon: "lock-keyhole", requiredPermissions: ["manage_platform_settings"] },
|
|
4644
5178
|
{ id: "nav_settings_mail", type: "url", label: "Email", url: "/apps/setup/system/settings/mail", icon: "mail", requiredPermissions: ["manage_platform_settings"] },
|
|
@@ -4666,8 +5200,12 @@ var SETUP_NAV_CONTRIBUTIONS = [
|
|
|
4666
5200
|
items: [
|
|
4667
5201
|
{ id: "nav_oauth_apps", type: "object", label: "OAuth Applications", objectName: "sys_oauth_application", icon: "app-window" },
|
|
4668
5202
|
{ id: "nav_jwks", type: "object", label: "Signing Keys (JWKS)", objectName: "sys_jwks", icon: "key-round" },
|
|
4669
|
-
|
|
4670
|
-
|
|
5203
|
+
// `sys_verification` (email/phone tokens) and `sys_device_code` (OAuth
|
|
5204
|
+
// device-grant codes) deliberately omit `list` from their `apiMethods`
|
|
5205
|
+
// (sensitive, ephemeral secrets — not browsable), so an object/list-view
|
|
5206
|
+
// nav entry for them can only ever render "failed to load". They're
|
|
5207
|
+
// reachable by id (get) when needed; no browse menu. (Re-adding requires
|
|
5208
|
+
// enabling `list` on the object — a security decision.)
|
|
4671
5209
|
{ id: "nav_accounts", type: "object", label: "Identity Links", objectName: "sys_account", icon: "link-2" },
|
|
4672
5210
|
{ id: "nav_user_preferences", type: "object", label: "User Preferences", objectName: "sys_user_preference", icon: "sliders" }
|
|
4673
5211
|
]
|
|
@@ -5053,7 +5591,13 @@ var ACCOUNT_APP = {
|
|
|
5053
5591
|
objectName: "sys_member",
|
|
5054
5592
|
viewName: "mine",
|
|
5055
5593
|
icon: "building-2",
|
|
5056
|
-
|
|
5594
|
+
// Membership is a multi-org concept: in single-org mode the
|
|
5595
|
+
// `mine` view is always empty (no sys_organization rows, no
|
|
5596
|
+
// auto-stamped memberships). Gate on the org-scoping service so
|
|
5597
|
+
// this entry disappears entirely — matching Organizations/
|
|
5598
|
+
// Invitations in setup-nav. `requiresObject: 'sys_member'` was
|
|
5599
|
+
// the wrong gate (the system object is always registered).
|
|
5600
|
+
requiresService: "org-scoping"
|
|
5057
5601
|
}
|
|
5058
5602
|
]
|
|
5059
5603
|
},
|
|
@@ -5146,6 +5690,12 @@ var SystemOverviewDashboard = Dashboard.create({
|
|
|
5146
5690
|
values: ["org_count"],
|
|
5147
5691
|
title: "Organizations",
|
|
5148
5692
|
type: "metric",
|
|
5693
|
+
// Organizations only exist under multi-tenant org-scoping. In a
|
|
5694
|
+
// single-tenant runtime the count is always 0 and the matching
|
|
5695
|
+
// nav entries (nav_organizations / nav_invitations) are hidden via
|
|
5696
|
+
// `requiresService: 'org-scoping'` — gate this KPI the same way so the
|
|
5697
|
+
// overview doesn't dangle a metric the admin can't act on.
|
|
5698
|
+
requiresService: "org-scoping",
|
|
5149
5699
|
layout: { x: 3, y: 0, w: 3, h: 2 },
|
|
5150
5700
|
colorVariant: "orange",
|
|
5151
5701
|
description: "Total organizations on the platform"
|
|
@@ -10661,6 +11211,7 @@ var zhCN = {
|
|
|
10661
11211
|
group_apps: { label: "\u5E94\u7528" },
|
|
10662
11212
|
nav_marketplace_browse: { label: "\u6D4F\u89C8\u5E94\u7528\u5E02\u573A" },
|
|
10663
11213
|
nav_marketplace_installed: { label: "\u5DF2\u5B89\u88C5\u5E94\u7528" },
|
|
11214
|
+
nav_cloud_connection: { label: "\u4E91\u8FDE\u63A5" },
|
|
10664
11215
|
group_people_org: { label: "\u4EBA\u5458\u4E0E\u7EC4\u7EC7" },
|
|
10665
11216
|
group_access_control: { label: "\u8BBF\u95EE\u63A7\u5236" },
|
|
10666
11217
|
group_approvals: { label: "\u5BA1\u6279" },
|
|
@@ -10675,6 +11226,7 @@ var zhCN = {
|
|
|
10675
11226
|
nav_organizations: { label: "\u7EC4\u7EC7" },
|
|
10676
11227
|
nav_invitations: { label: "\u9080\u8BF7" },
|
|
10677
11228
|
nav_roles: { label: "\u89D2\u8272" },
|
|
11229
|
+
nav_capabilities: { label: "\u80FD\u529B" },
|
|
10678
11230
|
nav_permission_sets: { label: "\u6743\u9650\u96C6" },
|
|
10679
11231
|
nav_sharing_rules: { label: "\u5171\u4EAB\u89C4\u5219" },
|
|
10680
11232
|
nav_record_shares: { label: "\u8BB0\u5F55\u5171\u4EAB" },
|
|
@@ -10683,6 +11235,8 @@ var zhCN = {
|
|
|
10683
11235
|
nav_approval_requests: { label: "\u5BA1\u6279\u7533\u8BF7" },
|
|
10684
11236
|
nav_approval_actions: { label: "\u5BA1\u6279\u5386\u53F2" },
|
|
10685
11237
|
nav_settings_hub: { label: "\u5168\u90E8\u8BBE\u7F6E" },
|
|
11238
|
+
nav_settings_localization: { label: "\u672C\u5730\u5316" },
|
|
11239
|
+
nav_settings_company: { label: "\u516C\u53F8\u4FE1\u606F" },
|
|
10686
11240
|
nav_settings_mail: { label: "\u90AE\u4EF6" },
|
|
10687
11241
|
nav_settings_branding: { label: "\u54C1\u724C" },
|
|
10688
11242
|
nav_settings_auth: { label: "\u8BA4\u8BC1" },
|
|
@@ -10696,10 +11250,9 @@ var zhCN = {
|
|
|
10696
11250
|
nav_sessions: { label: "\u4F1A\u8BDD" },
|
|
10697
11251
|
nav_audit_logs: { label: "\u5BA1\u8BA1\u65E5\u5FD7" },
|
|
10698
11252
|
nav_notifications: { label: "\u901A\u77E5" },
|
|
11253
|
+
nav_datasources: { label: "\u6570\u636E\u6E90" },
|
|
10699
11254
|
nav_oauth_apps: { label: "OAuth \u5E94\u7528" },
|
|
10700
11255
|
nav_jwks: { label: "\u7B7E\u540D\u5BC6\u94A5 (JWKS)" },
|
|
10701
|
-
nav_verifications: { label: "\u9A8C\u8BC1\u8BB0\u5F55" },
|
|
10702
|
-
nav_device_codes: { label: "\u8BBE\u5907\u4EE3\u7801" },
|
|
10703
11256
|
nav_accounts: { label: "\u8EAB\u4EFD\u94FE\u63A5" },
|
|
10704
11257
|
nav_user_preferences: { label: "\u7528\u6237\u504F\u597D" },
|
|
10705
11258
|
nav_metadata: { label: "\u5168\u90E8\u5143\u6570\u636E" }
|
|
@@ -23155,6 +23708,6 @@ function createPlatformObjectsPlugin() {
|
|
|
23155
23708
|
return new PlatformObjectsPlugin();
|
|
23156
23709
|
}
|
|
23157
23710
|
|
|
23158
|
-
export { ACCOUNT_APP, MetadataFormsTranslations, PlatformObjectsPlugin, SETUP_APP, SETUP_NAV_CONTRIBUTIONS, STUDIO_APP, SetupAppTranslations, SysAccount, SysApiKey, SysAttachment, SysBusinessUnit, SysBusinessUnitMember, SysDeviceCode, SysEmail, SysEmailTemplate, SysInvitation, SysJob, SysJobQueue, SysJobRun, SysJwks, SysMember, SysNotification, SysOauthAccessToken, SysOauthApplication, SysOauthConsent, SysOauthRefreshToken, SysOrganization, SysOrganizationDetailPage, SysReportSchedule, SysSavedReport, SysSecret, SysSession, SysSetting, SysSettingAudit, SysTeam, SysTeamMember, SysTwoFactor, SysUser, SysUserDetailPage, SysUserPreference, SysVerification, SystemOverviewDashboard, SystemOverviewDatasets, createPlatformObjectsPlugin, en, esES, jaJP, zhCN };
|
|
23711
|
+
export { ACCOUNT_APP, MetadataFormsTranslations, PlatformObjectsPlugin, SETUP_APP, SETUP_NAV_CONTRIBUTIONS, STUDIO_APP, SetupAppTranslations, SysAccount, SysApiKey, SysAttachment, SysBusinessUnit, SysBusinessUnitMember, SysDeviceCode, SysEmail, SysEmailTemplate, SysInvitation, SysJob, SysJobQueue, SysJobRun, SysJwks, SysMember, SysNotification, SysOauthAccessToken, SysOauthApplication, SysOauthConsent, SysOauthRefreshToken, SysOrganization, SysOrganizationDetailPage, SysReportSchedule, SysSavedReport, SysScimProvider, SysSecret, SysSession, SysSetting, SysSettingAudit, SysSsoProvider, SysTeam, SysTeamMember, SysTwoFactor, SysUser, SysUserDetailPage, SysUserPreference, SysVerification, SystemOverviewDashboard, SystemOverviewDatasets, createPlatformObjectsPlugin, en, esES, jaJP, zhCN };
|
|
23159
23712
|
//# sourceMappingURL=index.mjs.map
|
|
23160
23713
|
//# sourceMappingURL=index.mjs.map
|