Skip to content

Latest commit

 

History

History
2381 lines (1790 loc) · 97.5 KB

File metadata and controls

2381 lines (1790 loc) · 97.5 KB

Changelog

15.1.1

Patch Changes

  • 9dbb883: Contain the blast radius of a failing optional better-auth plugin: core email/password + session auth now stays up when an optional feature plugin throws during initialization.

    Previously, one throwing optional plugin (the 15.1.0 incident: @better-auth/oauth-provider threw Cannot set properties of undefined (setting 'modelName') from a 1.6/1.7 version mix) failed the whole lazily-built better-auth instance, turning EVERY auth endpoint — sign-up, sign-in, get-session — into a 500.

    AuthManager.buildPluginList now classifies plugins in two tiers. Optional feature plugins (organization, admin, phoneNumber, magicLink, genericOAuth, jwt+oauthProvider as one atomic unit, sso, scim, deviceAuthorization) are constructed through an isolation wrapper: on failure the feature is skipped with a loud actionable console.error, recorded in getDegradedAuthFeatures(), and its endpoints 404 while core auth keeps working. Security-bearing plugins (bearer, twoFactor, haveIBeenPwned, customSession with its ADR-0069 authGate) still fail hard — better a hard 500 than silently weakened auth (e.g. 2FA-enrolled accounts signing in on password alone).

    The OIDC discovery mount (/.well-known/{oauth-authorization-server,openid-configuration}) checks the degraded set and skips advertising an IdP whose endpoints did not come up, with a clear error log instead of sending external clients into 404s.

  • 01ba3b3: Fix fresh-project auth returning 500 on every endpoint (sign-up / sign-in / get-session) with Cannot set properties of undefined (setting 'modelName').

    The published manifest declared better-auth, @better-auth/core, @better-auth/oauth-provider, and @better-auth/sso as ^1.6.23, while only @better-auth/scim was pinned to 1.7.0-rc.1 (GHSA-j8v8-g9cx-5qf4 is fixed only in the 1.7.0 pre-release line). The framework workspace forces the whole better-auth family to 1.7.0-rc.1 via pnpm overrides, but overrides do not ship with published packages — a downstream npx create-objectstack install resolved the ^1.6.23 ranges to 1.6.23 (still the npm latest), and the resulting 1.7/1.6 mix crashes during better-auth initialization, so every fresh 15.1.0 project shipped with broken auth.

    All four packages are now pinned to the exact 1.7.0-rc.1 — the only combination the workspace actually builds and tests against. The pins will be relaxed to ^1.7.0 once a stable better-auth 1.7.0 ships. A new CI gate (scripts/check-override-consistency.mjs) fails whenever a pnpm-workspace override target is not reachable from a publishable package's declared range, so tested-vs-published drift like this cannot recur silently.

    • @objectstack/spec@15.1.1
    • @objectstack/core@15.1.1
    • @objectstack/types@15.1.1
    • @objectstack/platform-objects@15.1.1
    • @objectstack/rest@15.1.1

15.1.0

Patch Changes

  • f531a26: fix(plugin-auth): re-run membership backfill when app seeding settles (#2996)

    The ADR-0093 D6 membership backfill — the only safety net for users created by app seeds (raw engine.insert into sys_user bypasses better-auth's user.create.after reconciler) — ran only once on kernel:ready. When a seed bundle overruns its inline budget (OS_INLINE_SEED_BUDGET_MS, default 8s) it finishes in the background after kernel:ready, so its users stayed member-less in single-org auto mode until the next restart re-ran the backfill.

    AppPlugin now emits a new app:seeded lifecycle event when an app's inline seed settles (success, partial, or fallback) — carrying { appId, overBudget }, where overBudget: true marks the post-kernel:ready background case. plugin-auth subscribes and re-runs the (idempotent, self-guarding, opt-out-able) backfillMemberships on that signal, closing the window without waiting for a restart. No behavior change when a seed completes within budget, in multi-tenant mode, or under invite-only policy; OS_SKIP_MEMBERSHIP_BACKFILL=1 still opts out.

  • 4109153: Close the @better-auth/oauth-provider 1.7 schema drift that broke platform SSO (token exchange 500: table sys_oauth_access_token has no column named authorizationCodeId).

    • sys_oauth_access_token / sys_oauth_refresh_token: add authorization_code_id, resources, requested_user_info_claims, confirmation (+ access-token revoked; + refresh-token rotated_at, rotation_replay_response, rotation_replay_expires_at).
    • sys_oauth_consent: add resources, requested_user_info_claims.
    • sys_oauth_application: add jwks, jwks_uri, backchannel_logout_uri, backchannel_logout_session_required, dpop_bound_access_tokens.
    • New platform objects for the three models 1.7 introduced: sys_oauth_resource, sys_oauth_client_resource, sys_oauth_client_assertion (RFC 8707 resource indicators + RFC 7523 client-assertion replay prevention), registered in the auth manifest and mapped in buildOauthProviderPluginSchema().
    • All camelCase→snake_case fieldName mappings extended accordingly, and a new parity test (oauth-provider-schema-parity.test.ts) fails the build whenever a future better-auth bump introduces model fields our objects or mappings don't cover.
  • f531a26: fix(auth): align the better-auth family on 1.7.0-rc.1, implement the new adapter methods, and add the new sys_jwks columns (#2974)

    Remediating GHSA-p2fr-6hmx-4528 (@better-auth/oauth-provider) requires the 1.7 plugin line, which imports CLIENT_ASSERTION_TYPE and other symbols that only exist in @better-auth/core 1.7.x — so the whole better-auth family is pinned to 1.7.0-rc.1 together (mixing a 1.7 plugin with 1.6.23 core 500s on sign-in). better-auth 1.7 also extends its CustomAdapter contract with two new methods, which the ObjectQL adapter now implements:

    • consumeOne — atomic single-row consume (find the guarded row, delete it, return it), used by better-auth for single-use credential consumption (e.g. verification tokens on the sign-in path).
    • incrementOne — guarded counter mutation (field = field + delta per increment entry plus any absolute set values), returning the updated row or null when the guard matches nothing.

    Both are find-then-write mirrors of the existing delete / update methods (ObjectQL exposes no native atomic primitive) and honour the same core/plugin field-name bridging.

    better-auth 1.7 also extends its jwks model with two new optional columns, alg (signing algorithm, e.g. EdDSA) and crv (curve, e.g. Ed25519), and writes them when minting signing keys. The sys_jwks platform object gains the matching fields — without them every JWKS write failed (table sys_jwks has no column named alg), 500ing token signing and breaking session validation (sign-in succeeded but every authenticated request 401'd).

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [3fe9df1]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [4109153]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [627f225]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

  • Updated dependencies [f531a26]

    • @objectstack/spec@15.1.0
    • @objectstack/rest@15.1.0
    • @objectstack/platform-objects@15.1.0
    • @objectstack/core@15.1.0
    • @objectstack/types@15.1.0

15.0.0

Patch Changes

  • Updated dependencies [02a014b]
  • Updated dependencies [28b7c28]
  • Updated dependencies [13749ec]
  • Updated dependencies [e62c233]
  • Updated dependencies [ed61c9b]
  • Updated dependencies [a581a65]
  • Updated dependencies [31d04d4]
    • @objectstack/platform-objects@15.0.0
    • @objectstack/spec@15.0.0
    • @objectstack/core@15.0.0
    • @objectstack/rest@15.0.0
    • @objectstack/types@15.0.0

14.8.0

Patch Changes

  • Updated dependencies [16b4bf6]
  • Updated dependencies [16b4bf6]
  • Updated dependencies [10e8983]
  • Updated dependencies [607aaf4]
  • Updated dependencies [e46169c]
  • Updated dependencies [bb71321]
    • @objectstack/spec@14.8.0
    • @objectstack/rest@14.8.0
    • @objectstack/platform-objects@14.8.0
    • @objectstack/core@14.8.0
    • @objectstack/types@14.8.0

14.7.0

Minor Changes

  • 824a395: Tenancy mode as a first-class capability + a single owner for the user→membership lifecycle (ADR-0093, Phases 1–3).

    Tenancy service (@objectstack/types, @objectstack/plugin-auth). plugin-auth registers a tenancy service — the single source of truth for tenancy mode (mode, isolationActive, requested, degraded, defaultOrgId()). It derives isolationActive from the presence of the org-scoping service, so the enterprise @objectstack/organizations package lights it up with no change. SecurityPlugin's RLS-strip gate and /auth/config (features.multiOrgEnabled, new features.degradedTenancy) now consume it instead of re-deriving the fact.

    Fail-fast on degraded tenancy (@objectstack/cli, ADR-0093 D5). OS_MULTI_ORG_ENABLED=true without a working @objectstack/organizations now refuses to boot — a deployment that requested tenant isolation must not serve traffic without it (tenant RLS would be silently stripped). Escape hatch: OS_ALLOW_DEGRADED_TENANCY=1 boots in an explicitly branded degraded state (features.degradedTenancy). This may halt upgrades for deployments that were silently degraded — intentionally; install the enterprise package or set the escape hatch.

    Membership reconciler (@objectstack/plugin-auth, ADR-0093 D1–D3, D6). A single reconciler composed into better-auth's user.create.after hook owns the "every new user gets a membership" invariant across all creation paths (signup, admin create-user, import, SSO JIT). It yields to any existing membership (host hooks win), honors a new membershipPolicy: 'auto' | 'invite-only' auth option (default auto), and binds only to an unambiguous target org (single-org default; multi-org binds nothing). A bounded, idempotent kernel:ready backfill covers pre-existing member-less users in single-org/auto deployments (OS_SKIP_MEMBERSHIP_BACKFILL=1 to opt out). The endpoint-level create-user bind from #2882 now delegates to this shared reconciler.

    New env vars: OS_ALLOW_DEGRADED_TENANCY, OS_SKIP_MEMBERSHIP_BACKFILL. New docs: Deployment → Tenancy Modes & Membership.

Patch Changes

  • da5e686: fix(auth): create-user membership bind is tenancy-mode-aware; export the ADR-0093 host API

    Multi-org runtime verification (real @objectstack/organizations linked into a live stack) caught a gap in the #2884 endpoint bind: it resolved its target org via resolveDefaultOrgId (slug='default' first), so in a multi-org deployment — where the bootstrap default org coexists with real tenant orgs — /admin/create-user would have bound the new user into the default org, violating ADR-0093 D3 ("the framework never guesses in multi mode"). The bind now consults the tenancy service (getTenancy on the endpoint deps): single mode → default org, multi mode → no bind. Verified live: multi-org create-user and sign-up both leave the new user member-less (invites / host hooks own membership there); single-org behavior unchanged.

    Also exports reconcile-membership and tenancy-service from the package index as the public host API, and adds dogfood integration tests driving the REAL better-auth pipeline: sign-up membership via the reconciler hook alone, backfill bind + idempotency, invite-only refusal, and the yield-to-host-membership rule.

  • Updated dependencies [d6a72eb]

  • Updated dependencies [824a395]

    • @objectstack/spec@14.7.0
    • @objectstack/types@14.7.0
    • @objectstack/core@14.7.0
    • @objectstack/platform-objects@14.7.0
    • @objectstack/rest@14.7.0

14.6.0

Patch Changes

  • 160d565: fix(auth): guarantee an absolute https origin for every user-facing auth URL

    Follow-up to the invitation-link fix. Several other user-facing links were built from the raw config.baseUrl with no scheme guarantee, so a bare-host baseUrl (e.g. cloud.objectos.ai) produced relative-looking, unclickable links. All now flow through the hardened getCanonicalOrigin() (prepends https:// when the scheme is missing, trims a trailing slash):

    • better-auth baseURL — the reset-password, verify-email and magic-link email links are derived from it.
    • OAuth loginPage / consentPage redirect targets.
    • Device-authorization verificationUri.
    • The phone-invite SMS {{baseUrl}}.

    Deployments that already configure an absolute baseUrl are unaffected.

  • e4cf774: fix(auth): single-source Console page-URL construction; correct SMS + OAuth-callback landing paths

    Root-cause hardening after the invitation-link fixes. Every user-facing link to a Console page is ${origin}${uiBasePath}${path}, but that composition was hand-written at each call site — which is how the scheme / /_console prefix kept getting dropped one link at a time.

    plugin-auth

    • New single-source getConsolePageUrl(path) helper; loginPage, consentPage, device verificationUri and the invitation accept URL all compose through it, so future page links can't drift.
    • Phone-invite SMS now links to the actual Console sign-in page (${origin}${uiBasePath}/login) via a new {{loginUrl}} template variable instead of the bare origin. {{baseUrl}} is still provided for backward compatibility with tenant-overridden templates.

    client

    • signInWithProvider now defaults callbackURL to the current page (window.location.href) instead of a hard-coded origin + '/login'. The SDK cannot know the app's mount path (Console lives under /_console), so returning the user to where they started is the only base-path-correct default; it also mirrors linkSocial. Pass an explicit callbackURL to override.
  • Updated dependencies [609cb13]

  • Updated dependencies [ce6d151]

    • @objectstack/spec@14.6.0
    • @objectstack/platform-objects@14.6.0
    • @objectstack/core@14.6.0
    • @objectstack/rest@14.6.0
    • @objectstack/types@14.6.0

14.5.0

Minor Changes

  • a348394: feat(auth): identity write guard — managedBy: 'better-auth' is now enforced at the engine (ADR-0092 D2/D3/D6)

    Every object whose schema declares managedBy: 'better-auth' (sys_user, sys_member, sys_session, sys_api_key, …) is now protected by engine beforeInsert / beforeUpdate / beforeDelete hooks registered by plugin-auth: user-context writes through the generic data path are rejected fail-closed with 403 PERMISSION_DENIED, closing the hole where a wildcard admin permission set could raw-write any identity column (including email and credential stamps) via the data API. Internal writes are unaffected — the better-auth adapter, isSystem plugin/system contexts, and the identity import keep working unchanged.

    The only opening is a per-object update whitelist (registerManagedUpdateWhitelist(object, fields)): non-whitelisted fields are stripped from the payload, and a payload that strips to nothing throws. The first registration ships here: sys_user → { name, image } (pure profile fields), backed by the new shared SYS_USER_PROFILE_EDIT_FIELDS / SYS_USER_IMPORT_UPDATE_FIELDS constants — the import upsert's field discipline is now derived from the same module (subset-by-construction, no drift).

    After a guarded profile edit, an afterUpdate companion hook re-writes the user's cached {session, user} snapshots in better-auth's secondary storage (same TTL, mirror of better-auth's own refreshUserSessions) so session reads stay coherent; it rewrites rather than deletes, and no-ops when no secondary storage is wired.

    Migration note: server-side scripts that previously updated identity tables with a user execution context must either run with a system context ({ isSystem: true }) if they are genuinely internal, or move to the dedicated auth endpoints (invite / create-user / set-user-password / ban / better-auth APIs). Flows and automations that wrote non-profile sys_user columns under a user identity are now filtered the same way.

  • 5bced2f: feat(auth): passwordPolicy: 'none' is the identity import's new default — import provisions identity, not credentials

    POST /api/v1/auth/admin/import-users now supports (and defaults to) passwordPolicy: 'none': accounts are created without a credential record (better-auth's optional-password create), so no password material is generated, returned, or distributed at all. Users first sign in through a channel — phone OTP, magic link, or a password-reset link — and the Console's existing credential-less detection (hasLocalPassword() → set-initial-password) nudges them to set a password afterwards.

    The invite policy also no longer mints a throwaway password: it creates the same credential-less account and sends the set-your-password invitation (better-auth's reset flow creates the credential record on first set). temporary is unchanged and remains the fallback for deployments without email/SMS infrastructure.

    Breaking-ish note: passwordPolicy was previously required — requests that omitted it got a 400. They now succeed with the none behavior.

  • e2c05d6: feat(auth/i18n): localised, tenant-customisable phone SMS texts (#2815)

    The OTP and invitation SMS bodies were hard-coded English. They now resolve in two layers: a sys_notification_template row for (auth.phone_otp | auth.phone_invite, channel 'sms', locale) — editable in Setup, seeded once with built-in en/zh rows, tenant edits never overwritten — falling back to the bundled bilingual texts. The locale follows the deployment default (localization.locale setting, live-rebound); per-user locale is deferred until sys_user grows a locale column. The OTP wording is purpose-neutral (one provider template covers sign-in and reset, and the SMS reveals nothing about what the code unlocks). Template lookups are best-effort — an outage never blocks an OTP send — and the no-OTP-in-logs red line is unchanged.

Patch Changes

  • 3fd87b2: fix(auth): invitation accept link is now an absolute URL under the Console base

    sendInvitationEmail built the accept URL straight from config.baseUrl with no scheme guarantee and no UI mount prefix — ${baseUrl}/accept-invitation/<id>. Two problems surfaced in real deployments:

    1. When baseUrl was a bare host (e.g. cloud.objectos.ai, no scheme), the emailed link was relative-looking; email clients would not linkify it and clicking it went nowhere.
    2. The accept-invitation page is a Console SPA route mounted under uiBasePath (default /_console) — the same router/basename as /login, /register and /oauth/consent, and the exact link the Console itself generates for its "copy invitation link" action (${origin}${BASE_URL}accept-invitation/<id>). The root-path link omitted that prefix, so it 404'd at the host root instead of resolving to the page.

    The link is now built as ${origin}${uiBasePath}/accept-invitation/<id> via a hardened getCanonicalOrigin() that guarantees an absolute origin (prepends https:// when baseUrl has no scheme). The scheme hardening also applies to the OAuth issuer / consent / device-flow URLs that share the helper. Deployments that mount the Console elsewhere are honoured through the existing uiBasePath config.

  • Updated dependencies [526805e]

  • Updated dependencies [d79ca07]

  • Updated dependencies [4d9dd7b]

  • Updated dependencies [33ebd34]

  • Updated dependencies [c044f08]

  • Updated dependencies [01274eb]

  • Updated dependencies [8f23746]

  • Updated dependencies [b97af7e]

  • Updated dependencies [6da03ee]

    • @objectstack/spec@14.5.0
    • @objectstack/platform-objects@14.5.0
    • @objectstack/rest@14.5.0
    • @objectstack/core@14.5.0
    • @objectstack/types@14.5.0

14.4.0

Patch Changes

  • 9887465: fix(auth): make the self-service "Resend Verification Email" action work

    better-auth's stock POST /send-verification-email requires { email } in the body, but the sys_user resend_verification_email action (record-header button, "email unverified" record alert, and record-section quick action) fires with an empty body — so the request bounced with [body.email] Invalid input: expected string, received undefined and the button was permanently broken. A thin wrapper route now defaults the address to the authenticated caller's own session email when the body omits it, then re-dispatches through the real route. An explicitly-supplied email (admin / verify-screen path) passes through untouched.

  • Updated dependencies [7953832]

  • Updated dependencies [82e745e]

  • Updated dependencies [f3035bd]

  • Updated dependencies [82c0d94]

  • Updated dependencies [7449476]

    • @objectstack/spec@14.4.0
    • @objectstack/platform-objects@14.4.0
    • @objectstack/core@14.4.0
    • @objectstack/rest@14.4.0
    • @objectstack/types@14.4.0

14.3.0

Minor Changes

  • 2a71f48: feat(auth): admin direct user management, phone sign-in, and identity bulk import (#2766, re-scoped #2758)

    sys_user is managed by better-auth and its generic CRUD is suppressed, so until now the only way to add a teammate was the email-dependent invite flow. This ships three staged capabilities:

    • Admin direct user managementPOST /api/v1/auth/admin/create-user and a wrapped POST /api/v1/auth/admin/set-user-password (ADR-0068 platform-admin gate; better-auth pipeline so credentials are real). Optional generated temporary password (returned once, never persisted or logged) and a new sys_user.must_change_password flag enforced through the ADR-0069 authGate (403 PASSWORD_EXPIRED until the user changes it). New create_user action and upgraded set_user_password action on the Users list — pure schema, no frontend changes.
    • Phone sign-in (opt-in auth.plugins.phoneNumber) — better-auth phoneNumber plugin, phone+password only (POST /sign-in/phone-number); OTP flows stay off until SMS infrastructure exists. Adds sys_user.phone_number (unique) / phone_number_verified. Phone-only accounts get an undeliverable placeholder email (u-<random>@placeholder.invalid, never derived from the phone number); all auth mail callbacks refuse placeholder recipients.
    • Identity bulk importPOST /api/v1/auth/admin/import-users accepts the same payloads as the generic import routes (rows/csv/xlsx, dryRun, upsert by email or phone) but writes every row through better-auth. Password policies: invite (reset-link email per created user; requires an EmailService) and temporary (per-row one-time passwords + forced change). Sync only, ≤500 rows per request; no undo; upsert updates touch profile fields only and can never reset an existing user's password. prepareImportRequest and the CSV/xlsx parsers moved from rest-server.ts to an exported import-prepare.ts module (behavior unchanged).
  • c1064f1: feat(messaging/auth): SMS infrastructure + phone-number OTP first-login/reset (#2780)

    #2766 shipped phone+password sign-in but no OTP — the platform had no SMS delivery capability. This adds the missing infrastructure end to end:

    • New @objectstack/plugin-smsISmsService/ISmsTransport contracts (spec) with Aliyun SMS (ACS3-HMAC-SHA256, template-based) and Twilio transports plus a dev log fallback. Configured through the new sms settings namespace (live provider rebind, encrypted secrets, send-test action; OS_SMS_* env keys win at the resolver). Deliberately NO message persistence and NO body logging — SMS bodies carry OTP codes.
    • Messaging sms channel — registered at kernel:ready when an sms service is present; notify(channels:['sms']) resolves sys_user.phone_number, renders (topic,'sms',locale) templates, and inherits outbox retry/dead-letter.
    • Phone OTP flows open — the phoneNumber plugin's sendOTP / sendPasswordResetOTP now deliver via SMS, enabling /phone-number/send-otp + /verify (OTP sign-in/verification) and /phone-number/request-password-reset + /reset-password (self-service reset). Without a deliverable SMS service they keep failing loudly (NOT_SUPPORTED); features.phoneNumberOtp advertises real availability. Shipped with the abuse hardening: explicit allowedAttempts: 3, always-on per-number cooldown (60s) + rolling-hour cap (5, secondaryStorage-shared across nodes), /phone-number/* in the settings-bound per-IP rate-limit rules, and OTP codes never reach logs or error messages.
    • Import SMS invites/admin/import-users's invite policy now supports phone-only rows: a credential-free invitation SMS points the employee at phone-OTP first sign-in followed by self-set password; mixed files validate the reachable channel per row.

Patch Changes

  • Updated dependencies [2a71f48]
  • Updated dependencies [02f6af4]
  • Updated dependencies [c1064f1]
  • Updated dependencies [bea4b92]
    • @objectstack/platform-objects@14.3.0
    • @objectstack/rest@14.3.0
    • @objectstack/spec@14.3.0
    • @objectstack/core@14.3.0
    • @objectstack/types@14.3.0

14.2.0

Patch Changes

  • Updated dependencies [ac8f029]
  • Updated dependencies [4ab9958]
    • @objectstack/spec@14.2.0
    • @objectstack/platform-objects@14.2.0
    • @objectstack/core@14.2.0
    • @objectstack/types@14.2.0

14.1.0

Patch Changes

  • Updated dependencies [5a8465f]
  • Updated dependencies [7f8620b]
  • Updated dependencies [82ba3a6]
    • @objectstack/spec@14.1.0
    • @objectstack/core@14.1.0
    • @objectstack/platform-objects@14.1.0
    • @objectstack/types@14.1.0

14.0.0

Patch Changes

  • Updated dependencies [0a8e685]
  • Updated dependencies [afa8115]
  • Updated dependencies [80f12ca]
  • Updated dependencies [332b711]
  • Updated dependencies [e2fa074]
  • Updated dependencies [23c8668]
  • Updated dependencies [29f017d]
  • Updated dependencies [216fa9a]
  • Updated dependencies [6c22b12]
  • Updated dependencies [d0531c4]
  • Updated dependencies [cff5aac]
    • @objectstack/spec@14.0.0
    • @objectstack/platform-objects@14.0.0
    • @objectstack/core@14.0.0
    • @objectstack/types@14.0.0

13.0.0

Major Changes

  • 6d83431: ADR-0090 P1 breaking wave — permission model v2 concept convergence.

    Pre-launch one-step renames and secure defaults (no compatibility aliases, per ADR-0090 D3/D4 superseding ADR-0057 D5/D7's alias discipline):

    • sys_rolesys_position, sys_user_rolesys_user_position (field roleposition), sys_role_permission_setsys_position_permission_set (field role_idposition_id); RoleSchema/defineRolePositionSchema/definePosition with no parent (positions are flat; hierarchy lives on the business-unit tree).
    • ExecutionContext.roles[]positions[]; the EvalUser/CEL contract current_user.rolescurrent_user.positions (formula validators updated); stack property roles:positions:; metadata kinds role/profileposition (profile kind removed).
    • isProfile removed from PermissionSetSchema (ADR-0090 D2); isDefault narrows to an install-time suggestion; appDefaultProfileNameappDefaultPermissionSetName (isDefault-only).
    • OWD enum drops legacy aliases read/read_write/full; new optional externalSharingModel (external dial, private default) lands as P1 spec shape (ADR-0090 D11).
    • Secure default (D1): a custom object with an owner field and NO sharingModel now resolves private (was: fully public). System objects keep their explicit posture. Unrecognised stored values fail closed.
    • ExecutionContext gains the P1 principal-taxonomy shape (D10): principalKind / audience / onBehalfOf (optional, semantics phase in later).
    • Sharing recipients: roleposition (expanded via sys_user_position ∪ the better-auth membership transition source); role_and_subordinates removed — unit_and_subordinates now expands the business-unit subtree (finishes ADR-0057 D5's re-homing).

Minor Changes

  • 57b89b4: feat(mcp): the MCP surface is now default-on — a core platform capability (#2698)

    /api/v1/mcp is served (and advertised in /discovery) out of the box; the OAuth 2.1 authorization track and Dynamic Client Registration follow it, so a fresh deployment is connectable by any MCP client with zero configuration. Operators opt OUT with OS_MCP_SERVER_ENABLED=false.

    • New single decision point isMcpServerEnabled() in @objectstack/types (default on; explicit false/0/off/no disables). The runtime dispatcher's /mcp route gate, the CLI's MCP plugin auto-load, the REST /discovery advertisement, and the auth service's OAuth/DCR follow-defaults all delegate to it — the served route, the advertised route, and the authorization track can never disagree.
    • The env var is now effectively tri-state: unset → HTTP surface on; explicit true → additionally auto-start the long-lived stdio transport at boot (unchanged, still opt-in — a default must not claim the process's stdin/stdout); explicit false → everything off, fail-closed (404, no metadata, no DCR).
    • The OAuth 2.1 TLS rule is unaffected: on a plain-HTTP non-loopback origin the OAuth track stays dark and the default-on surface remains API-key-only.
  • 5be00c3: feat(mcp): spec-compliant OAuth 2.1 authorization for /api/v1/mcp (#2698)

    Any OAuth-capable MCP client (claude.ai custom connectors, Claude Desktop, Claude Code) can now connect to a deployment self-serve: no admin-minted API key, no central registry — you sign in through the browser as yourself and every tool call runs under your own permissions and row-level security.

    Each deployment is its own authorization server, backed by the embedded better-auth instance (@better-auth/oauth-provider). Rationale for the design decisions lives in #2698; the moving parts:

    • Discovery: /.well-known/oauth-protected-resource (RFC 9728, incl. the path-inserted variant for /api/v1/mcp) and /.well-known/oauth-authorization-server (RFC 8414, incl. the path-inserted variant for the /api/v1/auth issuer) are served from the deployment origin. 401s from /api/v1/mcp advertise the resource metadata via WWW-Authenticate, so clients bootstrap the flow automatically.
    • Dynamic Client Registration (RFC 7591) is enabled (unauthenticated, as the MCP spec requires) whenever the MCP surface is on — every deployment is a distinct AS, so clients cannot ship pre-registered IDs. Force it either way with OS_OIDC_DCR_ENABLED or the new plugins.dynamicClientRegistration auth-config field. The embedded AS itself auto-enables whenever the MCP surface is on — which is now the default (explicit OS_OIDC_PROVIDER_ENABLED=false still wins).
    • Authorization-code + PKCE flow with RFC 8707 resource binding: access tokens are minted with aud=<origin>/api/v1/mcp and verified locally (signature/issuer/audience/expiry) against the deployment's own JWKS — fail-closed parity with API keys: unknown/expired/wrong-audience tokens, sub-less M2M tokens, or a presented-but-invalid bearer never fall back to an ambient session, they 401.
    • Token → ExecutionContext: a valid access token resolves to the same principal-bound ExecutionContext as every other credential, single-sourced through resolveAuthzContext — OAuth adds a second provenance for the principal, not a second authz model. ExecutionContext gains an optional oauthScopes field carrying the token's granted scopes.
    • Coarse scopes → tool families, enforced at tool dispatch: data:read (list/describe/query/get), data:write (create/update/delete), actions:execute (list_actions/run_action). Constants live in @objectstack/spec/ai (MCP_OAUTH_SCOPES). Tools outside the grant are not registered — and therefore rejected — for that request. API-key and session principals are unaffected (not scope-limited).
    • TLS required, localhost exempt (OAuth 2.1): on a plain-HTTP non-loopback origin the OAuth track stays dark (no metadata, no bearer acceptance) and the endpoint remains API-key-only. Local clients reach intranet deployments; claude.ai web connectors additionally need public HTTPS reachability.

    API keys are unchanged (dual-track): x-api-key / Authorization: ApiKey / Authorization: Bearer osk_… keep working exactly as before for CI and headless agents — covered by new regression tests.

Patch Changes

  • Updated dependencies [6d83431]
  • Updated dependencies [01917c2]
  • Updated dependencies [b271691]
  • Updated dependencies [a5a1e41]
  • Updated dependencies [466adf6]
  • Updated dependencies [57b89b4]
  • Updated dependencies [5be00c3]
  • Updated dependencies [466adf6]
  • Updated dependencies [2bee609]
  • Updated dependencies [9fa84f9]
  • Updated dependencies [fc7e7f7]
    • @objectstack/spec@13.0.0
    • @objectstack/core@13.0.0
    • @objectstack/platform-objects@13.0.0
    • @objectstack/types@13.0.0

12.6.0

Patch Changes

  • Updated dependencies [6cebf22]
  • Updated dependencies [21420d9]
    • @objectstack/spec@12.6.0
    • @objectstack/core@12.6.0
    • @objectstack/platform-objects@12.6.0
    • @objectstack/types@12.6.0

12.5.0

Patch Changes

  • Updated dependencies [8b3d363]
    • @objectstack/spec@12.5.0
    • @objectstack/core@12.5.0
    • @objectstack/platform-objects@12.5.0
    • @objectstack/types@12.5.0

12.4.0

Patch Changes

  • Updated dependencies [60dc3ba]
    • @objectstack/spec@12.4.0
    • @objectstack/core@12.4.0
    • @objectstack/platform-objects@12.4.0
    • @objectstack/types@12.4.0

12.3.0

Patch Changes

  • Updated dependencies [e7eceec]
    • @objectstack/spec@12.3.0
    • @objectstack/core@12.3.0
    • @objectstack/platform-objects@12.3.0
    • @objectstack/types@12.3.0

12.2.0

Patch Changes

  • Updated dependencies [fce8ff4]
  • Updated dependencies [3962023]
  • Updated dependencies [2bb193d]
  • Updated dependencies [0426d27]
  • Updated dependencies [da807f7]
  • Updated dependencies [4f5b791]
    • @objectstack/spec@12.2.0
    • @objectstack/core@12.2.0
    • @objectstack/platform-objects@12.2.0
    • @objectstack/types@12.2.0

12.1.0

Patch Changes

  • Updated dependencies [93e6d02]
    • @objectstack/spec@12.1.0
    • @objectstack/core@12.1.0
    • @objectstack/platform-objects@12.1.0
    • @objectstack/types@12.1.0

12.0.0

Minor Changes

  • 07f055c: feat(auth): last-login audit fields — sys_user.last_login_at / last_login_ip (ADR-0069 D7)

    Completes the ADR-0069 D7 identity-field set: sys_user.last_login_at and sys_user.last_login_ip are stamped on every successful /sign-in/email by AuthManager.stampLastLogin (a best-effort after-hook, independent of the lockout-accounting path so it runs even when lockout is disabled). The IP is taken from the trusted forwarded headers (x-forwarded-forcf-connecting-ipx-real-ip), the same precedence as the D5 IP allow-list middleware, and capped to the 45-char column width. Both fields are system-managed, read-only, and land in the Admin group of sys_user.

    The rest of ADR-0069 P1 (password complexity/history/expiry, HIBP, account lockout, enforced MFA) was already implemented; this fills the one missing D7 field pair. ADR-0069 status updated Proposed → Accepted (P1/P2 implemented) with an implementation-status matrix reflecting what is landed vs the remaining P2 gaps (per-org IP ranges, shared-store rate limiting).

  • 1b1b34e: feat(auth): shared cross-node rate-limit + session store via the cache service (ADR-0069 D2)

    Multi-node deployments previously rate-limited per process — better-auth's default rateLimit store is in-memory, so each node counted independently and an attacker could rotate nodes to bypass the limit. AuthPlugin now wires the kernel cache service as better-auth's secondaryStorage and flips rateLimit.storage to 'secondary-storage', so rate-limit counters (and the session cache) are enforced against one shared store across every node — shared iff the cache service is (Redis adapter in a cluster; memory single-node, where behavior is unchanged). When no cache service is registered the plugin logs a warning that a multi-node deployment needs a shared cache (ADR-0069 honesty — no silent per-process limiting presented as global).

    New cacheSecondaryStorage(cache) adapter (ICacheService → better-auth SecondaryStorage). Note: the cache has no atomic increment, so under high concurrency the get→set counter path can slightly over-count — acceptable for a rate limiter and strictly better than independent per-node counters; a future cache adapter exposing atomic INCR can add an increment method for exact counting.

Patch Changes

  • Updated dependencies [a8df396]
  • Updated dependencies [e695fe0]
  • Updated dependencies [07f055c]
  • Updated dependencies [7c09621]
  • Updated dependencies [7709db4]
  • Updated dependencies [2082109]
  • Updated dependencies [7c09621]
  • Updated dependencies [9860de4]
  • Updated dependencies [069c205]
    • @objectstack/spec@12.0.0
    • @objectstack/platform-objects@12.0.0
    • @objectstack/core@12.0.0
    • @objectstack/types@12.0.0

11.10.0

Patch Changes

  • Updated dependencies [6a9397e]
  • Updated dependencies [c0efe5d]
    • @objectstack/spec@11.10.0
    • @objectstack/core@11.10.0
    • @objectstack/platform-objects@11.10.0
    • @objectstack/types@11.10.0

11.9.0

Patch Changes

  • Updated dependencies [d3595d9]
    • @objectstack/spec@11.9.0
    • @objectstack/core@11.9.0
    • @objectstack/platform-objects@11.9.0
    • @objectstack/types@11.9.0

11.8.0

Patch Changes

  • Updated dependencies [53d491a]
  • Updated dependencies [b84726b]
    • @objectstack/platform-objects@11.8.0
    • @objectstack/spec@11.8.0
    • @objectstack/core@11.8.0
    • @objectstack/types@11.8.0

11.7.0

Patch Changes

  • Updated dependencies [5178906]
    • @objectstack/spec@11.7.0
    • @objectstack/platform-objects@11.7.0
    • @objectstack/core@11.7.0
    • @objectstack/types@11.7.0

11.6.0

Patch Changes

  • @objectstack/spec@11.6.0
  • @objectstack/core@11.6.0
  • @objectstack/types@11.6.0
  • @objectstack/platform-objects@11.6.0

11.5.0

Patch Changes

  • Updated dependencies [6ee4f04]
  • Updated dependencies [c1e3a65]
    • @objectstack/spec@11.5.0
    • @objectstack/core@11.5.0
    • @objectstack/platform-objects@11.5.0
    • @objectstack/types@11.5.0

11.4.0

Patch Changes

  • Updated dependencies [5821c51]
  • Updated dependencies [a0fce3f]
    • @objectstack/spec@11.4.0
    • @objectstack/core@11.4.0
    • @objectstack/platform-objects@11.4.0
    • @objectstack/types@11.4.0

11.3.0

Patch Changes

  • 59576d0: fix(auth): restore the admin gate on POST /admin/oauth-application/toggle-disabled after ADR-0068

    ADR-0068 stopped customSession from synthesizing user.role = 'admin'; canonical roles now arrive in user.roles[] with user.isPlatformAdmin as a derived alias. The OAuth-client enable/disable route was missed in that migration and still gated on session.user.role !== 'admin', which now rejects even platform admins (the scalar is no longer synthesized). It now mirrors the sibling /admin/unlock-user gate: isPlatformAdmin / platform_admin in roles[], with the legacy role scalar as a fallback.

    Also corrects the now-stale customSession() doc comment in auth-manager that still described the removed user.role = 'admin' overwrite.

  • Updated dependencies [58e8e31]

  • Updated dependencies [b4a5df0]

    • @objectstack/spec@11.3.0
    • @objectstack/core@11.3.0
    • @objectstack/platform-objects@11.3.0
    • @objectstack/types@11.3.0

11.2.0

Patch Changes

  • Updated dependencies [d0f4b13]
  • Updated dependencies [302bdab]
    • @objectstack/spec@11.2.0
    • @objectstack/core@11.2.0
    • @objectstack/platform-objects@11.2.0
    • @objectstack/types@11.2.0

11.1.0

Minor Changes

  • cbc8c02: feat(auth): opt-in SSO domain verification (ADR-0024 ②)

    Add DNS-TXT domain-ownership verification for external SSO providers, gated behind a new OS_SSO_DOMAIN_VERIFICATION flag (off by default — today's register→login behavior is unchanged). When enabled, @better-auth/sso mounts /sso/request-domain-verification + /sso/verify-domain and enforces that a provider's email domain be DNS-verified before it may complete a login.

    • auth-manager.ts: new ssoDomainVerification enabled-flag (readBooleanEnv) → passes domainVerification: { enabled: true } to sso(); public isSsoDomainVerificationEnabled() helper.
    • register-sso-provider.ts: runRequestDomainVerification / runVerifyDomain bridges — re-dispatch through the gated better-auth endpoints and reshape the response into the { success, data } envelope the sys_sso_provider action resultDialog reads (request → ready-to-paste DNS TXT record; verify → clear success/error). A bare 404 from the inner endpoint is surfaced as "not enabled for this environment".
    • auth-plugin.ts: mount the two bridges as rawApp routes (/admin/sso/{request-domain-verification,verify-domain}).
    • sys_sso_provider: domain_verified field + list column + the two actions; domainVerified documented in AUTH_SSO_PROVIDER_SCHEMA.
  • ce0b4f6: Auth: password expiry — the session-validation gate (ADR-0069 D1, P1)

    Builds the authentication-policy session gate ADR-0069 needs and uses it for password expiry. When password_expiry_days (new auth setting, 0 = off) is exceeded, an authenticated user is blocked from protected REST resources with 403 PASSWORD_EXPIRED until they change their password — while auth + remediation paths stay reachable.

    • core: new pure evaluateAuthGate / isAuthGateAllowlisted helper (@objectstack/core/security) — single source of truth for the allow-list (auth endpoints, change-password, health, UI-bootstrap reads).
    • plugin-auth: customSession computes the gate posture once and attaches user.authGate; computeAuthGate reads sys_user.password_changed_at vs the configured window; password_changed_at is stamped on sign-up / change / reset; isAuthGateActive() keeps the gate zero-overhead when off.
    • platform-objects: new sys_user.password_changed_at column.
    • rest: resolveExecCtx carries authGate; enforceAuth blocks gated sessions (independent of requireAuth) using the core allow-list.
    • service-settings: new password_expiry_days field.

    Default-off / additive (no upgrade behavior change); a null password_changed_at never expires (existing users). Per ADR-0049 the setting ships with its enforcement; timestamps written as Date (ADR-0074).

    This gate is the shared seam for enforced MFA (ADR-0069 D3), which lands next as a small addition (a second authGate branch). The dispatcher/MCP path is a follow-up (tracked in #2375); the REST surface the Console uses is fully gated here.

  • 90bce88: Auth: enforced MFA (ADR-0069 D3, P1)

    Completes the session-validation gate: when mfa_required (new auth setting) is on, an authenticated user without TOTP enrolled is blocked from protected resources with 403 MFA_REQUIRED once their mfa_grace_period_days (default 7) window elapses — while the two-factor enrollment endpoints stay reachable so they can comply. Reuses the authGate seam shipped in #2388 (a second posture branch in computeAuthGate).

    • New auth settings mfa_required (toggle) + mfa_grace_period_days; enabling mfa_required also force-enables the twoFactor plugin so /two-factor/* enrollment exists.
    • New sys_user.mfa_required_at column — the grace clock, stamped lazily the first time a user is seen required-but-unenrolled.
    • isAuthGateActive() now also trips on mfa_required (still zero-overhead when off).

    Default-off / additive (no upgrade behavior change); per ADR-0049 the setting ships with its enforcement.

    Needs an objectui follow-up: the Console should handle a 403 MFA_REQUIRED by showing the TOTP-enrollment prompt. Per-org sys_organization.require_mfa and the dispatcher/MCP gate remain follow-ups (#2375).

  • 3209ec6: Auth: session controls — idle timeout, absolute max lifetime, concurrent cap (ADR-0069 D4, P2)

    Adds three auth session-control settings (all 0 = off):

    • session_idle_timeout_minutes — sign a user out after inactivity. Enforced in customSession: touches sys_session.last_activity_at (throttled to once a minute) and, once the idle window is exceeded, revokes the session.
    • session_absolute_max_hours — cap total session lifetime regardless of refresh; revoked once created_at is older than the cap.
    • max_concurrent_sessions_per_user — on sign-in, keep the newest N live sessions and revoke the rest (oldest first).

    Revocation expires the session in place (expires_at set to the past + revoked_at / revoke_reason stamped on new sys_session columns), so better-auth returns no session on the next request → the Console's existing 401 → login redirect handles it (no client change). Note: better-auth garbage-collects expired sessions, so the revoke_reason audit row is best-effort; the enforcement (session killed) is not.

    Default-off / additive (no upgrade behavior change); per ADR-0049 each setting ships with its enforcement.

  • 8c84c97: Auth: IP allow-list — network gating on the auth routes (ADR-0069 D5, P2)

    Adds an allowed_ip_ranges auth setting (CIDR ranges or exact IPs; empty = no restriction). A Hono middleware registered ahead of the better-auth handler in the auth-route registration rejects auth requests from a client IP outside the ranges with 403 IP_NOT_ALLOWED, before they reach better-auth.

    • Client IP is read trust-proxy-aware from x-forwarded-for (first hop) / cf-connecting-ip / x-real-ip.
    • The public render helpers (/config, /bootstrap-status) are exempt so a blocked client still gets a clean login page + a clear error.
    • Fails OPEN when the client IP can't be determined (no proxy header), so a misconfigured proxy is a no-op rather than a lockout — an admin enabling this must ensure forwarded headers are trusted.
    • IPv4 CIDR (a.b.c.d/n) + exact IPv4/IPv6 matching.

    Default-off / additive; per ADR-0049 the setting ships with its enforcement.

  • e011d42: Auth: per-org MFA + dispatcher/MCP gate — complete the ADR-0069 enforced-MFA story

    Two follow-ups that make enforced MFA total:

    • Per-org sys_organization.require_mfa — an org may require MFA above the global floor. computeAuthGate now treats the active org's require_mfa as an effective MFA requirement even when the global mfa_required is off; isAuthGateActive() stays cheap via a 60s-TTL "any org requires MFA" cache (lazy background refresh), so a brand-new per-org requirement activates the gate on the next request without per-request org queries.
    • Dispatcher/MCP gate — the auth-policy gate now also runs in the runtime dispatcher (after resolveExecutionContext), so MCP / GraphQL / embedded data paths enforce PASSWORD_EXPIRED / MFA_REQUIRED consistently with the REST seam (reusing the shared evaluateAuthGate allow-list). Previously only the REST surface (the Console) was gated.

    Default-off / additive. Per ADR-0049 each setting ships with its enforcement.

  • 6e5bdd5: feat(auth): SAML 2.0 SSO via @better-auth/sso (ADR-0069 P3)

    @better-auth/sso@1.6.20 ships full SAML 2.0 (samlify-backed), so SAML needs no custom plugin. Adds a register_saml_provider action on sys_sso_provider and a runRegisterSamlProviderFromForm bridge that reshapes the flat admin form into the nested samlConfig and re-dispatches through /sso/register (admin gate enforced), returning the SP ACS + metadata URLs to configure on the IdP. Updates ADR-0069 to correct the stale "SAML is out of better-auth core" premise.

Patch Changes

  • 18f9713: Auth: surface "SSO Providers" in the Setup app nav when SSO is enabled (ADR-0024 / cloud#551)

    The sys_sso_provider admin object (register / list / delete external OIDC IdPs) had no navigation entry, so an admin could only reach it by direct URL. AuthPlugin now contributes an "SSO Providers" entry into the Setup app's Access Control group — but only when the external-IdP RP is wired (AuthManager.isSsoWired(), which captures both self-host OS_SSO_ENABLED and the cloud per-env planAllowsSso arriving via plugins.sso). Owning-plugin-contributes pattern (ADR-0029 K2), mirroring plugin-security. isSsoWired() is made public for this gate.

  • 7cf81a7: Auth: org-scope registered SSO/SAML providers so any org admin can manage them (ADR-0024 / cloud#551)

    @better-auth/sso's provider-management endpoints (delete / update / domain verification) gate ORG-LESS providers on provider.userId === caller — only the original registrar could manage them, so a second org admin couldn't delete or verify an IdP someone else registered. The register bridges now resolve the caller's active organization (best-effort, via a /get-session re-dispatch) and scope the provider to it, so management gates on isOrgAdmin instead — any org owner/admin can manage the environment's IdPs. Falls back to org-less (no behavior change) when no active org is set.

    Verified E2E: an OIDC provider registered through the form lands with organization_id set to the env's org (was null); register + delete still succeed.

  • d7a88df: Auth: SSO quality polish (ADR-0024 / cloud#551)

    • plugin-auth: OS_OIDC_PROVIDER_ENABLED / OS_SSO_ENABLED / OS_SCIM_ENABLED now parse with the shared readBooleanEnv helper (same as OS_AUTH_TWO_FACTOR etc.), so the platform-standard truthy set works (true/1/yes/on, case-insensitive) instead of only the literal 'true' — a repeated operator footgun where OS_SSO_ENABLED=1 silently parsed as disabled. Added unit tests.
    • platform-objects: sys_sso_provider's list view gets a per-object empty state ("No SSO providers yet" + a pointer to "Register SSO Provider"), replacing the shared identity-object copy ("records are created automatically … cannot be added here") which is wrong for this object — it HAS a register action.
  • 4f8f108: Auth: make the open-source SSO-provider registration form produce a usable IdP (ADR-0024 / cloud#551)

    The sys_sso_provider register_sso_provider UI action posted FLAT form fields to @better-auth/sso's /sso/register, which expects the OIDC fields NESTED under oidcConfig. The top-level clientId/clientSecret were Zod-stripped, so the form persisted an oidc_config = null provider that could never complete a login ("Invalid SSO provider").

    • plugin-auth: new shared runRegisterSsoProviderFromForm helper reshapes the flat form body into the nested shape and re-dispatches it through the real /sso/register (so the admin gate, the public-routable trustedOrigins allowance, discovery hydration, and secret handling all still run). Exposed via a new /admin/sso/register bridge route on the host AuthPlugin. (The cloud per-env runtime mounts the same helper in its AuthProxyPlugin — mirrors set-initial-password.)
    • platform-objects: register_sso_provider retargets to /api/v1/auth/admin/sso/register and gains discoveryEndpoint, scopes, and attribute-mapping (mapId/mapEmail/mapName) fields. Open mechanism — keeps runtime IdP registration self-service in the OSS edition.

    Verified E2E: an admin registers an external OIDC IdP from the flat form → a member logs in through it (JIT-provisioned, sys_account.provider_id set); a non-admin is rejected (403) before discovery runs.

  • Updated dependencies [cbc8c02]

  • Updated dependencies [07c2773]

  • Updated dependencies [d7a88df]

  • Updated dependencies [4f8f108]

  • Updated dependencies [ce0b4f6]

  • Updated dependencies [90bce88]

  • Updated dependencies [3209ec6]

  • Updated dependencies [e011d42]

  • Updated dependencies [6e5bdd5]

  • Updated dependencies [9ccfcd6]

  • Updated dependencies [ecf193f]

  • Updated dependencies [51bec81]

  • Updated dependencies [3e593a7]

  • Updated dependencies [fdb41c0]

  • Updated dependencies [63d5403]

    • @objectstack/platform-objects@11.1.0
    • @objectstack/core@11.1.0
    • @objectstack/spec@11.1.0
    • @objectstack/types@11.1.0

11.0.0

Minor Changes

  • 21b3208: Auth: password complexity policy (ADR-0069 D1, P1)

    Adds password_require_complexity (toggle, default off) + password_min_classes (1–4, default 3) to the auth password-policy settings. A custom validator runs in the better-auth before hook on /sign-up/email, /reset-password, and /change-password, rejecting passwords that use fewer than password_min_classes of the four character classes (upper / lower / digit / symbol) with PASSWORD_POLICY_VIOLATION — better-auth natively enforces only min/max length.

    Default-off and additive (no upgrade behavior change); per ADR-0049 the setting ships with its enforcement. No new identity fields. Continues the ADR-0069 P1 password-policy work alongside the HIBP breached-password reject (#2361).

  • 9b5bf3d: Auth: password history / no-reuse (ADR-0069 D1, P1)

    Adds password_history_count (0–24, 0 = off) to the auth password-policy settings. On /change-password and /reset-password, a new password that matches the current password or any of the last N hashes is rejected with PASSWORD_REUSE. A new bounded sys_account.previous_password_hashes column (JSON ring, system-managed, hidden) backs the check; it is maintained by before/after hooks (capture the old hash, append on success).

    Reuses better-auth's native password.verify (no bespoke crypto) and resolves the reset-flow user via the same token lookup better-auth uses. Default-off / additive (no upgrade behavior change); per ADR-0049 the setting ships with its enforcement.

  • cb5b393: Auth: account lockout + rate-limit tuning (ADR-0069 D2, P1)

    Second slice of ADR-0069 — per-identity brute-force protection, reusing the setting→enforcement pattern from the HIBP PR.

    • Account lockout [custom][field]: new sys_user.failed_login_count / sys_user.locked_until columns; auth settings lockout_threshold (0 = off) + lockout_duration_minutes. Enforced in the /sign-in/email before/after hooks — failures increment the counter, crossing the threshold stamps locked_until, and a locked account is rejected even with the correct password (survives IP rotation, unlike rate limiting). A successful sign-in resets both.
    • Admin Unlock: new admin-guarded POST /api/v1/auth/admin/unlock-user route + an unlock_user action on sys_user.
    • Rate-limit tuning [native]: auth settings rate_limit_max / rate_limit_window_seconds wire better-auth's core rateLimit with stricter customRules for /sign-in/email, /sign-up/email, /request-password-reset, /reset-password.

    All settings default off / to safe values; additive (no upgrade behavior change). Per ADR-0049 each setting ships with its enforcement. Timestamps are written as Date (never epoch-ms) per ADR-0074.

  • ab5718a: Auth: reject breached passwords via Have I Been Pwned (ADR-0069 D1, P1)

    First slice of ADR-0069 (enterprise authentication hardening) and the enforcement-wired pattern template the rest of the ADR follows. Adds a password_reject_breached auth setting (default off) bound end-to-end to better-auth's native haveibeenpwned plugin — a k-anonymity range check on sign-up / change-password / reset-password (the plaintext password never leaves the process).

    • spec: new passwordRejectBreached flag on AuthPluginConfigSchema.
    • service-settings: new "Reject breached passwords" toggle in the auth manifest's password-policy group (global scope, manage_platform_settings).
    • plugin-auth: bindAuthSettings maps the setting into the plugin config; buildPluginList gates and mounts the haveIBeenPwned plugin (env OS_AUTH_PASSWORD_REJECT_BREACHED wins over config, mirroring OS_AUTH_TWO_FACTOR).
    • cli: surface the knob in the serve boot config alongside twoFactor.

    Default-off and additive — no behavior change on upgrade. Per ADR-0049 the toggle ships with its enforcement (no false surface). No new identity fields (the [custom] D1 items — complexity / expiry / history — land in follow-up PRs).

Patch Changes

  • caa3ef4: Auth: trust public-routable external-IdP origins at SSO registration (ADR-0024 / cloud#551)

    @better-auth/sso's discovery validation requires every IdP endpoint origin to be in trustedOrigins — even for a publicly-routable IdP. That broke ADR-0024's "register your OIDC IdP at runtime, no boot config" promise: registering any external IdP returned 400 discovery_untrusted_origin unless the operator had pre-listed it.

    When the external-SSO RP is enabled, trustedOrigins is now exposed as a per-request function that, for a POST /sso/register | /sso/update-provider, additionally trusts the public-routable issuer / oidcConfig endpoint origins declared in the request body (via @better-auth/core's own isPublicRoutableHost). Private / internal / loopback hosts are never auto-trusted — they still require explicit trustedOrigins config (the documented SSRF escape hatch), and better-auth's own DNS-resolution checks still apply.

    Verified: a same-origin public IdP (GitLab.com — issuer and all discovered endpoints on one origin, like Okta / Entra / Auth0 / Keycloak) now registers at runtime with no boot config (was a hard 400). The admin gate still fires first (a non-admin is rejected before discovery runs). Note: IdPs that split endpoints across multiple domains (e.g. Google's accounts.google.com + oauth2.googleapis.com) still need those extra origins in trustedOrigins.

  • 22b32c1: Auth: admin-gate self-service SSO provider registration + default-role JIT (ADR-0024 / cloud#551)

    @better-auth/sso's POST /sso/register only enforces org-admin when body.organizationId is supplied — a global (org-less) provider passed on nothing but a valid session, so any authenticated env member could register an env-wide external IdP (a JIT-provisioning / login-routing vector). This closed the "registerSSOProvider is admin-only" requirement of ADR-0024's first slice.

    • plugin-auth: a before-hook on /sso/register now requires the caller to be a platform admin OR an owner/admin of their active org, regardless of organizationId. Fail-closed; unauthenticated requests still fall through to sessionMiddleware (→ 401). New helpers resolveActor() (hook-order-independent cookie/bearer resolution) and isOrgOrPlatformAdmin() (mirrors customSession's role derivation; reads via withSystemReadContext).
    • plugin-auth: sso() now receives organizationProvisioning.defaultRole:'member' so a first-time federated login lands with an explicit role (over SecurityPlugin's member_default baseline).

    Additive and fail-closed — no behavior change for legitimate admins. The SSO mechanism stays framework-open (no identity-governance added).

  • 1e8a813: feat(auth): surface features.sso in the public /auth/config response

    getPublicConfig() reported every other auth capability flag (oidcProvider, twoFactor, multiOrgEnabled, …) but omitted enterprise SSO, even though the manager already computes whether the domain-routed @better-auth/sso plugin is wired (OS_SSO_ENABLED / plugins.sso). Without it the login UI had no signal to gate on, so it rendered a "Sign in with SSO" button unconditionally — and on a self-hosted / local deployment where SSO isn't wired, clicking it only then surfaced "No SSO provider is configured for this email domain."

    The config now includes features.sso. getPublicConfig() returns the coarse "is the plugin wired" flag — resolved with the EXACT logic that decides whether the plugin is mounted in buildPlugins(), so the advertised capability can never disagree with the actual /sign-in/sso route. The /auth/config route then refines it to "usable" via the new AuthManager.isSsoUsable(), which additionally requires at least one sys_sso_provider row to exist — so a freshly-enabled but unconfigured SSO setup doesn't advertise a button that errors for everyone. isSsoUsable() only queries when wired and fails open to the wired flag on any introspection error (no data engine, query failure), so config never 500s. The console login form consumes features.sso to hide the button (objectui side).

  • Updated dependencies [9b5bf3d]

  • Updated dependencies [cb5b393]

  • Updated dependencies [ab5718a]

  • Updated dependencies [4845c12]

  • Updated dependencies [c1a754a]

  • Updated dependencies [6fbe91f]

  • Updated dependencies [715d667]

  • Updated dependencies [5eef4cf]

  • Updated dependencies [72759e1]

  • Updated dependencies [6c4fbd9]

  • Updated dependencies [ef3ed67]

  • Updated dependencies [cd51229]

  • Updated dependencies [7697a0e]

  • Updated dependencies [e7e04f1]

  • Updated dependencies [cfd5ac4]

  • Updated dependencies [2be5c1f]

  • Updated dependencies [ad143ce]

  • Updated dependencies [5c4a8c8]

  • Updated dependencies [3afaeed]

  • Updated dependencies [5737261]

  • Updated dependencies [a619a3a]

  • Updated dependencies [f44c1bd]

  • Updated dependencies [795b6d1]

  • Updated dependencies [8801c02]

  • Updated dependencies [3d04e06]

  • Updated dependencies [4a84c98]

  • Updated dependencies [c715d25]

  • Updated dependencies [aa33b02]

  • Updated dependencies [d980f0d]

  • Updated dependencies [a658523]

  • Updated dependencies [82ff91c]

  • Updated dependencies [638f472]

    • @objectstack/platform-objects@11.0.0
    • @objectstack/spec@11.0.0
    • @objectstack/types@11.0.0
    • @objectstack/core@11.0.0

10.3.0

Patch Changes

  • @objectstack/spec@10.3.0
  • @objectstack/core@10.3.0
  • @objectstack/types@10.3.0
  • @objectstack/platform-objects@10.3.0

10.2.0

Patch Changes

  • Updated dependencies [b496498]
    • @objectstack/spec@10.2.0
    • @objectstack/core@10.2.0
    • @objectstack/platform-objects@10.2.0
    • @objectstack/types@10.2.0

10.1.0

Patch Changes

  • Updated dependencies [49da36e]
  • Updated dependencies [ac79f16]
    • @objectstack/spec@10.1.0
    • @objectstack/core@10.1.0
    • @objectstack/platform-objects@10.1.0
    • @objectstack/types@10.1.0

10.0.0

Patch Changes

  • Updated dependencies [d7ff626]
  • Updated dependencies [2a1b16b]
  • Updated dependencies [2256e93]
  • Updated dependencies [7108ff3]
  • Updated dependencies [30c0313]
  • Updated dependencies [e16f2a8]
  • Updated dependencies [e411a82]
  • Updated dependencies [ae271d0]
  • Updated dependencies [61ed5c7]
  • Updated dependencies [a581385]
  • Updated dependencies [d5f6d29]
  • Updated dependencies [220ce5b]
  • Updated dependencies [3efe334]
  • Updated dependencies [0df063e]
  • Updated dependencies [ce13bb8]
  • Updated dependencies [feead7e]
  • Updated dependencies [6ca20b3]
  • Updated dependencies [5f875fe]
  • Updated dependencies [b469950]
  • Updated dependencies [47d978a]
    • @objectstack/spec@10.0.0
    • @objectstack/platform-objects@10.0.0
    • @objectstack/core@10.0.0
    • @objectstack/types@10.0.0

9.11.0

Patch Changes

  • Updated dependencies [e7f6539]
  • Updated dependencies [2365d07]
  • Updated dependencies [6595b53]
  • Updated dependencies [fa8964d]
  • Updated dependencies [36138c7]
  • Updated dependencies [a8e4f3b]
  • Updated dependencies [4c213c2]
  • Updated dependencies [2afb612]
    • @objectstack/spec@9.11.0
    • @objectstack/core@9.11.0
    • @objectstack/platform-objects@9.11.0
    • @objectstack/types@9.11.0

9.10.0

Patch Changes

  • Updated dependencies [db02bd5]
  • Updated dependencies [641675d]
  • Updated dependencies [94e9040]
  • Updated dependencies [4331adb]
  • Updated dependencies [1f88fd9]
  • Updated dependencies [1f88fd9]
    • @objectstack/spec@9.10.0
    • @objectstack/platform-objects@9.10.0
    • @objectstack/core@9.10.0
    • @objectstack/types@9.10.0

9.9.1

Patch Changes

  • @objectstack/spec@9.9.1
  • @objectstack/core@9.9.1
  • @objectstack/types@9.9.1
  • @objectstack/platform-objects@9.9.1

9.9.0

Minor Changes

  • 0d4e3f3: feat(auth): password-policy & session settings — live, enforced (P0 security)

    Extends the existing auth settings manifest (global scope) with the security policy keys that are genuinely enforced today, rather than standing up a new security namespace full of non-functional toggles (which would be false surface):

    • Password policypassword_min_length (default 8), password_max_length (default 128). Enforced by better-auth on sign-up and password reset.
    • Sessionssession_expiry_days (default 7, absolute lifetime), session_refresh_days (default 1, refresh threshold).

    These ride the existing AuthPlugin.bindAuthSettingsAuthManager.applyConfigPatch path (read on kernel:ready, re-applied live via settings.subscribe('auth'), which invalidates the cached better-auth instance). Days are converted to seconds for better-auth's session.{expiresIn,updateAge}; unset (source: 'default') and malformed/non-positive values are ignored so the provider default holds. Ships en + zh-CN translations.

    Deliberately out of scope (no enforcement exists, so they're not declared as settings): MFA-required, IP allowlist, SSO/SAML, SCIM, API rate limits, password complexity/rotation/history. These are real features to be built, not settings toggles.

Patch Changes

  • Updated dependencies [84249a4]
  • Updated dependencies [11af299]
  • Updated dependencies [d5774b5]
  • Updated dependencies [134043a]
  • Updated dependencies [90108e0]
  • Updated dependencies [9afeb2d]
  • Updated dependencies [6bec07e]
  • Updated dependencies [601cc11]
  • Updated dependencies [575448d]
    • @objectstack/spec@9.9.0
    • @objectstack/core@9.9.0
    • @objectstack/platform-objects@9.9.0
    • @objectstack/types@9.9.0

9.8.0

Patch Changes

  • Updated dependencies [97c55b3]
  • Updated dependencies [1b1f490]
    • @objectstack/spec@9.8.0
    • @objectstack/core@9.8.0
    • @objectstack/platform-objects@9.8.0
    • @objectstack/types@9.8.0

9.7.0

Patch Changes

  • @objectstack/spec@9.7.0
  • @objectstack/core@9.7.0
  • @objectstack/types@9.7.0
  • @objectstack/platform-objects@9.7.0

9.6.0

Patch Changes

  • 1b82b64: auth: expose isPlatformAdmin on the customSession user payload

    The session already derives a coarse admin role for platform admins or active-org admins, but never surfaced the underlying platform-admin signal. Console action visible CEL predicates need it to gate platform-admin-only object actions (e.g. sys_environment.change_plan) without hiding org-admin actions. Both customSession return paths now carry the boolean; org-admins who are not platform admins correctly get isPlatformAdmin: false.

  • Updated dependencies [d1e930a]

  • Updated dependencies [71578f2]

  • Updated dependencies [5e3a301]

  • Updated dependencies [5db2742]

    • @objectstack/spec@9.6.0
    • @objectstack/core@9.6.0
    • @objectstack/platform-objects@9.6.0
    • @objectstack/types@9.6.0

9.5.1

Patch Changes

  • Updated dependencies [ee72aae]
    • @objectstack/spec@9.5.1
    • @objectstack/core@9.5.1
    • @objectstack/platform-objects@9.5.1
    • @objectstack/types@9.5.1

9.5.0

Patch Changes

  • Updated dependencies [d08551c]
  • Updated dependencies [5be7102]
  • Updated dependencies [707aeed]
  • Updated dependencies [7a103d4]
  • Updated dependencies [4b01250]
    • @objectstack/spec@9.5.0
    • @objectstack/platform-objects@9.5.0
    • @objectstack/core@9.5.0
    • @objectstack/types@9.5.0

9.4.0

Patch Changes

  • Updated dependencies [060467a]
  • Updated dependencies [0856476]
  • Updated dependencies [b678d8c]
  • Updated dependencies [b678d8c]
  • Updated dependencies [b678d8c]
    • @objectstack/spec@9.4.0
    • @objectstack/core@9.4.0
    • @objectstack/platform-objects@9.4.0
    • @objectstack/types@9.4.0

9.3.0

Patch Changes

  • Updated dependencies [1ada658]
  • Updated dependencies [3219191]
  • Updated dependencies [290f631]
  • Updated dependencies [50b7b47]
  • Updated dependencies [f15d6f6]
  • Updated dependencies [f8684ea]
  • Updated dependencies [c802327]
  • Updated dependencies [b4765be]
    • @objectstack/spec@9.3.0
    • @objectstack/platform-objects@9.3.0
    • @objectstack/core@9.3.0
    • @objectstack/types@9.3.0

9.2.0

Patch Changes

  • Updated dependencies [2f57b75]
  • Updated dependencies [2f57b75]
    • @objectstack/spec@9.2.0
    • @objectstack/core@9.2.0
    • @objectstack/platform-objects@9.2.0
    • @objectstack/types@9.2.0

9.1.0

Patch Changes

  • Updated dependencies [b9062c9]
    • @objectstack/spec@9.1.0
    • @objectstack/core@9.1.0
    • @objectstack/platform-objects@9.1.0
    • @objectstack/types@9.1.0

9.0.1

Patch Changes

  • Updated dependencies [1817845]
    • @objectstack/spec@9.0.1
    • @objectstack/core@9.0.1
    • @objectstack/platform-objects@9.0.1
    • @objectstack/types@9.0.1

9.0.0

Minor Changes

  • f533f42: Settings namespace environment overrides now use the canonical ObjectStack OS_<NAMESPACE>_<KEY> form, with no unprefixed aliases. For example, ai.openai_base_url is now OS_AI_OPENAI_BASE_URL, and feature_flags.ai_enabled is now OS_FEATURE_FLAGS_AI_ENABLED.

    The AI service now treats a stored or env-locked provider=memory setting as an explicit override, while the manifest default still leaves boot-time provider auto-detection intact.

    The auth plugin now binds the auth settings namespace to better-auth runtime configuration, exposes an extension hook for provider packages, and includes a basic Google sign-in implementation configured either in Setup → Authentication or by deployment-level GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.

Patch Changes

  • Updated dependencies [4c3f693]
  • Updated dependencies [0bf39f1]
  • Updated dependencies [f533f42]
  • Updated dependencies [1c83ee8]
    • @objectstack/spec@9.0.0
    • @objectstack/core@9.0.0
    • @objectstack/platform-objects@9.0.0
    • @objectstack/types@9.0.0

8.0.1

Patch Changes

  • @objectstack/spec@8.0.1
  • @objectstack/core@8.0.1
  • @objectstack/types@8.0.1
  • @objectstack/platform-objects@8.0.1

8.0.0

Patch Changes

  • 1e8b680: fix(security): close four P0 launch-readiness findings

    • plugin-auth (P0-1): generateSecret() now throws (fails boot) when no OS_AUTH_SECRET is set and NODE_ENV==='production', instead of silently falling back to a predictable dev-secret-<timestamp> (session forgery). The dev/test fallback is unchanged.
    • plugin-security (P0-2): the permission-resolution catch now fails closed — it logs at ERROR and throws PermissionDeniedError rather than return next(). A degraded metadata service can no longer let every authenticated request bypass RBAC/RLS. System operations still bypass as before.
    • driver-sql (P0-3): the contains / $contains operator now escapes LIKE metacharacters (% / _ / \) in the user value and binds an explicit ESCAPE '\', so a value of % matches literally instead of every row (filter bypass). Correct across SQLite/MySQL/Postgres.
    • driver-mongodb (P0-4): the field-operator translator now rejects unknown $-operators instead of passing them through, blocking $where / $function / $expr (server-side JS execution / query-intent bypass). All legitimate ObjectQL operators remain allowlisted.

    +12 regression tests across the four packages.

  • Updated dependencies [a46c017]

  • Updated dependencies [b990b89]

  • Updated dependencies [99111ec]

  • Updated dependencies [d5a8161]

  • Updated dependencies [5cf1f1b]

  • Updated dependencies [9ef89d4]

  • Updated dependencies [3306d2f]

  • Updated dependencies [c262301]

  • Updated dependencies [bc44195]

  • Updated dependencies [9e2e229]

    • @objectstack/spec@8.0.0
    • @objectstack/core@8.0.0
    • @objectstack/platform-objects@8.0.0
    • @objectstack/types@8.0.0

7.9.0

Patch Changes

  • @objectstack/spec@7.9.0
  • @objectstack/core@7.9.0
  • @objectstack/types@7.9.0
  • @objectstack/platform-objects@7.9.0

7.8.0

Patch Changes

  • Updated dependencies [06f2bbb]
  • Updated dependencies [36719db]
  • Updated dependencies [424ab26]
    • @objectstack/spec@7.8.0
    • @objectstack/core@7.8.0
    • @objectstack/platform-objects@7.8.0
    • @objectstack/types@7.8.0

7.7.0

Patch Changes

  • Updated dependencies [b391955]
  • Updated dependencies [f06b64e]
  • Updated dependencies [023bf93]
  • Updated dependencies [764c747]
    • @objectstack/spec@7.7.0
    • @objectstack/platform-objects@7.7.0
    • @objectstack/core@7.7.0
    • @objectstack/types@7.7.0

7.6.0

Patch Changes

  • 8c01eea: fix(dev): seed the dev admin in-process and fix the port-drift seed failure.

    os dev (and pnpm dev:showcase) seeded the admin over HTTP against a hard-coded localhost:3000. In dev, serve auto-shifts off a busy port, so the seed POST hit the wrong server (or nothing) and the running instance never got an admin. A second, divergent seed in plugin-dev inserted a credential-less sys_user row that could not log in.

    Consolidate to a single in-process seed:

    • @objectstack/plugin-authmaybeSeedDevAdmin() runs on kernel:ready and creates admin@objectos.ai / admin123 through better-auth's real signUpEmail pipeline (hashed credential), so the account is loginable; plugin-security then promotes it to platform admin. Empty-DB only (excludes the system service account), idempotent, never overwrites an existing account. Hard-gated to NODE_ENV=development; opt out with OS_SEED_ADMIN=0.
    • @objectstack/cli — removed the HTTP seed; --seed-admin now passes OS_SEED_ADMIN[_EMAIL|_PASSWORD] to the serve child. serve publishes its actually-bound port over IPC and to a runtime.<env>.json state file under OS_HOME.
    • @objectstack/plugin-dev — removed the credential-less raw insert; seedAdminUser maps to the unified OS_SEED_ADMIN toggle.
  • b7a4f14: fix(dev): surface the seeded dev-admin credentials in the serve startup banner.

    When the runtime seeds the dev admin on an empty DB, the confirmation was emitted via ctx.logger during runtime.start() — inside serve's boot-quiet window — so it was swallowed and never reached the console. plugin-auth now records the seed result on the auth service and serve prints it in the ready banner (after stdout is restored), e.g.:

      🔑  Dev admin: admin@objectos.ai / admin123
          seeded on empty DB · dev only — do not use in production
    

    Shown only when an admin was actually seeded this boot (empty DB) — never on a DB that already had a user, so stale credentials are never displayed. Visible in both serve --dev and os dev (the child's stdout is inherited).

  • Updated dependencies [955d4c8]

  • Updated dependencies [c4a4cbd]

  • Updated dependencies [b046ec2]

  • Updated dependencies [2170ad9]

  • Updated dependencies [02d6359]

  • Updated dependencies [7648242]

  • Updated dependencies [8fa1e7f]

  • Updated dependencies [7ae6abc]

  • Updated dependencies [55866f5]

  • Updated dependencies [60f9c45]

    • @objectstack/spec@7.6.0
    • @objectstack/platform-objects@7.6.0
    • @objectstack/core@7.6.0
    • @objectstack/types@7.6.0

7.5.0

Patch Changes

  • @objectstack/spec@7.5.0
  • @objectstack/core@7.5.0
  • @objectstack/types@7.5.0
  • @objectstack/platform-objects@7.5.0

7.4.1

Patch Changes

  • @objectstack/spec@7.4.1
  • @objectstack/core@7.4.1
  • @objectstack/types@7.4.1
  • @objectstack/platform-objects@7.4.1

7.4.0

Minor Changes

  • c72daad: ADR-0029 D7 — Setup app navigation contributions.

    Adds the UI-layer analog of object own/extend: a package can contribute navigation items into an app it does not own, so a shared admin app can be a thin shell while each capability plugin ships the menu for the objects it owns.

    • @objectstack/spec — new NavigationContributionSchema ({ app, group?, priority, items }) and an optional navigationContributions field on the manifest.
    • @objectstack/objectqlSchemaRegistry.registerAppNavContribution() plus lazy merge in getApp / getAllApps (by target group id + priority, cloning so the stored app is never mutated); the engine wires manifest.navigationContributions during app registration.
    • @objectstack/platform-objects — the Setup app becomes a shell of empty group anchors; its entries for platform-objects-owned objects move to SETUP_NAV_CONTRIBUTIONS.
    • @objectstack/plugin-auth — registers SETUP_NAV_CONTRIBUTIONS alongside the Setup app it already registers.
    • @objectstack/plugin-webhooks — contributes its Webhooks / Webhook Deliveries entries into the Setup group_integrations slot (it owns sys_webhook / sys_webhook_delivery per K2.a), demonstrating end-to-end cross-plugin contribution.

    The rendered Setup nav is identical to the former static artifact — just assembled from its owners. A disabled/absent capability contributes nothing and its slot stays empty (in addition to the existing requiresObject gating). This unblocks moving each remaining K2 domain's menu out of the monolith with its objects.

Patch Changes

  • Updated dependencies [23c7107]
  • Updated dependencies [c72daad]
  • Updated dependencies [4404572]
  • Updated dependencies [eea3f1b]
  • Updated dependencies [e478e0c]
  • Updated dependencies [4cc2ced]
  • Updated dependencies [13632b1]
  • Updated dependencies [f115182]
  • Updated dependencies [2faf9f2]
  • Updated dependencies [2faf9f2]
  • Updated dependencies [2faf9f2]
  • Updated dependencies [58b450b]
  • Updated dependencies [82eb6cf]
  • Updated dependencies [c381977]
  • Updated dependencies [13d8653]
  • Updated dependencies [ff3d006]
  • Updated dependencies [5e831de]
    • @objectstack/spec@7.4.0
    • @objectstack/platform-objects@7.4.0
    • @objectstack/core@7.4.0
    • @objectstack/types@7.4.0

7.3.0

Patch Changes

  • Updated dependencies [5e7c554]
    • @objectstack/spec@7.3.0
    • @objectstack/core@7.3.0
    • @objectstack/platform-objects@7.3.0
    • @objectstack/types@7.3.0

7.2.1

Patch Changes

  • 9096dfe: OS_ env-var prefix migration (issue #1382).

    All ObjectStack-owned environment variables now use the OS_ prefix. Legacy names still work for one release and emit a one-shot deprecation warning via the new readEnvWithDeprecation() helper in @objectstack/types.

    Renamed (with legacy fallback):

    New Legacy (deprecated)
    OS_AUTH_SECRET AUTH_SECRET, BETTER_AUTH_SECRET
    OS_AUTH_URL AUTH_BASE_URL, BETTER_AUTH_URL, OS_AUTH_BASE_URL
    OS_PORT PORT
    OS_DATABASE_URL DATABASE_URL
    OS_ROOT_DOMAIN ROOT_DOMAIN
    OS_MULTI_ORG_ENABLED OS_MULTI_TENANT
    OS_CORS_ENABLED CORS_ENABLED
    OS_CORS_ORIGIN CORS_ORIGIN
    OS_CORS_CREDENTIALS CORS_CREDENTIALS
    OS_CORS_MAX_AGE CORS_MAX_AGE
    OS_AI_MODEL AI_MODEL
    OS_MCP_SERVER_ENABLED MCP_SERVER_ENABLED
    OS_MCP_SERVER_NAME MCP_SERVER_NAME
    OS_MCP_SERVER_TRANSPORT MCP_SERVER_TRANSPORT
    OS_NODE_ID OBJECTSTACK_NODE_ID
    OS_METADATA_WRITABLE OBJECTSTACK_METADATA_WRITABLE
    OS_DEV_CRYPTO_KEY OBJECTSTACK_DEV_CRYPTO_KEY
    OS_HOME OBJECTSTACK_HOME

    Migration: rename in your .env. Legacy names continue to work this release and will be removed in a future major. Industry-standard names (NODE_ENV, HOME, OPENAI_API_KEY, TURSO_*, OAuth *_CLIENT_ID/SECRET, RESEND_API_KEY, POSTMARK_TOKEN, AI_GATEWAY_*, SMTP_*) are NOT renamed.

  • Updated dependencies [9096dfe]

    • @objectstack/types@7.2.1
    • @objectstack/spec@7.2.1
    • @objectstack/core@7.2.1
    • @objectstack/platform-objects@7.2.1

7.2.0

Patch Changes

  • @objectstack/spec@7.2.0
  • @objectstack/core@7.2.0
  • @objectstack/platform-objects@7.2.0

7.1.0

Patch Changes

  • Updated dependencies [6228609]
  • Updated dependencies [47a92f4]
    • @objectstack/platform-objects@7.1.0
    • @objectstack/spec@7.1.0
    • @objectstack/core@7.1.0

7.0.0

Patch Changes

  • 74470ad: New account App for self-service identity management + App.hidden shell hint

    Adds a dedicated Account App (name: 'account', icon user-circle) that exposes the three end-user identity surfaces:

    • Two-Factor Authenticationsys_two_factor
    • Linked Accountssys_account
    • OAuth Applicationssys_oauth_application

    The app declares no requiredPermissions, so every authenticated user can reach it — unlike Setup, which requires setup.access and therefore excludes the default member_default permission set. Combined with the C-tier resultDialog actions already shipped on these objects (2FA QR + backup codes, OAuth client_secret reveal, link_social redirect), this replaces the legacy standalone apps/account SPA with a single console + metadata-driven surface.

    New App.hidden: boolean field (packages/spec/src/ui/app.zod.ts) hides an app from the top-level App Switcher. Hidden apps stay fully routable and permission-checked; the shell is expected to surface them through the avatar / user dropdown instead. Mirrors the GitHub Settings / Google account chip / Salesforce Personal Settings pattern. The Account app is the first user.

    Wiring: plugin-auth registers ACCOUNT_APP alongside SETUP_APP / STUDIO_APP (packages/plugins/plugin-auth/src/auth-plugin.ts). The legacy duplicate entries inside Setup's Advanced group are kept unchanged — they remain admin-only for tenant-wide inspection.

    Follow-up for objectui: the shell's AppSwitcher and avatar DropdownMenu need updating to honour app.hidden (filter hidden apps out of the switcher; render them as dropdown menu entries). Tracked separately.

  • 257954d: Organization detail page — Members / Invitations / Teams tabs (slotted Page)

    Adds a record-detail Page for sys_organization (SysOrganizationDetailPage) so admins can manage the entire membership graph from a single record view instead of switching between three separate Setup list views.

    The page uses kind: 'slotted' and overrides only the tabs slot — header, actions, highlights, details and discussion fall through to the synthesized default, so the existing record-header actions (Set Active, Edit, Delete, Leave Organization) are preserved unchanged.

    Three tabs, each a record:related_list scoped by organization_id:

    • Memberssys_member (user, role, joined)
    • Invitationssys_invitation (email, role, status, expires, inviter)
    • Teamssys_team (name, created, updated)

    Per-row actions defined on each child object (invite_user, cancel_invitation, remove_member, transfer_ownership, create_team, …) are inherited unchanged — no admin endpoint is re-declared here.

    Deliberately omitted:

    • OAuth Appssys_oauth_application is owned by user_id, not organization_id; it surfaces on the user's Account view instead.
    • SSO — no sys_sso* object exists yet; will become a fourth tab when better-auth's SSO plugin lands.

    Package wiring:

    • @objectstack/platform-objects exposes a new ./pages subpath export and re-exports SysOrganizationDetailPage from the root.
    • plugin-auth registers it via the existing manifest.register({ ..., pages: [SysOrganizationDetailPage] }) call alongside the platform apps and dashboards.

    Verified end-to-end on the console-starter shell against example-crm — the three tabs render and the Members/Teams tables populate with the rows better-auth creates automatically when an org is provisioned.

  • Updated dependencies [74470ad]

  • Updated dependencies [d29617e]

  • Updated dependencies [dc72172]

  • Updated dependencies [d29617e]

  • Updated dependencies [010757b]

  • Updated dependencies [257954d]

    • @objectstack/spec@7.0.0
    • @objectstack/platform-objects@7.0.0
    • @objectstack/core@7.0.0

6.9.0

Patch Changes

  • @objectstack/spec@6.9.0
  • @objectstack/core@6.9.0
  • @objectstack/platform-objects@6.9.0

6.8.1

Patch Changes

  • @objectstack/spec@6.8.1
  • @objectstack/core@6.8.1
  • @objectstack/platform-objects@6.8.1

6.8.0

Patch Changes

  • Updated dependencies [6e88f77]
  • Updated dependencies [c8b9f57]
  • Updated dependencies [45d27c5]
    • @objectstack/spec@6.8.0
    • @objectstack/platform-objects@6.8.0
    • @objectstack/core@6.8.0

6.7.1

Patch Changes

  • @objectstack/spec@6.7.1
  • @objectstack/core@6.7.1
  • @objectstack/platform-objects@6.7.1

6.7.0

Patch Changes

  • Updated dependencies [430067b]
  • Updated dependencies [4f9e9d4]
  • Updated dependencies [4f9e9d4]
    • @objectstack/spec@6.7.0
    • @objectstack/platform-objects@6.7.0
    • @objectstack/core@6.7.0

6.6.0

Patch Changes

  • Updated dependencies [a49cfc2]
    • @objectstack/spec@6.6.0
    • @objectstack/core@6.6.0
    • @objectstack/platform-objects@6.6.0

6.5.1

Patch Changes

  • de239ef: Fix WebContainer (StackBlitz) sign-up / sign-in failing with INTERNAL_SERVER_ERROR: No request state found. Please make sure you are calling this function within a runWithRequestState callback.

    WebContainer reports itself as Node.js but its node:async_hooks implementation does not propagate AsyncLocalStorage context across await boundaries. As a result, better-auth's runWithRequestState wrap installed by handleRequest was lost as soon as the inner customSessiongetSession() call chain awaited anything, and every endpoint that reads request state (e.g. should-session-refresh, oauth) threw "No request state found".

    AuthManager now detects WebContainer and pre-populates better-auth's global requestStateAsyncStorage slot with a synchronous polyfill before better-auth instantiates its own. The polyfill correctly propagates the store through awaited promises within a single run() call, which is sufficient for WebContainer's single-flight dev server. Production environments (real Node, Bun, edge runtimes) continue to use the native AsyncLocalStorage and are unaffected.

    • @objectstack/spec@6.5.1
    • @objectstack/core@6.5.1
    • @objectstack/platform-objects@6.5.1

6.5.0

Patch Changes

  • @objectstack/spec@6.5.0
  • @objectstack/core@6.5.0
  • @objectstack/platform-objects@6.5.0

6.4.0

Patch Changes

  • 0bf6f9a: Add explicit @better-auth/core dependency.

    plugin-auth already pulled @better-auth/core transitively via @better-auth/oauth-provider, but several call sites in auth-manager.ts import from it directly. Promote it to a first-class dependency so the resolved version is stable across the workspace and pnpm install doesn't surface "module not found" against the transitive copy under stricter peer resolution.

    No behaviour change.

  • Updated dependencies [f8651cc]

  • Updated dependencies [f8651cc]

  • Updated dependencies [0bf6f9a]

    • @objectstack/spec@6.4.0
    • @objectstack/core@6.4.0
    • @objectstack/platform-objects@6.4.0

6.3.0

Patch Changes

  • @objectstack/spec@6.3.0
  • @objectstack/core@6.3.0
  • @objectstack/platform-objects@6.3.0

6.2.0

Minor Changes

  • b4c74a9: WebContainer (StackBlitz) signup compatibility: AuthManager now auto-detects WebContainer runtimes at construction time and swaps better-auth's default node:crypto.scrypt-based password hasher for the pure-JS hasher from @better-auth/utils/password (which uses @noble/hashes/scrypt under the hood).

    Why: WebContainer's node:crypto polyfill ships an incomplete scrypt implementation that throws TypeError: y.run is not a function on every signup, blocking template demos on StackBlitz. The pure-JS implementation is byte-compatible with the Node hasher (same scrypt params, same salt:keyHex storage format), so accounts created under either hasher remain mutually verifiable — no migration, no template changes.

    Scope: detection short-circuits to undefined on real Node, so production deployments are completely unaffected — the JS fallback module is only dynamically imported when one of process.versions.webcontainer, SHELL containing jsh, or STACKBLITZ env is present.

    Templates (@template/todo, @template/contracts, …) require no changes; the fix lives entirely inside @objectstack/plugin-auth.

Patch Changes

  • Updated dependencies [b4c74a9]
    • @objectstack/spec@6.2.0
    • @objectstack/core@6.2.0
    • @objectstack/platform-objects@6.2.0

6.1.1

Patch Changes

  • @objectstack/spec@6.1.1
  • @objectstack/core@6.1.1
  • @objectstack/platform-objects@6.1.1

6.1.0

Patch Changes

  • Updated dependencies [93c0589]
    • @objectstack/spec@6.1.0
    • @objectstack/core@6.1.0
    • @objectstack/platform-objects@6.1.0

6.0.0

Patch Changes

  • Updated dependencies [629a716]
  • Updated dependencies [dbc4f7d]
  • Updated dependencies [944f187]
    • @objectstack/spec@6.0.0
    • @objectstack/platform-objects@6.0.0
    • @objectstack/core@6.0.0

5.2.0

Patch Changes

  • Updated dependencies [bab2b20]
  • Updated dependencies [fa011d8]
  • Updated dependencies [f0f7c27]
  • Updated dependencies [b806f58]
    • @objectstack/platform-objects@5.2.0
    • @objectstack/spec@5.2.0
    • @objectstack/core@5.2.0

5.1.0

Patch Changes

  • Updated dependencies [75f4ee6]
  • Updated dependencies [823d559]
    • @objectstack/spec@5.1.0
    • @objectstack/platform-objects@5.1.0
    • @objectstack/core@5.1.0

5.0.0

Patch Changes

  • Updated dependencies [888a5c1]
  • Updated dependencies [2f9073a]
    • @objectstack/platform-objects@5.0.0
    • @objectstack/spec@5.0.0
    • @objectstack/core@5.0.0

4.2.0

Patch Changes

  • Updated dependencies [2869891]
    • @objectstack/spec@4.2.0
    • @objectstack/core@4.2.0
    • @objectstack/platform-objects@4.2.0

4.1.1

Patch Changes

  • @objectstack/spec@4.1.1
  • @objectstack/core@4.1.1
  • @objectstack/platform-objects@4.1.1

4.1.0

Patch Changes

  • Updated dependencies [2108c30]
  • Updated dependencies [23db640]
    • @objectstack/spec@4.1.0
    • @objectstack/core@4.1.0
    • @objectstack/platform-objects@4.1.0

4.0.5

Patch Changes

  • 15e0df6: chore: unify all package versions to a single patch release
  • Updated dependencies [15e0df6]
    • @objectstack/spec@4.0.5
    • @objectstack/core@4.0.5
    • @objectstack/platform-objects@4.0.5

Unreleased

Minor Changes

  • Always register better-auth's bearer() plugin so cross-origin browsers (where third-party cookies are blocked) and native mobile clients can authenticate via Authorization: Bearer <token> headers and pick up rotated tokens from the set-auth-token response header (fixes #1172).

4.0.4

Patch Changes

  • Updated dependencies [326b66b]
    • @objectstack/spec@4.0.4
    • @objectstack/core@4.0.4

4.0.3

Patch Changes

  • @objectstack/spec@4.0.3
  • @objectstack/core@4.0.3

4.0.2

Patch Changes

  • Updated dependencies [5f659e9]
    • @objectstack/spec@4.0.2
    • @objectstack/core@4.0.2

4.0.0

Patch Changes

  • e0b0a78: Deprecate DataEngineQueryOptions in favor of QueryAST-aligned EngineQueryOptions.

    Engine, Protocol, and Client now use standard QueryAST parameter names:

    • filterwhere
    • selectfields
    • sortorderBy
    • skipoffset
    • populateexpand
    • toplimit

    The old DataEngine* schemas and types are preserved with @deprecated markers for backward compatibility.

  • Updated dependencies [f08ffc3]

  • Updated dependencies [e0b0a78]

    • @objectstack/spec@4.0.0
    • @objectstack/core@4.0.0

3.3.1

Patch Changes

  • @objectstack/spec@3.3.1
  • @objectstack/core@3.3.1

3.3.0

Minor Changes

  • 814a6c4: sql driver

Patch Changes

  • @objectstack/spec@3.3.0
  • @objectstack/core@3.3.0

3.2.9

Patch Changes

  • @objectstack/spec@3.2.9
  • @objectstack/core@3.2.9

3.2.8

Patch Changes

  • 1fe5612: fix vercel
    • @objectstack/spec@3.2.8
    • @objectstack/core@3.2.8

3.2.7

Patch Changes

  • 35a1ebb: fix auth
    • @objectstack/spec@3.2.7
    • @objectstack/core@3.2.7

3.2.6

Patch Changes

  • @objectstack/spec@3.2.6
  • @objectstack/core@3.2.6

3.2.5

Patch Changes

  • e854538: fix beyyer-auth
    • @objectstack/spec@3.2.5
    • @objectstack/core@3.2.5

3.2.4

Patch Changes

  • f490991: fix better-auth
    • @objectstack/spec@3.2.4
    • @objectstack/core@3.2.4

3.2.3

Patch Changes

  • 0b1d7c9: fix auth
    • @objectstack/spec@3.2.3
    • @objectstack/core@3.2.3

3.2.2

Patch Changes

  • cfaabbb: fix: AuthPlugin error handling & database adapter config

    • AuthManager.handleRequest() now inspects response.status >= 500 and logs the error body via console.error, since better-auth catches internal errors and returns 500 Responses without throwing.
    • AuthPlugin.registerAuthRoutes() also logs 500+ responses via ctx.logger.error for structured plugin logging.
    • createDatabaseConfig() now wraps the ObjectQL adapter as a DBAdapterInstance factory function so better-auth's getBaseAdapter() correctly recognises it (via typeof database === "function" check) instead of falling through to the Kysely adapter path.
  • Updated dependencies [46defbb]

    • @objectstack/spec@3.2.2
    • @objectstack/core@3.2.2

3.2.1

Patch Changes

  • Updated dependencies [850b546]
    • @objectstack/spec@3.2.1
    • @objectstack/core@3.2.1

3.2.0

Patch Changes

  • Updated dependencies [5901c29]
    • @objectstack/spec@3.2.0
    • @objectstack/core@3.2.0

3.1.1

Patch Changes

  • Updated dependencies [953d667]
    • @objectstack/spec@3.1.1
    • @objectstack/core@3.1.1

3.1.0

Patch Changes

  • Updated dependencies [0088830]
    • @objectstack/spec@3.1.0
    • @objectstack/core@3.1.0

3.0.11

Patch Changes

  • Updated dependencies [92d9d99]
    • @objectstack/spec@3.0.11
    • @objectstack/core@3.0.11

3.0.10

Patch Changes

  • Updated dependencies [d1e5d31]
    • @objectstack/spec@3.0.10
    • @objectstack/core@3.0.10

3.0.9

Patch Changes

  • Updated dependencies [15e0df6]
    • @objectstack/spec@3.0.9
    • @objectstack/core@3.0.9

3.0.8

Patch Changes

  • Updated dependencies [5a968a2]
    • @objectstack/spec@3.0.8
    • @objectstack/core@3.0.8

3.0.7

Patch Changes

  • Updated dependencies [0119bd7]
  • Updated dependencies [5426bdf]
    • @objectstack/spec@3.0.7
    • @objectstack/core@3.0.7

3.0.6

Patch Changes

  • Updated dependencies [5df254c]
    • @objectstack/spec@3.0.6
    • @objectstack/core@3.0.6

3.0.5

Patch Changes

  • Updated dependencies [23a4a68]
    • @objectstack/spec@3.0.5
    • @objectstack/core@3.0.5

3.0.4

Patch Changes

  • Updated dependencies [d738987]
    • @objectstack/spec@3.0.4
    • @objectstack/core@3.0.4

3.0.3

Patch Changes

  • c7267f6: Patch release for maintenance updates and improvements.
  • Updated dependencies [c7267f6]
    • @objectstack/spec@3.0.3
    • @objectstack/core@3.0.3

3.0.2

Patch Changes

  • Updated dependencies [28985f5]
    • @objectstack/spec@3.0.2
    • @objectstack/core@3.0.2

3.0.1

Patch Changes

  • Updated dependencies [389725a]
    • @objectstack/spec@3.0.1
    • @objectstack/core@3.0.1

3.0.0

Major Changes

  • Release v3.0.0 — unified version bump for all ObjectStack packages.

Patch Changes

  • Updated dependencies
    • @objectstack/spec@3.0.0
    • @objectstack/core@3.0.0

2.0.7

Patch Changes

  • Updated dependencies
    • @objectstack/spec@2.0.7
    • @objectstack/core@2.0.7

2.0.6

Patch Changes

  • Patch release for maintenance and stability improvements
  • Updated dependencies
    • @objectstack/spec@2.0.6
    • @objectstack/core@2.0.6

2.0.5

Patch Changes

  • Unify all package versions with a patch release
  • Updated dependencies
    • @objectstack/spec@2.0.5
    • @objectstack/core@2.0.5

2.0.3

Patch Changes

  • Updated dependencies
    • @objectstack/spec@2.0.4
    • @objectstack/core@2.0.4

All notable changes to @objectstack/plugin-auth will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

2.0.2 - 2026-02-10

Added

  • Initial release of Auth Plugin
  • Integration with better-auth library for robust authentication
  • Session management and user authentication
  • Support for OAuth providers (Google, GitHub, Microsoft, etc.)
  • Organization/team support for multi-tenant applications
  • Two-factor authentication (2FA)
  • Passkey support
  • Magic link authentication
  • Configurable session expiry and refresh
  • Automatic HTTP route registration
  • Comprehensive test coverage

Security

  • Secure session token management
  • Encrypted secrets support
  • Rate limiting capabilities
  • CSRF protection