Allow changing username and password in settings; admin can delete duplicate accounts.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -387,20 +387,77 @@ def verify_login(username: str, password: str) -> Optional[dict]:
|
||||
|
||||
|
||||
def change_user_password(user_id: int, old_password: str, new_password: str) -> None:
|
||||
change_user_credentials(user_id, old_password, new_username=None, new_password=new_password)
|
||||
|
||||
|
||||
def change_user_credentials(
|
||||
user_id: int,
|
||||
old_password: str,
|
||||
new_username: Optional[str] = None,
|
||||
new_password: Optional[str] = None,
|
||||
) -> dict:
|
||||
"""校验当前密码后,可改用户名和/或密码。返回更新后的用户信息。"""
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
if len(new_password) < 6:
|
||||
new_username = (new_username or "").strip() or None
|
||||
new_password = new_password if new_password else None
|
||||
if new_password is not None and len(new_password) < 6:
|
||||
raise ValueError("新密码至少 6 位")
|
||||
if new_username is not None and len(new_username) < 2:
|
||||
raise ValueError("用户名至少 2 位")
|
||||
|
||||
with get_db() as conn:
|
||||
user = get_user(conn, user_id)
|
||||
if not user:
|
||||
raise ValueError("用户不存在")
|
||||
if not check_password_hash(user["password_hash"], old_password):
|
||||
raise ValueError("当前密码不正确")
|
||||
conn.execute(
|
||||
"UPDATE users SET password_hash=? WHERE id=?",
|
||||
(generate_password_hash(new_password), user_id),
|
||||
)
|
||||
|
||||
username_changed = bool(new_username and new_username != user["username"])
|
||||
password_changed = new_password is not None
|
||||
if not username_changed and not password_changed:
|
||||
raise ValueError("请修改用户名或填写新密码")
|
||||
|
||||
if username_changed:
|
||||
taken = conn.execute(
|
||||
"SELECT id FROM users WHERE username=? AND id!=?",
|
||||
(new_username, user_id),
|
||||
).fetchone()
|
||||
if taken:
|
||||
raise ValueError("用户名已被占用")
|
||||
conn.execute("UPDATE users SET username=? WHERE id=?", (new_username, user_id))
|
||||
|
||||
if password_changed:
|
||||
conn.execute(
|
||||
"UPDATE users SET password_hash=? WHERE id=?",
|
||||
(generate_password_hash(new_password), user_id),
|
||||
)
|
||||
|
||||
# 同步旧 settings 键(兼容)
|
||||
updated = get_user(conn, user_id)
|
||||
if updated and updated.get("role") == "admin":
|
||||
set_setting(conn, "admin_user", updated["username"])
|
||||
if password_changed:
|
||||
set_setting(conn, "admin_password_hash", updated["password_hash"])
|
||||
return updated or user
|
||||
|
||||
|
||||
def delete_account(conn: sqlite3.Connection, account_id: int) -> None:
|
||||
"""删除交易账户;若仍被用户绑定则拒绝。"""
|
||||
bound = conn.execute(
|
||||
"SELECT id, username FROM users WHERE account_id=?", (account_id,)
|
||||
).fetchone()
|
||||
if bound:
|
||||
raise ValueError(f"账户已绑定用户 {bound['username']},请先调整用户绑定")
|
||||
pending = conn.execute(
|
||||
"SELECT COUNT(*) AS c FROM activations WHERE account_id=? AND status='pending'",
|
||||
(account_id,),
|
||||
).fetchone()["c"]
|
||||
if pending:
|
||||
raise ValueError("该账户仍有未使用的激活码,请先作废或用完后再删")
|
||||
if not conn.execute("SELECT id FROM accounts WHERE id=?", (account_id,)).fetchone():
|
||||
raise ValueError("账户不存在")
|
||||
conn.execute("DELETE FROM accounts WHERE id=?", (account_id,))
|
||||
|
||||
|
||||
def create_user(
|
||||
|
||||
@@ -95,9 +95,43 @@ def register_routes(app: Flask) -> None:
|
||||
def settings_page():
|
||||
return render_template("settings.html")
|
||||
|
||||
@app.post("/settings/profile")
|
||||
@login_required
|
||||
def settings_profile():
|
||||
old_pwd = request.form.get("old_password") or ""
|
||||
new_username = (request.form.get("username") or "").strip()
|
||||
new_pwd = request.form.get("new_password") or ""
|
||||
new_pwd2 = request.form.get("new_password2") or ""
|
||||
if new_pwd or new_pwd2:
|
||||
if new_pwd != new_pwd2:
|
||||
flash("两次输入的新密码不一致", "error")
|
||||
return redirect(url_for("settings_page"))
|
||||
else:
|
||||
new_pwd = None
|
||||
try:
|
||||
prev = session.get("username") or ""
|
||||
updated = database.change_user_credentials(
|
||||
int(session["user_id"]),
|
||||
old_pwd,
|
||||
new_username=new_username,
|
||||
new_password=new_pwd,
|
||||
)
|
||||
session["username"] = updated["username"]
|
||||
session["user"] = updated["username"]
|
||||
parts = []
|
||||
if updated["username"] != prev:
|
||||
parts.append("用户名")
|
||||
if new_pwd:
|
||||
parts.append("密码")
|
||||
flash(("、".join(parts) or "资料") + "已更新", "ok")
|
||||
except ValueError as exc:
|
||||
flash(str(exc), "error")
|
||||
return redirect(url_for("settings_page"))
|
||||
|
||||
@app.post("/settings/password")
|
||||
@login_required
|
||||
def settings_password():
|
||||
"""兼容旧表单:仅改密码。"""
|
||||
old_pwd = request.form.get("old_password") or ""
|
||||
new_pwd = request.form.get("new_password") or ""
|
||||
new_pwd2 = request.form.get("new_password2") or ""
|
||||
@@ -216,6 +250,17 @@ def register_routes(app: Flask) -> None:
|
||||
flash("标注已保存", "ok")
|
||||
return redirect(url_for("accounts_page"))
|
||||
|
||||
@app.post("/accounts/<int:aid>/delete")
|
||||
@admin_required
|
||||
def accounts_delete(aid: int):
|
||||
try:
|
||||
with database.get_db() as conn:
|
||||
database.delete_account(conn, aid)
|
||||
flash("账户已删除", "ok")
|
||||
except ValueError as exc:
|
||||
flash(str(exc), "error")
|
||||
return redirect(url_for("accounts_page"))
|
||||
|
||||
@app.get("/machines")
|
||||
@login_required
|
||||
def machines_page():
|
||||
|
||||
@@ -39,7 +39,15 @@
|
||||
<button class="btn secondary" style="margin-top:6px" type="submit">保存</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>#{{ a.id }}</td>
|
||||
<td>
|
||||
#{{ a.id }}
|
||||
{% if can_create %}
|
||||
<form method="post" action="{{ url_for('accounts_delete', aid=a.id) }}" style="margin-top:8px"
|
||||
onsubmit="return confirm('确认删除账户 #{{ a.id }} {{ a.name }}?');">
|
||||
<button class="btn secondary" type="submit">删除</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<form method="post">
|
||||
<div class="form-row">
|
||||
<label>用户名</label>
|
||||
<input name="username" autocomplete="username" value="admin" required>
|
||||
<input name="username" autocomplete="username" placeholder="用户名" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>密码</label>
|
||||
|
||||
@@ -4,29 +4,34 @@
|
||||
<h2 style="margin:0 0 14px">系统设置</h2>
|
||||
<div class="grid cols-2">
|
||||
<div class="card">
|
||||
<h3>修改登录密码</h3>
|
||||
<p class="muted">修改当前登录用户密码,立即生效。管理员请在「用户」页创建普通用户。</p>
|
||||
<form method="post" action="{{ url_for('settings_password') }}" style="margin-top:12px">
|
||||
<h3>修改登录账号</h3>
|
||||
<p class="muted">可改用户名和/或密码;须验证当前密码。立即生效。</p>
|
||||
<form method="post" action="{{ url_for('settings_profile') }}" style="margin-top:12px">
|
||||
<input type="hidden" name="_prev_username" value="{{ current_user.username }}">
|
||||
<div class="form-row">
|
||||
<label>当前密码</label>
|
||||
<label>用户名</label>
|
||||
<input name="username" value="{{ current_user.username }}" required minlength="2" autocomplete="username">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>当前密码(必填)</label>
|
||||
<input name="old_password" type="password" autocomplete="current-password" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>新密码(至少 6 位)</label>
|
||||
<input name="new_password" type="password" autocomplete="new-password" required minlength="6">
|
||||
<label>新密码(不改请留空,至少 6 位)</label>
|
||||
<input name="new_password" type="password" autocomplete="new-password" minlength="6">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>确认新密码</label>
|
||||
<input name="new_password2" type="password" autocomplete="new-password" required minlength="6">
|
||||
<input name="new_password2" type="password" autocomplete="new-password" minlength="6">
|
||||
</div>
|
||||
<button class="btn" type="submit">保存密码</button>
|
||||
<button class="btn" type="submit">保存</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>服务信息</h3>
|
||||
<p class="muted">对外地址:{{ public_base }}</p>
|
||||
<p class="muted">端口:5800</p>
|
||||
<p class="muted">当前登录用户:{{ session.get('user') }}</p>
|
||||
<p class="muted">当前登录用户:{{ current_user.username }}({{ current_user.role }})</p>
|
||||
<p class="muted" style="margin-top:12px">通用 exe 放置目录:<code>releases/黄金市价辅助.exe</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user