diff --git a/README.md b/README.md index d9b296f..3770514 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,9 @@ Create and configure your `~/.claude-code-router/config.json` file. For more det The `config.json` file has several key sections: - **`PROXY_URL`** (optional): You can set a proxy for API requests, for example: `"PROXY_URL": "http://127.0.0.1:7890"`. - **`LOG`** (optional): You can enable logging by setting it to `true`. The log file will be located at `$HOME/.claude-code-router.log`. +- **`APIKEY`** (optional): You can set a secret key to authenticate requests. When set, clients must provide this key in the `Authorization` header (e.g., `Bearer your-secret-key`) or the `x-api-key` header. Example: `"APIKEY": "your-secret-key"`. +- **`HOST`** (optional): You can set the host address for the server. If `APIKEY` is not set, the host will be forced to `127.0.0.1` for security reasons to prevent unauthorized access. Example: `"HOST": "0.0.0.0"`. + - **`Providers`**: Used to configure different model providers. - **`Router`**: Used to set up routing rules. `default` specifies the default model, which will be used for all requests if no other route is configured. @@ -45,6 +48,7 @@ Here is a comprehensive example: ```json { + "APIKEY": "your-secret-key", "PROXY_URL": "http://127.0.0.1:7890", "LOG": true, "Providers": [ diff --git a/README_zh.md b/README_zh.md index e066ee1..2b6cdfc 100644 --- a/README_zh.md +++ b/README_zh.md @@ -36,6 +36,8 @@ npm install -g @musistudio/claude-code-router `config.json` 文件有几个关键部分: - **`PROXY_URL`** (可选): 您可以为 API 请求设置代理,例如:`"PROXY_URL": "http://127.0.0.1:7890"`。 - **`LOG`** (可选): 您可以通过将其设置为 `true` 来启用日志记录。日志文件将位于 `$HOME/.claude-code-router.log`。 +- **`APIKEY`** (可选): 您可以设置一个密钥来进行身份验证。设置后,客户端请求必须在 `Authorization` 请求头 (例如, `Bearer your-secret-key`) 或 `x-api-key` 请求头中提供此密钥。例如:`"APIKEY": "your-secret-key"`。 +- **`HOST`** (可选): 您可以设置服务的主机地址。如果未设置 `APIKEY`,出于安全考虑,主机地址将强制设置为 `127.0.0.1`,以防止未经授权的访问。例如:`"HOST": "0.0.0.0"`。 - **`Providers`**: 用于配置不同的模型提供商。 - **`Router`**: 用于设置路由规则。`default` 指定默认模型,如果未配置其他路由,则该模型将用于所有请求。 @@ -43,6 +45,7 @@ npm install -g @musistudio/claude-code-router ```json { + "APIKEY": "your-secret-key", "PROXY_URL": "http://127.0.0.1:7890", "LOG": true, "Providers": [ diff --git a/config.example.json b/config.example.json index a25995a..c5ad568 100644 --- a/config.example.json +++ b/config.example.json @@ -72,5 +72,7 @@ "background": "ollama,qwen2.5-coder:latest", "think": "deepseek,deepseek-reasoner", "longContext": "openrouter,google/gemini-2.5-pro-preview" - } -} \ No newline at end of file + }, + "APIKEY": "your-secret-key", + "HOST": "0.0.0.0" +} diff --git a/package.json b/package.json index 833ee21..18ccfac 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "devDependencies": { "esbuild": "^0.25.1", + "fastify": "^5.4.0", "shx": "^0.4.0", "typescript": "^5.8.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2c0b47..b28114d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ importers: esbuild: specifier: ^0.25.1 version: 0.25.5 + fastify: + specifier: ^5.4.0 + version: 5.4.0 shx: specifier: ^0.4.0 version: 0.4.0 diff --git a/src/index.ts b/src/index.ts index c1da773..553a45b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { join } from "path"; import { initConfig, initDir } from "./utils"; import { createServer } from "./server"; import { router } from "./utils/router"; +import { apiKeyAuth } from "./middleware/auth"; import { cleanupPidFile, isServiceRunning, @@ -46,6 +47,14 @@ async function run(options: RunOptions = {}) { await initializeClaudeConfig(); await initDir(); const config = await initConfig(); + let HOST = config.HOST; + + if (config.HOST && !config.APIKEY) { + HOST = "127.0.0.1"; + console.warn( + "⚠️ API key is not set. HOST is forced to 127.0.0.1." + ); + } const port = options.port || 3456; @@ -64,6 +73,7 @@ async function run(options: RunOptions = {}) { cleanupPidFile(); process.exit(0); }); + console.log(HOST) // Use port from environment variable if set (for background process) const servicePort = process.env.SERVICE_PORT @@ -74,6 +84,7 @@ async function run(options: RunOptions = {}) { initialConfig: { // ...config, providers: config.Providers || config.providers, + HOST: HOST, PORT: servicePort, LOG_FILE: join( homedir(), @@ -82,6 +93,7 @@ async function run(options: RunOptions = {}) { ), }, }); + server.addHook("preHandler", apiKeyAuth(config)); server.addHook("preHandler", async (req, reply) => router(req, reply, config) ); diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts new file mode 100644 index 0000000..fc4cd42 --- /dev/null +++ b/src/middleware/auth.ts @@ -0,0 +1,33 @@ +import { FastifyRequest, FastifyReply } from "fastify"; + +export const apiKeyAuth = + (config: any) => + (req: FastifyRequest, reply: FastifyReply, done: () => void) => { + if (["/", "/health"].includes(req.url)) { + return done(); + } + const apiKey = config.APIKEY; + + if (!apiKey) { + return done(); + } + + const authKey: string = + req.headers.authorization || req.headers["x-api-key"]; + if (!authKey) { + reply.status(401).send("APIKEY is missing"); + return; + } + let token = ""; + if (authKey.startsWith("Bearer")) { + token = authKey.split(" ")[1]; + } else { + token = authKey; + } + if (token !== apiKey) { + reply.status(401).send("Invalid API key"); + return; + } + + done(); + }; diff --git a/src/utils/codeCommand.ts b/src/utils/codeCommand.ts index 6a0e43a..136335b 100644 --- a/src/utils/codeCommand.ts +++ b/src/utils/codeCommand.ts @@ -4,9 +4,11 @@ import { decrementReferenceCount, } from "./processCheck"; import { closeService } from "./close"; +import { readConfigFile } from "."; export async function executeCodeCommand(args: string[] = []) { // Set environment variables + const config = await readConfigFile(); const env = { ...process.env, ANTHROPIC_AUTH_TOKEN: "test", @@ -14,6 +16,11 @@ export async function executeCodeCommand(args: string[] = []) { API_TIMEOUT_MS: "600000", }; + if (config?.APIKEY) { + env.ANTHROPIC_API_KEY = config.APIKEY; + delete env.ANTHROPIC_AUTH_TOKEN; + } + // Increment reference count when command starts incrementReferenceCount(); diff --git a/src/utils/index.ts b/src/utils/index.ts index 8077b11..5c43667 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -48,7 +48,7 @@ export const readConfigFile = async () => { return JSON.parse(config); } catch { const name = await question("Enter Provider Name: "); - const apiKey = await question("Enter Provider API KEY: "); + const APIKEY = await question("Enter Provider API KEY: "); const baseUrl = await question("Enter Provider URL: "); const model = await question("Enter MODEL Name: "); const config = Object.assign({}, DEFAULT_CONFIG, { @@ -56,7 +56,7 @@ export const readConfigFile = async () => { { name, api_base_url: baseUrl, - api_key: apiKey, + api_key: APIKEY, models: [model], }, ],