security: API Key 改为仅存储在浏览器 localStorage,不再经过服务器

This commit is contained in:
zwbcc
2026-03-28 21:17:17 +08:00
parent 62f040bb8c
commit c0840a299a
3 changed files with 38 additions and 44 deletions

44
app.js
View File

@@ -7,13 +7,19 @@ const fs = require('fs');
const PORT = 8195;
const HOST = '0.0.0.0';
const app = express();
app.use(express.json());
app.use(express.static(__dirname));
// ── Config ──────────────────────────────────────────────────────
const CONFIG_FILE = path.join(__dirname, 'config.json');
function loadConfig() {
try {
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
} catch {
return { apiKey: '', baseUrl: 'https://api.minimaxi.com' };
return { baseUrl: 'https://api.minimaxi.com' };
}
}
@@ -21,23 +27,17 @@ function saveConfig(cfg) {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
}
const app = express();
app.use(express.json());
app.use(express.static(__dirname));
// ── Config ──────────────────────────────────────────────────────
app.get('/api/config', (req, res) => {
const cfg = loadConfig();
res.json({ hasApiKey: !!cfg.apiKey, baseUrl: cfg.baseUrl || 'https://api.minimaxi.com' });
res.json({ baseUrl: cfg.baseUrl || 'https://api.minimaxi.com' });
});
app.post('/api/config', (req, res) => {
const { apiKey, baseUrl } = req.body;
if (typeof apiKey !== 'string' || typeof baseUrl !== 'string') {
const { baseUrl } = req.body;
if (typeof baseUrl !== 'string') {
return res.status(400).json({ error: '参数格式错误' });
}
const cfg = { apiKey: apiKey.trim(), baseUrl: baseUrl.trim() || 'https://api.minimaxi.com' };
const cfg = { baseUrl: baseUrl.trim() || 'https://api.minimaxi.com' };
saveConfig(cfg);
res.json({ ok: true });
});
@@ -46,9 +46,9 @@ app.post('/api/config', (req, res) => {
app.post('/api/generate', async (req, res) => {
const {
model, prompt, style, aspect_ratio,
model, prompt, aspect_ratio,
width, height, response_format, seed,
n, prompt_optimizer, aigc_watermark,
n, prompt_optimizer, aigc_watermark, apiKey,
} = req.body;
// Validation
@@ -58,9 +58,7 @@ app.post('/api/generate', async (req, res) => {
if (model !== 'image-01') {
return res.status(400).json({ error: 'model 参数无效' });
}
const cfg = loadConfig();
if (!cfg.apiKey) {
if (!apiKey || typeof apiKey !== 'string') {
return res.status(400).json({ error: '未配置 API Key请先在设置中填写。' });
}
@@ -78,7 +76,7 @@ app.post('/api/generate', async (req, res) => {
if (prompt_optimizer) payload.prompt_optimizer = true;
if (aigc_watermark) payload.aigc_watermark = true;
const baseUrl = cfg.baseUrl || 'https://api.minimaxi.com';
const baseUrl = loadConfig().baseUrl || 'https://api.minimaxi.com';
const endpoint = `${baseUrl}/v1/image_generation`;
let response;
@@ -86,7 +84,7 @@ app.post('/api/generate', async (req, res) => {
response = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${cfg.apiKey}`,
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
@@ -117,14 +115,14 @@ app.post('/api/generate', async (req, res) => {
// ── Task Status (future-proofing) ───────────────────────────────
app.get('/api/task/:id', async (req, res) => {
const cfg = loadConfig();
if (!cfg.apiKey) {
return res.status(400).json({ error: '未配置 API Key。' });
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(400).json({ error: '缺少 API Key。' });
}
const baseUrl = cfg.baseUrl || 'https://api.minimaxi.com';
const baseUrl = loadConfig().baseUrl || 'https://api.minimaxi.com';
try {
const response = await fetch(`${baseUrl}/v1/image_generation/${req.params.id}`, {
headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
headers: { 'Authorization': `Bearer ${apiKey}` },
});
const data = await response.json();
if (!response.ok) {