@objectstack/platform-objects 11.0.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/audit/index.d.mts +18 -346
- package/dist/audit/index.d.ts +18 -346
- package/dist/identity/index.d.mts +1418 -660
- package/dist/identity/index.d.ts +1418 -660
- package/dist/identity/index.js +178 -7
- package/dist/identity/index.js.map +1 -1
- package/dist/identity/index.mjs +178 -7
- package/dist/identity/index.mjs.map +1 -1
- package/dist/index.js +178 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +178 -7
- package/dist/index.mjs.map +1 -1
- package/dist/system/index.d.mts +6 -100
- package/dist/system/index.d.ts +6 -100
- package/package.json +3 -3
package/dist/identity/index.js
CHANGED
|
@@ -422,6 +422,25 @@ var SysUser = data.ObjectSchema.create({
|
|
|
422
422
|
group: "Admin",
|
|
423
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
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: data.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: data.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
|
+
}),
|
|
425
444
|
ai_access: data.Field.boolean({
|
|
426
445
|
label: "AI Access",
|
|
427
446
|
defaultValue: false,
|
|
@@ -594,6 +613,29 @@ var SysSession = data.ObjectSchema.create({
|
|
|
594
613
|
required: true,
|
|
595
614
|
group: "Session"
|
|
596
615
|
}),
|
|
616
|
+
// ── ADR-0069 D4 — session controls (idle / absolute / revoke) ──
|
|
617
|
+
last_activity_at: data.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: data.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: data.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
|
+
}),
|
|
597
639
|
// ── Active context (multi-org/team) ──────────────────────────
|
|
598
640
|
active_organization_id: data.Field.lookup("sys_organization", {
|
|
599
641
|
label: "Active Organization",
|
|
@@ -1114,6 +1156,17 @@ var SysOrganization = data.ObjectSchema.create({
|
|
|
1114
1156
|
description: "JSON-serialized organization metadata",
|
|
1115
1157
|
group: "Configuration"
|
|
1116
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: data.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
|
+
}),
|
|
1117
1170
|
// ── System ───────────────────────────────────────────────────
|
|
1118
1171
|
id: data.Field.text({
|
|
1119
1172
|
label: "Organization ID",
|
|
@@ -3298,6 +3351,22 @@ var SysSsoProvider = data.ObjectSchema.create({
|
|
|
3298
3351
|
icon: "shield-check",
|
|
3299
3352
|
isSystem: true,
|
|
3300
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"],
|
|
3301
3370
|
// ADR-0010 §3.7 — managed by better-auth; tenants may not edit schema.
|
|
3302
3371
|
protection: {
|
|
3303
3372
|
lock: "full",
|
|
@@ -3321,14 +3390,101 @@ var SysSsoProvider = data.ObjectSchema.create({
|
|
|
3321
3390
|
locations: ["list_toolbar"],
|
|
3322
3391
|
type: "api",
|
|
3323
3392
|
method: "POST",
|
|
3324
|
-
|
|
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",
|
|
3325
3400
|
refreshAfter: true,
|
|
3326
3401
|
params: [
|
|
3327
3402
|
{ 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
|
|
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 }
|
|
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 }
|
|
3332
3488
|
]
|
|
3333
3489
|
},
|
|
3334
3490
|
{
|
|
@@ -3355,9 +3511,17 @@ var SysSsoProvider = data.ObjectSchema.create({
|
|
|
3355
3511
|
name: "all",
|
|
3356
3512
|
label: "All",
|
|
3357
3513
|
data: { provider: "object", object: "sys_sso_provider" },
|
|
3358
|
-
columns: ["provider_id", "issuer", "domain", "created_at"],
|
|
3514
|
+
columns: ["provider_id", "issuer", "domain", "domain_verified", "created_at"],
|
|
3359
3515
|
sort: [{ field: "provider_id", order: "asc" }],
|
|
3360
|
-
pagination: { pageSize: 50 }
|
|
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
|
+
}
|
|
3361
3525
|
}
|
|
3362
3526
|
},
|
|
3363
3527
|
fields: {
|
|
@@ -3384,6 +3548,13 @@ var SysSsoProvider = data.ObjectSchema.create({
|
|
|
3384
3548
|
description: "Email domain routed to this IdP (e.g. acme.com)",
|
|
3385
3549
|
group: "Identity"
|
|
3386
3550
|
}),
|
|
3551
|
+
domain_verified: data.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
|
+
}),
|
|
3387
3558
|
oidc_config: data.Field.textarea({
|
|
3388
3559
|
label: "OIDC Config",
|
|
3389
3560
|
required: false,
|