Skip to content

fix(auth): register page creates the credential row, so new accounts can log in - #1104

Open
mmcintosh wants to merge 1 commit into
mainfrom
fix/register-form-credential
Open

mmcintosh wants to merge 1 commit into
mainfrom
fix/register-form-credential

Conversation

@mmcintosh

@mmcintosh mmcintosh commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Description

The register page posts to POST /auth/register/form (htmx), not to the JSON /auth/register. The form handler inserted auth_user but never the auth_account credential row that Better Auth signs in with.

As a result:

  • registration returned 200, and the first user was even made admin;
  • every sign-in then failed with "Invalid email or password" (Better Auth logs Credential account not found), until a later full bootstrap happened to backfill the row (with the KV marker, up to 24 hours).

The JSON route already wrote both rows. Only the path the UI uses was broken.

The deployment docs made it worse. They say the ADMIN_EMAIL / ADMIN_PASSWORD secrets create the admin at bootstrap, and nothing in the code reads them.

Refs #1054 (reported in detail by elpastiche on that issue).

Changes

  • routes/auth.ts, POST /auth/register/form:
    • writes auth_user and the credential row in one db.batch, exactly as the JSON route does;
    • for the first user, seeds document types and RBAC before creating the account, the same idempotent calls /auth/seed-admin makes;
    • checks that the admin role actually landed. If the role assignment fails, it removes the rows it just created and reports the failure, so a retry works instead of hitting "email already exists";
    • optional custom profile fields are saved after that step. A failure there is logged, and the account is still created.
  • Docs: the deployment, configuration, troubleshooting and core-plugin pages, plus docs/deployment.md.
    • They now say the first account registered becomes the admin, with a note to do it right after deploying.
    • They no longer document ADMIN_EMAIL / ADMIN_PASSWORD.
    • The "create admin via API" example is gone: the JSON route always creates a viewer.

Testing

  • auth-register-form.integration.test.ts (new, 6 tests): real SQLite, then sign-in through a real Better Auth instance.

    • The first (admin) user and a later user both sign in.
    • A wrong password gets 401.
    • A duplicate email writes no second user or credential row.
    • The first user on a database with no RBAC seed ends up an RBAC admin.
    • A forced role-assignment failure leaves no rows, and the retry succeeds.

    On main's handler, 5 of the 6 fail; the wrong-password case is the control and passes either way. The tests also pass on Better Auth 1.7.7 (fix(auth): move to Better Auth 1.7.7 and better-auth-cloudflare 0.4.1 #1103).

Unit Tests

  • Added/updated unit tests
  • All unit tests passing (this branch: 1936 / 0; all five batch PRs merged: 2000 / 0)

E2E Tests

  • Added/updated E2E tests: 113-register-form-login.spec.ts (@auth). It registers through the real page, then logs in through POST /auth/login, and a wrong password gets 401. It waits out spec 37 switching registration off on the shared preview database instead of toggling the setting itself.
  • All E2E tests passing (not run locally, per the repo's E2E policy)

Screenshots/Videos

N/A. The register page looks the same; the change is what the server writes. The docs changes are text only.

Checklist

  • Code follows project conventions
  • Tests added/updated and passing
  • Type checking passes
  • No console errors or warnings
  • Documentation updated (if needed)

…can log in

POST /auth/register/form (what the register page posts to) inserted auth_user but
not the auth_account credential row Better Auth reads the password from. Registration
returned 200, the first user was even made admin, and every sign-in then failed with
"Invalid email or password". The JSON /auth/register already wrote both rows; the form
handler now does the same, in one batch.

The first user is the admin, and admin access is RBAC-backed. A failed first boot can
leave the system roles unseeded (#1017), and addUserRoleByName then silently does
nothing. So for the first account the form now seeds the document types and system
roles first (idempotent, same as /auth/seed-admin) and checks the admin role actually
landed. If it can't be assigned, the new rows are removed and the error is reported,
so a retry works instead of hitting "email already exists". Optional custom profile
fields are saved after that; a failure there is logged rather than failing a working
account.

Docs: the deployment, configuration, troubleshooting and core-plugin pages said the
ADMIN_EMAIL / ADMIN_PASSWORD secrets create the admin at bootstrap. Nothing reads them.
They now say the first account registered becomes the admin, and drop the
"create admin via API" example (the JSON route always creates a viewer).

E2E 113 registers through the real page and logs in. It waits out spec 37's short
registration-disabled windows on the shared preview DB instead of toggling the setting.

Refs #1054

This branch was successfully deployed

1 active deployment
internal — 9a077c35 Deployed Oct 13, 2026 by mmcintosh via authorize #1608
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.

1 participant