deep-researcher / src /middleware.ts
Amiel's picture
Upload folder using huggingface_hub
676fc08 verified
import { NextResponse } from "next/server";
import { NextRequest } from "next/server";
import { getCustomModelList, multiApiKeyPolling } from "@/utils/model";
import { verifySignature } from "@/utils/signature";
const NODE_ENV = process.env.NODE_ENV;
const accessPassword = process.env.ACCESS_PASSWORD || "";
// AI provider API key
const GOOGLE_GENERATIVE_AI_API_KEY =
process.env.GOOGLE_GENERATIVE_AI_API_KEY || "";
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY || "";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY || "";
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || "";
const DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY || "";
const XAI_API_KEY = process.env.XAI_API_KEY || "";
const MISTRAL_API_KEY = process.env.MISTRAL_API_KEY || "";
const AZURE_API_KEY = process.env.AZURE_API_KEY || "";
const OPENAI_COMPATIBLE_API_KEY = process.env.OPENAI_COMPATIBLE_API_KEY || "";
// Search provider API key
const TAVILY_API_KEY = process.env.TAVILY_API_KEY || "";
const FIRECRAWL_API_KEY = process.env.FIRECRAWL_API_KEY || "";
const EXA_API_KEY = process.env.EXA_API_KEY || "";
const BOCHA_API_KEY = process.env.BOCHA_API_KEY || "";
// Disabled Provider
const DISABLED_AI_PROVIDER = process.env.NEXT_PUBLIC_DISABLED_AI_PROVIDER || "";
const DISABLED_SEARCH_PROVIDER =
process.env.NEXT_PUBLIC_DISABLED_SEARCH_PROVIDER || "";
const MODEL_LIST = process.env.NEXT_PUBLIC_MODEL_LIST || "";
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: "/api/:path*",
};
const ERRORS = {
NO_PERMISSIONS: {
code: 403,
message: "No permissions",
status: "FORBIDDEN",
},
NO_API_KEY: {
code: 500,
message: "The server does not have an API key.",
status: "Internal Server Error",
},
};
export async function middleware(request: NextRequest) {
if (NODE_ENV === "production") console.debug(request);
const disabledAIProviders =
DISABLED_AI_PROVIDER.length > 0 ? DISABLED_AI_PROVIDER.split(",") : [];
const disabledSearchProviders =
DISABLED_SEARCH_PROVIDER.length > 0
? DISABLED_SEARCH_PROVIDER.split(",")
: [];
const hasDisabledGeminiModel = () => {
if (request.method.toUpperCase() === "GET") return false;
const { availableModelList, disabledModelList } = getCustomModelList(
MODEL_LIST.length > 0 ? MODEL_LIST.split(",") : []
);
const isAvailableModel = availableModelList.some((availableModel) =>
request.nextUrl.pathname.includes(`models/${availableModel}:`)
);
if (isAvailableModel) return false;
if (disabledModelList.includes("all")) return true;
return disabledModelList.some((disabledModel) =>
request.nextUrl.pathname.includes(`models/${disabledModel}:`)
);
};
const hasDisabledAIModel = async () => {
if (request.method.toUpperCase() === "GET") return false;
const { model = "" } = await request.json();
const { availableModelList, disabledModelList } = getCustomModelList(
MODEL_LIST.length > 0 ? MODEL_LIST.split(",") : []
);
const isAvailableModel = availableModelList.some(
(availableModel) => availableModel === model
);
if (isAvailableModel) return false;
if (disabledModelList.includes("all")) return true;
return disabledModelList.some((disabledModel) => disabledModel === model);
};
if (request.nextUrl.pathname.startsWith("/api/ai/google")) {
const authorization = request.headers.get("x-goog-api-key") || "";
const isDisabledGeminiModel = hasDisabledGeminiModel();
if (
!verifySignature(authorization, accessPassword, Date.now()) ||
disabledAIProviders.includes("google") ||
isDisabledGeminiModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(GOOGLE_GENERATIVE_AI_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set(
"x-goog-api-client",
request.headers.get("x-goog-api-client") || "genai-js/0.24.0"
);
requestHeaders.set("x-goog-api-key", apiKey);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/ai/openrouter")) {
const authorization = request.headers.get("authorization") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledAIProviders.includes("openrouter") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(OPENROUTER_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/ai/openaicompatible")) {
const authorization = request.headers.get("authorization") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledAIProviders.includes("openaicompatible") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(OPENAI_COMPATIBLE_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/ai/openai")) {
const authorization = request.headers.get("authorization") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledAIProviders.includes("openai") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(OPENAI_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/ai/anthropic")) {
const authorization = request.headers.get("x-api-key") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(authorization, accessPassword, Date.now()) ||
disabledAIProviders.includes("anthropic") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(ANTHROPIC_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("x-api-key", apiKey);
requestHeaders.set(
"anthropic-version",
request.headers.get("anthropic-version") || "2023-06-01"
);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/ai/deepseek")) {
const authorization = request.headers.get("authorization") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledAIProviders.includes("deepseek") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(DEEPSEEK_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/ai/xai")) {
const authorization = request.headers.get("authorization") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledAIProviders.includes("xai") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(XAI_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/ai/mistral")) {
const authorization = request.headers.get("authorization") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledAIProviders.includes("mistral") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(MISTRAL_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/ai/azure")) {
const authorization = request.headers.get("authorization") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledAIProviders.includes("azure") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(AZURE_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
// The pollinations model only verifies access to the backend API
if (request.nextUrl.pathname.startsWith("/api/ai/pollinations")) {
const authorization = request.headers.get("authorization") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledAIProviders.includes("pollinations") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
}
// The ollama model only verifies access to the backend API
if (request.nextUrl.pathname.startsWith("/api/ai/ollama")) {
const authorization = request.headers.get("authorization") || "";
const isDisabledModel = await hasDisabledAIModel();
if (
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledAIProviders.includes("ollama") ||
isDisabledModel
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
}
if (request.nextUrl.pathname.startsWith("/api/search/tavily")) {
const authorization = request.headers.get("authorization") || "";
if (
request.method.toUpperCase() !== "POST" ||
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledSearchProviders.includes("tavily")
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(TAVILY_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/search/firecrawl")) {
const authorization = request.headers.get("authorization") || "";
if (
request.method.toUpperCase() !== "POST" ||
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledSearchProviders.includes("firecrawl")
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(FIRECRAWL_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/search/exa")) {
const authorization = request.headers.get("authorization") || "";
if (
request.method.toUpperCase() !== "POST" ||
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledSearchProviders.includes("exa")
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(EXA_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/search/bocha")) {
const authorization = request.headers.get("authorization") || "";
if (
request.method.toUpperCase() !== "POST" ||
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledSearchProviders.includes("bocha")
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const apiKey = multiApiKeyPolling(BOCHA_API_KEY);
if (apiKey) {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.set("Authorization", `Bearer ${apiKey}`);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} else {
return NextResponse.json(
{
error: ERRORS.NO_API_KEY,
},
{ status: 500 }
);
}
}
}
if (request.nextUrl.pathname.startsWith("/api/search/searxng")) {
const authorization = request.headers.get("authorization") || "";
if (
request.method.toUpperCase() !== "POST" ||
!verifySignature(
authorization.substring(7),
accessPassword,
Date.now()
) ||
disabledSearchProviders.includes("searxng")
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.delete("Authorization");
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
}
if (request.nextUrl.pathname.startsWith("/api/crawler")) {
const authorization = request.headers.get("authorization") || "";
if (
request.method.toUpperCase() !== "POST" ||
!verifySignature(authorization.substring(7), accessPassword, Date.now())
) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.delete("Authorization");
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
}
if (request.nextUrl.pathname.startsWith("/api/sse")) {
let authorization = request.headers.get("authorization") || "";
if (authorization !== "") {
authorization = authorization.substring(7);
} else if (request.method.toUpperCase() === "GET") {
authorization = request.nextUrl.searchParams.get("password") || "";
}
if (authorization !== accessPassword) {
return NextResponse.json(
{ error: ERRORS.NO_PERMISSIONS },
{ status: 403 }
);
} else {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.delete("Authorization");
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
}
if (request.nextUrl.pathname.startsWith("/api/mcp")) {
const authorization = request.headers.get("authorization") || "";
if (authorization.substring(7) !== accessPassword) {
const responseHeaders = new Headers();
responseHeaders.set("WWW-Authenticate", ERRORS.NO_PERMISSIONS.message);
return NextResponse.json(
{
error: 401,
error_description: ERRORS.NO_PERMISSIONS.message,
error_uri: request.nextUrl,
},
{ headers: responseHeaders, status: 401 }
);
} else {
const requestHeaders = new Headers();
requestHeaders.set(
"Content-Type",
request.headers.get("Content-Type") || "application/json"
);
requestHeaders.delete("Authorization");
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
}
return NextResponse.next();
}