diff --git a/src/app/api/models/alias/route.ts b/src/app/api/models/alias/route.ts index 2f18b95018..002a060dc3 100644 --- a/src/app/api/models/alias/route.ts +++ b/src/app/api/models/alias/route.ts @@ -2,43 +2,13 @@ import { NextResponse } from "next/server"; import { getModelAliases, setModelAlias, deleteModelAlias, isCloudEnabled } from "@/models"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; -import { extractApiKey, isValidApiKey } from "@/sse/services/auth"; -import { jwtVerify } from "jose"; -import { cookies } from "next/headers"; - -/** - * Verify authentication - check API key or JWT cookie - */ -async function verifyAuth(request) { - // Check API key (for external clients) - const apiKey = extractApiKey(request); - if (apiKey && (await isValidApiKey(apiKey))) { - return true; - } - - // Check JWT cookie (for dashboard session) - if (process.env.JWT_SECRET) { - try { - const cookieStore = await cookies(); - const token = cookieStore.get("auth_token")?.value; - if (token) { - const secret = new TextEncoder().encode(process.env.JWT_SECRET); - await jwtVerify(token, secret); - return true; - } - } catch { - // Invalid/expired token or cookies not available - } - } - - return false; -} +import { isAuthenticated } from "@/shared/utils/apiAuth"; // GET /api/models/alias - Get all aliases export async function GET(request) { try { // Require authentication for security - if (!(await verifyAuth(request))) { + if (!(await isAuthenticated(request))) { return NextResponse.json({ error: "Authentication required" }, { status: 401 }); } @@ -54,7 +24,7 @@ export async function GET(request) { export async function PUT(request) { try { // Require authentication for security - if (!(await verifyAuth(request))) { + if (!(await isAuthenticated(request))) { return NextResponse.json({ error: "Authentication required" }, { status: 401 }); } @@ -79,7 +49,7 @@ export async function PUT(request) { export async function DELETE(request) { try { // Require authentication for security - if (!(await verifyAuth(request))) { + if (!(await isAuthenticated(request))) { return NextResponse.json({ error: "Authentication required" }, { status: 401 }); } diff --git a/src/app/api/provider-models/route.ts b/src/app/api/provider-models/route.ts index 0c62ab790a..39b1fcf37d 100644 --- a/src/app/api/provider-models/route.ts +++ b/src/app/api/provider-models/route.ts @@ -4,37 +4,7 @@ import { addCustomModel, removeCustomModel, } from "@/lib/localDb"; -import { extractApiKey, isValidApiKey } from "@/sse/services/auth"; -import { jwtVerify } from "jose"; -import { cookies } from "next/headers"; - -/** - * Verify authentication - check API key or JWT cookie - */ -async function verifyAuth(request) { - // Check API key (for external clients) - const apiKey = extractApiKey(request); - if (apiKey && (await isValidApiKey(apiKey))) { - return true; - } - - // Check JWT cookie (for dashboard session) - if (process.env.JWT_SECRET) { - try { - const cookieStore = await cookies(); - const token = cookieStore.get("auth_token")?.value; - if (token) { - const secret = new TextEncoder().encode(process.env.JWT_SECRET); - await jwtVerify(token, secret); - return true; - } - } catch { - // Invalid/expired token or cookies not available - } - } - - return false; -} +import { isAuthenticated } from "@/shared/utils/apiAuth"; /** * GET /api/provider-models?provider= @@ -43,7 +13,7 @@ async function verifyAuth(request) { export async function GET(request) { try { // Require authentication for security - if (!(await verifyAuth(request))) { + if (!(await isAuthenticated(request))) { return Response.json( { error: { message: "Authentication required", type: "invalid_api_key" } }, { status: 401 } @@ -71,7 +41,7 @@ export async function GET(request) { export async function POST(request) { try { // Require authentication for security - if (!(await verifyAuth(request))) { + if (!(await isAuthenticated(request))) { return Response.json( { error: { message: "Authentication required", type: "invalid_api_key" } }, { status: 401 } @@ -104,7 +74,7 @@ export async function POST(request) { export async function DELETE(request) { try { // Require authentication for security - if (!(await verifyAuth(request))) { + if (!(await isAuthenticated(request))) { return Response.json( { error: { message: "Authentication required", type: "invalid_api_key" } }, { status: 401 } diff --git a/src/app/api/v1/models/route.ts b/src/app/api/v1/models/route.ts index 4bf1dce795..f938f2618a 100644 --- a/src/app/api/v1/models/route.ts +++ b/src/app/api/v1/models/route.ts @@ -8,9 +8,7 @@ import { getSettings, getProviderNodes, } from "@/lib/localDb"; -import { extractApiKey, isValidApiKey } from "@/sse/services/auth"; -import { jwtVerify } from "jose"; -import { cookies } from "next/headers"; +import { isAuthenticated } from "@/shared/utils/apiAuth"; import { getAllEmbeddingModels } from "@omniroute/open-sse/config/embeddingRegistry.ts"; import { getAllImageModels } from "@omniroute/open-sse/config/imageRegistry.ts"; import { getAllRerankModels } from "@omniroute/open-sse/config/rerankRegistry.ts"; @@ -113,35 +111,7 @@ export async function GET(request: Request) { settings = await getSettings(); } catch {} if (settings.requireAuthForModels === true) { - // Check authentication: API key OR dashboard session (JWT cookie) - // Supports dual auth: Bearer token for external clients, cookie for dashboard. - let isAuthenticated = false; - - // 1. Check API key (for external clients) - const apiKey = extractApiKey(request); - if (apiKey && (await isValidApiKey(apiKey))) { - isAuthenticated = true; - } - - // 2. Check JWT cookie (for dashboard session) - // The auth_token cookie has sameSite:lax + httpOnly, which already - // prevents cross-origin abuse — no additional origin check needed. - // Same pattern as shared/utils/apiAuth.ts verifyAuth(). - if (!isAuthenticated && process.env.JWT_SECRET) { - try { - const cookieStore = await cookies(); - const token = cookieStore.get("auth_token")?.value; - if (token) { - const secret = new TextEncoder().encode(process.env.JWT_SECRET); - await jwtVerify(token, secret); - isAuthenticated = true; - } - } catch { - // Invalid/expired token or cookies not available — not authenticated - } - } - - if (!isAuthenticated) { + if (!(await isAuthenticated(request))) { return Response.json( { error: { diff --git a/src/shared/utils/apiAuth.ts b/src/shared/utils/apiAuth.ts index 937315b523..b9e8770192 100644 --- a/src/shared/utils/apiAuth.ts +++ b/src/shared/utils/apiAuth.ts @@ -8,6 +8,7 @@ */ import { jwtVerify } from "jose"; +import { cookies } from "next/headers"; import { getSettings } from "@/lib/localDb"; // ──────────────── Public Routes (No Auth Required) ──────────────── @@ -78,6 +79,46 @@ export async function verifyAuth(request: any): Promise { return "Authentication required"; } +/** + * Check if a request is authenticated — boolean convenience wrapper for route handlers. + * + * Uses `cookies()` from next/headers (App Router compatible) and Bearer API key. + * Returns true if authenticated, false otherwise. + * + * Unlike `verifyAuth`, this does NOT check `isAuthRequired()` — callers that + * need to conditionally skip auth should check that separately. + */ +export async function isAuthenticated(request: Request): Promise { + // 1. Check API key (for external clients) + const authHeader = request.headers.get("authorization"); + if (authHeader?.startsWith("Bearer ")) { + const apiKey = authHeader.slice(7); + try { + const { validateApiKey } = await import("@/lib/db/apiKeys"); + if (await validateApiKey(apiKey)) return true; + } catch { + // DB not ready or import error + } + } + + // 2. Check JWT cookie (for dashboard session) + if (process.env.JWT_SECRET) { + try { + const cookieStore = await cookies(); + const token = cookieStore.get("auth_token")?.value; + if (token) { + const secret = new TextEncoder().encode(process.env.JWT_SECRET); + await jwtVerify(token, secret); + return true; + } + } catch { + // Invalid/expired token or cookies not available + } + } + + return false; +} + /** * Check if a route is in the public (no-auth) allowlist. */