eed57cd10b
proxy.ts required a session cookie for every non-public /api/v1/* request, rejecting with 401 before the request ever reached requireSessionOrApiKey (lib/api-auth.ts) — the only place that actually verifies a Bearer API key and updates lastUsedAt. Pure API-key clients never send a session cookie, so every single API-key request was blocked at the middleware layer; the lastUsedAt update code was correct but unreachable. Now lets requests with an `Authorization: Bearer ek_...` header through to the route, which still does the real verification (and 401s itself on an invalid/unknown key) — middleware just stops pre-emptively rejecting valid ones. Also added error logging to the fire-and-forget lastUsedAt update, previously silent on failure. Verified locally: hashed a raw key, confirmed it matched the stored hash (so the lookup itself was never the problem), reproduced the 401 against the unpatched middleware, then confirmed both the 200 response and lastUsedAt populating correctly after the fix — visible in the real Settings → API Keys UI.
47 lines
1.8 KiB
TypeScript
47 lines
1.8 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { getSessionCookie } from "better-auth/cookies";
|
|
|
|
const PUBLIC_PATHS = ["/login", "/signup", "/verify-email", "/forgot-password", "/reset-password", "/api/auth", "/r/", "/u/", "/s/", "/docs", "/api/v1/openapi.json", "/api/webhooks", "/api/v1/invites/", "/api/internal/"];
|
|
const ADMIN_PATHS = ["/admin"];
|
|
|
|
export async function proxy(request: NextRequest) {
|
|
const { pathname } = request.nextUrl;
|
|
|
|
const isPublic = PUBLIC_PATHS.some((p) => pathname.startsWith(p));
|
|
const isAdmin = ADMIN_PATHS.some((p) => pathname.startsWith(p));
|
|
const isApi = pathname.startsWith("/api/v1");
|
|
|
|
if (isPublic) return NextResponse.next();
|
|
|
|
// API-key clients authenticate via `Authorization: Bearer ek_...`, not a
|
|
// session cookie — they'd otherwise be rejected here before ever reaching
|
|
// requireSessionOrApiKey (lib/api-auth.ts), which is the only place that
|
|
// actually verifies the key. Defer to it instead of requiring a cookie.
|
|
const authHeader = request.headers.get("authorization");
|
|
const hasApiKeyHeader = isApi && authHeader?.startsWith("Bearer ek_");
|
|
|
|
const sessionCookie = getSessionCookie(request);
|
|
|
|
if (!sessionCookie && !hasApiKeyHeader) {
|
|
if (isApi) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
return NextResponse.redirect(new URL("/login", request.url));
|
|
}
|
|
|
|
if (isAdmin) {
|
|
// Role check happens inside admin pages — middleware only checks auth
|
|
// Full role verification requires DB lookup, done server-side in admin layout
|
|
}
|
|
|
|
return NextResponse.next();
|
|
}
|
|
|
|
export default proxy;
|
|
|
|
export const config = {
|
|
matcher: [
|
|
"/((?!_next/static|_next/image|favicon.ico|icon.svg|icon-192.svg|icon-512.svg|manifest.webmanifest|sw.js|public/).*)",
|
|
],
|
|
};
|