Skip to content

fix(auth): serve set-initial-password on per-environment runtimes (#1544)#1577

Merged
xuyushun441-sys merged 2 commits into
mainfrom
fix/1544-set-initial-password-runtime
Jun 4, 2026
Merged

fix(auth): serve set-initial-password on per-environment runtimes (#1544)#1577
xuyushun441-sys merged 2 commits into
mainfrom
fix/1544-set-initial-password-runtime

Conversation

@xuyushun441-sys
Copy link
Copy Markdown
Contributor

Problem

On a per-environment (cloud) runtime, the SSO-as-owner recovery flow redirects to "Set a recovery password", which POSTs /api/v1/auth/set-initial-password404, dead-ending first-time owner setup (#1544).

Root cause

set-initial-password is a custom route registered only by the full AuthPlugin. That plugin is skipped on a per-environment kernel (auth is served by AuthProxyPlugin), which forwards /api/v1/auth/* to better-auth — and better-auth has no such route, because its setPassword op is server-only by design (createAuthEndpoint({…}) with no HTTP path; setting a password without proving the old one is privilege-sensitive). So the route only ever existed on the host kernel.

Fix

  • New shared helper runSetInitialPassword(authApi, request) — wraps better-auth's auth.api.setPassword and maps better-call APIErrors onto the { success, error: { code, message } } envelope the client parses. Single source of truth so the two mount points can't drift.
  • AuthPlugin now calls the helper, dropping ~50 lines of hand-rolled getSession/hash/createAccount/length-checks that had drifted from better-auth's own validation surface.
  • AuthProxyPlugin short-circuits set-initial-password before forwarding (resolves the 404), using the same helper.
  • Unit tests for the helper: missing/non-JSON arg, success + session-header forwarding, PASSWORD_ALREADY_SET→409, length errors, 401, 500 fallback.

Not in this PR (follow-up)

The recovery UI currently lives inside the full-shell profile page (/_console/system/profile?recovery_needed=true). Making it a standalone, shell-less page like login (/set-password) is an objectui change; the one-line redirect-target flip here is paired with it to avoid a broken interim state.

Verification

  • tsc --noEmit clean: plugin-auth + runtime
  • plugin-auth tests: 95 pass (88 existing + 7 new)

🤖 Generated with Claude Code

)

better-auth's `setPassword` is a server-only API (registered with no HTTP
path on purpose — setting a password without proving the old one is
privilege-sensitive). The full AuthPlugin wrapped it in a custom
`/api/v1/auth/set-initial-password` route, but that plugin is SKIPPED on a
per-environment (cloud) runtime, where auth is served by AuthProxyPlugin.
There the route was absent, so the SSO-onboarded "Set local password"
recovery flow POSTed it and 404'd, dead-ending first-time owner setup.

- Extract the route body into a shared `runSetInitialPassword` helper that
  wraps `auth.api.setPassword` + normalises better-call APIErrors onto our
  `{ success, error }` envelope.
- AuthPlugin now calls the helper (drops ~50 lines of hand-rolled
  getSession/hash/createAccount that had drifted from better-auth's own
  validation surface).
- AuthProxyPlugin short-circuits `set-initial-password` before forwarding to
  better-auth, using the same helper so the two mount points can't drift.
- Unit tests for the helper (missing arg, success header forwarding,
  already-set→409, length errors, 401, 500 fallback).

Redirect target (profile → standalone /set-password page) is a follow-up
paired with the objectui page; not flipped here to avoid a broken interim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 4, 2026 10:58am

Request Review

)

Now that objectui ships a shell-less `/set-password` auth surface (sibling of
login/reset-password), point the sso-exchange "no credential yet" redirect at
it instead of the full-shell profile page + `?recovery_needed=true` banner.
Conventional shape for an auth screen; the profile page drops the recovery
special-casing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants