gen-authcode.js:
- 新增 --relay-key/-k 参数,替换 ANTHROPIC_API_KEY 为中转站限额子 Key
- 新增 --user/-u 参数(仅显示标识)
- 多用户模式输出 secrets-{token前8位}.enc,单用户仍输出 secrets.enc
install.ps1:
- 新增 Resolve-SecretsFile: 优先找 secrets-XXXXXXXX.enc,回退 secrets.enc
- Decrypt-Secrets 按 token 前8位定位加密文件
auto-setup.ps1:
- Phase 4 同步 Resolve-SecretsFile 逻辑(GUI 路径)
- 文件未找到时弹窗提示拉取对应文件
Bookworm-Setup.sh:
- 新增 resolve_secrets_file() bash 函数
- 解密循环按 token 前8位定位 .enc 文件
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
164 lines
6.9 KiB
JavaScript
164 lines
6.9 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
/**
|
|
* Bookworm 授权码生成工具 (管理员使用)
|
|
*
|
|
* 用法:
|
|
* node gen-authcode.js <days> [选项]
|
|
*
|
|
* 选项:
|
|
* --relay-key, -k <key> 中转站限额子 Key (替换 ANTHROPIC_API_KEY)
|
|
* --user, -u <name> 用户标识 (仅用于显示, 不影响加密)
|
|
* [secrets.txt路径] 明文凭证文件 (默认: ./secrets.txt)
|
|
*
|
|
* 示例:
|
|
* node gen-authcode.js 30 # 共享 Key (单用户)
|
|
* node gen-authcode.js 30 --relay-key sk-relay-xxx # 独立限额 Key
|
|
* node gen-authcode.js 90 -k sk-relay-xxx -u alice # 指定用户名
|
|
*
|
|
* 原理:
|
|
* 1. 生成随机 24位Hex Token (96bit 熵)
|
|
* 2. Token 前8位 = 文件 ID → 输出 secrets-XXXXXXXX.enc (多用户模式)
|
|
* 无 --relay-key 时 → 输出 secrets.enc (单用户/共享模式)
|
|
* 3. 授权码 BW-YYYYMMDD-TOKEN (发给用户)
|
|
* 4. 安全说明: Token = 解密密钥, YYYYMMDD = 客户端到期校验
|
|
*/
|
|
const crypto = require('crypto');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ALGO = 'aes-256-cbc';
|
|
const ITERATIONS = 600000;
|
|
const DIGEST = 'sha256';
|
|
const SALT_LEN = 16;
|
|
const KEY_LEN = 32;
|
|
const IV_LEN = 16;
|
|
|
|
function deriveKey(password, salt) {
|
|
return crypto.pbkdf2Sync(password, salt, ITERATIONS, KEY_LEN + IV_LEN, DIGEST);
|
|
}
|
|
|
|
function encrypt(plaintext, password) {
|
|
const salt = crypto.randomBytes(SALT_LEN);
|
|
const derived = deriveKey(password, salt);
|
|
const key = derived.slice(0, KEY_LEN);
|
|
const iv = derived.slice(KEY_LEN, KEY_LEN + IV_LEN);
|
|
const cipher = crypto.createCipheriv(ALGO, key, iv);
|
|
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
return Buffer.concat([Buffer.from('BWENC1'), salt, encrypted]);
|
|
}
|
|
|
|
// ─── 解析 CLI 参数 ───────────────────────────────────────
|
|
const rawArgs = process.argv.slice(2);
|
|
const DAYS = parseInt(rawArgs[0]);
|
|
|
|
if (!DAYS || DAYS < 1 || DAYS > 3650) {
|
|
console.error('用法: node gen-authcode.js <有效天数> [选项]');
|
|
console.error('选项:');
|
|
console.error(' --relay-key, -k <key> 中转站限额子 Key');
|
|
console.error(' --user, -u <name> 用户标识 (仅显示)');
|
|
console.error(' [secrets.txt路径] 默认: ./secrets.txt');
|
|
console.error('');
|
|
console.error('示例:');
|
|
console.error(' node gen-authcode.js 30 # 共享模式');
|
|
console.error(' node gen-authcode.js 30 -k sk-relay-xxx -u alice # 独立限额');
|
|
process.exit(1);
|
|
}
|
|
|
|
let relayKey = null;
|
|
let userName = null;
|
|
let secretsTxtArg = null;
|
|
|
|
for (let i = 1; i < rawArgs.length; i++) {
|
|
const a = rawArgs[i];
|
|
if (a === '--relay-key' || a === '-k') { relayKey = rawArgs[++i]; }
|
|
else if (a === '--user' || a === '-u') { userName = rawArgs[++i]; }
|
|
else if (!a.startsWith('-')) { secretsTxtArg = a; }
|
|
}
|
|
|
|
const SCRIPT_DIR = path.dirname(path.resolve(__filename));
|
|
const SECRETS_TXT = secretsTxtArg || path.join(SCRIPT_DIR, 'secrets.txt');
|
|
|
|
if (!fs.existsSync(SECRETS_TXT)) {
|
|
console.error(`[错误] 找不到 secrets.txt: ${SECRETS_TXT}`);
|
|
console.error('请先创建 secrets.txt, 每行格式: KEY=VALUE');
|
|
process.exit(1);
|
|
}
|
|
|
|
if (relayKey && !/^[A-Za-z0-9\-_.]+$/.test(relayKey)) {
|
|
console.error('[错误] --relay-key 格式不合法');
|
|
process.exit(1);
|
|
}
|
|
|
|
// ─── 生成到期日 ──────────────────────────────────────────
|
|
const expiry = new Date();
|
|
expiry.setDate(expiry.getDate() + DAYS);
|
|
const pad = n => String(n).padStart(2, '0');
|
|
const expiryStr = `${expiry.getFullYear()}${pad(expiry.getMonth()+1)}${pad(expiry.getDate())}`;
|
|
const expiryDisplay = `${expiryStr.slice(0,4)}-${expiryStr.slice(4,6)}-${expiryStr.slice(6,8)}`;
|
|
|
|
// ─── 生成随机 Token ──────────────────────────────────────
|
|
const token = crypto.randomBytes(12).toString('hex'); // 小写 24位
|
|
const authCode = `BW-${expiryStr}-${token.toUpperCase()}`;
|
|
const fileId = token.slice(0, 8); // 前8位作为文件 ID
|
|
|
|
// ─── 构建待加密内容 ──────────────────────────────────────
|
|
let secretsPlain = fs.readFileSync(SECRETS_TXT, 'utf8').trim();
|
|
|
|
const multiUser = !!relayKey;
|
|
|
|
if (multiUser) {
|
|
// 用中转站 relay key 替换 ANTHROPIC_API_KEY
|
|
if (/^ANTHROPIC_API_KEY=/m.test(secretsPlain)) {
|
|
secretsPlain = secretsPlain.replace(
|
|
/^ANTHROPIC_API_KEY=.*/m,
|
|
`ANTHROPIC_API_KEY=${relayKey}`
|
|
);
|
|
} else {
|
|
secretsPlain = `ANTHROPIC_API_KEY=${relayKey}\n${secretsPlain}`;
|
|
}
|
|
}
|
|
|
|
// ─── 加密并写出 ──────────────────────────────────────────
|
|
const encBuffer = encrypt(secretsPlain, token);
|
|
|
|
let outFileName, outFilePath;
|
|
if (multiUser) {
|
|
outFileName = `secrets-${fileId}.enc`;
|
|
} else {
|
|
outFileName = 'secrets.enc';
|
|
}
|
|
outFilePath = path.join(SCRIPT_DIR, outFileName);
|
|
fs.writeFileSync(outFilePath, encBuffer);
|
|
|
|
// ─── 输出 ────────────────────────────────────────────────
|
|
const modeLabel = multiUser
|
|
? `多用户独立 Key (${userName || '未命名'})`
|
|
: '共享 Key (单/多用户共享)';
|
|
|
|
console.log('\n═══════════════════════════════════════════════════');
|
|
console.log(' Bookworm 授权码生成完毕');
|
|
console.log('═══════════════════════════════════════════════════');
|
|
console.log('');
|
|
console.log(` 模式: ${modeLabel}`);
|
|
if (userName) console.log(` 用户: ${userName}`);
|
|
console.log(` 授权码: ${authCode}`);
|
|
console.log(` 有效期: ${DAYS} 天 (至 ${expiryDisplay})`);
|
|
if (multiUser) {
|
|
console.log(` Relay Key: ${relayKey.slice(0,12)}... (已替换 ANTHROPIC_API_KEY)`);
|
|
console.log(` 文件 ID: ${fileId} → ${outFileName}`);
|
|
}
|
|
console.log('');
|
|
console.log(' ▶ 操作步骤:');
|
|
console.log(` 1. 将授权码发给用户: ${authCode}`);
|
|
console.log(` 2. 推送 ${outFileName} 到 Gitea:`);
|
|
console.log(` git add ${outFileName} && git commit -m "add user ${userName || fileId}" && git push`);
|
|
console.log('');
|
|
console.log(' ⚠ 安全提醒:');
|
|
console.log(' - 授权码即解密密钥, 请勿通过不安全渠道明文发送');
|
|
console.log(` - 到期日 (${expiryDisplay}) 后自动失效`);
|
|
if (multiUser) {
|
|
console.log(` - 各用户 secrets-XXXXXXXX.enc 独立, 轮换互不影响`);
|
|
}
|
|
console.log('═══════════════════════════════════════════════════\n');
|