feat: add authentication to alias API and improve model save error handling

This commit is contained in:
nyatoru
2026-02-24 14:28:51 +07:00
parent ddb02d6464
commit 243cc4b60b
3 changed files with 122 additions and 7 deletions

View File

@@ -1498,8 +1498,8 @@ function CompatibleModelsSection({
setAdding(true);
try {
// Save to customModels DB so it shows up in /v1/models
await fetch("/api/provider-models", {
// Save to customModels DB FIRST - only create alias if this succeeds
const customModelRes = await fetch("/api/provider-models", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
@@ -1509,11 +1509,18 @@ function CompatibleModelsSection({
source: "manual",
}),
});
// Also create alias for routing
if (!customModelRes.ok) {
const errorData = await customModelRes.json().catch(() => ({}));
throw new Error(errorData.error?.message || "Failed to save custom model");
}
// Only create alias after customModel is saved successfully
await onSetAlias(modelId, resolvedAlias, providerStorageAlias);
setNewModel("");
} catch (error) {
console.log("Error adding model:", error);
alert(error instanceof Error ? error.message : "Failed to add model. Please try again.");
} finally {
setAdding(false);
}
@@ -1540,8 +1547,9 @@ function CompatibleModelsSection({
if (!modelId) return false;
const resolvedAlias = resolveAlias(modelId);
if (!resolvedAlias) return false;
// Save to customModels DB so it shows up in /v1/models
await fetch("/api/provider-models", {
// Save to customModels DB FIRST - only create alias if this succeeds
const customModelRes = await fetch("/api/provider-models", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
@@ -1551,7 +1559,13 @@ function CompatibleModelsSection({
source: "imported",
}),
});
// Also create alias for routing
if (!customModelRes.ok) {
console.error("Failed to save imported model to customModels DB");
return false;
}
// Only create alias after customModel is saved successfully
await onSetAlias(modelId, resolvedAlias, providerStorageAlias);
return true;
}

View File

@@ -2,10 +2,46 @@ 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;
}
// GET /api/models/alias - Get all aliases
export async function GET() {
export async function GET(request) {
try {
// Require authentication for security
if (!(await verifyAuth(request))) {
return NextResponse.json({ error: "Authentication required" }, { status: 401 });
}
const aliases = await getModelAliases();
return NextResponse.json({ aliases });
} catch (error) {
@@ -17,6 +53,11 @@ export async function GET() {
// PUT /api/models/alias - Set model alias
export async function PUT(request) {
try {
// Require authentication for security
if (!(await verifyAuth(request))) {
return NextResponse.json({ error: "Authentication required" }, { status: 401 });
}
const body = await request.json();
const { model, alias } = body;
@@ -37,6 +78,11 @@ export async function PUT(request) {
// DELETE /api/models/alias?alias=xxx - Delete alias
export async function DELETE(request) {
try {
// Require authentication for security
if (!(await verifyAuth(request))) {
return NextResponse.json({ error: "Authentication required" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const alias = searchParams.get("alias");

View File

@@ -4,6 +4,37 @@ 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;
}
/**
* GET /api/provider-models?provider=<id>
@@ -11,6 +42,14 @@ import {
*/
export async function GET(request) {
try {
// Require authentication for security
if (!(await verifyAuth(request))) {
return Response.json(
{ error: { message: "Authentication required", type: "invalid_api_key" } },
{ status: 401 }
);
}
const { searchParams } = new URL(request.url);
const provider = searchParams.get("provider");
@@ -31,6 +70,14 @@ export async function GET(request) {
*/
export async function POST(request) {
try {
// Require authentication for security
if (!(await verifyAuth(request))) {
return Response.json(
{ error: { message: "Authentication required", type: "invalid_api_key" } },
{ status: 401 }
);
}
const body = await request.json();
const { provider, modelId, modelName, source } = body;
@@ -56,6 +103,14 @@ export async function POST(request) {
*/
export async function DELETE(request) {
try {
// Require authentication for security
if (!(await verifyAuth(request))) {
return Response.json(
{ error: { message: "Authentication required", type: "invalid_api_key" } },
{ status: 401 }
);
}
const { searchParams } = new URL(request.url);
const provider = searchParams.get("provider");
const modelId = searchParams.get("model");