Expand e2e auth coverage, Vitest CI, and shared URL defaults
CI / skip-ci-check (pull_request) Successful in 4s
CI / docker-ci (pull_request) Successful in 6s
CI / secret-scan (pull_request) Successful in 13s
CI / e2e (pull_request) Successful in 2m14s
CI / viewer-unit (pull_request) Successful in 2m38s

Wire viewer-frontend Vitest into Gitea Actions, centralize DEV base URLs
in env-defaults.json, expand playkit API catalog specs, and land auth/mail
flows plus verify-email public-host redirect and idle-logout e2e hooks.
This commit is contained in:
2026-07-14 18:49:42 -04:00
parent 62d5a66ffa
commit 47b15049bc
25 changed files with 602 additions and 125 deletions
@@ -1,43 +1,53 @@
import { NextRequest, NextResponse } from 'next/server';
import { prismaAuth } from '@/lib/db';
/**
* Public site origin for redirects. Behind Caddy, `request.url` is often
* http://localhost:3001 which sends browsers (and e2e) to a LAN/loopback
* host — same class of bug as NEXTAUTH_URL → 10.x (#57).
*/
function publicOrigin(request: NextRequest): string {
const fromEnv = (process.env.NEXTAUTH_URL || process.env.AUTH_URL || '').replace(/\/$/, '');
if (fromEnv) return fromEnv;
const xfProto = request.headers.get('x-forwarded-proto');
const xfHost = request.headers.get('x-forwarded-host') || request.headers.get('host');
if (xfHost) {
const proto = xfProto || 'https';
return `${proto}://${xfHost.split(',')[0].trim()}`;
}
return request.nextUrl.origin;
}
function redirectLogin(request: NextRequest, query: string): NextResponse {
return NextResponse.redirect(new URL(`/login?${query}`, publicOrigin(request)));
}
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const token = searchParams.get('token');
if (!token) {
return NextResponse.redirect(
new URL('/login?error=missing_token', request.url)
);
return redirectLogin(request, 'error=missing_token');
}
// Find user with this token
const user = await prismaAuth.user.findUnique({
where: { emailConfirmationToken: token },
});
if (!user) {
return NextResponse.redirect(
new URL('/login?error=invalid_token', request.url)
);
return redirectLogin(request, 'error=invalid_token');
}
// Check if token has expired
if (user.emailConfirmationTokenExpiry && user.emailConfirmationTokenExpiry < new Date()) {
return NextResponse.redirect(
new URL('/login?error=token_expired', request.url)
);
return redirectLogin(request, 'error=token_expired');
}
// Check if already verified
if (user.emailVerified) {
return NextResponse.redirect(
new URL('/login?message=already_verified', request.url)
);
return redirectLogin(request, 'message=already_verified');
}
// Verify the email
await prismaAuth.user.update({
where: { id: user.id },
data: {
@@ -47,21 +57,9 @@ export async function GET(request: NextRequest) {
},
});
// Redirect to login with success message
return NextResponse.redirect(
new URL('/login?verified=true', request.url)
);
} catch (error: any) {
return redirectLogin(request, 'verified=true');
} catch (error: unknown) {
console.error('Error verifying email:', error);
return NextResponse.redirect(
new URL('/login?error=verification_failed', request.url)
);
return redirectLogin(request, 'error=verification_failed');
}
}