mirror of
https://github.com/Gloridust/WechatOnCloud.git
synced 2026-06-16 19:53:53 +08:00
feat: 超管密码离线找回 + 改密/重置二次确认 + GHCR 手动构建文档
- 密码找回:accounts.json 给用户加 "resetPassword": true(兼容 reset_password), 重启面板即把其密码重置为 PANEL_ADMIN_PASSWORD(默认 wechat)、解禁并清除标记 - 改密/重置密码弹窗新增「再次输入新密码」二次确认:两次不一致则拦截, 避免浏览器自动填充/手误把密码静默设成非预期值导致锁死 - README:补充方式 B(本机 buildx 手动多架构构建推送 GHCR)+ Release 的 latest 注意事项 + 「重置超管密码(离线找回)」操作步骤 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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>`(实例 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] 已重置用户 '<用户名>' 的密码`。
|
||||
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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: ()
|
||||
<form className="card modal" onClick={(e) => e.stopPropagation()} onSubmit={submit}>
|
||||
<h2>重置「{user.username}」的密码</h2>
|
||||
<PasswordInput placeholder="新密码(至少 6 位)" autoComplete="new-password" value={pw} onChange={setPw} />
|
||||
{err && <div className="error">{err}</div>}
|
||||
<PasswordInput placeholder="再次输入新密码" autoComplete="new-password" value={confirm} onChange={setConfirm} />
|
||||
{(mismatch || err) && <div className="error">{mismatch ? '两次输入的新密码不一致' : err}</div>}
|
||||
<div className="modal-actions">
|
||||
<button type="button" className="btn" onClick={onClose}>
|
||||
取消
|
||||
</button>
|
||||
<button className="btn btn-primary" disabled={busy || pw.length < 6}>
|
||||
<button className="btn btn-primary" disabled={busy || pw.length < 6 || pw !== confirm}>
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -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?: (
|
||||
<h2>修改密码</h2>
|
||||
<PasswordInput placeholder="原密码" autoComplete="current-password" value={oldPassword} onChange={setOld} />
|
||||
<PasswordInput placeholder="新密码(至少 6 位)" autoComplete="new-password" value={newPassword} onChange={setNew} />
|
||||
<PasswordInput placeholder="再次输入新密码" autoComplete="new-password" value={confirm} onChange={setConfirm} />
|
||||
{mismatch && <div className="error">两次输入的新密码不一致</div>}
|
||||
{msg && <div className={msg === '修改成功' ? 'ok' : 'error'}>{msg}</div>}
|
||||
<div className="modal-actions">
|
||||
<button type="button" className="btn" onClick={onClose}>
|
||||
取消
|
||||
</button>
|
||||
<button className="btn btn-primary" disabled={busy || !oldPassword || !newPassword}>
|
||||
<button className="btn btn-primary" disabled={!canSubmit}>
|
||||
确定
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user