Skip to content

fix(auth): pin Better Auth to 1.6.25 - #1062

Closed
shoveller wants to merge 4 commits into
SonicJs-Org:mainfrom
illuwa-soft:fix/auth-pin-better-auth-1.6.25
Closed

shoveller wants to merge 4 commits into
SonicJs-Org:mainfrom
illuwa-soft:fix/auth-pin-better-auth-1.6.25

Conversation

@shoveller

Copy link
Copy Markdown

Description

Prevents fresh @sonicjs-cms/core consumers from resolving Better Auth 1.7.x before SonicJS has migrated its auth_account schema to the new issuer-scoped account identity model.

Fixes #1061

Changes

  • Pin better-auth, @better-auth/core, @better-auth/drizzle-adapter, and @better-auth/telemetry to the repository-tested 1.6.25 release.
  • Keep the root and standalone core lockfile manifest snapshots aligned with the published core manifest.
  • Add a deterministic regression test requiring the complete Better Auth family to use one exact schema-compatible version.
  • Ignore the repository-local .worktrees/ directory used for isolated contribution work.

@better-auth/core is intentionally direct: a packed-consumer check showed that pinning only the other three packages still allowed peer dependency resolution to install @better-auth/core@1.7.1 alongside 1.6.25.

This urgent patch does not change the database schema or runtime auth code. A coordinated Better Auth 1.7 issuer migration remains follow-up work.

Testing

Unit Tests

  • Added better-auth-dependency-compat.test.ts
  • Verified the test fails with the previous caret ranges
  • Targeted regression test passes
  • Full unit suite passes: 110 files passed, 6 skipped; 1,731 tests passed, 247 skipped
  • Coverage suite passes with the same test totals

Package Consumer Check

  • Packed packages/core with npm pack
  • Installed the tarball into a fresh temporary consumer using --package-lock-only
  • Confirmed exactly one resolved version of each package, all 1.6.25:
    • better-auth
    • @better-auth/core
    • @better-auth/drizzle-adapter
    • @better-auth/telemetry

Other Verification

  • npm run lint --workspace=@sonicjs-cms/core (passes with existing warnings)
  • npm run type-check
  • npm run build:core
  • git diff --check
  • New Playwright spec: not applicable because this patch changes package resolution only; existing auth E2E behavior is unchanged

Known Repository Baseline Issue

npm ci --ignore-scripts currently fails before installation because the checked-in root lockfile already drifts from unrelated workspace manifests (esbuild, eslint, and sharp). The repository CI uses npm install, which succeeds. This PR deliberately avoids including the resulting 1,500+ lines of unrelated lockfile churn.

Screenshots/Videos

Not applicable; no UI changes.

Checklist

  • Code follows project conventions
  • Tests added and passing
  • Type checking passes
  • No new console errors or warnings
  • Documentation update not required for this compatibility pin
  • No migration or fixture changes

Pin the full Better Auth dependency family to the schema-compatible version and add a manifest regression test for fresh package consumers.\n\nFixes #1

Signed-off-by: 서재원 <cinos81@gmail.com>
@mmcintosh

Copy link
Copy Markdown
Collaborator

Confirming this fix targets a live, repo-wide CI problem — with a reproduction and the concrete mechanism.

Root cause, reproduced. packages/core/package.json carries "better-auth": "^1.6.13" (and the scoped packages at ^1.6.23). With no lockfile constraint — i.e. a fresh install, which is exactly what the CI preview deploy and any new @sonicjs-cms/core consumer do — that caret resolves to the current latest, 1.7.1:

$ echo '{"dependencies":{"better-auth":"^1.6.13"}}' > package.json && npm i --package-lock-only
# → better-auth 1.7.1
$ echo '{"dependencies":{"better-auth":"1.6.25"}}' > package.json && npm i --package-lock-only   # this PR's pin
# → better-auth 1.6.25

The committed lockfile currently pins the family to 1.6.22, so local npm ci passes while any fresh resolve floats to 1.7 — a classic works-locally / breaks-on-fresh-install split.

Why 1.7 breaks auth specifically. better-auth 1.7.0 ships breaking schema changes — most relevantly "Accounts now require Account.issuer… Must apply account-identity backfill before deploying", plus the joins-config move that "requires regenerating schemas." SonicJS's 0001_core.sql was written for 1.6.x: auth_account has account_id/provider_id but no issuer column (and no backfill). So under 1.7 the account/session/login operations hit a mismatched schema and fail at runtime, while non-auth routes are unaffected.

Observed impact. On the shared PR preview lane this shows up as a consistent block of @auth/session failures — 02c-otp-login, 02d-magic-link-auth, 38/68/80-user-profile*, 85-admin-panel-roles, 02-authentication, 67-rbac — on PRs otherwise unrelated to auth. I hit exactly this set on a templates-only PR (#1066), and the failures were invariant across a test-only re-push, i.e. not caused by that PR. Same root cause as #1054 (can't log in on a fresh production deploy).

Pinning the whole family to an exact, schema-compatible 1.6.25 (+ the guard test here) is the right fix. It looks stalled, though: the authorize check is still pending (external-contributor workflow authorization), so CI hasn't run, and there's no review yet. Authorizing + landing this should green the @auth E2E lane across every open PR.

(Reproduced locally; I did not run the auth E2E under 1.7 — that path needs the preview deploy — but the resolution float and the 1.7 schema break are both verified above.)

lane711 pushed a commit that referenced this pull request Oct 22, 2026
Better Auth >=1.4.0-beta.18 <1.7.7 is affected by GHSA-965c-763c-88jm
(critical; no 1.6 backport), so this moves forward instead of pinning
1.6.25. Exact-pin approach from #1062 (@shoveller).

- Pin better-auth, @better-auth/core, drizzle-adapter, telemetry to exactly
  1.7.7 and better-auth-cloudflare to exactly 0.4.1; drizzle-orm peer
  ^0.45.2, and the create-app starter template to match.
- Both lockfiles regenerated with npm 10. packages/core/package-lock.json
  was already out of sync on main (graphql/graphql-yoga missing; npm ci
  failed).
- Organization field mapping uses the drizzle property key `tenantId`
  (1.7.3+ validates the drizzle schema at startup).
- `updatedAt` default and on-update on the auth_tenant* drizzle tables
  (no migration).
- Verification values consumed from D1 (`storeInDatabase`, a single atomic
  DELETE ... RETURNING); rate-limit counters in memory; own KV
  SecondaryStorage typed against Better Auth's full interface (KV has no
  atomic getAndDelete / increment).
- Plugins passed inside withCloudflare so its plugin and storage check run
  (this also mounts its session-gated GET /auth/cloudflare/geolocation).
- Organization endpoints closed in two layers: app.ts returns 404 for
  /auth/organization/*, and Better Auth `disabledPaths` lists every path the
  organization plugin mounts (derived from the plugin, so a new endpoint is
  covered). Either layer alone holds. Tenants belong to TenantService; with
  the mapping fixed, these endpoints would otherwise let a tenant member
  with the org `admin` role rewrite auth_tenant.domain (host routing).
  allowUserToCreateOrganization: false as well.
- Passwords capped at 128 characters wherever SonicJS sets one (routes,
  create-sonicjs prompt/flag, self-host seed); 1.7 refuses longer ones at
  sign-in.
- Docker rate-limit comment corrected: clients can rotate
  cf-connecting-ip / x-real-ip for a fresh bucket there (same on 1.6).
- Tests over real Better Auth + SQLite, including password rows written
  outside Better Auth (POST /auth/register, join, seeds) and organization
  update / invite-member under each guard layer alone, with a positive
  control; harness applies 0003; E2E spec 112.

Fixes #1061. Refs #1054. Supersedes #1062.
lane711 added a commit that referenced this pull request Oct 23, 2026
* fix(auth): move to Better Auth 1.7.7 and better-auth-cloudflare 0.4.1

Better Auth >=1.4.0-beta.18 <1.7.7 is affected by GHSA-965c-763c-88jm
(critical; no 1.6 backport), so this moves forward instead of pinning
1.6.25. Exact-pin approach from #1062 (@shoveller).

- Pin better-auth, @better-auth/core, drizzle-adapter, telemetry to exactly
  1.7.7 and better-auth-cloudflare to exactly 0.4.1; drizzle-orm peer
  ^0.45.2, and the create-app starter template to match.
- Both lockfiles regenerated with npm 10. packages/core/package-lock.json
  was already out of sync on main (graphql/graphql-yoga missing; npm ci
  failed).
- Organization field mapping uses the drizzle property key `tenantId`
  (1.7.3+ validates the drizzle schema at startup).
- `updatedAt` default and on-update on the auth_tenant* drizzle tables
  (no migration).
- Verification values consumed from D1 (`storeInDatabase`, a single atomic
  DELETE ... RETURNING); rate-limit counters in memory; own KV
  SecondaryStorage typed against Better Auth's full interface (KV has no
  atomic getAndDelete / increment).
- Plugins passed inside withCloudflare so its plugin and storage check run
  (this also mounts its session-gated GET /auth/cloudflare/geolocation).
- Organization endpoints closed in two layers: app.ts returns 404 for
  /auth/organization/*, and Better Auth `disabledPaths` lists every path the
  organization plugin mounts (derived from the plugin, so a new endpoint is
  covered). Either layer alone holds. Tenants belong to TenantService; with
  the mapping fixed, these endpoints would otherwise let a tenant member
  with the org `admin` role rewrite auth_tenant.domain (host routing).
  allowUserToCreateOrganization: false as well.
- Passwords capped at 128 characters wherever SonicJS sets one (routes,
  create-sonicjs prompt/flag, self-host seed); 1.7 refuses longer ones at
  sign-in.
- Docker rate-limit comment corrected: clients can rotate
  cf-connecting-ip / x-real-ip for a fresh bucket there (same on 1.6).
- Tests over real Better Auth + SQLite, including password rows written
  outside Better Auth (POST /auth/register, join, seeds) and organization
  update / invite-member under each guard layer alone, with a positive
  control; harness applies 0003; E2E spec 112.

Fixes #1061. Refs #1054. Supersedes #1062.

* fix(auth): harden Better Auth 1.7.7 upgrade review findings

Address 6 code-review findings from PR #1103:

1. Rename E2E spec 112 → 124 (R11 numbering convention)
2. Organization 404 guard: app.all instead of GET/POST only, covers all
   HTTP methods; add explicit /auth/organization (bare path) route
3. organizationPaths filter: also match bare '/organization' endpoint
4. secondary-storage set(): treat ttl≤0 as immediate delete instead of
   clamping to 60s
5. secondaryStorage key: omit from options when CACHE_KV is absent,
   avoiding a present-but-undefined key
6. createAuth: warn when extendBetterAuth removes disabledPaths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(e2e): stabilize redirect management test (80-redirect-management)

Two bugs causing hard failure in CI:

1. Post-submit navigation race: waitForLoadState('networkidle') resolves
   before the server-side redirect fires, so toHaveURL sees the edit URL.
   Fix: waitForURL with 15s timeout instead.

2. Non-unique destination: retries accumulate redirects all sharing
   destination '/updated-destination-3', causing strict mode violations.
   Fix: timestamp-based unique destinations, and assert on the
   source+destination compound selector.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(redirect): swap HTMX 400 error responses into form-messages

HTMX 1.x does not swap 4xx responses by default, so server-rendered
validation errors (duplicate source URL) were silently dropped. Add a
beforeSwap handler that allows 400/422 responses to be swapped into
#form-messages.

Also stabilize the E2E test: wait for the POST response and assert 400
status instead of a fixed 1s timeout, and use waitForURL for the first
create's post-submit redirect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(e2e): add polling for cache invalidation test (cross-isolate delay)

Cloudflare Workers can route sequential requests to different isolates,
each with its own in-memory cache. A POST (create/update) invalidates
the cache on one isolate, but a subsequent GET may hit a different
isolate still serving stale data.

Add a poll-and-retry helper so the test tolerates the propagation delay
instead of failing deterministically on multi-isolate deployments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(tests): address PR review comments — edge cases and assertions

- Add below-floor assertion (1.7.6 < 1.7.7) in version guard test
- Add ttl<=0 delete-path coverage in secondary-storage test
- Cover bare /auth/organization path in E2E org-endpoint test
- Fail on OAuth 5xx instead of skipping in GHSA test
- Assert pollListForItem precondition in cache-invalidation update test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Mark McIntosh <mmcintosh@infowall.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lane711

lane711 commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Thanks for jumping on the Better Auth version pin @shoveller — that was a timely catch to keep things stable. 🙏

This is superseded by #1133, which upgrades to Better Auth 1.7.7 (a full upgrade rather than a pin to 1.6.25) and has been merged to main. It will ship in the next release.

Closing in favour of the merged upgrade.

@lane711 lane711 closed this Oct 23, 2026

This branch had an error being deployed

1 failed deployment
external — c77571b3 Deployed Sep 12, 2026 by mmcintosh via authorize #1602
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(auth): prevent Better Auth 1.7 from resolving in new consumers

3 participants