Wire full Better Auth flow with feature flags + callback normalizer - #268
Merged
Conversation
- Add `magicLink` and `passkey` plugins server-side, gated by
`OPENKITTEN_MAGIC_LINK_ENABLED` / `OPENKITTEN_PASSKEY_ENABLED`
(default disabled). Wire matching client plugins on `auth-client.ts`
and conditional UI in `auth-provider.tsx`.
- Add env-gated Google + GitHub social providers (`socialProviders.ts`)
driven by `GOOGLE_CLIENT_ID/SECRET` and `GITHUB_CLIENT_ID/SECRET`.
- Introduce chatacademia-style callback storage (sessionStorage +
localStorage) so OAuth and magic-link returns survive cross-tab and
cross-window flows. New `lib/{normalize,produce,retrieve,consume}-callback`
with try/catch hardening for Safari private mode and quota-exceeded.
- Add `/auth-callback` route that consumes the stored target and routes
unverified users to the new `verify-email` view.
- Override Better Auth's `callbackURL` for verification + magic-link
emails so they land on the SPA's `/auth-callback`, not the API.
Validate origin to reject external redirects.
- Pass `redirectTo` to `requestPasswordReset` so reset emails point at
the SPA's `/auth/reset-password` page.
- Replace `isProduction` with `isLive` / `isLocal` in world-util,
derived from `OPENKITTEN_LOCAL`. Add `isMagicLinkEnabled` and
`isPasskeyEnabled` flags. Vite injects them into the SPA bundle via
per-key `process.env.X` defines.
- Add `passkey` table + relations via `@better-auth/cli generate`,
migrated with drizzle-kit. Cookie attributes now scale `secure` with
`isLive`.
- `.env.example` documents every env var. `.env.local` is dev-only.
100% test coverage maintained across all changed packages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
magicLink+passkeyBetter Auth plugins (gated by env, off by default), plus Google + GitHub social providers, end-to-end on server and SPA./auth-callbackroute, and averify-emailview for unverified users.isProductionwithisLive/isLocalin world-util, addisMagicLinkEnabled/isPasskeyEnabled, and inject them into the SPA bundle via Vite per-keyprocess.envdefines.What changed
Server (
@openkitten/world-server)auth.tsx: conditionalmagicLink()+passkey()plugin registration, env-gatedsocialProviders,defaultCookieAttributesscaled byisLive,ensureAuthCallbackvalidates origin againstworldURLbefore forwarding.passkeytable + relations + migration0002_conscious_maestro.sql(generated via@better-auth/cli generateanddrizzle-kit generate).lib/social-providers.ts,lib/emails/magic-link.tsx.SPA (
@openkitten/world-spa)auth-callbackroute +verify-emailcomponent cover the new flows.lib/{normalize,produce,retrieve,consume}-callback.tsimplement dual-storage callback survival with try/catch for Safari private / quota-exceeded.lib/{authenticate,get-session,session-query-options}.tswrap Better Auth UI's session query for clientLoader use.auth-provider.tsxreadsmagicLink={isMagicLinkEnabled}/passkey={isPasskeyEnabled}from world-util;auth-client.tsaddsmagicLinkClient()+passkeyClient().forgot-password.tsxpassesredirectTo: ${worldURL}/auth/reset-passwordso reset emails land on the SPA.sign-up.tsxalways routes through/auth-callback.authenticated.tsxlayout uses the newauthenticate()helper.auth.tsxroute falls back to stored callback when no?redirectTo=query is present.vite.config.tsloads.env.localfrom project root and injectsOPENKITTEN_*flags via per-keyprocess.env.Xdefines.Util (
@openkitten/world-util)is-live.ts,is-local.ts,is-magic-link-enabled.ts,is-passkey-enabled.ts.Env
.env.exampledocuments every env var.Test plan
bun --bun tsc --buildcleanbun --bun biome checkcleanbun run --workspaces --if-present test— 1517 tests pass across 9 workspaces/auth/sign-inrenders all four methods (email+password, magic-link, passkey, Google, GitHub) with both flags enabledOPENKITTEN_MAGIC_LINK_ENABLED=andOPENKITTEN_PASSKEY_ENABLED=empty hides their buttons and returns 404 from their endpointssignIn.magicLinkAPI call → visited/auth/magic-link/verify?token=…→ server set session cookie → 302 to/auth-callback→ consumed stored callback/game→ landed on/gamegenerate-register-options,generate-authenticate-options,list-user-passkeys) return valid WebAuthn payloads withchallenge+rpId/auth/sign-in/socialfor bothgoogleandgithubreturns provider authorization URL with the devclient_idechoed backNotes
connect ECONNREFUSED 127.0.0.1:587untilSMTP_HOST/USER/PASSare set — the plugin is correctly wired and stores the token in Redis even when the email fails. Production deployments need real SMTP credentials.serverURLandworldURLare localhost ports in dev (same-site). Production with separate hostnames will needcrossSubDomainCookiesconfig — out of scope for this PR.🤖 Generated with Claude Code