From 5b2dcecaa8028805c2ab528ef7581036574036cb Mon Sep 17 00:00:00 2001 From: bookworm-admin Date: Tue, 30 Jun 2026 00:57:28 +0800 Subject: [PATCH] Path A: strip admin authcode generator from public boot repo Remove gen-authcode.js, admin-authcode-gui.ps1, admin-authcode.ico (authcode-generation source + admin GUI tooling) from the public boot repo. Authcode minting is now server-side only (issuer container /app/admin/gen-authcode.js). build.ps1 -Admin packaging path removed accordingly; Setup.exe path unchanged. HEAD-only removal (git history retained; stripped content carries no plaintext secret, authorization protected by server-side pepper). Co-Authored-By: Claude Opus 4.8 (1M context) --- admin-authcode-gui.ps1 | 482 ----------------------------------------- admin-authcode.ico | Bin 45177 -> 0 bytes build.ps1 | 64 +----- gen-authcode.js | 166 -------------- 4 files changed, 7 insertions(+), 705 deletions(-) delete mode 100644 admin-authcode-gui.ps1 delete mode 100644 admin-authcode.ico delete mode 100644 gen-authcode.js diff --git a/admin-authcode-gui.ps1 b/admin-authcode-gui.ps1 deleted file mode 100644 index c9133e5..0000000 --- a/admin-authcode-gui.ps1 +++ /dev/null @@ -1,482 +0,0 @@ -<# -.SYNOPSIS - Bookworm 授权码生成器 (管理员 GUI 工具) -.DESCRIPTION - 内部调用 node gen-authcode.js, 提供可视化界面生成多用户授权码。 - 打包命令: 见 build.ps1 -Admin -#> - -# 全局错误捕获 (PS2EXE -NoOutput 会吞掉所有错误, 这里确保弹窗显示) -try { - -$ErrorActionPreference = "Stop" -Add-Type -AssemblyName System.Windows.Forms -Add-Type -AssemblyName System.Drawing -[System.Windows.Forms.Application]::EnableVisualStyles() - -# ─── 路径 ───────────────────────────────────────────── -$ScriptDir = if ($PSScriptRoot) { $PSScriptRoot } -elseif ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName -match '\.exe$') { - Split-Path -Parent ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName) -} elseif ($MyInvocation.MyCommand.Path) { - Split-Path -Parent $MyInvocation.MyCommand.Path -} else { $PWD.Path } - -# gen-authcode.js / secrets.txt 查找: 当前目录 → 父目录 (dist/ 内运行时) -$GenScript = Join-Path $ScriptDir "gen-authcode.js" -if (-not (Test-Path $GenScript)) { - $parentDir = Split-Path $ScriptDir -Parent - $GenScript = Join-Path $parentDir "gen-authcode.js" - if (Test-Path $GenScript) { $ScriptDir = $parentDir } # 切到父目录 -} -$SecretsTxt = Join-Path $ScriptDir "secrets.txt" - -# ─── 品牌色 ─────────────────────────────────────────── -$brandBlue = [System.Drawing.Color]::FromArgb(88, 101, 242) -$brandDark = [System.Drawing.Color]::FromArgb(24, 25, 38) -$brandLight = [System.Drawing.Color]::FromArgb(245, 246, 250) -$cardBg = [System.Drawing.Color]::FromArgb(248, 249, 253) -$successGreen = [System.Drawing.Color]::FromArgb(35, 134, 54) -$warningOrange = [System.Drawing.Color]::FromArgb(227, 137, 29) -$textPrimary = [System.Drawing.Color]::FromArgb(36, 41, 47) -$textSecondary = [System.Drawing.Color]::FromArgb(110, 119, 129) -$inputBorder = [System.Drawing.Color]::FromArgb(208, 215, 222) - -# ─── 主窗口 ─────────────────────────────────────────── -$form = New-Object System.Windows.Forms.Form -$form.Text = "Bookworm 授权码生成器" -$form.ClientSize = New-Object System.Drawing.Size(540, 594) -$form.StartPosition = "CenterScreen" -$form.FormBorderStyle = "FixedSingle" -$form.MaximizeBox = $false -$form.BackColor = [System.Drawing.Color]::White -$form.Font = New-Object System.Drawing.Font("Segoe UI", 9) - -# ─── 标题栏 (深色渐变 + 副标题) ────────────────────── -$header = New-Object System.Windows.Forms.Panel -$header.Dock = "Top" -$header.Size = New-Object System.Drawing.Size(540, 70) -$header.BackColor = $brandDark -$form.Controls.Add($header) - -$titleLabel = New-Object System.Windows.Forms.Label -$titleLabel.Location = New-Object System.Drawing.Point(24, 12) -$titleLabel.Size = New-Object System.Drawing.Size(500, 28) -$titleLabel.Text = "Bookworm 授权码生成器" -$titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 15, [System.Drawing.FontStyle]::Bold) -$titleLabel.ForeColor = [System.Drawing.Color]::White -$header.Controls.Add($titleLabel) - -$subtitleLabel = New-Object System.Windows.Forms.Label -$subtitleLabel.Location = New-Object System.Drawing.Point(24, 42) -$subtitleLabel.Size = New-Object System.Drawing.Size(500, 18) -$subtitleLabel.Text = "管理员专用 · 为每位用户生成独立授权码与加密凭证" -$subtitleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 8.5) -$subtitleLabel.ForeColor = [System.Drawing.Color]::FromArgb(160, 170, 200) -$header.Controls.Add($subtitleLabel) - -# ═══ 输入卡片 (Panel 模拟卡片) ═══════════════════════ -$inputCard = New-Object System.Windows.Forms.Panel -$inputCard.Location = New-Object System.Drawing.Point(20, 84) -$inputCard.Size = New-Object System.Drawing.Size(500, 186) -$inputCard.BackColor = $cardBg -$form.Controls.Add($inputCard) - -# ── 卡片标题 -$inputTitle = New-Object System.Windows.Forms.Label -$inputTitle.Location = New-Object System.Drawing.Point(16, 10) -$inputTitle.Size = New-Object System.Drawing.Size(200, 20) -$inputTitle.Text = "用户信息" -$inputTitle.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold) -$inputTitle.ForeColor = $brandBlue -$inputCard.Controls.Add($inputTitle) - -# ── 用户名 -$lblUser = New-Object System.Windows.Forms.Label -$lblUser.Location = New-Object System.Drawing.Point(16, 40) -$lblUser.Size = New-Object System.Drawing.Size(90, 22) -$lblUser.Text = "用户标识" -$lblUser.ForeColor = $textPrimary -$lblUser.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight -$inputCard.Controls.Add($lblUser) -$txtUser = New-Object System.Windows.Forms.TextBox -$txtUser.Location = New-Object System.Drawing.Point(114, 38) -$txtUser.Size = New-Object System.Drawing.Size(370, 26) -$txtUser.Font = New-Object System.Drawing.Font("Segoe UI", 10) -$txtUser.BorderStyle = "FixedSingle" -$inputCard.Controls.Add($txtUser) - -# ── Relay Key -$lblKey = New-Object System.Windows.Forms.Label -$lblKey.Location = New-Object System.Drawing.Point(16, 74) -$lblKey.Size = New-Object System.Drawing.Size(90, 22) -$lblKey.Text = "Relay Key" -$lblKey.ForeColor = $textPrimary -$lblKey.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight -$inputCard.Controls.Add($lblKey) -$txtKey = New-Object System.Windows.Forms.TextBox -$txtKey.Location = New-Object System.Drawing.Point(114, 72) -$txtKey.Size = New-Object System.Drawing.Size(370, 26) -$txtKey.Font = New-Object System.Drawing.Font("Segoe UI", 10) -$txtKey.PasswordChar = '*' -$txtKey.BorderStyle = "FixedSingle" -$inputCard.Controls.Add($txtKey) - -$chkShowKey = New-Object System.Windows.Forms.CheckBox -$chkShowKey.Location = New-Object System.Drawing.Point(114, 102) -$chkShowKey.Size = New-Object System.Drawing.Size(100, 20) -$chkShowKey.Text = "显示 Key" -$chkShowKey.ForeColor = $textSecondary -$chkShowKey.Font = New-Object System.Drawing.Font("Segoe UI", 8) -$chkShowKey.Add_CheckedChanged({ $txtKey.PasswordChar = if ($chkShowKey.Checked) { [char]0 } else { '*' } }) -$inputCard.Controls.Add($chkShowKey) - -# ── 有效期 -$lblDays = New-Object System.Windows.Forms.Label -$lblDays.Location = New-Object System.Drawing.Point(16, 132) -$lblDays.Size = New-Object System.Drawing.Size(90, 22) -$lblDays.Text = "有效期" -$lblDays.ForeColor = $textPrimary -$lblDays.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight -$inputCard.Controls.Add($lblDays) -$cmbDays = New-Object System.Windows.Forms.ComboBox -$cmbDays.Location = New-Object System.Drawing.Point(114, 130) -$cmbDays.Size = New-Object System.Drawing.Size(80, 26) -$cmbDays.DropDownStyle = "DropDownList" -$cmbDays.Font = New-Object System.Drawing.Font("Segoe UI", 10) -$null = $cmbDays.Items.AddRange(@(7, 14, 30, 60, 90, 180, 365)) -$cmbDays.SelectedIndex = 2 -$inputCard.Controls.Add($cmbDays) - -$lblDaysUnit = New-Object System.Windows.Forms.Label -$lblDaysUnit.Location = New-Object System.Drawing.Point(198, 132) -$lblDaysUnit.Size = New-Object System.Drawing.Size(30, 22) -$lblDaysUnit.Text = "天" -$lblDaysUnit.ForeColor = $textSecondary -$inputCard.Controls.Add($lblDaysUnit) - -$lblDaysHint = New-Object System.Windows.Forms.Label -$lblDaysHint.Location = New-Object System.Drawing.Point(240, 132) -$lblDaysHint.Size = New-Object System.Drawing.Size(240, 22) -$lblDaysHint.Text = "" -$lblDaysHint.ForeColor = $textSecondary -$lblDaysHint.Font = New-Object System.Drawing.Font("Segoe UI", 8.5) -$inputCard.Controls.Add($lblDaysHint) -$cmbDays.Add_SelectedIndexChanged({ - $d = [int]$cmbDays.SelectedItem - $lblDaysHint.Text = "-> 到期: " + (Get-Date).AddDays($d).ToString("yyyy-MM-dd") -}) -$cmbDays.SelectedIndex = 2 - -# ── 环境状态行 ── -$nodeOK = [bool](Get-Command node -ErrorAction SilentlyContinue) -$secretsOK = Test-Path $SecretsTxt - -$lblStatus1 = New-Object System.Windows.Forms.Label -$lblStatus1.Location = New-Object System.Drawing.Point(16, 160) -$lblStatus1.Size = New-Object System.Drawing.Size(230, 18) -$lblStatus1.Font = New-Object System.Drawing.Font("Segoe UI", 8) -if ($secretsOK) { - $lineCount = (Get-Content $SecretsTxt -ErrorAction SilentlyContinue | Where-Object { $_ -match '=' }).Count - $lblStatus1.Text = "secrets.txt: $lineCount 个凭证" - $lblStatus1.ForeColor = $successGreen -} else { - $lblStatus1.Text = "secrets.txt: 未找到" - $lblStatus1.ForeColor = [System.Drawing.Color]::Red -} -$inputCard.Controls.Add($lblStatus1) - -$lblStatus2 = New-Object System.Windows.Forms.Label -$lblStatus2.Location = New-Object System.Drawing.Point(250, 160) -$lblStatus2.Size = New-Object System.Drawing.Size(230, 18) -$lblStatus2.Font = New-Object System.Drawing.Font("Segoe UI", 8) -if ($nodeOK) { - $nodeVer = try { (& node --version 2>$null) } catch { "" } - $lblStatus2.Text = "Node.js: $nodeVer" - $lblStatus2.ForeColor = $successGreen -} else { - $lblStatus2.Text = "Node.js: 未安装" - $lblStatus2.ForeColor = [System.Drawing.Color]::Red -} -$inputCard.Controls.Add($lblStatus2) - -# ═══ 操作按钮 ═════════════════════════════════════════ -$btnGenerate = New-Object System.Windows.Forms.Button -$btnGenerate.Location = New-Object System.Drawing.Point(138, 282) -$btnGenerate.Size = New-Object System.Drawing.Size(180, 42) -$btnGenerate.Text = " 生成授权码" -$btnGenerate.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold) -$btnGenerate.FlatStyle = "Flat" -$btnGenerate.BackColor = $brandBlue -$btnGenerate.ForeColor = [System.Drawing.Color]::White -$btnGenerate.FlatAppearance.BorderSize = 0 -$btnGenerate.Cursor = [System.Windows.Forms.Cursors]::Hand -$btnGenerate.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter -$form.Controls.Add($btnGenerate) - -$btnClear = New-Object System.Windows.Forms.Button -$btnClear.Location = New-Object System.Drawing.Point(330, 282) -$btnClear.Size = New-Object System.Drawing.Size(72, 42) -$btnClear.Text = "清空" -$btnClear.Font = New-Object System.Drawing.Font("Segoe UI", 9) -$btnClear.FlatStyle = "Flat" -$btnClear.BackColor = [System.Drawing.Color]::White -$btnClear.ForeColor = $textSecondary -$btnClear.FlatAppearance.BorderColor = $inputBorder -$btnClear.FlatAppearance.BorderSize = 1 -$form.Controls.Add($btnClear) - -# ═══ 结果卡片 ═════════════════════════════════════════ -$resultCard = New-Object System.Windows.Forms.Panel -$resultCard.Location = New-Object System.Drawing.Point(20, 338) -$resultCard.Size = New-Object System.Drawing.Size(500, 220) -$resultCard.BackColor = $cardBg -$form.Controls.Add($resultCard) - -$lblResultTitle = New-Object System.Windows.Forms.Label -$lblResultTitle.Location = New-Object System.Drawing.Point(16, 10) -$lblResultTitle.Size = New-Object System.Drawing.Size(200, 20) -$lblResultTitle.Text = "生成结果" -$lblResultTitle.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold) -$lblResultTitle.ForeColor = $brandBlue -$resultCard.Controls.Add($lblResultTitle) - -$txtAuthCode = New-Object System.Windows.Forms.TextBox -$txtAuthCode.Location = New-Object System.Drawing.Point(16, 38) -$txtAuthCode.Size = New-Object System.Drawing.Size(380, 34) -$txtAuthCode.Font = New-Object System.Drawing.Font("Consolas", 13, [System.Drawing.FontStyle]::Bold) -$txtAuthCode.ReadOnly = $true -$txtAuthCode.BackColor = [System.Drawing.Color]::White -$txtAuthCode.ForeColor = $brandDark -$txtAuthCode.BorderStyle = "FixedSingle" -$txtAuthCode.Text = "" -$resultCard.Controls.Add($txtAuthCode) - -$btnCopy = New-Object System.Windows.Forms.Button -$btnCopy.Location = New-Object System.Drawing.Point(404, 38) -$btnCopy.Size = New-Object System.Drawing.Size(80, 34) -$btnCopy.Text = "复制" -$btnCopy.Font = New-Object System.Drawing.Font("Segoe UI", 9) -$btnCopy.FlatStyle = "Flat" -$btnCopy.BackColor = $brandBlue -$btnCopy.ForeColor = [System.Drawing.Color]::White -$btnCopy.FlatAppearance.BorderSize = 0 -$btnCopy.Cursor = [System.Windows.Forms.Cursors]::Hand -$btnCopy.Enabled = $false -$resultCard.Controls.Add($btnCopy) - -$lblDetails = New-Object System.Windows.Forms.Label -$lblDetails.Location = New-Object System.Drawing.Point(16, 82) -$lblDetails.Size = New-Object System.Drawing.Size(468, 66) -$lblDetails.Text = "点击「生成授权码」后,结果将显示在此处。" -$lblDetails.ForeColor = $textSecondary -$lblDetails.Font = New-Object System.Drawing.Font("Segoe UI", 8.5) -$resultCard.Controls.Add($lblDetails) - -# 推送到 Gitea 按钮 -$btnPush = New-Object System.Windows.Forms.Button -$btnPush.Location = New-Object System.Drawing.Point(16, 156) -$btnPush.Size = New-Object System.Drawing.Size(468, 38) -$btnPush.Text = "推送到 Gitea (git add + commit + push)" -$btnPush.Font = New-Object System.Drawing.Font("Segoe UI", 10) -$btnPush.FlatStyle = "Flat" -$btnPush.BackColor = $successGreen -$btnPush.ForeColor = [System.Drawing.Color]::White -$btnPush.FlatAppearance.BorderSize = 0 -$btnPush.Cursor = [System.Windows.Forms.Cursors]::Hand -$btnPush.Enabled = $false -$resultCard.Controls.Add($btnPush) - -# ═══ 状态栏 ═══════════════════════════════════════════ -$statusBar = New-Object System.Windows.Forms.Label -$statusBar.Location = New-Object System.Drawing.Point(0, 568) -$statusBar.Size = New-Object System.Drawing.Size(540, 26) -$statusBar.BackColor = $brandDark -$statusBar.ForeColor = [System.Drawing.Color]::FromArgb(160, 170, 200) -$statusBar.Text = " 就绪 | $ScriptDir" -$statusBar.Font = New-Object System.Drawing.Font("Segoe UI", 8) -$statusBar.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft -$form.Controls.Add($statusBar) - -# ─── 事件处理 ───────────────────────────────────────── - -$btnCopy.Add_Click({ - if ($txtAuthCode.Text) { - [System.Windows.Forms.Clipboard]::SetText($txtAuthCode.Text) - $statusBar.Text = " 已复制到剪贴板" - $statusBar.ForeColor = $successGreen - } -}) - -$btnPush.Add_Click({ - $btnPush.Enabled = $false - $btnPush.Text = "推送中..." - $statusBar.Text = " git add + commit + push ..." - $statusBar.ForeColor = $warningOrange - [System.Windows.Forms.Application]::DoEvents() - - try { - $gitExe = (Get-Command git -ErrorAction Stop).Source - $userName = if ($global:lastGenUser) { $global:lastGenUser } else { "user" } - - # git add secrets-*.enc - $psi = New-Object System.Diagnostics.ProcessStartInfo - $psi.FileName = $gitExe - $psi.Arguments = "add secrets-*.enc" - $psi.UseShellExecute = $false - $psi.RedirectStandardOutput = $true - $psi.RedirectStandardError = $true - $psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8 - $psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8 - $psi.CreateNoWindow = $true - $psi.WorkingDirectory = $ScriptDir - $p = [System.Diagnostics.Process]::Start($psi) - $p.WaitForExit(15000) - - # git commit - $psi.Arguments = "commit -m `"add user $userName`"" - $p = [System.Diagnostics.Process]::Start($psi) - $p.StandardOutput.ReadToEnd() | Out-Null - $p.WaitForExit(15000) - - # git push - $statusBar.Text = " git push 中 (可能需要几秒)..." - [System.Windows.Forms.Application]::DoEvents() - $psi.Arguments = "push" - $p = [System.Diagnostics.Process]::Start($psi) - $pushOut = $p.StandardError.ReadToEnd() # git push 输出在 stderr - $p.WaitForExit(30000) - - if ($p.ExitCode -eq 0) { - $btnPush.Text = "已推送" - $btnPush.BackColor = [System.Drawing.Color]::FromArgb(200, 220, 200) - $btnPush.ForeColor = $successGreen - $statusBar.Text = " 推送成功 — 现在可以把授权码发给 $userName" - $statusBar.ForeColor = $successGreen - } else { - throw "git push 失败: $pushOut" - } - } catch { - $btnPush.Text = "推送失败 (点击重试)" - $btnPush.BackColor = [System.Drawing.Color]::FromArgb(220, 200, 200) - $btnPush.ForeColor = [System.Drawing.Color]::Red - $btnPush.Enabled = $true - $statusBar.Text = " 推送失败: $_" - $statusBar.ForeColor = [System.Drawing.Color]::Red - [System.Windows.Forms.MessageBox]::Show("推送失败:`n$_`n`n请检查 Git 配置和网络。", "Git 错误", "OK", "Error") - } -}) - -$btnClear.Add_Click({ - $txtUser.Text = "" - $txtKey.Text = "" - $txtAuthCode.Text = "" - $lblDetails.Text = "" - $btnCopy.Enabled = $false - $statusBar.Text = " 已清空" - $statusBar.ForeColor = [System.Drawing.Color]::Gray -}) - -$btnGenerate.Add_Click({ - # 校验 - $user = $txtUser.Text.Trim() - $key = $txtKey.Text.Trim() - $days = $cmbDays.SelectedItem.ToString() - - if (-not $user) { - [System.Windows.Forms.MessageBox]::Show("请输入用户标识", "缺少用户名", "OK", "Warning") - $txtUser.Focus(); return - } - if (-not $key) { - [System.Windows.Forms.MessageBox]::Show("请输入 Relay Sub-Key`n(从中转站后台为用户创建)", "缺少 Sub-Key", "OK", "Warning") - $txtKey.Focus(); return - } - if (-not (Test-Path $SecretsTxt)) { - [System.Windows.Forms.MessageBox]::Show("secrets.txt 不存在:`n$SecretsTxt`n`n请先创建 secrets.txt (每行 KEY=VALUE)", "缺少凭证文件", "OK", "Error") - return - } - if (-not $nodeOK) { - [System.Windows.Forms.MessageBox]::Show("Node.js 未安装。`n请先安装: https://nodejs.org", "缺少 Node.js", "OK", "Error") - return - } - - $statusBar.Text = " 生成中..." - $statusBar.ForeColor = $warningOrange - $btnGenerate.Enabled = $false - [System.Windows.Forms.Application]::DoEvents() - - try { - # 用 Process + UTF8 编码读取 (PS2EXE 默认 GBK 会乱码) - $psi = New-Object System.Diagnostics.ProcessStartInfo - $psi.FileName = (Get-Command node -ErrorAction Stop).Source - $psi.Arguments = "`"$GenScript`" $days -k `"$key`" -u `"$user`"" - $psi.UseShellExecute = $false - $psi.RedirectStandardOutput = $true - $psi.RedirectStandardError = $true - $psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8 - $psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8 - $psi.CreateNoWindow = $true - $psi.WorkingDirectory = $ScriptDir - $proc = [System.Diagnostics.Process]::Start($psi) - $stdout = $proc.StandardOutput.ReadToEnd() - $stderr = $proc.StandardError.ReadToEnd() - $proc.WaitForExit() - - if ($proc.ExitCode -ne 0) { - throw "gen-authcode.js 退出码 $($proc.ExitCode)`n$stderr" - } - - # 解析输出 (编码无关: PS2EXE 下中文可能乱码, 只匹配 ASCII 格式) - $authCode = if ($stdout -match '(BW-\d{8}-[A-F0-9]{24})') { $Matches[1] } else { "" } - $fileId = if ($stdout -match '(secrets-[a-f0-9]{8}\.enc)') { $Matches[1] } else { "" } - $expiry = if ($stdout -match '(\d{4}-\d{2}-\d{2})') { $Matches[1] } else { "" } - - if ($authCode) { - $txtAuthCode.Text = $authCode - $lblDetails.Text = "用户: $user`n加密文件: $fileId`n有效期: $days 天 (至 $expiry)`n路径: $ScriptDir\$fileId" - $btnCopy.Enabled = $true - $btnPush.Enabled = $true - $global:lastGenUser = $user - $global:lastGenFile = $fileId - $statusBar.Text = " 生成成功 — 点击「推送到 Gitea」完成部署" - $statusBar.ForeColor = $successGreen - - # 追加历史记录 (仅本机, 已在 .gitignore) - $historyFile = Join-Path $ScriptDir "authcode-history.log" - $logLine = "$(Get-Date -Format 'yyyy-MM-dd HH:mm') $($user.PadRight(12)) $fileId $($days)天 至$expiry $authCode" - try { Add-Content -Path $historyFile -Value $logLine -Encoding utf8 } catch {} - } else { - throw "无法解析授权码输出:`n$stdout" - } - } catch { - $txtAuthCode.Text = "" - $lblDetails.Text = "错误: $_" - $btnCopy.Enabled = $false - $statusBar.Text = " 生成失败" - $statusBar.ForeColor = [System.Drawing.Color]::Red - [System.Windows.Forms.MessageBox]::Show("生成失败:`n$_", "错误", "OK", "Error") - } finally { - $btnGenerate.Enabled = $true - } -}) - -# ─── 启动 ───────────────────────────────────────────── -$form.Add_Shown({ $txtUser.Focus() }) -[System.Windows.Forms.Application]::Run($form) - -} catch { - # 全局错误弹窗 (PS2EXE 下唯一的错误可见方式) - $errMsg = "Bookworm AuthGen 启动失败:`n`n" + - "错误: $($_.Exception.Message)`n" + - "行号: $($_.InvocationInfo.ScriptLineNumber)`n" + - "代码: $($_.InvocationInfo.Line.Trim())`n`n" + - "ScriptDir: $ScriptDir`n" + - "GenScript: $GenScript`n" + - "SecretsTxt: $SecretsTxt" - try { - [System.Windows.Forms.MessageBox]::Show($errMsg, "AuthGen 错误", "OK", "Error") - } catch { - # 如果连 MsgBox 都失败 (WinForms 未加载), 写文件 - $errMsg | Out-File "$env:TEMP\bookworm-authgen-error.txt" -Encoding utf8 - } -} diff --git a/admin-authcode.ico b/admin-authcode.ico deleted file mode 100644 index 3dfd0d638645e5077b55f12744ee6f7fef991639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45177 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

VW(`AXCxU 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૽W|6xrp6z1up^W&iR)k+# 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 param( - [switch]$Setup, # 只打 Bookworm-Setup.exe - [switch]$Admin # 只打 gen-authcode.exe + [switch]$Setup # 保留参数兼容旧调用; 当前仅产出 Bookworm-Setup.exe ) $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $DistDir = Join-Path $ScriptDir "dist" -# 默认两个都打 -$buildSetup = $Setup -or (-not $Setup -and -not $Admin) -$buildAdmin = $Admin -or (-not $Setup -and -not $Admin) +# 仅产出用户安装器 (admin 授权码工具已剥离, 见 .NOTES) +$buildSetup = $true function Write-Step($msg) { Write-Host "" @@ -148,53 +146,6 @@ if ($buildSetup) { } } -# ════════════════════════════════════════════════════════ -# 2. Bookworm-AuthGen.exe (admin-authcode-gui.ps1 → PS2EXE) -# ════════════════════════════════════════════════════════ -if ($buildAdmin) { - Write-Step "打包 Bookworm-AuthGen.exe (PS2EXE GUI)" - - $inputPs1 = Join-Path $ScriptDir "admin-authcode-gui.ps1" - $outputExe = Join-Path $DistDir "Bookworm-AuthGen.exe" - - if (-not (Test-Path $inputPs1)) { - Write-Fail "找不到 admin-authcode-gui.ps1" - exit 1 - } - - Write-Host " 输入: $inputPs1" -ForegroundColor Gray - Write-Host " 输出: $outputExe" -ForegroundColor Gray - - # 优先用书虫学者图标, 回退到 B 圆 - $adminIcon = Join-Path $ScriptDir "admin-authcode.ico" - if (-not (Test-Path $adminIcon)) { $adminIcon = Join-Path $ScriptDir "bookworm-desktop.ico" } - - $ps2exeArgs = @{ - InputFile = $inputPs1 - OutputFile = $outputExe - Title = "Bookworm AuthCode Generator" - Description = "Bookworm 授权码生成器 (管理员工具)" - Company = "Bookworm" - Version = "1.5.1.0" - NoConsole = $true - NoOutput = $true - NoError = $true - } - if (Test-Path $adminIcon) { - $ps2exeArgs.IconFile = $adminIcon - Write-Host " 图标: $adminIcon" -ForegroundColor Gray - } - Invoke-ps2exe @ps2exeArgs - - if (Test-Path $outputExe) { - $sizeKB = [math]::Round((Get-Item $outputExe).Length / 1KB) - Write-OK "Bookworm-AuthGen.exe 打包完成 (${sizeKB} KB)" - } else { - Write-Fail "Bookworm-AuthGen.exe 打包失败" - exit 1 - } -} - # ════════════════════════════════════════════════════════ # 完成 # ════════════════════════════════════════════════════════ @@ -226,5 +177,4 @@ Get-ChildItem $DistDir | ForEach-Object { Write-Host "" Write-Host " 分发说明:" -ForegroundColor Gray Write-Host " Bookworm-Setup.exe → 用户安装器 (公开下载)" -ForegroundColor Gray -Write-Host " Bookworm-AuthGen.exe → 管理员授权码生成器 (勿对外分发)" -ForegroundColor Gray Write-Host "" diff --git a/gen-authcode.js b/gen-authcode.js deleted file mode 100644 index b3c08e9..0000000 --- a/gen-authcode.js +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env node -'use strict'; -/** - * Bookworm 授权码生成工具 (管理员使用) - * - * 用法: - * node gen-authcode.js [选项] - * - * 选项: - * --relay-key, -k 中转站限额子 Key (替换 ANTHROPIC_API_KEY) - * --user, -u 用户标识 (仅用于显示, 不影响加密) - * [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'); - console.error(' --user, -u 用户标识 (仅显示)'); - 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; } -} - -// pkg 打包后 __filename 指向虚拟快照路径,需用 process.execPath 获取真实目录 -const SCRIPT_DIR = path.dirname( - typeof process.pkg !== 'undefined' ? process.execPath : 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');