@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/identity/index.mjs
CHANGED
|
@@ -33,6 +33,13 @@ var SysUser = ObjectSchema.create({
|
|
|
33
33
|
locations: ["list_toolbar"],
|
|
34
34
|
type: "api",
|
|
35
35
|
target: "/api/v1/auth/organization/invite-member",
|
|
36
|
+
// Org invitations are a multi-org-only flow (the endpoint resolves
|
|
37
|
+
// an active org that does not exist in single-org mode). Hide the
|
|
38
|
+
// affordance unless multi-org is enabled — matching the
|
|
39
|
+
// `create_organization` gate on sys_organization. This action is the
|
|
40
|
+
// most exposed of the set because the Users list is always reachable
|
|
41
|
+
// in single-org, unlike the org/membership lists.
|
|
42
|
+
visible: "features.multiOrgEnabled != false",
|
|
36
43
|
successMessage: "Invitation sent",
|
|
37
44
|
refreshAfter: true,
|
|
38
45
|
params: [
|
|
@@ -76,6 +83,21 @@ var SysUser = ObjectSchema.create({
|
|
|
76
83
|
successMessage: "User unbanned",
|
|
77
84
|
refreshAfter: true
|
|
78
85
|
},
|
|
86
|
+
{
|
|
87
|
+
// ADR-0069 D2 — clear a brute-force lockout early (locked_until auto-
|
|
88
|
+
// expires, but an admin can release a user immediately). Hits the
|
|
89
|
+
// plugin-auth custom route, which is admin-guarded server-side.
|
|
90
|
+
name: "unlock_user",
|
|
91
|
+
label: "Unlock Account",
|
|
92
|
+
icon: "lock-open",
|
|
93
|
+
variant: "secondary",
|
|
94
|
+
locations: ["list_item"],
|
|
95
|
+
type: "api",
|
|
96
|
+
target: "/api/v1/auth/admin/unlock-user",
|
|
97
|
+
recordIdParam: "userId",
|
|
98
|
+
successMessage: "Account unlocked",
|
|
99
|
+
refreshAfter: true
|
|
100
|
+
},
|
|
79
101
|
{
|
|
80
102
|
name: "set_user_password",
|
|
81
103
|
label: "Set Password",
|
|
@@ -152,7 +174,11 @@ var SysUser = ObjectSchema.create({
|
|
|
152
174
|
locations: ["record_header", "record_more", "record_section"],
|
|
153
175
|
type: "api",
|
|
154
176
|
target: "/api/v1/auth/change-password",
|
|
155
|
-
|
|
177
|
+
// Managed (IdP-provisioned) users hold no local credential — hide the
|
|
178
|
+
// password form so they can't self-mint a password that bypasses
|
|
179
|
+
// enforced SSO. The break-glass owner (env-native, or flipped back when
|
|
180
|
+
// their break-glass password is set) keeps it. ADR-0024 D4/D5.2.
|
|
181
|
+
visible: 'record.id == ctx.user.id && record.source != "idp_provisioned"',
|
|
156
182
|
successMessage: "Password changed",
|
|
157
183
|
refreshAfter: false,
|
|
158
184
|
params: [
|
|
@@ -169,7 +195,9 @@ var SysUser = ObjectSchema.create({
|
|
|
169
195
|
locations: ["record_header", "record_more", "record_section"],
|
|
170
196
|
type: "api",
|
|
171
197
|
target: "/api/v1/auth/change-email",
|
|
172
|
-
|
|
198
|
+
// A managed user's email is owned by the IdP — a local change would
|
|
199
|
+
// desync. Hide for IdP-provisioned; env-native users keep it.
|
|
200
|
+
visible: 'record.id == ctx.user.id && record.source != "idp_provisioned"',
|
|
173
201
|
successMessage: "Verification email sent \u2014 check the new address to confirm.",
|
|
174
202
|
refreshAfter: false,
|
|
175
203
|
params: [
|
|
@@ -200,7 +228,9 @@ var SysUser = ObjectSchema.create({
|
|
|
200
228
|
locations: ["record_more", "record_section"],
|
|
201
229
|
type: "api",
|
|
202
230
|
target: "/api/v1/auth/delete-user",
|
|
203
|
-
|
|
231
|
+
// Self-delete needs a local password; managed users are deprovisioned
|
|
232
|
+
// via the IdP (org-removal / SCIM), not local self-service. Hide for them.
|
|
233
|
+
visible: 'record.id == ctx.user.id && record.source != "idp_provisioned"',
|
|
204
234
|
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.",
|
|
205
235
|
successMessage: "Account deleted",
|
|
206
236
|
refreshAfter: false,
|
|
@@ -283,7 +313,7 @@ var SysUser = ObjectSchema.create({
|
|
|
283
313
|
name: "all_users",
|
|
284
314
|
label: "All Users",
|
|
285
315
|
data: { provider: "object", object: "sys_user" },
|
|
286
|
-
columns: ["name", "email", "email_verified", "two_factor_enabled", "created_at"],
|
|
316
|
+
columns: ["name", "email", "email_verified", "source", "two_factor_enabled", "created_at"],
|
|
287
317
|
sort: [{ field: "name", order: "asc" }],
|
|
288
318
|
pagination: { pageSize: 50 }
|
|
289
319
|
},
|
|
@@ -370,6 +400,51 @@ var SysUser = ObjectSchema.create({
|
|
|
370
400
|
group: "Admin",
|
|
371
401
|
description: "When set, the ban auto-clears at this time."
|
|
372
402
|
}),
|
|
403
|
+
// ── Anti-brute-force (ADR-0069 D2) — owned by objectql, better-auth is
|
|
404
|
+
// oblivious. The auth manager's sign-in hooks maintain these: failures
|
|
405
|
+
// increment the counter; crossing `lockout_threshold` stamps
|
|
406
|
+
// `locked_until`; a successful sign-in resets both. Admins can clear
|
|
407
|
+
// them early via the Unlock action.
|
|
408
|
+
failed_login_count: Field.number({
|
|
409
|
+
label: "Failed Login Count",
|
|
410
|
+
required: false,
|
|
411
|
+
defaultValue: 0,
|
|
412
|
+
readonly: true,
|
|
413
|
+
group: "Admin",
|
|
414
|
+
description: "Consecutive failed sign-in attempts; reset to 0 on success. Maintained by the auth manager."
|
|
415
|
+
}),
|
|
416
|
+
locked_until: Field.datetime({
|
|
417
|
+
label: "Locked Until",
|
|
418
|
+
required: false,
|
|
419
|
+
readonly: true,
|
|
420
|
+
group: "Admin",
|
|
421
|
+
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."
|
|
422
|
+
}),
|
|
423
|
+
// ADR-0069 D1 — last password change; drives password-expiry enforcement.
|
|
424
|
+
// Stamped on sign-up / change-password / reset-password. Null = never
|
|
425
|
+
// expires (until the user next changes their password).
|
|
426
|
+
password_changed_at: Field.datetime({
|
|
427
|
+
label: "Password Changed At",
|
|
428
|
+
required: false,
|
|
429
|
+
readonly: true,
|
|
430
|
+
group: "Admin",
|
|
431
|
+
description: "Timestamp of the last password change. Backs password_expiry_days; system-managed."
|
|
432
|
+
}),
|
|
433
|
+
// ADR-0069 D3 — when enforced MFA first applied to this user; starts the
|
|
434
|
+
// grace clock. Stamped lazily at session validation; system-managed.
|
|
435
|
+
mfa_required_at: Field.datetime({
|
|
436
|
+
label: "MFA Required At",
|
|
437
|
+
required: false,
|
|
438
|
+
readonly: true,
|
|
439
|
+
group: "Admin",
|
|
440
|
+
description: "When enforced MFA first applied to this user (grace-period clock). Backs mfa_required; system-managed."
|
|
441
|
+
}),
|
|
442
|
+
ai_access: Field.boolean({
|
|
443
|
+
label: "AI Access",
|
|
444
|
+
defaultValue: false,
|
|
445
|
+
group: "Admin",
|
|
446
|
+
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)."
|
|
447
|
+
}),
|
|
373
448
|
// ── Profile ──────────────────────────────────────────────────
|
|
374
449
|
image: Field.url({
|
|
375
450
|
label: "Profile Image",
|
|
@@ -390,6 +465,28 @@ var SysUser = ObjectSchema.create({
|
|
|
390
465
|
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."
|
|
391
466
|
}),
|
|
392
467
|
// ── System (auto-managed, hidden from create/edit forms) ─────
|
|
468
|
+
// Identity provenance (ADR-0024 D4). `idp_provisioned` users were
|
|
469
|
+
// JIT-created on first federated login (a `sys_account` exists for an
|
|
470
|
+
// external/OIDC provider — e.g. the cloud-as-IdP `objectstack-cloud`
|
|
471
|
+
// provider, or a customer's own IdP); `env_native` users registered
|
|
472
|
+
// locally (email/password) or are app end-users. Stamped automatically by
|
|
473
|
+
// the AuthManager `account.create.after` hook — never edited by hand.
|
|
474
|
+
// Drives the managed-vs-native user-mgmt UI gating (the password /
|
|
475
|
+
// identity-edit actions hide for managed users, who hold no local
|
|
476
|
+
// credential) and is the marker SCIM lifecycle keys off. Owned by objectql
|
|
477
|
+
// (better-auth is oblivious to this column — like `ai_access`).
|
|
478
|
+
source: Field.select({
|
|
479
|
+
label: "Identity Source",
|
|
480
|
+
required: false,
|
|
481
|
+
readonly: true,
|
|
482
|
+
group: "System",
|
|
483
|
+
defaultValue: "env_native",
|
|
484
|
+
options: [
|
|
485
|
+
{ label: "IdP-Provisioned", value: "idp_provisioned" },
|
|
486
|
+
{ label: "Env-Native", value: "env_native" }
|
|
487
|
+
],
|
|
488
|
+
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."
|
|
489
|
+
}),
|
|
393
490
|
id: Field.text({
|
|
394
491
|
label: "User ID",
|
|
395
492
|
required: true,
|
|
@@ -514,6 +611,29 @@ var SysSession = ObjectSchema.create({
|
|
|
514
611
|
required: true,
|
|
515
612
|
group: "Session"
|
|
516
613
|
}),
|
|
614
|
+
// ── ADR-0069 D4 — session controls (idle / absolute / revoke) ──
|
|
615
|
+
last_activity_at: Field.datetime({
|
|
616
|
+
label: "Last Activity At",
|
|
617
|
+
required: false,
|
|
618
|
+
readonly: true,
|
|
619
|
+
group: "Session",
|
|
620
|
+
description: "Timestamp of the last request on this session; drives idle-timeout. System-managed."
|
|
621
|
+
}),
|
|
622
|
+
revoked_at: Field.datetime({
|
|
623
|
+
label: "Revoked At",
|
|
624
|
+
required: false,
|
|
625
|
+
readonly: true,
|
|
626
|
+
group: "Session",
|
|
627
|
+
description: "When set, this session was revoked (idle / absolute-max / concurrent-cap / admin). System-managed."
|
|
628
|
+
}),
|
|
629
|
+
revoke_reason: Field.text({
|
|
630
|
+
label: "Revoke Reason",
|
|
631
|
+
required: false,
|
|
632
|
+
maxLength: 64,
|
|
633
|
+
readonly: true,
|
|
634
|
+
group: "Session",
|
|
635
|
+
description: "Why the session was revoked (idle_timeout, absolute_max, concurrent_cap, \u2026)."
|
|
636
|
+
}),
|
|
517
637
|
// ── Active context (multi-org/team) ──────────────────────────
|
|
518
638
|
active_organization_id: Field.lookup("sys_organization", {
|
|
519
639
|
label: "Active Organization",
|
|
@@ -755,6 +875,16 @@ var SysAccount = ObjectSchema.create({
|
|
|
755
875
|
label: "Password Hash",
|
|
756
876
|
required: false,
|
|
757
877
|
description: "Hashed password for email/password provider"
|
|
878
|
+
}),
|
|
879
|
+
// ADR-0069 D1 — bounded ring of previous password hashes (JSON array of
|
|
880
|
+
// strings), used to reject password reuse on change/reset. Maintained by
|
|
881
|
+
// the auth manager; never exposed in UI.
|
|
882
|
+
previous_password_hashes: Field.textarea({
|
|
883
|
+
label: "Previous Password Hashes",
|
|
884
|
+
required: false,
|
|
885
|
+
readonly: true,
|
|
886
|
+
hidden: true,
|
|
887
|
+
description: "JSON array of prior password hashes (bounded by password_history_count); reuse-prevention only. System-managed."
|
|
758
888
|
})
|
|
759
889
|
},
|
|
760
890
|
indexes: [
|
|
@@ -820,7 +950,13 @@ var SysVerification = ObjectSchema.create({
|
|
|
820
950
|
})
|
|
821
951
|
},
|
|
822
952
|
indexes: [
|
|
823
|
-
|
|
953
|
+
// `value` must NOT be unique. better-auth's oauth-provider stores OIDC
|
|
954
|
+
// authorization codes in this table with `value` = a JSON blob keyed by
|
|
955
|
+
// user+client+state, which can legitimately repeat. A UNIQUE constraint
|
|
956
|
+
// makes `/api/v1/auth/oauth2/authorize` fail (`UNIQUE constraint failed:
|
|
957
|
+
// sys_verification.value`) → 503, breaking cloud-as-IdP SSO entirely.
|
|
958
|
+
// better-auth keys verification lookups on `identifier`, not `value`.
|
|
959
|
+
{ fields: ["value"], unique: false },
|
|
824
960
|
{ fields: ["identifier"], unique: false },
|
|
825
961
|
{ fields: ["expires_at"], unique: false }
|
|
826
962
|
],
|
|
@@ -888,6 +1024,10 @@ var SysOrganization = ObjectSchema.create({
|
|
|
888
1024
|
recordIdParam: "organizationId",
|
|
889
1025
|
// better-auth `organization/update` nests editable fields under `data`.
|
|
890
1026
|
bodyShape: { wrap: "data" },
|
|
1027
|
+
// Org-admin actions are multi-org-only; hide them in single-org for
|
|
1028
|
+
// consistency with `create_organization` (the org list is empty there,
|
|
1029
|
+
// but this also guards direct record-URL access).
|
|
1030
|
+
visible: "features.multiOrgEnabled != false",
|
|
891
1031
|
successMessage: "Organization updated",
|
|
892
1032
|
refreshAfter: true,
|
|
893
1033
|
params: [
|
|
@@ -906,6 +1046,7 @@ var SysOrganization = ObjectSchema.create({
|
|
|
906
1046
|
type: "api",
|
|
907
1047
|
target: "/api/v1/auth/organization/delete",
|
|
908
1048
|
recordIdParam: "organizationId",
|
|
1049
|
+
visible: "features.multiOrgEnabled != false",
|
|
909
1050
|
confirmText: "Delete this organization? All members will lose access immediately. This cannot be undone.",
|
|
910
1051
|
successMessage: "Organization deleted",
|
|
911
1052
|
refreshAfter: true
|
|
@@ -924,6 +1065,7 @@ var SysOrganization = ObjectSchema.create({
|
|
|
924
1065
|
type: "api",
|
|
925
1066
|
target: "/api/v1/auth/organization/set-active",
|
|
926
1067
|
recordIdParam: "organizationId",
|
|
1068
|
+
visible: "features.multiOrgEnabled != false",
|
|
927
1069
|
successMessage: "Active organization switched",
|
|
928
1070
|
refreshAfter: true
|
|
929
1071
|
},
|
|
@@ -940,6 +1082,7 @@ var SysOrganization = ObjectSchema.create({
|
|
|
940
1082
|
type: "api",
|
|
941
1083
|
target: "/api/v1/auth/organization/leave",
|
|
942
1084
|
recordIdParam: "organizationId",
|
|
1085
|
+
visible: "features.multiOrgEnabled != false",
|
|
943
1086
|
confirmText: "Leave this organization? You will lose access to all of its resources.",
|
|
944
1087
|
successMessage: "You have left the organization",
|
|
945
1088
|
refreshAfter: true
|
|
@@ -961,6 +1104,7 @@ var SysOrganization = ObjectSchema.create({
|
|
|
961
1104
|
type: "api",
|
|
962
1105
|
target: "/api/v1/cloud/organizations/{id}/change-slug",
|
|
963
1106
|
method: "POST",
|
|
1107
|
+
visible: "features.multiOrgEnabled != false",
|
|
964
1108
|
confirmText: "Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue?",
|
|
965
1109
|
successMessage: "Organization slug changed",
|
|
966
1110
|
refreshAfter: true,
|
|
@@ -1010,6 +1154,17 @@ var SysOrganization = ObjectSchema.create({
|
|
|
1010
1154
|
description: "JSON-serialized organization metadata",
|
|
1011
1155
|
group: "Configuration"
|
|
1012
1156
|
}),
|
|
1157
|
+
// ADR-0069 D3 — per-org MFA tightening above the global floor. When true,
|
|
1158
|
+
// members of this org must enrol TOTP to access data (enforced at the
|
|
1159
|
+
// session-validation gate). An org can only tighten, never loosen, the
|
|
1160
|
+
// global `mfa_required` setting.
|
|
1161
|
+
require_mfa: Field.boolean({
|
|
1162
|
+
label: "Require Multi-Factor Auth",
|
|
1163
|
+
required: false,
|
|
1164
|
+
defaultValue: false,
|
|
1165
|
+
group: "Configuration",
|
|
1166
|
+
description: "When true, every member of this organization must enroll an authenticator app to access data."
|
|
1167
|
+
}),
|
|
1013
1168
|
// ── System ───────────────────────────────────────────────────
|
|
1014
1169
|
id: Field.text({
|
|
1015
1170
|
label: "Organization ID",
|
|
@@ -1059,7 +1214,10 @@ var SysMember = ObjectSchema.create({
|
|
|
1059
1214
|
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
1060
1215
|
},
|
|
1061
1216
|
description: "Organization membership records",
|
|
1062
|
-
|
|
1217
|
+
// Org-independent title: organization_id is null in single-org mode, so a
|
|
1218
|
+
// '{user_id} in {organization_id}' format renders "… in null". User + role
|
|
1219
|
+
// identifies the membership in both single- and multi-org deployments.
|
|
1220
|
+
titleFormat: "{user_id} ({role})",
|
|
1063
1221
|
compactLayout: ["user_id", "organization_id", "role"],
|
|
1064
1222
|
// Row-level actions: better-auth `organization/update-member-role` and
|
|
1065
1223
|
// `organization/remove-member`. Generic CRUD is suppressed on better-auth
|
|
@@ -1080,6 +1238,11 @@ var SysMember = ObjectSchema.create({
|
|
|
1080
1238
|
locations: ["list_toolbar"],
|
|
1081
1239
|
type: "api",
|
|
1082
1240
|
target: "/api/v1/auth/organization/add-member",
|
|
1241
|
+
// Org-membership mutations are multi-org-only: the better-auth
|
|
1242
|
+
// endpoints resolve an active org that does not exist in single-org
|
|
1243
|
+
// mode, so these actions would fail at the API. Gate every one on
|
|
1244
|
+
// the multi-org flag (mirrors sys_organization.create_organization).
|
|
1245
|
+
visible: "features.multiOrgEnabled != false",
|
|
1083
1246
|
successMessage: "Member added",
|
|
1084
1247
|
refreshAfter: true,
|
|
1085
1248
|
params: [
|
|
@@ -1097,6 +1260,7 @@ var SysMember = ObjectSchema.create({
|
|
|
1097
1260
|
type: "api",
|
|
1098
1261
|
target: "/api/v1/auth/organization/update-member-role",
|
|
1099
1262
|
recordIdParam: "memberId",
|
|
1263
|
+
visible: "features.multiOrgEnabled != false",
|
|
1100
1264
|
successMessage: "Member role updated",
|
|
1101
1265
|
refreshAfter: true,
|
|
1102
1266
|
params: [
|
|
@@ -1113,6 +1277,7 @@ var SysMember = ObjectSchema.create({
|
|
|
1113
1277
|
type: "api",
|
|
1114
1278
|
target: "/api/v1/auth/organization/remove-member",
|
|
1115
1279
|
recordIdParam: "memberIdOrEmail",
|
|
1280
|
+
visible: "features.multiOrgEnabled != false",
|
|
1116
1281
|
confirmText: "Remove this member from the organization? They will lose access to all org resources.",
|
|
1117
1282
|
successMessage: "Member removed",
|
|
1118
1283
|
refreshAfter: true
|
|
@@ -1134,7 +1299,7 @@ var SysMember = ObjectSchema.create({
|
|
|
1134
1299
|
target: "/api/v1/auth/organization/update-member-role",
|
|
1135
1300
|
recordIdParam: "memberId",
|
|
1136
1301
|
bodyExtra: { role: "owner" },
|
|
1137
|
-
visible: "record.role != 'owner'",
|
|
1302
|
+
visible: "record.role != 'owner' && features.multiOrgEnabled != false",
|
|
1138
1303
|
confirmText: "Transfer ownership of this organization to the selected member? You will be demoted to admin and lose owner-only privileges.",
|
|
1139
1304
|
successMessage: "Ownership transferred",
|
|
1140
1305
|
refreshAfter: true
|
|
@@ -1219,7 +1384,10 @@ var SysInvitation = ObjectSchema.create({
|
|
|
1219
1384
|
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
1220
1385
|
},
|
|
1221
1386
|
description: "Organization invitations for user onboarding",
|
|
1222
|
-
|
|
1387
|
+
// Title by invitee email rather than organization_id: the latter is null in
|
|
1388
|
+
// single-org mode (renders "Invitation to null"), and the recipient email is
|
|
1389
|
+
// the more useful identifier in both modes anyway.
|
|
1390
|
+
titleFormat: "Invitation for {email}",
|
|
1223
1391
|
compactLayout: ["email", "organization_id", "status"],
|
|
1224
1392
|
// Custom actions — generic CRUD is suppressed (better-auth-managed).
|
|
1225
1393
|
// Mirror the `invite_user` toolbar action from sys_user here so admins
|
|
@@ -1233,6 +1401,13 @@ var SysInvitation = ObjectSchema.create({
|
|
|
1233
1401
|
locations: ["list_toolbar"],
|
|
1234
1402
|
type: "api",
|
|
1235
1403
|
target: "/api/v1/auth/organization/invite-member",
|
|
1404
|
+
// Inviting/managing invitations is a multi-org-only flow (the
|
|
1405
|
+
// endpoint resolves an active org absent in single-org mode). Gate
|
|
1406
|
+
// the admin-side actions on the multi-org flag (mirrors
|
|
1407
|
+
// sys_organization.create_organization). The recipient-side
|
|
1408
|
+
// accept/reject actions below stay record-gated — they are
|
|
1409
|
+
// unreachable in single-org anyway (no invitation rows exist).
|
|
1410
|
+
visible: "features.multiOrgEnabled != false",
|
|
1236
1411
|
successMessage: "Invitation sent",
|
|
1237
1412
|
refreshAfter: true,
|
|
1238
1413
|
params: [
|
|
@@ -1250,6 +1425,7 @@ var SysInvitation = ObjectSchema.create({
|
|
|
1250
1425
|
type: "api",
|
|
1251
1426
|
target: "/api/v1/auth/organization/cancel-invitation",
|
|
1252
1427
|
recordIdParam: "invitationId",
|
|
1428
|
+
visible: "features.multiOrgEnabled != false",
|
|
1253
1429
|
confirmText: "Cancel this invitation? The recipient will no longer be able to accept it.",
|
|
1254
1430
|
successMessage: "Invitation canceled",
|
|
1255
1431
|
refreshAfter: true
|
|
@@ -1263,6 +1439,7 @@ var SysInvitation = ObjectSchema.create({
|
|
|
1263
1439
|
type: "api",
|
|
1264
1440
|
target: "/api/v1/auth/organization/invite-member",
|
|
1265
1441
|
bodyExtra: { resend: true },
|
|
1442
|
+
visible: "features.multiOrgEnabled != false",
|
|
1266
1443
|
successMessage: "Invitation resent",
|
|
1267
1444
|
refreshAfter: true,
|
|
1268
1445
|
params: [
|
|
@@ -1447,6 +1624,10 @@ var SysTeam = ObjectSchema.create({
|
|
|
1447
1624
|
locations: ["list_toolbar"],
|
|
1448
1625
|
type: "api",
|
|
1449
1626
|
target: "/api/v1/auth/organization/create-team",
|
|
1627
|
+
// Teams are nested inside organizations — a multi-org-only concept.
|
|
1628
|
+
// Gate every team mutation on the multi-org flag so the affordances
|
|
1629
|
+
// disappear in single-org (mirrors sys_organization.create_organization).
|
|
1630
|
+
visible: "features.multiOrgEnabled != false",
|
|
1450
1631
|
successMessage: "Team created",
|
|
1451
1632
|
refreshAfter: true,
|
|
1452
1633
|
params: [
|
|
@@ -1467,6 +1648,7 @@ var SysTeam = ObjectSchema.create({
|
|
|
1467
1648
|
target: "/api/v1/auth/organization/update-team",
|
|
1468
1649
|
recordIdParam: "teamId",
|
|
1469
1650
|
bodyShape: { wrap: "data" },
|
|
1651
|
+
visible: "features.multiOrgEnabled != false",
|
|
1470
1652
|
successMessage: "Team updated",
|
|
1471
1653
|
refreshAfter: true,
|
|
1472
1654
|
params: [
|
|
@@ -1485,6 +1667,7 @@ var SysTeam = ObjectSchema.create({
|
|
|
1485
1667
|
type: "api",
|
|
1486
1668
|
target: "/api/v1/auth/organization/remove-team",
|
|
1487
1669
|
recordIdParam: "teamId",
|
|
1670
|
+
visible: "features.multiOrgEnabled != false",
|
|
1488
1671
|
confirmText: "Delete this team? Members will lose any team-scoped access. This cannot be undone.",
|
|
1489
1672
|
successMessage: "Team deleted",
|
|
1490
1673
|
refreshAfter: true
|
|
@@ -1595,6 +1778,10 @@ var SysTeamMember = ObjectSchema.create({
|
|
|
1595
1778
|
locations: ["list_toolbar"],
|
|
1596
1779
|
type: "api",
|
|
1597
1780
|
target: "/api/v1/auth/organization/add-team-member",
|
|
1781
|
+
// Team membership lives under organizations — multi-org-only. Gate
|
|
1782
|
+
// both mutations so they vanish in single-org (mirrors
|
|
1783
|
+
// sys_organization.create_organization).
|
|
1784
|
+
visible: "features.multiOrgEnabled != false",
|
|
1598
1785
|
successMessage: "Team member added",
|
|
1599
1786
|
refreshAfter: true,
|
|
1600
1787
|
params: [
|
|
@@ -1615,6 +1802,7 @@ var SysTeamMember = ObjectSchema.create({
|
|
|
1615
1802
|
locations: ["list_item"],
|
|
1616
1803
|
type: "api",
|
|
1617
1804
|
target: "/api/v1/auth/organization/remove-team-member",
|
|
1805
|
+
visible: "features.multiOrgEnabled != false",
|
|
1618
1806
|
confirmText: "Remove this user from the team? They will lose any team-scoped access.",
|
|
1619
1807
|
successMessage: "Team member removed",
|
|
1620
1808
|
refreshAfter: true,
|
|
@@ -3154,7 +3342,343 @@ var SysJwks = ObjectSchema.create({
|
|
|
3154
3342
|
mru: false
|
|
3155
3343
|
}
|
|
3156
3344
|
});
|
|
3345
|
+
var SysSsoProvider = ObjectSchema.create({
|
|
3346
|
+
name: "sys_sso_provider",
|
|
3347
|
+
label: "SSO Provider",
|
|
3348
|
+
pluralLabel: "SSO Providers",
|
|
3349
|
+
icon: "shield-check",
|
|
3350
|
+
isSystem: true,
|
|
3351
|
+
managedBy: "better-auth",
|
|
3352
|
+
// ADR-0024 — env-global, ADMIN-ONLY identity config. Two orthogonal controls:
|
|
3353
|
+
// • `tenancy.enabled: false` — the env IS the tenant; providers are env-wide,
|
|
3354
|
+
// not org-partitioned. Opting out of multi-tenancy lets a platform admin's
|
|
3355
|
+
// `viewAllRecords` superuser bypass see every provider (without it, the
|
|
3356
|
+
// `member_default` wildcard `tenant_isolation` RLS denies every row, since
|
|
3357
|
+
// better-auth writes via its adapter with no tenantId → `organization_id`
|
|
3358
|
+
// is never stamped).
|
|
3359
|
+
// • `requiredPermissions: ['manage_platform_settings']` — object-level
|
|
3360
|
+
// capability gate (ADR-0066 D3) so ordinary members are denied entirely
|
|
3361
|
+
// (without it, tenancy-disabled + `member_default`'s `'*': allowRead` would
|
|
3362
|
+
// leak providers to every authenticated user).
|
|
3363
|
+
// Together: admins see all env providers; non-admins get 403. better-auth's
|
|
3364
|
+
// own endpoints already read via a system context. (Env-only object — no
|
|
3365
|
+
// control-plane cross-tenant risk.)
|
|
3366
|
+
tenancy: { enabled: false, strategy: "shared" },
|
|
3367
|
+
requiredPermissions: ["manage_platform_settings"],
|
|
3368
|
+
// ADR-0010 §3.7 — managed by better-auth; tenants may not edit schema.
|
|
3369
|
+
protection: {
|
|
3370
|
+
lock: "full",
|
|
3371
|
+
reason: "Identity table managed by better-auth (@better-auth/sso) \u2014 see ADR-0024.",
|
|
3372
|
+
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
3373
|
+
},
|
|
3374
|
+
description: "External SSO identity providers (OIDC / SAML) this environment federates login to",
|
|
3375
|
+
displayNameField: "provider_id",
|
|
3376
|
+
titleFormat: "{provider_id}",
|
|
3377
|
+
compactLayout: ["provider_id", "issuer", "domain"],
|
|
3378
|
+
// All mutations go through @better-auth/sso's endpoints under
|
|
3379
|
+
// /api/v1/auth/sso/* (register / delete-provider) rather than the generic
|
|
3380
|
+
// data layer, so server-side config validation + secret handling run.
|
|
3381
|
+
actions: [
|
|
3382
|
+
{
|
|
3383
|
+
name: "register_sso_provider",
|
|
3384
|
+
label: "Register SSO Provider",
|
|
3385
|
+
icon: "plus-circle",
|
|
3386
|
+
variant: "primary",
|
|
3387
|
+
mode: "create",
|
|
3388
|
+
locations: ["list_toolbar"],
|
|
3389
|
+
type: "api",
|
|
3390
|
+
method: "POST",
|
|
3391
|
+
// Routed through the env-side bridge (plugin-auth `auth-plugin.ts`), which
|
|
3392
|
+
// reshapes these FLAT form fields into the nested `oidcConfig` body that
|
|
3393
|
+
// `@better-auth/sso`'s /sso/register requires, then re-dispatches to it
|
|
3394
|
+
// (so the admin gate + discovery hydration run). Posting straight to
|
|
3395
|
+
// /sso/register would drop clientId/clientSecret (top-level → Zod-stripped)
|
|
3396
|
+
// and persist an unusable `oidc_config = null` provider.
|
|
3397
|
+
target: "/api/v1/auth/admin/sso/register",
|
|
3398
|
+
refreshAfter: true,
|
|
3399
|
+
params: [
|
|
3400
|
+
{ name: "providerId", label: "Provider ID", type: "text", required: true, helpText: 'Stable identifier, e.g. "okta" or "acme-entra".' },
|
|
3401
|
+
{ 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." },
|
|
3402
|
+
{ name: "domain", label: "Email Domain", type: "text", required: true, helpText: "Users with this email domain are routed to this IdP, e.g. acme.com." },
|
|
3403
|
+
{ name: "clientId", label: "Client ID", type: "text", required: true, helpText: "OAuth client ID issued by the IdP for this environment." },
|
|
3404
|
+
{ name: "clientSecret", label: "Client Secret", type: "text", required: true, helpText: "OAuth client secret (stored encrypted by better-auth)." },
|
|
3405
|
+
{ name: "discoveryEndpoint", label: "Discovery URL", type: "text", required: false, helpText: "Optional. OIDC discovery document URL. Leave blank to derive `<issuer>/.well-known/openid-configuration`." },
|
|
3406
|
+
{ 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".' },
|
|
3407
|
+
{ 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".' },
|
|
3408
|
+
{ name: "mapEmail", label: "Map: Email claim", type: "text", required: false, placeholder: "email", helpText: 'Optional. Claim mapped to email. Defaults to "email".' },
|
|
3409
|
+
{ name: "mapName", label: "Map: Name claim", type: "text", required: false, placeholder: "name", helpText: 'Optional. Claim mapped to display name. Defaults to "name".' }
|
|
3410
|
+
]
|
|
3411
|
+
},
|
|
3412
|
+
{
|
|
3413
|
+
name: "register_saml_provider",
|
|
3414
|
+
label: "Register SAML Provider",
|
|
3415
|
+
icon: "shield",
|
|
3416
|
+
variant: "primary",
|
|
3417
|
+
mode: "create",
|
|
3418
|
+
locations: ["list_toolbar"],
|
|
3419
|
+
type: "api",
|
|
3420
|
+
method: "POST",
|
|
3421
|
+
// SAML 2.0 via @better-auth/sso (samlify-backed). Routed through the
|
|
3422
|
+
// env-side bridge (plugin-auth `auth-plugin.ts` → register-saml), which
|
|
3423
|
+
// reshapes these FLAT IdP fields into the nested `samlConfig` body that
|
|
3424
|
+
// @better-auth/sso's /sso/register requires (entryPoint/cert/callbackUrl/
|
|
3425
|
+
// spMetadata/identifierFormat), derives the per-provider ACS URL, and
|
|
3426
|
+
// re-dispatches to /sso/register (admin gate runs). The response returns
|
|
3427
|
+
// the SP ACS + metadata URLs to configure on the IdP.
|
|
3428
|
+
target: "/api/v1/auth/admin/sso/register-saml",
|
|
3429
|
+
refreshAfter: true,
|
|
3430
|
+
params: [
|
|
3431
|
+
{ name: "providerId", label: "Provider ID", type: "text", required: true, helpText: 'Stable identifier, e.g. "acme-saml".' },
|
|
3432
|
+
{ name: "issuer", label: "IdP Entity ID", type: "text", required: true, helpText: "The IdP\u2019s SAML EntityID (issuer), e.g. https://saml.acme.com/entityid." },
|
|
3433
|
+
{ name: "domain", label: "Email Domain", type: "text", required: true, helpText: "Users with this email domain are routed to this IdP, e.g. acme.com." },
|
|
3434
|
+
{ name: "entryPoint", label: "IdP SSO URL", type: "text", required: true, helpText: "The IdP\u2019s SAML single sign-on (redirect) endpoint that receives the SAMLRequest." },
|
|
3435
|
+
{ 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." },
|
|
3436
|
+
{ 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." }
|
|
3437
|
+
]
|
|
3438
|
+
},
|
|
3439
|
+
{
|
|
3440
|
+
name: "request_domain_verification",
|
|
3441
|
+
label: "Request Domain Verification",
|
|
3442
|
+
icon: "globe",
|
|
3443
|
+
variant: "secondary",
|
|
3444
|
+
locations: ["list_item", "record_header"],
|
|
3445
|
+
type: "api",
|
|
3446
|
+
method: "POST",
|
|
3447
|
+
// ADR-0024 ② (opt-in OS_SSO_DOMAIN_VERIFICATION). Asks @better-auth/sso
|
|
3448
|
+
// for a one-time DNS-TXT challenge and reveals the ready-to-paste record
|
|
3449
|
+
// ONCE via `resultDialog`. Routed through the env bridge (plugin-auth /
|
|
3450
|
+
// cloud AuthProxyPlugin) which reshapes the `{domainVerificationToken}`
|
|
3451
|
+
// response into the `{ data: { dnsRecordName, dnsRecordValue } }` envelope
|
|
3452
|
+
// the dialog reads. When the feature is OFF the bridge returns a clear
|
|
3453
|
+
// "not enabled for this environment" error instead of a bare 404.
|
|
3454
|
+
target: "/api/v1/auth/admin/sso/request-domain-verification",
|
|
3455
|
+
params: [
|
|
3456
|
+
{ name: "providerId", field: "provider_id", defaultFromRow: true, required: true },
|
|
3457
|
+
{ name: "domain", field: "domain", defaultFromRow: true, required: false }
|
|
3458
|
+
],
|
|
3459
|
+
resultDialog: {
|
|
3460
|
+
title: "Verify your domain",
|
|
3461
|
+
description: "Add the DNS TXT record below at your domain\u2019s DNS provider, then run \u201CVerify Domain\u201D. The token is shown once.",
|
|
3462
|
+
acknowledge: "Done",
|
|
3463
|
+
fields: [
|
|
3464
|
+
{ path: "data.dnsRecordType", label: "Record type", format: "text" },
|
|
3465
|
+
{ path: "data.dnsRecordName", label: "Name / Host", format: "secret" },
|
|
3466
|
+
{ path: "data.dnsRecordValue", label: "Value", format: "secret" }
|
|
3467
|
+
]
|
|
3468
|
+
}
|
|
3469
|
+
},
|
|
3470
|
+
{
|
|
3471
|
+
name: "verify_domain",
|
|
3472
|
+
label: "Verify Domain",
|
|
3473
|
+
icon: "shield-check",
|
|
3474
|
+
variant: "secondary",
|
|
3475
|
+
locations: ["list_item", "record_header"],
|
|
3476
|
+
type: "api",
|
|
3477
|
+
method: "POST",
|
|
3478
|
+
// ADR-0024 ②. Re-checks the DNS-TXT record and flips `domain_verified`
|
|
3479
|
+
// on success. Routed through the env bridge, which maps @better-auth/sso's
|
|
3480
|
+
// empty 204 / 502 into a clear success/error toast.
|
|
3481
|
+
target: "/api/v1/auth/admin/sso/verify-domain",
|
|
3482
|
+
successMessage: "Domain ownership verified",
|
|
3483
|
+
refreshAfter: true,
|
|
3484
|
+
params: [
|
|
3485
|
+
{ name: "providerId", field: "provider_id", defaultFromRow: true, required: true }
|
|
3486
|
+
]
|
|
3487
|
+
},
|
|
3488
|
+
{
|
|
3489
|
+
name: "delete_sso_provider",
|
|
3490
|
+
label: "Delete SSO Provider",
|
|
3491
|
+
icon: "trash-2",
|
|
3492
|
+
variant: "danger",
|
|
3493
|
+
mode: "delete",
|
|
3494
|
+
locations: ["list_item", "record_header"],
|
|
3495
|
+
type: "api",
|
|
3496
|
+
method: "POST",
|
|
3497
|
+
target: "/api/v1/auth/sso/delete-provider",
|
|
3498
|
+
confirmText: "Delete this SSO provider? Users from its domain will no longer be able to sign in through it.",
|
|
3499
|
+
successMessage: "SSO provider deleted",
|
|
3500
|
+
refreshAfter: true,
|
|
3501
|
+
params: [
|
|
3502
|
+
{ name: "providerId", field: "provider_id", defaultFromRow: true, required: true }
|
|
3503
|
+
]
|
|
3504
|
+
}
|
|
3505
|
+
],
|
|
3506
|
+
listViews: {
|
|
3507
|
+
all: {
|
|
3508
|
+
type: "grid",
|
|
3509
|
+
name: "all",
|
|
3510
|
+
label: "All",
|
|
3511
|
+
data: { provider: "object", object: "sys_sso_provider" },
|
|
3512
|
+
columns: ["provider_id", "issuer", "domain", "domain_verified", "created_at"],
|
|
3513
|
+
sort: [{ field: "provider_id", order: "asc" }],
|
|
3514
|
+
pagination: { pageSize: 50 },
|
|
3515
|
+
// Per-object empty state — the shared identity-object copy ("created
|
|
3516
|
+
// automatically … cannot be added here") is wrong for this object, which
|
|
3517
|
+
// HAS a "Register SSO Provider" action. Point admins at it instead.
|
|
3518
|
+
emptyState: {
|
|
3519
|
+
title: "No SSO providers yet",
|
|
3520
|
+
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.",
|
|
3521
|
+
icon: "log-in"
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
},
|
|
3525
|
+
fields: {
|
|
3526
|
+
id: Field.text({ label: "ID", required: true, readonly: true, group: "System" }),
|
|
3527
|
+
provider_id: Field.text({
|
|
3528
|
+
label: "Provider ID",
|
|
3529
|
+
required: true,
|
|
3530
|
+
searchable: true,
|
|
3531
|
+
maxLength: 255,
|
|
3532
|
+
description: "Stable provider identifier (unique within the environment)",
|
|
3533
|
+
group: "Identity"
|
|
3534
|
+
}),
|
|
3535
|
+
issuer: Field.text({
|
|
3536
|
+
label: "Issuer",
|
|
3537
|
+
required: true,
|
|
3538
|
+
maxLength: 2048,
|
|
3539
|
+
description: "IdP issuer URL",
|
|
3540
|
+
group: "Identity"
|
|
3541
|
+
}),
|
|
3542
|
+
domain: Field.text({
|
|
3543
|
+
label: "Email Domain",
|
|
3544
|
+
required: true,
|
|
3545
|
+
maxLength: 255,
|
|
3546
|
+
description: "Email domain routed to this IdP (e.g. acme.com)",
|
|
3547
|
+
group: "Identity"
|
|
3548
|
+
}),
|
|
3549
|
+
domain_verified: Field.boolean({
|
|
3550
|
+
label: "Domain Verified",
|
|
3551
|
+
defaultValue: false,
|
|
3552
|
+
readonly: true,
|
|
3553
|
+
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.",
|
|
3554
|
+
group: "Identity"
|
|
3555
|
+
}),
|
|
3556
|
+
oidc_config: Field.textarea({
|
|
3557
|
+
label: "OIDC Config",
|
|
3558
|
+
required: false,
|
|
3559
|
+
description: "JSON: clientId, clientSecret, endpoints, scopes, mapping, pkce (managed by better-auth)",
|
|
3560
|
+
group: "Protocol"
|
|
3561
|
+
}),
|
|
3562
|
+
saml_config: Field.textarea({
|
|
3563
|
+
label: "SAML Config",
|
|
3564
|
+
required: false,
|
|
3565
|
+
description: "JSON: entryPoint, cert, identifierFormat, mapping (managed by better-auth)",
|
|
3566
|
+
group: "Protocol"
|
|
3567
|
+
}),
|
|
3568
|
+
user_id: Field.lookup("sys_user", {
|
|
3569
|
+
label: "Registered By",
|
|
3570
|
+
required: false,
|
|
3571
|
+
description: "User who registered this provider",
|
|
3572
|
+
group: "System"
|
|
3573
|
+
}),
|
|
3574
|
+
organization_id: Field.text({
|
|
3575
|
+
label: "Organization",
|
|
3576
|
+
required: false,
|
|
3577
|
+
maxLength: 255,
|
|
3578
|
+
description: "Organization scope (when org-scoped SSO is used)",
|
|
3579
|
+
group: "System"
|
|
3580
|
+
}),
|
|
3581
|
+
created_at: Field.datetime({ label: "Created At", defaultValue: "NOW()", readonly: true, group: "System" }),
|
|
3582
|
+
updated_at: Field.datetime({ label: "Updated At", defaultValue: "NOW()", readonly: true, group: "System" })
|
|
3583
|
+
},
|
|
3584
|
+
indexes: [
|
|
3585
|
+
{ fields: ["provider_id"], unique: true },
|
|
3586
|
+
{ fields: ["domain"] },
|
|
3587
|
+
{ fields: ["user_id"] }
|
|
3588
|
+
],
|
|
3589
|
+
enable: {
|
|
3590
|
+
trackHistory: true,
|
|
3591
|
+
searchable: true,
|
|
3592
|
+
apiEnabled: true,
|
|
3593
|
+
// Mutations go through /api/v1/auth/sso/* (register / delete-provider);
|
|
3594
|
+
// the generic data layer is read-only so sysadmins cannot bypass
|
|
3595
|
+
// server-side validation / secret handling.
|
|
3596
|
+
apiMethods: ["get", "list"],
|
|
3597
|
+
trash: false,
|
|
3598
|
+
mru: false
|
|
3599
|
+
}
|
|
3600
|
+
});
|
|
3601
|
+
var SysScimProvider = ObjectSchema.create({
|
|
3602
|
+
name: "sys_scim_provider",
|
|
3603
|
+
label: "SCIM Provider",
|
|
3604
|
+
pluralLabel: "SCIM Providers",
|
|
3605
|
+
icon: "users",
|
|
3606
|
+
isSystem: true,
|
|
3607
|
+
managedBy: "better-auth",
|
|
3608
|
+
// ADR-0010 §3.7 — managed by better-auth; tenants may not edit schema.
|
|
3609
|
+
protection: {
|
|
3610
|
+
lock: "full",
|
|
3611
|
+
reason: "Identity table managed by better-auth (@better-auth/scim) \u2014 see ADR-0071.",
|
|
3612
|
+
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
3613
|
+
},
|
|
3614
|
+
description: "SCIM 2.0 connections (bearer tokens) external IdPs use to provision/deprovision this environment's users",
|
|
3615
|
+
displayNameField: "provider_id",
|
|
3616
|
+
titleFormat: "{provider_id}",
|
|
3617
|
+
compactLayout: ["provider_id", "organization_id"],
|
|
3618
|
+
listViews: {
|
|
3619
|
+
all: {
|
|
3620
|
+
type: "grid",
|
|
3621
|
+
name: "all",
|
|
3622
|
+
label: "All",
|
|
3623
|
+
data: { provider: "object", object: "sys_scim_provider" },
|
|
3624
|
+
// scim_token is intentionally excluded — never surface the credential.
|
|
3625
|
+
columns: ["provider_id", "organization_id", "created_at"],
|
|
3626
|
+
sort: [{ field: "provider_id", order: "asc" }],
|
|
3627
|
+
pagination: { pageSize: 50 }
|
|
3628
|
+
}
|
|
3629
|
+
},
|
|
3630
|
+
fields: {
|
|
3631
|
+
id: Field.text({ label: "ID", required: true, readonly: true, group: "System" }),
|
|
3632
|
+
provider_id: Field.text({
|
|
3633
|
+
label: "Provider ID",
|
|
3634
|
+
required: true,
|
|
3635
|
+
searchable: true,
|
|
3636
|
+
maxLength: 255,
|
|
3637
|
+
description: 'Stable SCIM provider identifier (e.g. "okta-scim")',
|
|
3638
|
+
group: "Identity"
|
|
3639
|
+
}),
|
|
3640
|
+
scim_token: Field.text({
|
|
3641
|
+
label: "SCIM Token (hash)",
|
|
3642
|
+
required: false,
|
|
3643
|
+
readonly: true,
|
|
3644
|
+
maxLength: 1024,
|
|
3645
|
+
description: "Hashed bearer credential for this SCIM connection \u2014 the plaintext is shown once at generate-token. Sensitive; do not expose.",
|
|
3646
|
+
group: "Secret"
|
|
3647
|
+
}),
|
|
3648
|
+
organization_id: Field.text({
|
|
3649
|
+
label: "Organization",
|
|
3650
|
+
required: false,
|
|
3651
|
+
maxLength: 255,
|
|
3652
|
+
description: "Organization scope of this token (org-scoped tokens restrict provisioning to that org)",
|
|
3653
|
+
group: "System"
|
|
3654
|
+
}),
|
|
3655
|
+
user_id: Field.lookup("sys_user", {
|
|
3656
|
+
label: "Owned By",
|
|
3657
|
+
required: false,
|
|
3658
|
+
description: "User who generated this token (when provider-ownership is enabled)",
|
|
3659
|
+
group: "System"
|
|
3660
|
+
}),
|
|
3661
|
+
created_at: Field.datetime({ label: "Created At", defaultValue: "NOW()", readonly: true, group: "System" }),
|
|
3662
|
+
updated_at: Field.datetime({ label: "Updated At", defaultValue: "NOW()", readonly: true, group: "System" })
|
|
3663
|
+
},
|
|
3664
|
+
indexes: [
|
|
3665
|
+
{ fields: ["provider_id"], unique: true },
|
|
3666
|
+
{ fields: ["organization_id"] },
|
|
3667
|
+
{ fields: ["user_id"] }
|
|
3668
|
+
],
|
|
3669
|
+
enable: {
|
|
3670
|
+
trackHistory: true,
|
|
3671
|
+
searchable: false,
|
|
3672
|
+
apiEnabled: true,
|
|
3673
|
+
// Mutations + token issuance go through @better-auth/scim's endpoints
|
|
3674
|
+
// under /api/v1/auth/scim/*; the generic data layer is read-only so the
|
|
3675
|
+
// credential cannot be written/bypassed through it.
|
|
3676
|
+
apiMethods: ["list"],
|
|
3677
|
+
trash: false,
|
|
3678
|
+
mru: false
|
|
3679
|
+
}
|
|
3680
|
+
});
|
|
3157
3681
|
|
|
3158
|
-
export { SysAccount, SysApiKey, SysBusinessUnit, SysBusinessUnitMember, SysDeviceCode, SysInvitation, SysJwks, SysMember, SysOauthAccessToken, SysOauthApplication, SysOauthConsent, SysOauthRefreshToken, SysOrganization, SysSession, SysTeam, SysTeamMember, SysTwoFactor, SysUser, SysUserPreference, SysVerification };
|
|
3682
|
+
export { SysAccount, SysApiKey, SysBusinessUnit, SysBusinessUnitMember, SysDeviceCode, SysInvitation, SysJwks, SysMember, SysOauthAccessToken, SysOauthApplication, SysOauthConsent, SysOauthRefreshToken, SysOrganization, SysScimProvider, SysSession, SysSsoProvider, SysTeam, SysTeamMember, SysTwoFactor, SysUser, SysUserPreference, SysVerification };
|
|
3159
3683
|
//# sourceMappingURL=index.mjs.map
|
|
3160
3684
|
//# sourceMappingURL=index.mjs.map
|