Auth codes use format BW-YYYYMMDD-TOKEN (24-hex, 96-bit entropy). Token doubles as the AES-256-CBC decryption key for secrets.enc. Expiry is enforced client-side; format/expiry errors don't consume the 3 valid-attempt quota. - gen-authcode.js: new admin tool — generates BW auth code + re-encrypts secrets.enc - install.ps1: Parse-AuthCode validates format/expiry, Decrypt-Secrets uses token as key - auto-setup.ps1: Show-AuthCodeDialog WinForms input + Parse-AuthCode-GUI loop - Bookworm-Setup.sh: parse_authcode() bash function + while-loop with format/expiry handling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
97 lines
4.1 KiB
JavaScript
97 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
/**
|
|
* Bookworm 授权码生成工具 (管理员使用)
|
|
*
|
|
* 用法:
|
|
* node gen-authcode.js <days> [secrets.txt路径]
|
|
* node gen-authcode.js 30
|
|
* node gen-authcode.js 90 /path/to/secrets.txt
|
|
*
|
|
* 原理:
|
|
* 1. 生成随机 24位Hex Token (96bit 熵)
|
|
* 2. 将 Token 作为密码重新加密 secrets.enc (BWENC1 格式)
|
|
* 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()]);
|
|
// 与 crypto-helper.js 保持 BWENC1 格式兼容
|
|
return Buffer.concat([Buffer.from('BWENC1'), salt, encrypted]);
|
|
}
|
|
|
|
// ─── CLI ────────────────────────────────────────────────────────
|
|
const DAYS = parseInt(process.argv[2]);
|
|
if (!DAYS || DAYS < 1 || DAYS > 3650) {
|
|
console.error('用法: node gen-authcode.js <有效天数> [secrets.txt路径]');
|
|
console.error('示例: node gen-authcode.js 30');
|
|
console.error('示例: node gen-authcode.js 90 /path/to/secrets.txt');
|
|
process.exit(1);
|
|
}
|
|
|
|
const SCRIPT_DIR = path.dirname(path.resolve(__filename));
|
|
const SECRETS_TXT = process.argv[3] || path.join(SCRIPT_DIR, 'secrets.txt');
|
|
const SECRETS_ENC = path.join(SCRIPT_DIR, 'secrets.enc');
|
|
|
|
if (!fs.existsSync(SECRETS_TXT)) {
|
|
console.error(`[错误] 找不到 secrets.txt: ${SECRETS_TXT}`);
|
|
console.error('请先创建 secrets.txt, 每行格式: KEY=VALUE');
|
|
process.exit(1);
|
|
}
|
|
|
|
// 生成到期日 (YYYYMMDD)
|
|
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())}`;
|
|
|
|
// 生成随机 Token (12 bytes = 24 hex, 96bit 熵)
|
|
const token = crypto.randomBytes(12).toString('hex'); // 小写
|
|
|
|
// 构造授权码 (Token 展示为大写, 解密时转小写)
|
|
const authCode = `BW-${expiryStr}-${token.toUpperCase()}`;
|
|
|
|
// 读取并加密 secrets.txt
|
|
const secretsPlain = fs.readFileSync(SECRETS_TXT, 'utf8').trim();
|
|
const encBuffer = encrypt(secretsPlain, token); // 用小写 token 加密
|
|
fs.writeFileSync(SECRETS_ENC, encBuffer);
|
|
|
|
const expiryDisplay = `${expiryStr.slice(0,4)}-${expiryStr.slice(4,6)}-${expiryStr.slice(6,8)}`;
|
|
|
|
console.log('\n═══════════════════════════════════════════════════');
|
|
console.log(' Bookworm 授权码生成完毕');
|
|
console.log('═══════════════════════════════════════════════════');
|
|
console.log('');
|
|
console.log(` 授权码: ${authCode}`);
|
|
console.log(` 有效期: ${DAYS} 天 (至 ${expiryDisplay})`);
|
|
console.log('');
|
|
console.log(' ▶ 操作步骤:');
|
|
console.log(' 1. 将授权码通过微信/邮件发给用户');
|
|
console.log(' 2. 推送新 secrets.enc 到 Gitea:');
|
|
console.log(' git add secrets.enc && git commit -m "update secrets" && git push');
|
|
console.log('');
|
|
console.log(' ⚠ 安全提醒:');
|
|
console.log(' - 授权码即解密密钥, 请勿通过不安全渠道明文发送');
|
|
console.log(` - 到期日 (${expiryDisplay}) 后自动失效, 无需撤销操作`);
|
|
console.log('═══════════════════════════════════════════════════\n');
|