mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
feat: implement API key requirement toggle
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Card, Button, Input, Modal, CardSkeleton } from "@/shared/components";
|
||||
import { Card, Button, Input, Modal, CardSkeleton, Toggle } from "@/shared/components";
|
||||
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
|
||||
|
||||
const DEFAULT_CLOUD_URL = process.env.NEXT_PUBLIC_CLOUD_URL || "";
|
||||
@@ -16,6 +16,7 @@ export default function APIPageClient({ machineId }) {
|
||||
const [createdKey, setCreatedKey] = useState(null);
|
||||
|
||||
// Cloud sync state
|
||||
const [requireApiKey, setRequireApiKey] = useState(false);
|
||||
const [cloudEnabled, setCloudEnabled] = useState(false);
|
||||
const [cloudUrl, setCloudUrl] = useState(DEFAULT_CLOUD_URL);
|
||||
const [cloudUrlInput, setCloudUrlInput] = useState(DEFAULT_CLOUD_URL);
|
||||
@@ -63,6 +64,7 @@ export default function APIPageClient({ machineId }) {
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setCloudEnabled(data.cloudEnabled || false);
|
||||
setRequireApiKey(data.requireApiKey || false);
|
||||
const url = data.cloudUrl || DEFAULT_CLOUD_URL;
|
||||
setCloudUrl(url);
|
||||
setCloudUrlInput(url);
|
||||
@@ -72,6 +74,19 @@ export default function APIPageClient({ machineId }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRequireApiKey = async (value) => {
|
||||
try {
|
||||
const res = await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ requireApiKey: value }),
|
||||
});
|
||||
if (res.ok) setRequireApiKey(value);
|
||||
} catch (error) {
|
||||
console.log("Error updating requireApiKey:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const keysRes = await fetch("/api/keys");
|
||||
@@ -361,6 +376,19 @@ export default function APIPageClient({ machineId }) {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pb-4 mb-4 border-b border-border">
|
||||
<div>
|
||||
<p className="font-medium">Require API key</p>
|
||||
<p className="text-sm text-text-muted">
|
||||
Requests without a valid key will be rejected
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={requireApiKey}
|
||||
onChange={() => handleRequireApiKey(!requireApiKey)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{keys.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 text-primary mb-4">
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
extractApiKey,
|
||||
isValidApiKey,
|
||||
} from "../services/auth.js";
|
||||
import { getSettings } from "@/lib/localDb";
|
||||
import { getModelInfo, getComboModels } from "../services/model.js";
|
||||
import { handleChatCore } from "open-sse/handlers/chatCore.js";
|
||||
import { errorResponse, unavailableResponse } from "open-sse/utils/error.js";
|
||||
@@ -57,16 +58,16 @@ export async function handleChat(request, clientRawRequest = null) {
|
||||
log.debug("AUTH", "No API key provided (local mode)");
|
||||
}
|
||||
|
||||
// Optional strict API key mode for /v1 endpoints.
|
||||
// Keep disabled by default to preserve local-mode compatibility.
|
||||
if (process.env.REQUIRE_API_KEY === "true") {
|
||||
// Enforce API key if enabled in settings
|
||||
const settings = await getSettings();
|
||||
if (settings.requireApiKey) {
|
||||
if (!apiKey) {
|
||||
log.warn("AUTH", "Missing API key while REQUIRE_API_KEY=true");
|
||||
log.warn("AUTH", "Missing API key (requireApiKey=true)");
|
||||
return errorResponse(HTTP_STATUS.UNAUTHORIZED, "Missing API key");
|
||||
}
|
||||
const valid = await isValidApiKey(apiKey);
|
||||
if (!valid) {
|
||||
log.warn("AUTH", "Invalid API key while REQUIRE_API_KEY=true");
|
||||
log.warn("AUTH", "Invalid API key (requireApiKey=true)");
|
||||
return errorResponse(HTTP_STATUS.UNAUTHORIZED, "Invalid API key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
extractApiKey,
|
||||
isValidApiKey,
|
||||
} from "../services/auth.js";
|
||||
import { getSettings } from "@/lib/localDb";
|
||||
import { getModelInfo } from "../services/model.js";
|
||||
import { handleEmbeddingsCore } from "open-sse/handlers/embeddingsCore.js";
|
||||
import { errorResponse, unavailableResponse } from "open-sse/utils/error.js";
|
||||
@@ -40,15 +41,16 @@ export async function handleEmbeddings(request) {
|
||||
log.debug("AUTH", "No API key provided (local mode)");
|
||||
}
|
||||
|
||||
// Optional strict API key validation
|
||||
if (process.env.REQUIRE_API_KEY === "true") {
|
||||
// Enforce API key if enabled in settings
|
||||
const settings = await getSettings();
|
||||
if (settings.requireApiKey) {
|
||||
if (!apiKey) {
|
||||
log.warn("AUTH", "Missing API key while REQUIRE_API_KEY=true");
|
||||
log.warn("AUTH", "Missing API key (requireApiKey=true)");
|
||||
return errorResponse(HTTP_STATUS.UNAUTHORIZED, "Missing API key");
|
||||
}
|
||||
const valid = await isValidApiKey(apiKey);
|
||||
if (!valid) {
|
||||
log.warn("AUTH", "Invalid API key while REQUIRE_API_KEY=true");
|
||||
log.warn("AUTH", "Invalid API key (requireApiKey=true)");
|
||||
return errorResponse(HTTP_STATUS.UNAUTHORIZED, "Invalid API key");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user