File size: 3,299 Bytes
9e4583c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | /**
* API Authentication Guard β Shared utility for protecting management API routes.
*
* Provides dual-mode auth: JWT cookie (dashboard session) or Bearer API key.
* Used by the middleware (proxy.ts) to guard /api/* management routes.
*
* @module shared/utils/apiAuth
*/
import { jwtVerify } from "jose";
import { getSettings } from "@/lib/localDb";
// ββββββββββββββββ Public Routes (No Auth Required) ββββββββββββββββ
/**
* Routes that are ALWAYS accessible without authentication.
* Pattern matching: startsWith check against the pathname.
*/
const PUBLIC_API_ROUTES = [
// Auth flow β must be accessible to unauthenticated users
"/api/auth/login",
"/api/auth/logout",
"/api/auth/status",
// Settings check β used by login page / onboarding
"/api/settings/require-login",
// Init β first-run setup
"/api/init",
// Health monitoring β probes must work without auth
"/api/monitoring/health",
// LLM proxy routes β use their own API key auth in the SSE layer
"/api/v1/",
// Cloud routes β use Bearer API key auth internally
"/api/cloud/",
// OAuth callback routes β provider redirects back here
"/api/oauth/",
];
// ββββββββββββββββ Auth Verification ββββββββββββββββ
/**
* Check if a request is authenticated via JWT cookie or Bearer API key.
*
* @returns null if authenticated, error message string if not
*/
export async function verifyAuth(request: any): Promise<string | null> {
// 1. Check JWT cookie (dashboard session)
const token = request.cookies.get("auth_token")?.value;
if (token && process.env.JWT_SECRET) {
try {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
await jwtVerify(token, secret);
return null; // β Authenticated via cookie
} catch {
// Invalid/expired token β fall through to API key check
}
}
// 2. Check Bearer API key
const authHeader = request.headers.get("authorization");
if (authHeader?.startsWith("Bearer ")) {
const apiKey = authHeader.slice(7);
try {
// Dynamic import to avoid circular dependencies during build
const { validateApiKey } = await import("@/lib/db/apiKeys");
const isValid = await validateApiKey(apiKey);
if (isValid) return null; // β Authenticated via API key
} catch {
// DB not ready or import error β deny access
}
}
return "Authentication required";
}
/**
* Check if a route is in the public (no-auth) allowlist.
*/
export function isPublicRoute(pathname: string): boolean {
return PUBLIC_API_ROUTES.some((route) => pathname.startsWith(route));
}
/**
* Check if authentication is required based on settings.
* If requireLogin is false AND no password is set, auth is skipped.
*/
export async function isAuthRequired(): Promise<boolean> {
try {
const settings = await getSettings();
if (settings.requireLogin === false) return false;
// If no password set and no env override, don't require auth (fresh install)
if (!settings.password && !process.env.INITIAL_PASSWORD) return false;
return true;
} catch {
// On error, require auth (secure by default)
return true;
}
}
|