feat(auth): better-auth 1.5.5 -> 1.6.x + resendOTP via @rafters/better-auth-resend (#122)#130
Merged
Merged
Conversation
This was referenced May 16, 2026
…r-auth-resend (#122) Drops the console.log OTP placeholder. OTP now delivers via Resend through @rafters/better-auth-resend (renders @rafters/mail-react-email OtpEmail template, posts to https://api.resend.com/emails). Workspace plumbing (extends the rafters pattern from #107) - pnpm-workspace.yaml: glob now includes ../mail/packages/{core,resend, react-email,better-auth-resend} as workspace:* source. Catalog backfilled with mail's entries (drizzle-orm, tsup, @cloudflare/ workers-types). Sanctioned pattern per 019e2840. - apps/web/package.json: @rafters/better-auth-resend, @rafters/mail, @rafters/mail-react-email, @rafters/mail-resend added as workspace:* deps. better-auth and @better-auth/passkey bumped 1.5.5 -> ^1.6.0. drizzle-orm bumped ^0.45.1 -> ^0.45.2 to satisfy better-auth 1.6.11's drizzle-adapter peer. - root package.json: @rafters/better-auth-resend added as devDep so root vitest can resolve it from tests/. Wiring - apps/web/src/auth.ts: import resendOTP, replace console.log stub. IIFE constructs the Resend sender once per buildAuth call (Zod parse + React Email renderer setup is non-trivial); wraps to bridge resendOTP's (email, otp) signature to better-auth 1.6's ({email, otp, type}, ctx?) shape. buildAuth is itself memoized per D1Database via the existing authCache WeakMap. - wrangler.jsonc: vars.FROM_EMAIL = "noreply@rafters.studio". RESEND_API_KEY documented as wrangler secret put provisioning. - .dev.vars: RESEND_API_KEY placeholder for local dev. - worker-configuration.d.ts: regen picks up FROM_EMAIL + RESEND_API_KEY. Tests - tests/api/auth.test.ts (new): asserts resendOTP returns a 2-arity function from a valid config, and rejects empty apiKey + invalid fromEmail (Zod boundary contract). Verification - pnpm typecheck: clean. - pnpm test: 27 passed (was 24, +3 auth wiring), 2 skipped. - pnpm build (wrangler dry-run): clean; FROM_EMAIL appears in the binding inventory. Out of scope - Polar webhook verification + audit (#124) - Inbound email via @rafters/mail-cloudflare (#123) - Production wrangler config + secrets provisioning (#125) - Workers-pool integration test for the auth handler (defer to #126 e2e suite which can hit a deployed worker) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
257a2ab to
0842c16
Compare
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.
Closes #122. Stacked on #128 (drops Astro from apps/web). Will rebase once #128 merges.
What
Drops the
console.logOTP placeholder. OTP now delivers via Resend through@rafters/better-auth-resend(renders theOtpEmailtemplate from@rafters/mail-react-email, posts to Resend's API).Workspace plumbing
Extends the cross-repo pattern from #107 (rafters packages) to mail packages.
pnpm-workspace.yaml: glob now includes../mail/packages/{core,resend,react-email,better-auth-resend}asworkspace:*source. Catalog backfilled with mail's entries (drizzle-orm,tsup,@cloudflare/workers-types). Sanctioned pattern per 019e2840.apps/web/package.json:@rafters/better-auth-resend,@rafters/mail,@rafters/mail-react-email,@rafters/mail-resendasworkspace:*. better-auth +@better-auth/passkeybumped 1.5.5 -> ^1.6.0.drizzle-orm^0.45.1 -> ^0.45.2 to satisfy better-auth 1.6.11's drizzle-adapter peer.package.json:@rafters/better-auth-resendadded as devDep so root vitest can resolve it fromtests/.Wiring
apps/web/src/auth.ts:Why the IIFE:
resendOTPreturns(email, otp) => Promise<void>but better-auth 1.6'ssendVerificationOTPis({email, otp, type}, ctx?) => Promise<void>. The IIFE constructs the Resend sender once (Zod parse + React Email renderer setup is non-trivial), then wraps to bridge signatures.buildAuthis itself memoized perD1Databasevia the existingauthCacheWeakMap.wrangler.jsonc:vars.FROM_EMAIL = \"noreply@rafters.studio\".RESEND_API_KEYis provisioned viawrangler secret put(#125 covers prod secrets).Tests
tests/api/auth.test.ts(new): assertsresendOTPreturns a 2-arity function from valid config, and rejects emptyapiKey+ invalidfromEmail(Zod boundary contract). Workers-pool integration test for the auth handler is deferred to #126's e2e suite (would need to extendtest-worker.tsto mount auth + provision a test better-auth schema; too much for this PR).Verification
pnpm typecheckcleanpnpm test: 27 passed (was 24, +3 auth wiring), 2 skippedpnpm build(wrangler deploy --dry-run) clean;FROM_EMAILappears in the binding inventoryRESEND_API_KEY(operator step, not in CI)Out of scope
Test plan
pnpm typecheckcleanpnpm test27 pass / 2 skippnpm build(wrangler dry-run) cleanCo-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com