From a23ce5b08f15ed3074493ccbeeb09a1b4bd45ae5 Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Sat, 9 May 2026 17:39:18 +0800 Subject: [PATCH] require api auth token --- .env.example | 9 ++++-- API图片修改-Agent.md | 65 ++++++++++++++++++++++++++++++-------------- README.md | 29 ++++++++++++++++++-- server/index.ts | 19 ++++++++++--- 4 files changed, 93 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index 1f03db6..0120232 100644 --- a/.env.example +++ b/.env.example @@ -14,9 +14,12 @@ APP_URL="MY_APP_URL" # API_PORT: HTTP API server port. Frontend still runs on 3000 by default. API_PORT="3002" -# API_AUTH_TOKEN: Optional token for API calls. Leave empty to disable auth. -# When set, send Authorization: Bearer or x-api-key: . -API_AUTH_TOKEN="" +# API_AUTH_TOKEN: Required token for protected API calls. +# Send it as Authorization: Bearer or x-api-key: . +API_AUTH_TOKEN="CHANGE_ME_TO_A_LONG_RANDOM_TOKEN" + +# API_AUTH_DISABLED: Only set this to true for local-only development. +API_AUTH_DISABLED="false" # API callers can also send a temporary Gemini key per request with: # x-gemini-api-key: diff --git a/API图片修改-Agent.md b/API图片修改-Agent.md index 222018c..c77ff68 100644 --- a/API图片修改-Agent.md +++ b/API图片修改-Agent.md @@ -7,7 +7,35 @@ - 本机:`http://localhost:3002` - 局域网:`http://192.168.31.204:3002` -## 认证与 API Key +## 必须携带 API 访问密钥 + +所有受保护接口都需要 API 访问密钥。这个密钥不是 Gemini API Key,而是本服务自己的调用密钥,用来防止局域网内其他人随意调用你的接口。 + +在服务端 `.env.local` 中配置: + +```env +API_AUTH_TOKEN="YOUR_LONG_RANDOM_API_TOKEN" +``` + +调用时二选一携带: + +```txt +Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN +``` + +或: + +```txt +x-api-key: YOUR_LONG_RANDOM_API_TOKEN +``` + +如果没有配置 `API_AUTH_TOKEN`,`/api/generate`、`/api/edit-image`、`/api/analyze-document`、`/api/config/*` 等接口会拒绝请求。只在本机临时开发时,才可以设置: + +```env +API_AUTH_DISABLED="true" +``` + +## Gemini API Key Gemini API Key 有三种传入方式,按优先级从高到低: @@ -15,28 +43,17 @@ Gemini API Key 有三种传入方式,按优先级从高到低: 2. 单次请求 JSON/Form 字段:`apiKey=YOUR_GEMINI_API_KEY` 3. 服务端环境变量:`.env.local` 中的 `GEMINI_API_KEY` -运行中更新服务端 Key: +运行中更新服务端 Gemini Key: ```bash curl -X POST http://localhost:3002/api/config/api-key \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -d "{\"apiKey\":\"YOUR_GEMINI_API_KEY\",\"persist\":true}" ``` `persist:true` 会写入本项目的 `.env.local`,该文件不会提交到 git。 -如果服务设置了 `API_AUTH_TOKEN`,还需要在每次请求中加入: - -```txt -Authorization: Bearer YOUR_API_AUTH_TOKEN -``` - -或: - -```txt -x-api-key: YOUR_API_AUTH_TOKEN -``` - ## 健康检查 ```bash @@ -50,7 +67,8 @@ curl http://localhost:3002/api/health "ok": true, "apiPort": 3002, "hasGeminiApiKey": true, - "authEnabled": false, + "authEnabled": true, + "authRequired": true, "acceptsPerRequestApiKey": true } ``` @@ -61,6 +79,7 @@ curl http://localhost:3002/api/health ```bash curl -X POST http://localhost:3002/api/edit-image \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -H "x-gemini-api-key: YOUR_GEMINI_API_KEY" \ -F "prompt=保留主体不变,把背景改成干净的白色摄影棚,增强产品质感" \ -F "imageSize=1K" \ @@ -108,6 +127,7 @@ curl -X POST http://localhost:3002/api/edit-image \ ```bash curl -X POST http://localhost:3002/api/generate \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -H "x-gemini-api-key: YOUR_GEMINI_API_KEY" \ -d "{ \"prompt\":\"把这张图改成赛博朋克夜景风格,但保留人物脸部特征\", @@ -154,6 +174,7 @@ curl -X POST http://localhost:3002/api/generate \ ```bash curl -X POST http://localhost:3002/api/analyze-document \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -H "x-gemini-api-key: YOUR_GEMINI_API_KEY" \ -F "prompt=用中文总结这份文档,提取关键结论和待办事项" \ -F "files=@report.pdf" @@ -176,6 +197,7 @@ curl -X POST http://localhost:3002/api/analyze-document \ ```bash curl -X POST http://localhost:3002/api/edit-image \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -H "x-gemini-api-key: YOUR_GEMINI_API_KEY" \ -F "prompt=参考第二张图的色调,修改第一张图" \ -F "files=@source.png" \ @@ -195,14 +217,17 @@ curl -X POST http://localhost:3002/api/edit-image \ 常见错误: +- `Unauthorized. Send Authorization: Bearer YOUR_API_AUTH_TOKEN or x-api-key.`:缺少 API 访问密钥 +- `API_AUTH_TOKEN is required.`:服务端没有配置 API 访问密钥 - `prompt or instruction is required.`:缺少修改指令 - `Gemini API key is required.`:没有配置或传入 Gemini API Key - `Remote file is too large.`:URL 文件超过大小限制 ## Agent 调用建议 -1. 优先使用 `POST /api/edit-image` + multipart 上传真实图片文件。 -2. 如果图片已经是 base64,使用 `POST /api/generate` 的 `images[].base64`。 -3. 每次调用都带 `x-gemini-api-key`,可避免依赖服务端环境。 -4. 取返回的 `images[0].dataUrl` 给用户预览;保存文件时解码 `images[0].data`。 -5. 修改指令尽量包含保留内容、需要改变的内容、输出用途和画幅比例。 +1. 每次调用都必须带 `Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN` 或 `x-api-key`。 +2. Gemini Key 建议用 `x-gemini-api-key` 单次传入,避免依赖服务端环境。 +3. 优先使用 `POST /api/edit-image` + multipart 上传真实图片文件。 +4. 如果图片已经是 base64,使用 `POST /api/generate` 的 `images[].base64`。 +5. 取返回的 `images[0].dataUrl` 给用户预览;保存文件时解码 `images[0].data`。 +6. 修改指令尽量包含保留内容、需要改变的内容、输出用途和画幅比例。 diff --git a/README.md b/README.md index 652d477..4349edf 100644 --- a/README.md +++ b/README.md @@ -23,17 +23,38 @@ View your app in AI Studio: https://ai.studio/apps/96002e3b-5eec-4566-85e8-71871 The UI runs on port `3000`. The HTTP API runs separately on port `3002`. -1. Set `GEMINI_API_KEY` in `.env.local` or your shell. +1. Set `GEMINI_API_KEY` and `API_AUTH_TOKEN` in `.env.local` or your shell. 2. Start the API: `npm run api` 3. Check the API: `http://localhost:3002/api/health` +Example `.env.local`: + +```env +GEMINI_API_KEY="YOUR_GEMINI_API_KEY" +API_AUTH_TOKEN="YOUR_LONG_RANDOM_API_TOKEN" +API_PORT="3002" +``` + +All protected API calls must send one of these headers: + +```txt +Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN +``` + +or: + +```txt +x-api-key: YOUR_LONG_RANDOM_API_TOKEN +``` + You can change the server API key without restarting: ```bash curl -X POST http://localhost:3002/api/config/api-key \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -d "{\"apiKey\":\"YOUR_GEMINI_API_KEY\",\"persist\":true}" ``` @@ -42,6 +63,7 @@ You can also pass a temporary Gemini key for one call: ```bash curl -X POST http://localhost:3002/api/generate \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -H "x-gemini-api-key: YOUR_GEMINI_API_KEY" \ -d "{\"prompt\":\"Create a clean product poster\"}" ``` @@ -53,6 +75,7 @@ Generate or edit with JSON/base64: ```bash curl -X POST http://localhost:3002/api/generate \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -d "{\"prompt\":\"Create a clean product poster for a white coffee mug\",\"imageSize\":\"1K\",\"aspectRatio\":\"1:1\"}" ``` @@ -60,6 +83,7 @@ Upload an image or document with a prompt: ```bash curl -X POST http://localhost:3002/api/generate/upload \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -F "prompt=Change the background to a bright studio scene" \ -F "imageSize=1K" \ -F "aspectRatio=1:1" \ @@ -70,10 +94,11 @@ Analyze a document: ```bash curl -X POST http://localhost:3002/api/analyze-document \ + -H "Authorization: Bearer YOUR_LONG_RANDOM_API_TOKEN" \ -F "prompt=Summarize this document in Chinese" \ -F "files=@report.pdf" ``` -Optional API auth: set `API_AUTH_TOKEN`, then send either `Authorization: Bearer ` or `x-api-key: `. +API auth is required by default. For local-only development, you can set `API_AUTH_DISABLED=true`, but do not use that on a LAN or server. For Agent-facing image editing instructions, see `API图片修改-Agent.md`. diff --git a/server/index.ts b/server/index.ts index 3079c9e..952a045 100644 --- a/server/index.ts +++ b/server/index.ts @@ -51,6 +51,7 @@ const apiPort = Number(process.env.API_PORT || 3002); const defaultImageModel = process.env.GEMINI_IMAGE_MODEL || 'gemini-3.1-flash-image-preview'; const defaultTextModel = process.env.GEMINI_TEXT_MODEL || 'gemini-2.5-flash'; const apiAuthToken = process.env.API_AUTH_TOKEN || ''; +const apiAuthDisabled = process.env.API_AUTH_DISABLED === 'true'; if (!runtimeApiKey) { console.warn('GEMINI_API_KEY/API_KEY is not set. API calls will fail until it is configured.'); @@ -70,11 +71,19 @@ app.use((req, res, next) => { app.use(express.json({ limit: process.env.API_JSON_LIMIT || '50mb' })); function requireAuth(req: express.Request, res: express.Response, next: express.NextFunction) { - if (!apiAuthToken) { + if (apiAuthDisabled) { next(); return; } + if (!apiAuthToken) { + res.status(503).json({ + ok: false, + error: 'API_AUTH_TOKEN is required. Set it in .env.local, or set API_AUTH_DISABLED=true for local-only development.', + }); + return; + } + const authHeader = req.header('authorization') || ''; const bearerToken = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : ''; const headerToken = req.header('x-api-key') || ''; @@ -86,7 +95,7 @@ function requireAuth(req: express.Request, res: express.Response, next: express. res.status(401).json({ ok: false, - error: 'Unauthorized. Send Authorization: Bearer or x-api-key.', + error: 'Unauthorized. Send Authorization: Bearer YOUR_API_AUTH_TOKEN or x-api-key.', }); } @@ -323,7 +332,8 @@ app.get('/api/health', (_req, res) => { ok: true, apiPort, hasGeminiApiKey: Boolean(runtimeApiKey), - authEnabled: Boolean(apiAuthToken), + authEnabled: Boolean(apiAuthToken) && !apiAuthDisabled, + authRequired: !apiAuthDisabled, acceptsPerRequestApiKey: true, defaultImageModel, defaultTextModel, @@ -336,7 +346,8 @@ app.get('/api/config', requireAuth, (_req, res) => { apiPort, hasGeminiApiKey: Boolean(runtimeApiKey), apiKeyPreview: runtimeApiKey ? `${runtimeApiKey.slice(0, 6)}...${runtimeApiKey.slice(-4)}` : '', - authEnabled: Boolean(apiAuthToken), + authEnabled: Boolean(apiAuthToken) && !apiAuthDisabled, + authRequired: !apiAuthDisabled, defaultImageModel, defaultTextModel, });