Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a17583d3fc | |||
| 62c634ae78 | |||
| 02dc8ea0f3 | |||
| a40c463a87 |
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
---
|
||||
|
||||
## [1.2.0] — 2026-02-22
|
||||
|
||||
> ### ✨ Feature Release — Dashboard Session Auth for Models Endpoint
|
||||
>
|
||||
> Dashboard users can now access `/v1/models` via their existing session when API key auth is required.
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **JWT Session Auth Fallback** — When `requireAuthForModels` is enabled, the `/v1/models` endpoint now accepts both API key (Bearer token) for external clients **and** the dashboard JWT session cookie (`auth_token`), allowing logged-in dashboard users to view models without needing an explicit API key ([PR #110](https://github.com/diegosouzapw/OmniRoute/pull/110) by [@nyatoru](https://github.com/nyatoru))
|
||||
|
||||
### 🔧 Improvements
|
||||
|
||||
- **401 instead of 404** — Authentication failures on `/v1/models` now return `401 Unauthorized` with a structured JSON error body (OpenAI-compatible format) instead of a generic `404 Not Found`, improving debuggability for API clients
|
||||
- **Simplified auth logic** — Refactored the JWT cookie verification to reuse the same pattern as `apiAuth.ts`, removing redundant same-origin detection (~60 lines) since the `sameSite:lax` + `httpOnly` cookie flags already provide equivalent CSRF protection
|
||||
|
||||
---
|
||||
|
||||
## [1.1.1] — 2026-02-22
|
||||
|
||||
> ### 🐛 Bugfix Release — API Key Creation & Codex Team Plan Quotas
|
||||
@@ -450,6 +467,7 @@ New environment variables:
|
||||
|
||||
---
|
||||
|
||||
[1.2.0]: https://github.com/diegosouzapw/OmniRoute/releases/tag/v1.2.0
|
||||
[1.1.1]: https://github.com/diegosouzapw/OmniRoute/releases/tag/v1.1.1
|
||||
[1.0.7]: https://github.com/diegosouzapw/OmniRoute/releases/tag/v1.0.7
|
||||
[1.0.6]: https://github.com/diegosouzapw/OmniRoute/releases/tag/v1.0.6
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS } from "@/shared/constants/models
|
||||
import { AI_PROVIDERS } from "@/shared/constants/providers";
|
||||
import { getProviderConnections, getCombos, getAllCustomModels, getSettings } from "@/lib/localDb";
|
||||
import { extractApiKey, isValidApiKey } from "@/sse/services/auth";
|
||||
import { jwtVerify } from "jose";
|
||||
import { cookies } from "next/headers";
|
||||
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";
|
||||
@@ -97,16 +99,53 @@ export async function OPTIONS() {
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
// Issue #100: Optionally require API key for /models (security hardening)
|
||||
// When enabled, unauthenticated requests get 404 to hide endpoint existence
|
||||
// Issue #100: Optionally require authentication for /models (security hardening)
|
||||
// When enabled, unauthenticated requests get 401 with proper error response.
|
||||
// Supports API key (Bearer token) for external clients and JWT cookie for dashboard.
|
||||
let settings: Record<string, any> = {};
|
||||
try {
|
||||
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))) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
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) {
|
||||
return Response.json(
|
||||
{
|
||||
error: {
|
||||
message: "Authentication required",
|
||||
type: "invalid_request_error",
|
||||
code: "invalid_api_key",
|
||||
},
|
||||
},
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user