This commit resolves multiple build errors and adds a workaround for environments where Prisma engine binaries cannot be downloaded due to network restrictions. Changes: - Fix TypeScript error: Remove invalid request.ip property access in NextAuth route - Add missing config import in auth.ts for sessionSecret - Add dynamic = 'force-dynamic' to API routes to prevent static generation - Create Prisma stub generator script for build-time type checking - Update build script to use stub generator instead of prisma generate - Add binaryTargets to Prisma schema configuration The stub generator allows the Next.js build to complete successfully in environments where Prisma binaries cannot be downloaded (403 Forbidden errors from binaries server). The actual Prisma engines will need to be available at runtime in production deployments. All routes are now properly configured as dynamic server-rendered routes.
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import type { NextRequest } from "next/server";
|
|
import { handlers } from "@/src/lib/auth";
|
|
import { isRateLimited, registerFailedAttempt, resetAttempts } from "@/src/lib/rate-limit";
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export const { GET } = handlers;
|
|
|
|
function getClientIp(request: NextRequest): string {
|
|
// Get client IP from headers
|
|
// In production, ensure your reverse proxy (Caddy) sets these headers correctly
|
|
const forwarded = request.headers.get("x-forwarded-for");
|
|
if (forwarded) {
|
|
return forwarded.split(",")[0]?.trim() || "unknown";
|
|
}
|
|
const real = request.headers.get("x-real-ip");
|
|
if (real) {
|
|
return real.trim();
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
function buildRateLimitKey(ip: string, username: string) {
|
|
const normalizedUsername = username.trim().toLowerCase() || "unknown";
|
|
return `login:${ip}:${normalizedUsername}`;
|
|
}
|
|
|
|
function buildBlockedResponse(retryAfterMs?: number) {
|
|
const retryAfterSeconds = retryAfterMs ? Math.ceil(retryAfterMs / 1000) : 60;
|
|
const retryAfterMinutes = Math.max(1, Math.ceil(retryAfterSeconds / 60));
|
|
return NextResponse.json(
|
|
{
|
|
error: `Too many login attempts. Try again in about ${retryAfterMinutes} minute${retryAfterMinutes === 1 ? "" : "s"}.`
|
|
},
|
|
{
|
|
status: 429,
|
|
headers: {
|
|
"Retry-After": retryAfterSeconds.toString()
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const formData = await request.clone().formData();
|
|
const username = String(formData.get("username") ?? "");
|
|
const ip = getClientIp(request);
|
|
const rateLimitKey = buildRateLimitKey(ip, username);
|
|
|
|
const limitation = isRateLimited(rateLimitKey);
|
|
if (limitation.blocked) {
|
|
return buildBlockedResponse(limitation.retryAfterMs);
|
|
}
|
|
|
|
const response = await handlers.POST(request);
|
|
|
|
if (response.status >= 200 && response.status < 300) {
|
|
resetAttempts(rateLimitKey);
|
|
return response;
|
|
}
|
|
|
|
if (response.status === 401) {
|
|
const result = registerFailedAttempt(rateLimitKey);
|
|
if (result.blocked) {
|
|
return buildBlockedResponse(result.retryAfterMs);
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|