Files
OpenFrame/app/api/auth/verify-email/route.ts
T
yusufipk 5871d4d87d fix(auth): build verify-email redirects from the configured public origin
Redirects were built relative to `request.url`, which behind a reverse proxy
resolves to the container-internal address. Verification succeeded but the
browser was sent to localhost:3000, so users saw a connection error instead of
the "email verified" confirmation.

Add getPublicOrigin() (NEXTAUTH_URL, then NEXT_PUBLIC_APP_URL, falling back to
the request origin for local development) and use it for every verify-email
redirect. The legacy GET redirect in the watch session route had the same
defect and is fixed alongside it.
2026-07-25 14:57:57 +07:00

40 lines
1.4 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { consumeVerificationToken } from '@/lib/email-verification';
import { rateLimit } from '@/lib/rate-limit';
import { logError } from '@/lib/logger';
import { getPublicOrigin } from '@/lib/request-origin';
// A raw 32-byte hex token is exactly 64 characters.
const TOKEN_REGEX = /^[0-9a-f]{64}$/;
export async function GET(request: NextRequest) {
// Redirect targets must be built from the public origin, not `request.url`:
// behind a reverse proxy the latter is the container-internal address and the
// user lands on a dead host even though verification succeeded.
const origin = getPublicOrigin(request);
const redirectTo = (path: string) => NextResponse.redirect(new URL(path, origin));
try {
// Rate-limit by IP to prevent token enumeration attacks.
const limited = await rateLimit(request, 'verify-email');
if (limited) return limited;
const token = request.nextUrl.searchParams.get('token');
if (!token || !TOKEN_REGEX.test(token.trim())) {
return redirectTo('/login?error=InvalidVerificationToken');
}
const email = await consumeVerificationToken(token.trim());
if (!email) {
return redirectTo('/login?error=InvalidVerificationToken');
}
return redirectTo('/login?verified=true');
} catch (err) {
logError('Email verification error:', err);
return redirectTo('/login?error=VerificationFailed');
}
}