@objectstack/platform-objects 10.3.0 → 11.0.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 +155 -18
- package/dist/audit/index.d.ts +155 -18
- package/dist/identity/index.d.mts +19777 -13874
- package/dist/identity/index.d.ts +19777 -13874
- package/dist/identity/index.js +363 -8
- package/dist/identity/index.js.map +1 -1
- package/dist/identity/index.mjs +362 -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 +398 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +397 -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 +44 -6
- package/dist/system/index.d.ts +44 -6
- package/package.json +3 -3
package/dist/identity/index.js
CHANGED
|
@@ -35,6 +35,13 @@ var SysUser = data.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 = data.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 = data.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 = data.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 = data.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 = data.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,32 @@ var SysUser = data.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: data.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: data.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
|
+
ai_access: data.Field.boolean({
|
|
426
|
+
label: "AI Access",
|
|
427
|
+
defaultValue: false,
|
|
428
|
+
group: "Admin",
|
|
429
|
+
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)."
|
|
430
|
+
}),
|
|
375
431
|
// ── Profile ──────────────────────────────────────────────────
|
|
376
432
|
image: data.Field.url({
|
|
377
433
|
label: "Profile Image",
|
|
@@ -392,6 +448,28 @@ var SysUser = data.ObjectSchema.create({
|
|
|
392
448
|
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
449
|
}),
|
|
394
450
|
// ── System (auto-managed, hidden from create/edit forms) ─────
|
|
451
|
+
// Identity provenance (ADR-0024 D4). `idp_provisioned` users were
|
|
452
|
+
// JIT-created on first federated login (a `sys_account` exists for an
|
|
453
|
+
// external/OIDC provider — e.g. the cloud-as-IdP `objectstack-cloud`
|
|
454
|
+
// provider, or a customer's own IdP); `env_native` users registered
|
|
455
|
+
// locally (email/password) or are app end-users. Stamped automatically by
|
|
456
|
+
// the AuthManager `account.create.after` hook — never edited by hand.
|
|
457
|
+
// Drives the managed-vs-native user-mgmt UI gating (the password /
|
|
458
|
+
// identity-edit actions hide for managed users, who hold no local
|
|
459
|
+
// credential) and is the marker SCIM lifecycle keys off. Owned by objectql
|
|
460
|
+
// (better-auth is oblivious to this column — like `ai_access`).
|
|
461
|
+
source: data.Field.select({
|
|
462
|
+
label: "Identity Source",
|
|
463
|
+
required: false,
|
|
464
|
+
readonly: true,
|
|
465
|
+
group: "System",
|
|
466
|
+
defaultValue: "env_native",
|
|
467
|
+
options: [
|
|
468
|
+
{ label: "IdP-Provisioned", value: "idp_provisioned" },
|
|
469
|
+
{ label: "Env-Native", value: "env_native" }
|
|
470
|
+
],
|
|
471
|
+
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."
|
|
472
|
+
}),
|
|
395
473
|
id: data.Field.text({
|
|
396
474
|
label: "User ID",
|
|
397
475
|
required: true,
|
|
@@ -757,6 +835,16 @@ var SysAccount = data.ObjectSchema.create({
|
|
|
757
835
|
label: "Password Hash",
|
|
758
836
|
required: false,
|
|
759
837
|
description: "Hashed password for email/password provider"
|
|
838
|
+
}),
|
|
839
|
+
// ADR-0069 D1 — bounded ring of previous password hashes (JSON array of
|
|
840
|
+
// strings), used to reject password reuse on change/reset. Maintained by
|
|
841
|
+
// the auth manager; never exposed in UI.
|
|
842
|
+
previous_password_hashes: data.Field.textarea({
|
|
843
|
+
label: "Previous Password Hashes",
|
|
844
|
+
required: false,
|
|
845
|
+
readonly: true,
|
|
846
|
+
hidden: true,
|
|
847
|
+
description: "JSON array of prior password hashes (bounded by password_history_count); reuse-prevention only. System-managed."
|
|
760
848
|
})
|
|
761
849
|
},
|
|
762
850
|
indexes: [
|
|
@@ -822,7 +910,13 @@ var SysVerification = data.ObjectSchema.create({
|
|
|
822
910
|
})
|
|
823
911
|
},
|
|
824
912
|
indexes: [
|
|
825
|
-
|
|
913
|
+
// `value` must NOT be unique. better-auth's oauth-provider stores OIDC
|
|
914
|
+
// authorization codes in this table with `value` = a JSON blob keyed by
|
|
915
|
+
// user+client+state, which can legitimately repeat. A UNIQUE constraint
|
|
916
|
+
// makes `/api/v1/auth/oauth2/authorize` fail (`UNIQUE constraint failed:
|
|
917
|
+
// sys_verification.value`) → 503, breaking cloud-as-IdP SSO entirely.
|
|
918
|
+
// better-auth keys verification lookups on `identifier`, not `value`.
|
|
919
|
+
{ fields: ["value"], unique: false },
|
|
826
920
|
{ fields: ["identifier"], unique: false },
|
|
827
921
|
{ fields: ["expires_at"], unique: false }
|
|
828
922
|
],
|
|
@@ -890,6 +984,10 @@ var SysOrganization = data.ObjectSchema.create({
|
|
|
890
984
|
recordIdParam: "organizationId",
|
|
891
985
|
// better-auth `organization/update` nests editable fields under `data`.
|
|
892
986
|
bodyShape: { wrap: "data" },
|
|
987
|
+
// Org-admin actions are multi-org-only; hide them in single-org for
|
|
988
|
+
// consistency with `create_organization` (the org list is empty there,
|
|
989
|
+
// but this also guards direct record-URL access).
|
|
990
|
+
visible: "features.multiOrgEnabled != false",
|
|
893
991
|
successMessage: "Organization updated",
|
|
894
992
|
refreshAfter: true,
|
|
895
993
|
params: [
|
|
@@ -908,6 +1006,7 @@ var SysOrganization = data.ObjectSchema.create({
|
|
|
908
1006
|
type: "api",
|
|
909
1007
|
target: "/api/v1/auth/organization/delete",
|
|
910
1008
|
recordIdParam: "organizationId",
|
|
1009
|
+
visible: "features.multiOrgEnabled != false",
|
|
911
1010
|
confirmText: "Delete this organization? All members will lose access immediately. This cannot be undone.",
|
|
912
1011
|
successMessage: "Organization deleted",
|
|
913
1012
|
refreshAfter: true
|
|
@@ -926,6 +1025,7 @@ var SysOrganization = data.ObjectSchema.create({
|
|
|
926
1025
|
type: "api",
|
|
927
1026
|
target: "/api/v1/auth/organization/set-active",
|
|
928
1027
|
recordIdParam: "organizationId",
|
|
1028
|
+
visible: "features.multiOrgEnabled != false",
|
|
929
1029
|
successMessage: "Active organization switched",
|
|
930
1030
|
refreshAfter: true
|
|
931
1031
|
},
|
|
@@ -942,6 +1042,7 @@ var SysOrganization = data.ObjectSchema.create({
|
|
|
942
1042
|
type: "api",
|
|
943
1043
|
target: "/api/v1/auth/organization/leave",
|
|
944
1044
|
recordIdParam: "organizationId",
|
|
1045
|
+
visible: "features.multiOrgEnabled != false",
|
|
945
1046
|
confirmText: "Leave this organization? You will lose access to all of its resources.",
|
|
946
1047
|
successMessage: "You have left the organization",
|
|
947
1048
|
refreshAfter: true
|
|
@@ -963,6 +1064,7 @@ var SysOrganization = data.ObjectSchema.create({
|
|
|
963
1064
|
type: "api",
|
|
964
1065
|
target: "/api/v1/cloud/organizations/{id}/change-slug",
|
|
965
1066
|
method: "POST",
|
|
1067
|
+
visible: "features.multiOrgEnabled != false",
|
|
966
1068
|
confirmText: "Renaming the slug rewrites every platform subdomain for this org and parks the old slug for 90 days. Continue?",
|
|
967
1069
|
successMessage: "Organization slug changed",
|
|
968
1070
|
refreshAfter: true,
|
|
@@ -1061,7 +1163,10 @@ var SysMember = data.ObjectSchema.create({
|
|
|
1061
1163
|
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
1062
1164
|
},
|
|
1063
1165
|
description: "Organization membership records",
|
|
1064
|
-
|
|
1166
|
+
// Org-independent title: organization_id is null in single-org mode, so a
|
|
1167
|
+
// '{user_id} in {organization_id}' format renders "… in null". User + role
|
|
1168
|
+
// identifies the membership in both single- and multi-org deployments.
|
|
1169
|
+
titleFormat: "{user_id} ({role})",
|
|
1065
1170
|
compactLayout: ["user_id", "organization_id", "role"],
|
|
1066
1171
|
// Row-level actions: better-auth `organization/update-member-role` and
|
|
1067
1172
|
// `organization/remove-member`. Generic CRUD is suppressed on better-auth
|
|
@@ -1082,6 +1187,11 @@ var SysMember = data.ObjectSchema.create({
|
|
|
1082
1187
|
locations: ["list_toolbar"],
|
|
1083
1188
|
type: "api",
|
|
1084
1189
|
target: "/api/v1/auth/organization/add-member",
|
|
1190
|
+
// Org-membership mutations are multi-org-only: the better-auth
|
|
1191
|
+
// endpoints resolve an active org that does not exist in single-org
|
|
1192
|
+
// mode, so these actions would fail at the API. Gate every one on
|
|
1193
|
+
// the multi-org flag (mirrors sys_organization.create_organization).
|
|
1194
|
+
visible: "features.multiOrgEnabled != false",
|
|
1085
1195
|
successMessage: "Member added",
|
|
1086
1196
|
refreshAfter: true,
|
|
1087
1197
|
params: [
|
|
@@ -1099,6 +1209,7 @@ var SysMember = data.ObjectSchema.create({
|
|
|
1099
1209
|
type: "api",
|
|
1100
1210
|
target: "/api/v1/auth/organization/update-member-role",
|
|
1101
1211
|
recordIdParam: "memberId",
|
|
1212
|
+
visible: "features.multiOrgEnabled != false",
|
|
1102
1213
|
successMessage: "Member role updated",
|
|
1103
1214
|
refreshAfter: true,
|
|
1104
1215
|
params: [
|
|
@@ -1115,6 +1226,7 @@ var SysMember = data.ObjectSchema.create({
|
|
|
1115
1226
|
type: "api",
|
|
1116
1227
|
target: "/api/v1/auth/organization/remove-member",
|
|
1117
1228
|
recordIdParam: "memberIdOrEmail",
|
|
1229
|
+
visible: "features.multiOrgEnabled != false",
|
|
1118
1230
|
confirmText: "Remove this member from the organization? They will lose access to all org resources.",
|
|
1119
1231
|
successMessage: "Member removed",
|
|
1120
1232
|
refreshAfter: true
|
|
@@ -1136,7 +1248,7 @@ var SysMember = data.ObjectSchema.create({
|
|
|
1136
1248
|
target: "/api/v1/auth/organization/update-member-role",
|
|
1137
1249
|
recordIdParam: "memberId",
|
|
1138
1250
|
bodyExtra: { role: "owner" },
|
|
1139
|
-
visible: "record.role != 'owner'",
|
|
1251
|
+
visible: "record.role != 'owner' && features.multiOrgEnabled != false",
|
|
1140
1252
|
confirmText: "Transfer ownership of this organization to the selected member? You will be demoted to admin and lose owner-only privileges.",
|
|
1141
1253
|
successMessage: "Ownership transferred",
|
|
1142
1254
|
refreshAfter: true
|
|
@@ -1221,7 +1333,10 @@ var SysInvitation = data.ObjectSchema.create({
|
|
|
1221
1333
|
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
1222
1334
|
},
|
|
1223
1335
|
description: "Organization invitations for user onboarding",
|
|
1224
|
-
|
|
1336
|
+
// Title by invitee email rather than organization_id: the latter is null in
|
|
1337
|
+
// single-org mode (renders "Invitation to null"), and the recipient email is
|
|
1338
|
+
// the more useful identifier in both modes anyway.
|
|
1339
|
+
titleFormat: "Invitation for {email}",
|
|
1225
1340
|
compactLayout: ["email", "organization_id", "status"],
|
|
1226
1341
|
// Custom actions — generic CRUD is suppressed (better-auth-managed).
|
|
1227
1342
|
// Mirror the `invite_user` toolbar action from sys_user here so admins
|
|
@@ -1235,6 +1350,13 @@ var SysInvitation = data.ObjectSchema.create({
|
|
|
1235
1350
|
locations: ["list_toolbar"],
|
|
1236
1351
|
type: "api",
|
|
1237
1352
|
target: "/api/v1/auth/organization/invite-member",
|
|
1353
|
+
// Inviting/managing invitations is a multi-org-only flow (the
|
|
1354
|
+
// endpoint resolves an active org absent in single-org mode). Gate
|
|
1355
|
+
// the admin-side actions on the multi-org flag (mirrors
|
|
1356
|
+
// sys_organization.create_organization). The recipient-side
|
|
1357
|
+
// accept/reject actions below stay record-gated — they are
|
|
1358
|
+
// unreachable in single-org anyway (no invitation rows exist).
|
|
1359
|
+
visible: "features.multiOrgEnabled != false",
|
|
1238
1360
|
successMessage: "Invitation sent",
|
|
1239
1361
|
refreshAfter: true,
|
|
1240
1362
|
params: [
|
|
@@ -1252,6 +1374,7 @@ var SysInvitation = data.ObjectSchema.create({
|
|
|
1252
1374
|
type: "api",
|
|
1253
1375
|
target: "/api/v1/auth/organization/cancel-invitation",
|
|
1254
1376
|
recordIdParam: "invitationId",
|
|
1377
|
+
visible: "features.multiOrgEnabled != false",
|
|
1255
1378
|
confirmText: "Cancel this invitation? The recipient will no longer be able to accept it.",
|
|
1256
1379
|
successMessage: "Invitation canceled",
|
|
1257
1380
|
refreshAfter: true
|
|
@@ -1265,6 +1388,7 @@ var SysInvitation = data.ObjectSchema.create({
|
|
|
1265
1388
|
type: "api",
|
|
1266
1389
|
target: "/api/v1/auth/organization/invite-member",
|
|
1267
1390
|
bodyExtra: { resend: true },
|
|
1391
|
+
visible: "features.multiOrgEnabled != false",
|
|
1268
1392
|
successMessage: "Invitation resent",
|
|
1269
1393
|
refreshAfter: true,
|
|
1270
1394
|
params: [
|
|
@@ -1449,6 +1573,10 @@ var SysTeam = data.ObjectSchema.create({
|
|
|
1449
1573
|
locations: ["list_toolbar"],
|
|
1450
1574
|
type: "api",
|
|
1451
1575
|
target: "/api/v1/auth/organization/create-team",
|
|
1576
|
+
// Teams are nested inside organizations — a multi-org-only concept.
|
|
1577
|
+
// Gate every team mutation on the multi-org flag so the affordances
|
|
1578
|
+
// disappear in single-org (mirrors sys_organization.create_organization).
|
|
1579
|
+
visible: "features.multiOrgEnabled != false",
|
|
1452
1580
|
successMessage: "Team created",
|
|
1453
1581
|
refreshAfter: true,
|
|
1454
1582
|
params: [
|
|
@@ -1469,6 +1597,7 @@ var SysTeam = data.ObjectSchema.create({
|
|
|
1469
1597
|
target: "/api/v1/auth/organization/update-team",
|
|
1470
1598
|
recordIdParam: "teamId",
|
|
1471
1599
|
bodyShape: { wrap: "data" },
|
|
1600
|
+
visible: "features.multiOrgEnabled != false",
|
|
1472
1601
|
successMessage: "Team updated",
|
|
1473
1602
|
refreshAfter: true,
|
|
1474
1603
|
params: [
|
|
@@ -1487,6 +1616,7 @@ var SysTeam = data.ObjectSchema.create({
|
|
|
1487
1616
|
type: "api",
|
|
1488
1617
|
target: "/api/v1/auth/organization/remove-team",
|
|
1489
1618
|
recordIdParam: "teamId",
|
|
1619
|
+
visible: "features.multiOrgEnabled != false",
|
|
1490
1620
|
confirmText: "Delete this team? Members will lose any team-scoped access. This cannot be undone.",
|
|
1491
1621
|
successMessage: "Team deleted",
|
|
1492
1622
|
refreshAfter: true
|
|
@@ -1597,6 +1727,10 @@ var SysTeamMember = data.ObjectSchema.create({
|
|
|
1597
1727
|
locations: ["list_toolbar"],
|
|
1598
1728
|
type: "api",
|
|
1599
1729
|
target: "/api/v1/auth/organization/add-team-member",
|
|
1730
|
+
// Team membership lives under organizations — multi-org-only. Gate
|
|
1731
|
+
// both mutations so they vanish in single-org (mirrors
|
|
1732
|
+
// sys_organization.create_organization).
|
|
1733
|
+
visible: "features.multiOrgEnabled != false",
|
|
1600
1734
|
successMessage: "Team member added",
|
|
1601
1735
|
refreshAfter: true,
|
|
1602
1736
|
params: [
|
|
@@ -1617,6 +1751,7 @@ var SysTeamMember = data.ObjectSchema.create({
|
|
|
1617
1751
|
locations: ["list_item"],
|
|
1618
1752
|
type: "api",
|
|
1619
1753
|
target: "/api/v1/auth/organization/remove-team-member",
|
|
1754
|
+
visible: "features.multiOrgEnabled != false",
|
|
1620
1755
|
confirmText: "Remove this user from the team? They will lose any team-scoped access.",
|
|
1621
1756
|
successMessage: "Team member removed",
|
|
1622
1757
|
refreshAfter: true,
|
|
@@ -3156,6 +3291,224 @@ var SysJwks = data.ObjectSchema.create({
|
|
|
3156
3291
|
mru: false
|
|
3157
3292
|
}
|
|
3158
3293
|
});
|
|
3294
|
+
var SysSsoProvider = data.ObjectSchema.create({
|
|
3295
|
+
name: "sys_sso_provider",
|
|
3296
|
+
label: "SSO Provider",
|
|
3297
|
+
pluralLabel: "SSO Providers",
|
|
3298
|
+
icon: "shield-check",
|
|
3299
|
+
isSystem: true,
|
|
3300
|
+
managedBy: "better-auth",
|
|
3301
|
+
// ADR-0010 §3.7 — managed by better-auth; tenants may not edit schema.
|
|
3302
|
+
protection: {
|
|
3303
|
+
lock: "full",
|
|
3304
|
+
reason: "Identity table managed by better-auth (@better-auth/sso) \u2014 see ADR-0024.",
|
|
3305
|
+
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
3306
|
+
},
|
|
3307
|
+
description: "External SSO identity providers (OIDC / SAML) this environment federates login to",
|
|
3308
|
+
displayNameField: "provider_id",
|
|
3309
|
+
titleFormat: "{provider_id}",
|
|
3310
|
+
compactLayout: ["provider_id", "issuer", "domain"],
|
|
3311
|
+
// All mutations go through @better-auth/sso's endpoints under
|
|
3312
|
+
// /api/v1/auth/sso/* (register / delete-provider) rather than the generic
|
|
3313
|
+
// data layer, so server-side config validation + secret handling run.
|
|
3314
|
+
actions: [
|
|
3315
|
+
{
|
|
3316
|
+
name: "register_sso_provider",
|
|
3317
|
+
label: "Register SSO Provider",
|
|
3318
|
+
icon: "plus-circle",
|
|
3319
|
+
variant: "primary",
|
|
3320
|
+
mode: "create",
|
|
3321
|
+
locations: ["list_toolbar"],
|
|
3322
|
+
type: "api",
|
|
3323
|
+
method: "POST",
|
|
3324
|
+
target: "/api/v1/auth/sso/register",
|
|
3325
|
+
refreshAfter: true,
|
|
3326
|
+
params: [
|
|
3327
|
+
{ name: "providerId", label: "Provider ID", type: "text", required: true, helpText: 'Stable identifier, e.g. "okta" or "acme-entra".' },
|
|
3328
|
+
{ name: "issuer", label: "Issuer URL", type: "text", required: true, helpText: "IdP issuer / discovery base, e.g. https://acme.okta.com." },
|
|
3329
|
+
{ name: "domain", label: "Email Domain", type: "text", required: true, helpText: "Users with this email domain are routed to this IdP." },
|
|
3330
|
+
{ name: "clientId", label: "Client ID", type: "text", required: true },
|
|
3331
|
+
{ name: "clientSecret", label: "Client Secret", type: "text", required: true }
|
|
3332
|
+
]
|
|
3333
|
+
},
|
|
3334
|
+
{
|
|
3335
|
+
name: "delete_sso_provider",
|
|
3336
|
+
label: "Delete SSO Provider",
|
|
3337
|
+
icon: "trash-2",
|
|
3338
|
+
variant: "danger",
|
|
3339
|
+
mode: "delete",
|
|
3340
|
+
locations: ["list_item", "record_header"],
|
|
3341
|
+
type: "api",
|
|
3342
|
+
method: "POST",
|
|
3343
|
+
target: "/api/v1/auth/sso/delete-provider",
|
|
3344
|
+
confirmText: "Delete this SSO provider? Users from its domain will no longer be able to sign in through it.",
|
|
3345
|
+
successMessage: "SSO provider deleted",
|
|
3346
|
+
refreshAfter: true,
|
|
3347
|
+
params: [
|
|
3348
|
+
{ name: "providerId", field: "provider_id", defaultFromRow: true, required: true }
|
|
3349
|
+
]
|
|
3350
|
+
}
|
|
3351
|
+
],
|
|
3352
|
+
listViews: {
|
|
3353
|
+
all: {
|
|
3354
|
+
type: "grid",
|
|
3355
|
+
name: "all",
|
|
3356
|
+
label: "All",
|
|
3357
|
+
data: { provider: "object", object: "sys_sso_provider" },
|
|
3358
|
+
columns: ["provider_id", "issuer", "domain", "created_at"],
|
|
3359
|
+
sort: [{ field: "provider_id", order: "asc" }],
|
|
3360
|
+
pagination: { pageSize: 50 }
|
|
3361
|
+
}
|
|
3362
|
+
},
|
|
3363
|
+
fields: {
|
|
3364
|
+
id: data.Field.text({ label: "ID", required: true, readonly: true, group: "System" }),
|
|
3365
|
+
provider_id: data.Field.text({
|
|
3366
|
+
label: "Provider ID",
|
|
3367
|
+
required: true,
|
|
3368
|
+
searchable: true,
|
|
3369
|
+
maxLength: 255,
|
|
3370
|
+
description: "Stable provider identifier (unique within the environment)",
|
|
3371
|
+
group: "Identity"
|
|
3372
|
+
}),
|
|
3373
|
+
issuer: data.Field.text({
|
|
3374
|
+
label: "Issuer",
|
|
3375
|
+
required: true,
|
|
3376
|
+
maxLength: 2048,
|
|
3377
|
+
description: "IdP issuer URL",
|
|
3378
|
+
group: "Identity"
|
|
3379
|
+
}),
|
|
3380
|
+
domain: data.Field.text({
|
|
3381
|
+
label: "Email Domain",
|
|
3382
|
+
required: true,
|
|
3383
|
+
maxLength: 255,
|
|
3384
|
+
description: "Email domain routed to this IdP (e.g. acme.com)",
|
|
3385
|
+
group: "Identity"
|
|
3386
|
+
}),
|
|
3387
|
+
oidc_config: data.Field.textarea({
|
|
3388
|
+
label: "OIDC Config",
|
|
3389
|
+
required: false,
|
|
3390
|
+
description: "JSON: clientId, clientSecret, endpoints, scopes, mapping, pkce (managed by better-auth)",
|
|
3391
|
+
group: "Protocol"
|
|
3392
|
+
}),
|
|
3393
|
+
saml_config: data.Field.textarea({
|
|
3394
|
+
label: "SAML Config",
|
|
3395
|
+
required: false,
|
|
3396
|
+
description: "JSON: entryPoint, cert, identifierFormat, mapping (managed by better-auth)",
|
|
3397
|
+
group: "Protocol"
|
|
3398
|
+
}),
|
|
3399
|
+
user_id: data.Field.lookup("sys_user", {
|
|
3400
|
+
label: "Registered By",
|
|
3401
|
+
required: false,
|
|
3402
|
+
description: "User who registered this provider",
|
|
3403
|
+
group: "System"
|
|
3404
|
+
}),
|
|
3405
|
+
organization_id: data.Field.text({
|
|
3406
|
+
label: "Organization",
|
|
3407
|
+
required: false,
|
|
3408
|
+
maxLength: 255,
|
|
3409
|
+
description: "Organization scope (when org-scoped SSO is used)",
|
|
3410
|
+
group: "System"
|
|
3411
|
+
}),
|
|
3412
|
+
created_at: data.Field.datetime({ label: "Created At", defaultValue: "NOW()", readonly: true, group: "System" }),
|
|
3413
|
+
updated_at: data.Field.datetime({ label: "Updated At", defaultValue: "NOW()", readonly: true, group: "System" })
|
|
3414
|
+
},
|
|
3415
|
+
indexes: [
|
|
3416
|
+
{ fields: ["provider_id"], unique: true },
|
|
3417
|
+
{ fields: ["domain"] },
|
|
3418
|
+
{ fields: ["user_id"] }
|
|
3419
|
+
],
|
|
3420
|
+
enable: {
|
|
3421
|
+
trackHistory: true,
|
|
3422
|
+
searchable: true,
|
|
3423
|
+
apiEnabled: true,
|
|
3424
|
+
// Mutations go through /api/v1/auth/sso/* (register / delete-provider);
|
|
3425
|
+
// the generic data layer is read-only so sysadmins cannot bypass
|
|
3426
|
+
// server-side validation / secret handling.
|
|
3427
|
+
apiMethods: ["get", "list"],
|
|
3428
|
+
trash: false,
|
|
3429
|
+
mru: false
|
|
3430
|
+
}
|
|
3431
|
+
});
|
|
3432
|
+
var SysScimProvider = data.ObjectSchema.create({
|
|
3433
|
+
name: "sys_scim_provider",
|
|
3434
|
+
label: "SCIM Provider",
|
|
3435
|
+
pluralLabel: "SCIM Providers",
|
|
3436
|
+
icon: "users",
|
|
3437
|
+
isSystem: true,
|
|
3438
|
+
managedBy: "better-auth",
|
|
3439
|
+
// ADR-0010 §3.7 — managed by better-auth; tenants may not edit schema.
|
|
3440
|
+
protection: {
|
|
3441
|
+
lock: "full",
|
|
3442
|
+
reason: "Identity table managed by better-auth (@better-auth/scim) \u2014 see ADR-0071.",
|
|
3443
|
+
docsUrl: "https://docs.objectstack.ai/adr/0010-metadata-protection"
|
|
3444
|
+
},
|
|
3445
|
+
description: "SCIM 2.0 connections (bearer tokens) external IdPs use to provision/deprovision this environment's users",
|
|
3446
|
+
displayNameField: "provider_id",
|
|
3447
|
+
titleFormat: "{provider_id}",
|
|
3448
|
+
compactLayout: ["provider_id", "organization_id"],
|
|
3449
|
+
listViews: {
|
|
3450
|
+
all: {
|
|
3451
|
+
type: "grid",
|
|
3452
|
+
name: "all",
|
|
3453
|
+
label: "All",
|
|
3454
|
+
data: { provider: "object", object: "sys_scim_provider" },
|
|
3455
|
+
// scim_token is intentionally excluded — never surface the credential.
|
|
3456
|
+
columns: ["provider_id", "organization_id", "created_at"],
|
|
3457
|
+
sort: [{ field: "provider_id", order: "asc" }],
|
|
3458
|
+
pagination: { pageSize: 50 }
|
|
3459
|
+
}
|
|
3460
|
+
},
|
|
3461
|
+
fields: {
|
|
3462
|
+
id: data.Field.text({ label: "ID", required: true, readonly: true, group: "System" }),
|
|
3463
|
+
provider_id: data.Field.text({
|
|
3464
|
+
label: "Provider ID",
|
|
3465
|
+
required: true,
|
|
3466
|
+
searchable: true,
|
|
3467
|
+
maxLength: 255,
|
|
3468
|
+
description: 'Stable SCIM provider identifier (e.g. "okta-scim")',
|
|
3469
|
+
group: "Identity"
|
|
3470
|
+
}),
|
|
3471
|
+
scim_token: data.Field.text({
|
|
3472
|
+
label: "SCIM Token (hash)",
|
|
3473
|
+
required: false,
|
|
3474
|
+
readonly: true,
|
|
3475
|
+
maxLength: 1024,
|
|
3476
|
+
description: "Hashed bearer credential for this SCIM connection \u2014 the plaintext is shown once at generate-token. Sensitive; do not expose.",
|
|
3477
|
+
group: "Secret"
|
|
3478
|
+
}),
|
|
3479
|
+
organization_id: data.Field.text({
|
|
3480
|
+
label: "Organization",
|
|
3481
|
+
required: false,
|
|
3482
|
+
maxLength: 255,
|
|
3483
|
+
description: "Organization scope of this token (org-scoped tokens restrict provisioning to that org)",
|
|
3484
|
+
group: "System"
|
|
3485
|
+
}),
|
|
3486
|
+
user_id: data.Field.lookup("sys_user", {
|
|
3487
|
+
label: "Owned By",
|
|
3488
|
+
required: false,
|
|
3489
|
+
description: "User who generated this token (when provider-ownership is enabled)",
|
|
3490
|
+
group: "System"
|
|
3491
|
+
}),
|
|
3492
|
+
created_at: data.Field.datetime({ label: "Created At", defaultValue: "NOW()", readonly: true, group: "System" }),
|
|
3493
|
+
updated_at: data.Field.datetime({ label: "Updated At", defaultValue: "NOW()", readonly: true, group: "System" })
|
|
3494
|
+
},
|
|
3495
|
+
indexes: [
|
|
3496
|
+
{ fields: ["provider_id"], unique: true },
|
|
3497
|
+
{ fields: ["organization_id"] },
|
|
3498
|
+
{ fields: ["user_id"] }
|
|
3499
|
+
],
|
|
3500
|
+
enable: {
|
|
3501
|
+
trackHistory: true,
|
|
3502
|
+
searchable: false,
|
|
3503
|
+
apiEnabled: true,
|
|
3504
|
+
// Mutations + token issuance go through @better-auth/scim's endpoints
|
|
3505
|
+
// under /api/v1/auth/scim/*; the generic data layer is read-only so the
|
|
3506
|
+
// credential cannot be written/bypassed through it.
|
|
3507
|
+
apiMethods: ["list"],
|
|
3508
|
+
trash: false,
|
|
3509
|
+
mru: false
|
|
3510
|
+
}
|
|
3511
|
+
});
|
|
3159
3512
|
|
|
3160
3513
|
exports.SysAccount = SysAccount;
|
|
3161
3514
|
exports.SysApiKey = SysApiKey;
|
|
@@ -3170,7 +3523,9 @@ exports.SysOauthApplication = SysOauthApplication;
|
|
|
3170
3523
|
exports.SysOauthConsent = SysOauthConsent;
|
|
3171
3524
|
exports.SysOauthRefreshToken = SysOauthRefreshToken;
|
|
3172
3525
|
exports.SysOrganization = SysOrganization;
|
|
3526
|
+
exports.SysScimProvider = SysScimProvider;
|
|
3173
3527
|
exports.SysSession = SysSession;
|
|
3528
|
+
exports.SysSsoProvider = SysSsoProvider;
|
|
3174
3529
|
exports.SysTeam = SysTeam;
|
|
3175
3530
|
exports.SysTeamMember = SysTeamMember;
|
|
3176
3531
|
exports.SysTwoFactor = SysTwoFactor;
|