From eddc35ecaeabcd6e4488ef89e4d4ed3c743d323c Mon Sep 17 00:00:00 2001 From: Gloridust Date: Sun, 31 May 2026 13:29:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B6=85=E7=AE=A1=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E7=A6=BB=E7=BA=BF=E6=89=BE=E5=9B=9E=20+=20=E6=94=B9=E5=AF=86/?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E4=BA=8C=E6=AC=A1=E7=A1=AE=E8=AE=A4=20+=20GH?= =?UTF-8?q?CR=20=E6=89=8B=E5=8A=A8=E6=9E=84=E5=BB=BA=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 密码找回:accounts.json 给用户加 "resetPassword": true(兼容 reset_password), 重启面板即把其密码重置为 PANEL_ADMIN_PASSWORD(默认 wechat)、解禁并清除标记 - 改密/重置密码弹窗新增「再次输入新密码」二次确认:两次不一致则拦截, 避免浏览器自动填充/手误把密码静默设成非预期值导致锁死 - README:补充方式 B(本机 buildx 手动多架构构建推送 GHCR)+ Release 的 latest 注意事项 + 「重置超管密码(离线找回)」操作步骤 Co-Authored-By: Claude Opus 4.8 --- README.md | 25 +++++++++++++++++++++++++ panel/server/src/store.ts | 17 +++++++++++++++++ panel/web/src/pages/Admin.tsx | 11 +++++++++-- panel/web/src/pages/Dashboard.tsx | 12 +++++++++++- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c80993b..cf6f5a7 100644 --- a/README.md +++ b/README.md @@ -282,9 +282,34 @@ docker buildx build --platform linux/amd64,linux/arm64 \ | 过段时间掉登录 | 微信桌面会话会定期失效,需手机重新扫码(见技术方案 6.2) | | 下载 / 更新微信失败 | 腾讯 CDN 偶发波动,重新点「下载并安装 / 更新」即可;脚本已内置主/备 CDN 自动回退 | | 架构不支持报错 | 微信仅提供 x86_64 / arm64;其他架构下载时会在面板状态里报错 | +| 忘记超管密码 | 见下方「重置超管密码」离线找回 | 查看面板日志:`docker logs -f woc-panel`;查看某实例日志:`docker logs -f woc-wx-`(实例 ID 可在面板看到,或 `docker ps | grep woc-wx`)。 +### 重置超管密码(离线找回) + +管理员密码无法被他人重置,忘记时按以下步骤离线找回: + +```bash +docker compose stop panel # 1) 先停面板,避免覆盖你的手动修改 +``` + +2) 编辑 `./data-panel/accounts.json`,给对应用户对象加一行 `"resetPassword": true`: + +```json +{ + "id": "...", "username": "admin", "role": "admin", + "passwordHash": "...", "disabled": false, + "resetPassword": true +} +``` + +```bash +docker compose up -d # 3) 重启,面板启动时会重置该账号 +``` + +重启后该账号密码被重置为 `PANEL_ADMIN_PASSWORD`(即 `.env` 的 `WOC_PASSWORD`,默认 `wechat`),并自动**解禁**、清除该标记;用此密码登录后请立即在「修改密码」改掉。日志会打印 `[store] 已重置用户 '<用户名>' 的密码`。 + --- ## 目录结构 diff --git a/panel/server/src/store.ts b/panel/server/src/store.ts index e17a441..385acd8 100644 --- a/panel/server/src/store.ts +++ b/panel/server/src/store.ts @@ -16,6 +16,10 @@ export interface User { allowedInstances: string[]; // 仍在使用初始默认密码时为 true,前端据此提示尽快改密;任意一次改密/重置后清除。 mustChangePassword?: boolean; + // 离线密码找回:在 accounts.json 手动把某用户置为 true,重启面板即重置其密码并清除此标记。 + // 兼容下划线写法 reset_password。 + resetPassword?: boolean; + reset_password?: boolean; } // 初始默认管理员密码;管理员仍在用它时强烈提示改密。 @@ -87,6 +91,19 @@ export function initStore() { } } } + // 离线密码找回:忘记超管密码时,停掉面板 → 在 accounts.json 给该用户加 "resetPassword": true + // → 重启面板。这里把其密码重置为 PANEL_ADMIN_PASSWORD(默认 wechat)、解禁,并清除标记。 + for (const u of data.users) { + if ((u as any).resetPassword === true || (u as any).reset_password === true) { + const pw = process.env.PANEL_ADMIN_PASSWORD || DEFAULT_ADMIN_PASSWORD; + u.passwordHash = bcrypt.hashSync(pw, 10); + u.mustChangePassword = pw === DEFAULT_ADMIN_PASSWORD; // 重置成默认密码则提示尽快改密 + u.disabled = false; + delete (u as any).resetPassword; + delete (u as any).reset_password; + console.log(`[store] 已重置用户 '${u.username}' 的密码(resetPassword 标记,密码=PANEL_ADMIN_PASSWORD 或默认 wechat)`); + } + } persist(); } diff --git a/panel/web/src/pages/Admin.tsx b/panel/web/src/pages/Admin.tsx index bd5df2b..a00b389 100644 --- a/panel/web/src/pages/Admin.tsx +++ b/panel/web/src/pages/Admin.tsx @@ -309,11 +309,17 @@ function RenameInstance({ inst, onClose, onDone }: { inst: InstanceWithStatus; o function ResetPassword({ user, onClose, onDone }: { user: PanelUser; onClose: () => void; onDone: () => void }) { const [pw, setPw] = useState(''); + const [confirm, setConfirm] = useState(''); const [err, setErr] = useState(''); const [busy, setBusy] = useState(false); + const mismatch = confirm.length > 0 && pw !== confirm; const submit = async (e: React.FormEvent) => { e.preventDefault(); setErr(''); + if (pw !== confirm) { + setErr('两次输入的新密码不一致'); + return; + } setBusy(true); try { await api.resetUser(user.id, pw); @@ -329,12 +335,13 @@ function ResetPassword({ user, onClose, onDone }: { user: PanelUser; onClose: ()
e.stopPropagation()} onSubmit={submit}>

重置「{user.username}」的密码

- {err &&
{err}
} + + {(mismatch || err) &&
{mismatch ? '两次输入的新密码不一致' : err}
}
-
diff --git a/panel/web/src/pages/Dashboard.tsx b/panel/web/src/pages/Dashboard.tsx index e6f9587..a066813 100644 --- a/panel/web/src/pages/Dashboard.tsx +++ b/panel/web/src/pages/Dashboard.tsx @@ -206,12 +206,20 @@ function InstanceCard({ function ChangePassword({ onClose, onSaved }: { onClose: () => void; onSaved?: () => void }) { const [oldPassword, setOld] = useState(''); const [newPassword, setNew] = useState(''); + const [confirm, setConfirm] = useState(''); const [msg, setMsg] = useState(''); const [busy, setBusy] = useState(false); + const mismatch = confirm.length > 0 && newPassword !== confirm; + const canSubmit = !busy && !!oldPassword && newPassword.length >= 6 && newPassword === confirm; + const submit = async (e: React.FormEvent) => { e.preventDefault(); setMsg(''); + if (newPassword !== confirm) { + setMsg('两次输入的新密码不一致'); + return; + } setBusy(true); try { await api.changePassword(oldPassword, newPassword); @@ -231,12 +239,14 @@ function ChangePassword({ onClose, onSaved }: { onClose: () => void; onSaved?: (

修改密码

+ + {mismatch &&
两次输入的新密码不一致
} {msg &&
{msg}
}
-