fix(auth): email verification for signup#149
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
✏️ Tip: You can disable in-progress messages and the fortune message in your review settings. Tip CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.Add a .trivyignore file to your project to customize which findings Trivy reports. 📝 WalkthroughWalkthroughAdds Resend-based email delivery: new email utility, integration into registration and password-reset flows, RESEND_API_KEY/EMAIL_FROM env placeholders, and the "resend" dependency—emails are sent for verification and password reset (with non-blocking sends and environment-based fallback logging). Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant WebApp
participant AuthService
participant ResendService
User->>WebApp: Submit registration form
WebApp->>AuthService: register(credentials)
AuthService->>AuthService: create user & generate verify token
AuthService->>ResendService: sendVerificationEmail(to, verifyUrl)
ResendService->>ResendService: build HTML template
ResendService->>ResendService: call Resend API
ResendService-->>AuthService: success / error
AuthService-->>WebApp: registration result
WebApp-->>User: show confirmation
sequenceDiagram
participant User
participant WebApp
participant AuthService
participant ResendService
User->>WebApp: Request password reset
WebApp->>AuthService: requestPasswordReset(email)
AuthService->>AuthService: generate reset token & URL
AuthService->>ResendService: sendPasswordResetEmail(email, resetUrl)
ResendService->>ResendService: build reset HTML
ResendService->>ResendService: call Resend API
ResendService-->>AuthService: success / error
AuthService-->>WebApp: reset requested
WebApp-->>User: instruct to check email
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
.env.example (1)
79-84: Email config is placed under the STORAGE section header.The Resend email variables are documented under the
# STORAGEsection (Line 71). Consider adding a dedicated🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env.example around lines 79 - 84, The RESEND_EMAIL settings (RESEND_API_KEY and EMAIL_FROM) are incorrectly documented under the STORAGE header; move those lines into a new dedicated "# EMAIL" section (or place them adjacent to STORAGE but under an "# EMAIL" header) so email config is clearly separated from blob storage. Update the .env.example by cutting the RESEND_API_KEY and EMAIL_FROM lines from under the STORAGE block and pasting them under a new "# EMAIL" heading with the same comments, ensuring the variable names (RESEND_API_KEY, EMAIL_FROM) remain unchanged.apps/web-next/src/lib/email.ts (1)
13-16:getResendClient()creates a newResendinstance on every call.This is a minor inefficiency. Consider caching the client in a module-level variable (lazy singleton) so it's only instantiated once per cold start.
♻️ Proposed refactor
-function getResendClient(): Resend | null { - if (!RESEND_API_KEY) return null; - return new Resend(RESEND_API_KEY); -} +let _resendClient: Resend | null | undefined; +function getResendClient(): Resend | null { + if (_resendClient !== undefined) return _resendClient; + _resendClient = RESEND_API_KEY ? new Resend(RESEND_API_KEY) : null; + return _resendClient; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web-next/src/lib/email.ts` around lines 13 - 16, getResendClient currently new-allocates a Resend on every call; change it to a lazy singleton by adding a module-level variable (e.g., let resendClient: Resend | null | undefined) and have getResendClient check that variable: if undefined and RESEND_API_KEY is present, assign new Resend(RESEND_API_KEY) to it, then return the cached instance (or null when no key). Keep the function name getResendClient and references to Resend and RESEND_API_KEY intact so the rest of the module continues to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web-next/src/lib/api/auth.ts`:
- Around line 781-784: The email verification logging prints user.email
directly; update the logging in the email verification branch (the console.log
lines inside the conditional) to use sanitizeForLog(user.email) the same way
requestPasswordReset does (i.e., replace user.email with
sanitizeForLog(user.email)) so logs are consistent and protected against log
injection.
In `@apps/web-next/src/lib/email.ts`:
- Around line 24-26: The displayName value is interpolated raw into the HTML
email (via the greeting and later template usage), which allows HTML injection;
add an HTML-escaping helper (e.g., escapeHtml) and apply it to displayName
wherever used (replace direct uses of displayName with escapeHtml(displayName))
including the greeting variable and any other template interpolations, and
ensure the helper escapes &, <, >, ", and ' to their HTML entities before
returning the escaped string.
- Around line 162-166: When RESEND_API_KEY is missing the functions
sendVerificationEmail and sendPasswordResetEmail currently return { success:
true } and silently skip sending; change both to return { success: false } and
emit a warning in production (use processLogger.warn or console.warn) that the
API key is missing and the verification/reset URL (verifyUrl/resetUrl) was not
sent; update the branch that checks for !client in sendVerificationEmail (and
mirror the same change in sendPasswordResetEmail) so misconfigured production
deployments are observable.
---
Nitpick comments:
In @.env.example:
- Around line 79-84: The RESEND_EMAIL settings (RESEND_API_KEY and EMAIL_FROM)
are incorrectly documented under the STORAGE header; move those lines into a new
dedicated "# EMAIL" section (or place them adjacent to STORAGE but under an "#
EMAIL" header) so email config is clearly separated from blob storage. Update
the .env.example by cutting the RESEND_API_KEY and EMAIL_FROM lines from under
the STORAGE block and pasting them under a new "# EMAIL" heading with the same
comments, ensuring the variable names (RESEND_API_KEY, EMAIL_FROM) remain
unchanged.
In `@apps/web-next/src/lib/email.ts`:
- Around line 13-16: getResendClient currently new-allocates a Resend on every
call; change it to a lazy singleton by adding a module-level variable (e.g., let
resendClient: Resend | null | undefined) and have getResendClient check that
variable: if undefined and RESEND_API_KEY is present, assign new
Resend(RESEND_API_KEY) to it, then return the cached instance (or null when no
key). Keep the function name getResendClient and references to Resend and
RESEND_API_KEY intact so the rest of the module continues to work.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json,!package-lock.json
📒 Files selected for processing (5)
.env.exampleapps/web-next/.env.local.exampleapps/web-next/package.jsonapps/web-next/src/lib/api/auth.tsapps/web-next/src/lib/email.ts
eliteprox
left a comment
There was a problem hiding this comment.
Changes look good to me, just need to apply code suggestions to prevent html injection in emails and logs as suggested by coderabbit.
| if (!client) { | ||
| if (process.env.NODE_ENV !== 'production') { | ||
| console.log('[EMAIL] (no RESEND_API_KEY) Verification URL:', verifyUrl); | ||
| } |
There was a problem hiding this comment.
Add logging for when RESEND_API_KEY is not configured
| } | |
| } else { | |
| console.error('[EMAIL] RESEND_API_KEY is not configured — verification email not sent'); | |
| return { success: false, error: 'Email service not configured' }; | |
| } |
Fixes #148 - Add Resend integration for sending verification and password reset emails - Wire register() to send verification email after user creation - Add professional Livepeer community email templates (HTML) - Wire requestPasswordReset to send real emails via Resend - Add RESEND_API_KEY and EMAIL_FROM to env examples - Dev fallback: log links to console when RESEND_API_KEY is unset Co-authored-by: Cursor <cursoragent@cursor.com>
- Escape displayName with HTML entities before interpolating into email
templates to prevent XSS via user-controlled input
- Use lazy singleton for Resend client to avoid re-instantiation per call
- Return { success: false } when RESEND_API_KEY is missing so
misconfigured production deployments are observable
- Sanitize user.email with sanitizeForLog() in verification logging,
consistent with the password-reset path
- Move email env vars to dedicated EMAIL section in .env.example
Made-with: Cursor
ea0200b to
94ee096
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/web-next/src/lib/api/auth.ts (1)
278-281: Consider replacing fire-and-forget email send with durable dispatch.Line 279 launches an unawaited async send in a serverless-oriented module; this can be dropped if the request lifecycle ends early. Prefer a durable outbox/job queue, or at least a bounded await strategy, for better delivery reliability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web-next/src/lib/api/auth.ts` around lines 278 - 281, The current fire-and-forget call to sendVerificationEmail(user.id) is unreliable in serverless lifecycles; replace it with a durable dispatch: either enqueue a job to your existing job queue/outbox (e.g., push a record to EmailOutbox.create({...}) or JobQueue.enqueue({type: 'sendVerification', userId: user.id})) or perform a bounded await with timeout to ensure the function is attempted before the request ends; update the auth registration flow where sendVerificationEmail is invoked to write to the outbox/queue (or call a helper like enqueueVerificationEmail(user.id)) instead of directly calling sendVerificationEmail(...), and ensure the background worker consumes the outbox to perform the actual send.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.env.example:
- Around line 83-88: Update the .env.example comment block for RESEND_API_KEY to
indicate it is required in production: change the RESEND_API_KEY comment to read
something like "[REQUIRED for production] Get API key from
https://resend.com/api-keys" and replace the "Without this, verification/reset
links are logged to console (dev only)" line with a clear warning that auth
emails will not be sent in production without RESEND_API_KEY; also mark
EMAIL_FROM as optional but note it won't override delivery if RESEND_API_KEY is
missing.
In `@apps/web-next/src/lib/api/auth.ts`:
- Around line 675-676: Replace the ad-hoc app origin construction that uses
process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' with the existing
getAppUrl() helper so email links use the proper fallback (VERCEL_URL) in
deployments; update the code that builds resetUrl (and the similar verification
URL at the other site) to call getAppUrl() and then interpolate the token (e.g.,
const appUrl = getAppUrl(); const resetUrl =
`${appUrl}/reset-password?token=${token}`) so both resetUrl and the verification
link use getAppUrl().
---
Nitpick comments:
In `@apps/web-next/src/lib/api/auth.ts`:
- Around line 278-281: The current fire-and-forget call to
sendVerificationEmail(user.id) is unreliable in serverless lifecycles; replace
it with a durable dispatch: either enqueue a job to your existing job
queue/outbox (e.g., push a record to EmailOutbox.create({...}) or
JobQueue.enqueue({type: 'sendVerification', userId: user.id})) or perform a
bounded await with timeout to ensure the function is attempted before the
request ends; update the auth registration flow where sendVerificationEmail is
invoked to write to the outbox/queue (or call a helper like
enqueueVerificationEmail(user.id)) instead of directly calling
sendVerificationEmail(...), and ensure the background worker consumes the outbox
to perform the actual send.
| # Email (Resend) — verification and password reset | ||
| # Get API key from https://resend.com/api-keys | ||
| # Without this, verification/reset links are logged to console (dev only) | ||
| # RESEND_API_KEY= | ||
| # Optional: override sender (default: NaaP Platform <onboarding@resend.dev>) | ||
| # EMAIL_FROM=NaaP <noreply@yourdomain.com> |
There was a problem hiding this comment.
Mark RESEND_API_KEY as required in production docs.
The new auth flow depends on email delivery for verification/reset, but this block currently reads as optional. Please mark RESEND_API_KEY as [REQUIRED for production] to reduce misconfigured deployments where auth emails never send.
📝 Suggested doc tweak
-# Email (Resend) — verification and password reset
+# Email (Resend) — verification and password reset
# Get API key from https://resend.com/api-keys
# Without this, verification/reset links are logged to console (dev only)
-# RESEND_API_KEY=
+# [REQUIRED for production] RESEND_API_KEY=
# Optional: override sender (default: NaaP Platform <onboarding@resend.dev>)
# EMAIL_FROM=NaaP <noreply@yourdomain.com>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Email (Resend) — verification and password reset | |
| # Get API key from https://resend.com/api-keys | |
| # Without this, verification/reset links are logged to console (dev only) | |
| # RESEND_API_KEY= | |
| # Optional: override sender (default: NaaP Platform <onboarding@resend.dev>) | |
| # EMAIL_FROM=NaaP <noreply@yourdomain.com> | |
| # Email (Resend) — verification and password reset | |
| # Get API key from https://resend.com/api-keys | |
| # Without this, verification/reset links are logged to console (dev only) | |
| # [REQUIRED for production] RESEND_API_KEY= | |
| # Optional: override sender (default: NaaP Platform <onboarding@resend.dev>) | |
| # EMAIL_FROM=NaaP <noreply@yourdomain.com> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.env.example around lines 83 - 88, Update the .env.example comment block for
RESEND_API_KEY to indicate it is required in production: change the
RESEND_API_KEY comment to read something like "[REQUIRED for production] Get API
key from https://resend.com/api-keys" and replace the "Without this,
verification/reset links are logged to console (dev only)" line with a clear
warning that auth emails will not be sent in production without RESEND_API_KEY;
also mark EMAIL_FROM as optional but note it won't override delivery if
RESEND_API_KEY is missing.
| const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'; | ||
| const resetUrl = `${appUrl}/reset-password?token=${token}`; |
There was a problem hiding this comment.
Use getAppUrl() for email links to avoid broken localhost URLs.
Line 675 and Line 773 rebuild app origin from NEXT_PUBLIC_APP_URL || 'http://localhost:3000', which bypasses the existing getAppUrl() fallback logic (VERCEL_URL). In deployments where NEXT_PUBLIC_APP_URL is unset, reset/verification emails can contain localhost links.
🔧 Proposed fix
- const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
+ const appUrl = getAppUrl();
const resetUrl = `${appUrl}/reset-password?token=${token}`;- const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
+ const appUrl = getAppUrl();
const verifyUrl = `${appUrl}/verify-email?token=${token}`;Also applies to: 773-774
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web-next/src/lib/api/auth.ts` around lines 675 - 676, Replace the ad-hoc
app origin construction that uses process.env.NEXT_PUBLIC_APP_URL ||
'http://localhost:3000' with the existing getAppUrl() helper so email links use
the proper fallback (VERCEL_URL) in deployments; update the code that builds
resetUrl (and the similar verification URL at the other site) to call
getAppUrl() and then interpolate the token (e.g., const appUrl = getAppUrl();
const resetUrl = `${appUrl}/reset-password?token=${token}`) so both resetUrl and
the verification link use getAppUrl().
| // In production, send email. For now, log to console (never log token in production) | ||
| const safeEmail = sanitizeForLog(email); | ||
| if (process.env.NODE_ENV !== 'production') { | ||
| const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'; |
Fixes #148
Summary
Email verification for account signup was not configured — users never received verification emails after registering.
Changes
lib/email.tswith Resend for sending verification and password reset emailssendVerificationEmail(user.id)after creating user (non-blocking)requestPasswordResetto send real emails via ResendRESEND_API_KEYandEMAIL_FROMto.env.exampleand.env.local.exampleRESEND_API_KEYis unsetTest plan
RESEND_API_KEYconfigured on Vercel for productionMade with Cursor
Summary by CodeRabbit
New Features
Documentation