fix windows/linux get system uuid error
This commit is contained in:
18
src/cli.ts
18
src/cli.ts
@@ -215,22 +215,10 @@ async function main() {
|
||||
|
||||
// Get service info and open UI
|
||||
const serviceInfo = await getServiceInfo();
|
||||
|
||||
// Generate temporary API key based on system UUID
|
||||
let tempApiKey = "";
|
||||
try {
|
||||
const { getTempAPIKey } = require("./utils");
|
||||
tempApiKey = await getTempAPIKey();
|
||||
} catch (error: any) {
|
||||
console.warn("Warning: Failed to generate temporary API key:", error.message);
|
||||
console.warn("Continuing without temporary API key...");
|
||||
}
|
||||
|
||||
|
||||
// Add temporary API key as URL parameter if successfully generated
|
||||
const uiUrl = tempApiKey
|
||||
? `${serviceInfo.endpoint}/ui/?tempApiKey=${tempApiKey}`
|
||||
: `${serviceInfo.endpoint}/ui/`;
|
||||
|
||||
const uiUrl = `${serviceInfo.endpoint}/ui/`;
|
||||
|
||||
console.log(`Opening UI at ${uiUrl}`);
|
||||
|
||||
// Open URL in browser based on platform
|
||||
|
||||
@@ -1,40 +1,29 @@
|
||||
import { FastifyRequest, FastifyReply } from "fastify";
|
||||
import { getTempAPIKey } from "../utils/systemUUID";
|
||||
|
||||
export const apiKeyAuth =
|
||||
(config: any) =>
|
||||
async (req: FastifyRequest, reply: FastifyReply, done: () => void) => {
|
||||
// Check for temp API key in query parameters or headers
|
||||
let tempApiKey = null;
|
||||
if (req.query && (req.query as any).tempApiKey) {
|
||||
tempApiKey = (req.query as any).tempApiKey;
|
||||
} else if (req.headers['x-temp-api-key']) {
|
||||
tempApiKey = req.headers['x-temp-api-key'] as string;
|
||||
}
|
||||
|
||||
// If temp API key is provided, validate it
|
||||
if (tempApiKey) {
|
||||
try {
|
||||
const expectedTempKey = await getTempAPIKey();
|
||||
|
||||
// If temp key matches, grant temporary full access
|
||||
if (tempApiKey === expectedTempKey) {
|
||||
(req as any).accessLevel = "full";
|
||||
(req as any).isTempAccess = true;
|
||||
return done();
|
||||
}
|
||||
} catch (error) {
|
||||
// If there's an error generating temp key, continue with normal auth
|
||||
console.warn("Failed to verify temporary API key:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Public endpoints that don't require authentication
|
||||
if (["/", "/health"].includes(req.url) || req.url.startsWith("/ui")) {
|
||||
return done();
|
||||
}
|
||||
|
||||
const apiKey = config.APIKEY;
|
||||
if (!apiKey) {
|
||||
// If no API key is set, enable CORS for local
|
||||
const allowedOrigins = [
|
||||
`http://127.0.0.1:${config.PORT || 3456}`,
|
||||
`http://localhost:${config.PORT || 3456}`,
|
||||
];
|
||||
if (req.headers.origin && allowedOrigins.includes(req.headers.origin)) {
|
||||
reply.status(403).send("CORS not allowed for this origin");
|
||||
return;
|
||||
} else {
|
||||
reply.header('Access-Control-Allow-Origin', `http://127.0.0.1:${config.PORT || 3456}`);
|
||||
reply.header('Access-Control-Allow-Origin', `http://localhost:${config.PORT || 3456}`);
|
||||
}
|
||||
return done();
|
||||
}
|
||||
const isConfigEndpoint = req.url.startsWith("/api/config");
|
||||
const isRestartEndpoint = req.url === "/api/restart";
|
||||
|
||||
@@ -42,56 +31,47 @@ export const apiKeyAuth =
|
||||
if (isConfigEndpoint || isRestartEndpoint) {
|
||||
// Attach access level to request for later use
|
||||
(req as any).accessLevel = "restricted";
|
||||
|
||||
|
||||
// If no API key is set in config, allow restricted access
|
||||
if (!apiKey) {
|
||||
(req as any).accessLevel = "restricted";
|
||||
return done();
|
||||
}
|
||||
|
||||
// Check for temporary access via query parameter (for UI)
|
||||
if ((req as any).isTempAccess) {
|
||||
return done();
|
||||
}
|
||||
|
||||
|
||||
// If API key is set, check authentication
|
||||
const authHeaderValue = req.headers.authorization || req.headers["x-api-key"];
|
||||
const authKey: string = Array.isArray(authHeaderValue) ? authHeaderValue[0] : authHeaderValue || "";
|
||||
|
||||
const authHeaderValue =
|
||||
req.headers.authorization || req.headers["x-api-key"];
|
||||
const authKey: string = Array.isArray(authHeaderValue)
|
||||
? authHeaderValue[0]
|
||||
: authHeaderValue || "";
|
||||
|
||||
if (!authKey) {
|
||||
(req as any).accessLevel = "restricted";
|
||||
return done();
|
||||
}
|
||||
|
||||
|
||||
let token = "";
|
||||
if (authKey.startsWith("Bearer")) {
|
||||
token = authKey.split(" ")[1];
|
||||
} else {
|
||||
token = authKey;
|
||||
}
|
||||
|
||||
|
||||
if (token !== apiKey) {
|
||||
(req as any).accessLevel = "restricted";
|
||||
return done();
|
||||
}
|
||||
|
||||
|
||||
// Full access for authenticated users
|
||||
(req as any).accessLevel = "full";
|
||||
return done();
|
||||
}
|
||||
|
||||
// For other non-config endpoints, use existing logic
|
||||
if (!apiKey) {
|
||||
return done();
|
||||
}
|
||||
|
||||
// Check for temporary access via query parameter (for UI)
|
||||
if ((req as any).isTempAccess) {
|
||||
return done();
|
||||
}
|
||||
|
||||
const authHeaderValue = req.headers.authorization || req.headers["x-api-key"];
|
||||
const authKey: string = Array.isArray(authHeaderValue) ? authHeaderValue[0] : authHeaderValue || "";
|
||||
const authHeaderValue =
|
||||
req.headers.authorization || req.headers["x-api-key"];
|
||||
const authKey: string = Array.isArray(authHeaderValue)
|
||||
? authHeaderValue[0]
|
||||
: authHeaderValue || "";
|
||||
if (!authKey) {
|
||||
reply.status(401).send("APIKEY is missing");
|
||||
return;
|
||||
@@ -102,7 +82,7 @@ export const apiKeyAuth =
|
||||
} else {
|
||||
token = authKey;
|
||||
}
|
||||
|
||||
|
||||
if (token !== apiKey) {
|
||||
reply.status(401).send("Invalid API key");
|
||||
return;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import Server from "@musistudio/llms";
|
||||
import { readConfigFile, writeConfigFile } from "./utils";
|
||||
import { CONFIG_FILE } from "./constants";
|
||||
import { readConfigFile, writeConfigFile, backupConfigFile } from "./utils";
|
||||
import { join } from "path";
|
||||
import { readFileSync } from "fs";
|
||||
import fastifyStatic from "@fastify/static";
|
||||
|
||||
export const createServer = (config: any): Server => {
|
||||
@@ -10,16 +8,6 @@ export const createServer = (config: any): Server => {
|
||||
|
||||
// Add endpoint to read config.json with access control
|
||||
server.app.get("/api/config", async (req, reply) => {
|
||||
// Get access level from request (set by auth middleware)
|
||||
const accessLevel = (req as any).accessLevel || "restricted";
|
||||
|
||||
// If restricted access, return 401
|
||||
if (accessLevel === "restricted") {
|
||||
reply.status(401).send("API key required to access configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
// For full access (including temp API key), return complete config
|
||||
return await readConfigFile();
|
||||
});
|
||||
|
||||
@@ -37,38 +25,17 @@ export const createServer = (config: any): Server => {
|
||||
|
||||
// Add endpoint to save config.json with access control
|
||||
server.app.post("/api/config", async (req, reply) => {
|
||||
// Only allow full access users to save config
|
||||
const accessLevel = (req as any).accessLevel || "restricted";
|
||||
if (accessLevel !== "full") {
|
||||
reply.status(403).send("Full access required to modify configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
const newConfig = req.body;
|
||||
|
||||
|
||||
// Backup existing config file if it exists
|
||||
const { backupConfigFile } = await import("./utils");
|
||||
const backupPath = await backupConfigFile();
|
||||
if (backupPath) {
|
||||
console.log(`Backed up existing configuration file to ${backupPath}`);
|
||||
}
|
||||
|
||||
|
||||
await writeConfigFile(newConfig);
|
||||
return { success: true, message: "Config saved successfully" };
|
||||
});
|
||||
|
||||
// Add endpoint for testing full access without modifying config
|
||||
server.app.post("/api/config/test", async (req, reply) => {
|
||||
// Only allow full access users to test config access
|
||||
const accessLevel = (req as any).accessLevel || "restricted";
|
||||
if (accessLevel !== "full") {
|
||||
reply.status(403).send("Full access required to test configuration access");
|
||||
return;
|
||||
}
|
||||
|
||||
// Return success without modifying anything
|
||||
return { success: true, message: "Access granted" };
|
||||
});
|
||||
|
||||
// Add endpoint to restart the service with access control
|
||||
server.app.post("/api/restart", async (req, reply) => {
|
||||
@@ -78,13 +45,16 @@ export const createServer = (config: any): Server => {
|
||||
reply.status(403).send("Full access required to restart service");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
reply.send({ success: true, message: "Service restart initiated" });
|
||||
|
||||
// Restart the service after a short delay to allow response to be sent
|
||||
setTimeout(() => {
|
||||
const { spawn } = require("child_process");
|
||||
spawn(process.execPath, [process.argv[1], "restart"], { detached: true, stdio: "ignore" });
|
||||
spawn(process.execPath, [process.argv[1], "restart"], {
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
HOME_DIR,
|
||||
PLUGINS_DIR,
|
||||
} from "../constants";
|
||||
import { getSystemUUID, generateTempAPIKey, getTempAPIKey } from "./systemUUID";
|
||||
import { cleanupLogFiles } from "./logCleanup";
|
||||
|
||||
const ensureDir = async (dir_path: string) => {
|
||||
@@ -139,8 +138,5 @@ export const initConfig = async () => {
|
||||
return config;
|
||||
};
|
||||
|
||||
// 导出系统UUID相关函数
|
||||
export { getSystemUUID, generateTempAPIKey, getTempAPIKey };
|
||||
|
||||
// 导出日志清理函数
|
||||
export { cleanupLogFiles };
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import { execSync } from "child_process";
|
||||
import { createHash } from "crypto";
|
||||
import os from "os";
|
||||
|
||||
/**
|
||||
* 跨平台获取系统UUID
|
||||
* @returns 系统UUID字符串
|
||||
*/
|
||||
export async function getSystemUUID(): Promise<string> {
|
||||
const platform = os.platform();
|
||||
|
||||
try {
|
||||
let uuid: string;
|
||||
|
||||
switch (platform) {
|
||||
case "win32": // Windows
|
||||
uuid = execSync("wmic csproduct get UUID", { encoding: "utf8" })
|
||||
.split("\n")[1]
|
||||
.trim();
|
||||
break;
|
||||
|
||||
case "darwin": // macOS
|
||||
uuid = execSync(
|
||||
"system_profiler SPHardwareDataType | grep 'Hardware UUID'",
|
||||
{ encoding: "utf8" }
|
||||
)
|
||||
.split(":")[1]
|
||||
.trim();
|
||||
break;
|
||||
|
||||
case "linux": // Linux
|
||||
// 尝试使用 dmidecode (需要 root 权限)
|
||||
try {
|
||||
uuid = execSync("dmidecode -s system-uuid", { encoding: "utf8" }).trim();
|
||||
} catch (dmidecodeError) {
|
||||
// 如果 dmidecode 失败,尝试读取 sysfs (不需要 root 权限,但可能没有权限)
|
||||
try {
|
||||
uuid = execSync("cat /sys/class/dmi/id/product_uuid", { encoding: "utf8" }).trim();
|
||||
} catch (sysfsError) {
|
||||
throw new Error("无法在Linux系统上获取系统UUID,可能需要root权限");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`不支持的操作系统: ${platform}`);
|
||||
}
|
||||
|
||||
return uuid;
|
||||
} catch (error) {
|
||||
throw new Error(`获取系统UUID失败: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于系统UUID生成固定的临时API密钥
|
||||
* @param systemUUID 系统UUID
|
||||
* @returns 生成的API密钥
|
||||
*/
|
||||
export function generateTempAPIKey(systemUUID: string): string {
|
||||
// 使用SHA-256哈希算法确保一致性
|
||||
const hash = createHash("sha256");
|
||||
hash.update(systemUUID);
|
||||
// 添加盐值以增加安全性
|
||||
hash.update("claude-code-router-temp-key-salt");
|
||||
// 生成32字符的十六进制字符串
|
||||
return hash.digest("hex").substring(0, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取临时API密钥(完整的便利函数)
|
||||
* @returns 临时API密钥
|
||||
*/
|
||||
export async function getTempAPIKey(): Promise<string> {
|
||||
const uuid = await getSystemUUID();
|
||||
return generateTempAPIKey(uuid);
|
||||
}
|
||||
@@ -128,20 +128,6 @@ export function ConfigProvider({ children }: ConfigProviderProps) {
|
||||
fetchConfig();
|
||||
}, [hasFetched, apiKey]);
|
||||
|
||||
// Check if user has full access
|
||||
useEffect(() => {
|
||||
const checkAccess = async () => {
|
||||
if (config) {
|
||||
const hasFullAccess = await api.checkFullAccess();
|
||||
// Store access level in a global state or context if needed
|
||||
// For now, we'll just log it
|
||||
console.log('User has full access:', hasFullAccess);
|
||||
}
|
||||
};
|
||||
|
||||
checkAccess();
|
||||
}, [config]);
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider value={{ config, setConfig, error }}>
|
||||
{children}
|
||||
|
||||
@@ -140,22 +140,6 @@ class ApiClient {
|
||||
return this.post<Config>('/config', config);
|
||||
}
|
||||
|
||||
// Check if user has full access
|
||||
async checkFullAccess(): Promise<boolean> {
|
||||
try {
|
||||
// Try to access test endpoint (won't actually modify anything)
|
||||
// This will return 403 if user doesn't have full access
|
||||
await this.post<Config>('/config/test', { test: true });
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
if (error.message && error.message.includes('403')) {
|
||||
return false;
|
||||
}
|
||||
// For other errors, assume user has access (to avoid blocking legitimate users)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get providers
|
||||
async getProviders(): Promise<Provider[]> {
|
||||
return this.get<Provider[]>('/api/providers');
|
||||
|
||||
Reference in New Issue
Block a user