feat: extract shared auth utility a cooldown/availability checksnd fix custom provider model resolution

This commit is contained in:
nyatoru
2026-02-24 21:19:57 +07:00
parent 619c99ce4c
commit 4ea0426034
4 changed files with 51 additions and 100 deletions

View File

@@ -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 });
}

View File

@@ -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=<id>
@@ -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 }

View File

@@ -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: {

View File

@@ -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<string | null> {
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<boolean> {
// 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.
*/