mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
feat: extract shared auth utility a cooldown/availability checksnd fix custom provider model resolution
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user