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 PORT = 8195;
|
||||||
const HOST = '0.0.0.0';
|
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');
|
const CONFIG_FILE = path.join(__dirname, 'config.json');
|
||||||
|
|
||||||
function loadConfig() {
|
function loadConfig() {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
||||||
} catch {
|
} 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));
|
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) => {
|
app.get('/api/config', (req, res) => {
|
||||||
const cfg = loadConfig();
|
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) => {
|
app.post('/api/config', (req, res) => {
|
||||||
const { apiKey, baseUrl } = req.body;
|
const { baseUrl } = req.body;
|
||||||
if (typeof apiKey !== 'string' || typeof baseUrl !== 'string') {
|
if (typeof baseUrl !== 'string') {
|
||||||
return res.status(400).json({ error: '参数格式错误' });
|
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);
|
saveConfig(cfg);
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
});
|
});
|
||||||
@@ -46,9 +46,9 @@ app.post('/api/config', (req, res) => {
|
|||||||
|
|
||||||
app.post('/api/generate', async (req, res) => {
|
app.post('/api/generate', async (req, res) => {
|
||||||
const {
|
const {
|
||||||
model, prompt, style, aspect_ratio,
|
model, prompt, aspect_ratio,
|
||||||
width, height, response_format, seed,
|
width, height, response_format, seed,
|
||||||
n, prompt_optimizer, aigc_watermark,
|
n, prompt_optimizer, aigc_watermark, apiKey,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
@@ -58,9 +58,7 @@ app.post('/api/generate', async (req, res) => {
|
|||||||
if (model !== 'image-01') {
|
if (model !== 'image-01') {
|
||||||
return res.status(400).json({ error: 'model 参数无效' });
|
return res.status(400).json({ error: 'model 参数无效' });
|
||||||
}
|
}
|
||||||
|
if (!apiKey || typeof apiKey !== 'string') {
|
||||||
const cfg = loadConfig();
|
|
||||||
if (!cfg.apiKey) {
|
|
||||||
return res.status(400).json({ error: '未配置 API Key,请先在设置中填写。' });
|
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 (prompt_optimizer) payload.prompt_optimizer = true;
|
||||||
if (aigc_watermark) payload.aigc_watermark = 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`;
|
const endpoint = `${baseUrl}/v1/image_generation`;
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
@@ -86,7 +84,7 @@ app.post('/api/generate', async (req, res) => {
|
|||||||
response = await fetch(endpoint, {
|
response = await fetch(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${cfg.apiKey}`,
|
'Authorization': `Bearer ${apiKey}`,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
@@ -117,14 +115,14 @@ app.post('/api/generate', async (req, res) => {
|
|||||||
// ── Task Status (future-proofing) ───────────────────────────────
|
// ── Task Status (future-proofing) ───────────────────────────────
|
||||||
|
|
||||||
app.get('/api/task/:id', async (req, res) => {
|
app.get('/api/task/:id', async (req, res) => {
|
||||||
const cfg = loadConfig();
|
const apiKey = req.headers['x-api-key'];
|
||||||
if (!cfg.apiKey) {
|
if (!apiKey) {
|
||||||
return res.status(400).json({ error: '未配置 API Key。' });
|
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 {
|
try {
|
||||||
const response = await fetch(`${baseUrl}/v1/image_generation/${req.params.id}`, {
|
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();
|
const data = await response.json();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -262,7 +262,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="baseUrlInput">API 地址</label>
|
<label for="baseUrlInput">API 地址</label>
|
||||||
|
|||||||
24
ui.js
24
ui.js
@@ -94,6 +94,7 @@ function buildPayload() {
|
|||||||
aspect_ratio: aspectRatio.value,
|
aspect_ratio: aspectRatio.value,
|
||||||
response_format: responseFormat.value,
|
response_format: responseFormat.value,
|
||||||
n: n,
|
n: n,
|
||||||
|
apiKey: localStorage.getItem('imgGen-apiKey') || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (seed) payload.seed = seed;
|
if (seed) payload.seed = seed;
|
||||||
@@ -179,11 +180,11 @@ async function copySrc(src, fmt, btn) {
|
|||||||
// ── Init ────────────────────────────────────────────────────────
|
// ── Init ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
const savedKey = localStorage.getItem('imgGen-apiKey') || '';
|
||||||
const cfg = await api('/api/config');
|
const savedBaseUrl = localStorage.getItem('imgGen-baseUrl') || 'https://api.minimaxi.com';
|
||||||
if (!cfg.hasApiKey) apiKeyAlert.style.display = 'flex';
|
if (!savedKey) apiKeyAlert.style.display = 'flex';
|
||||||
baseUrlInput.value = cfg.baseUrl || 'https://api.minimaxi.com';
|
apiKeyInput.value = savedKey;
|
||||||
} catch { /* non-fatal */ }
|
baseUrlInput.value = savedBaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Generate ────────────────────────────────────────────────────
|
// ── Generate ────────────────────────────────────────────────────
|
||||||
@@ -269,17 +270,12 @@ saveSettingsBtn.addEventListener('click', async () => {
|
|||||||
clearError();
|
clearError();
|
||||||
const apiKey = apiKeyInput.value.trim();
|
const apiKey = apiKeyInput.value.trim();
|
||||||
const baseUrl = baseUrlInput.value.trim() || 'https://api.minimaxi.com';
|
const baseUrl = baseUrlInput.value.trim() || 'https://api.minimaxi.com';
|
||||||
try {
|
|
||||||
await api('/api/config', {
|
localStorage.setItem('imgGen-apiKey', apiKey);
|
||||||
method: 'POST',
|
localStorage.setItem('imgGen-baseUrl', baseUrl);
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ apiKey, baseUrl }),
|
|
||||||
});
|
|
||||||
if (apiKey) apiKeyAlert.style.display = 'none';
|
if (apiKey) apiKeyAlert.style.display = 'none';
|
||||||
closeModal();
|
closeModal();
|
||||||
} catch (err) {
|
|
||||||
showError(err.message);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Theme switcher ─────────────────────────────────────────────
|
// ── Theme switcher ─────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user