security: API Key 改为仅存储在浏览器 localStorage,不再经过服务器
This commit is contained in:
44
app.js
44
app.js
@@ -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) {
|
||||
|
||||
@@ -262,7 +262,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="field-hint">请从 <a href="https://platform.minimaxi.com/user-center/payment/token-plan" target="_blank" rel="noopener">platform.minimaxi.com</a> 获取</p>
|
||||
<p class="field-hint">存储在浏览器本地,不会上传服务器。<a href="https://platform.minimaxi.com/user-center/payment/token-plan" target="_blank" rel="noopener">获取 API Key →</a></p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="baseUrlInput">API 地址</label>
|
||||
|
||||
24
ui.js
24
ui.js
@@ -94,6 +94,7 @@ function buildPayload() {
|
||||
aspect_ratio: aspectRatio.value,
|
||||
response_format: responseFormat.value,
|
||||
n: n,
|
||||
apiKey: localStorage.getItem('imgGen-apiKey') || '',
|
||||
};
|
||||
|
||||
if (seed) payload.seed = seed;
|
||||
@@ -179,11 +180,11 @@ async function copySrc(src, fmt, btn) {
|
||||
// ── Init ────────────────────────────────────────────────────────
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const cfg = await api('/api/config');
|
||||
if (!cfg.hasApiKey) apiKeyAlert.style.display = 'flex';
|
||||
baseUrlInput.value = cfg.baseUrl || 'https://api.minimaxi.com';
|
||||
} catch { /* non-fatal */ }
|
||||
const savedKey = localStorage.getItem('imgGen-apiKey') || '';
|
||||
const savedBaseUrl = localStorage.getItem('imgGen-baseUrl') || 'https://api.minimaxi.com';
|
||||
if (!savedKey) apiKeyAlert.style.display = 'flex';
|
||||
apiKeyInput.value = savedKey;
|
||||
baseUrlInput.value = savedBaseUrl;
|
||||
}
|
||||
|
||||
// ── Generate ────────────────────────────────────────────────────
|
||||
@@ -269,17 +270,12 @@ saveSettingsBtn.addEventListener('click', async () => {
|
||||
clearError();
|
||||
const apiKey = apiKeyInput.value.trim();
|
||||
const baseUrl = baseUrlInput.value.trim() || 'https://api.minimaxi.com';
|
||||
try {
|
||||
await api('/api/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ apiKey, baseUrl }),
|
||||
});
|
||||
|
||||
localStorage.setItem('imgGen-apiKey', apiKey);
|
||||
localStorage.setItem('imgGen-baseUrl', baseUrl);
|
||||
|
||||
if (apiKey) apiKeyAlert.style.display = 'none';
|
||||
closeModal();
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Theme switcher ─────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user