diff --git a/admin/dashboard/blueprints/auth.py b/admin/dashboard/blueprints/auth.py index 4b62d1b..971ea84 100644 --- a/admin/dashboard/blueprints/auth.py +++ b/admin/dashboard/blueprints/auth.py @@ -1,4 +1,4 @@ -from flask import Blueprint, render_template, request, redirect, url_for, session, current_app +from flask import Blueprint, render_template, request, redirect, url_for, session, current_app, jsonify from functools import wraps from loguru import logger @@ -22,13 +22,29 @@ def login_required(f): def login(): error = None if request.method == 'POST': - username = request.form['username'] - password = request.form['password'] + # 使用 strip 规避用户误输入首尾空格导致的误判。 + username = str(request.form.get('username', '') or '').strip() + password = str(request.form.get('password', '') or '') # 从应用上下文获取服务器实例,而不是从蓝图对象 server = current_app.dashboard_server + admin_db = getattr(server, "admin_account_db", None) - if username == server.username and password == server.password: + # 优先使用数据库账号体系鉴权;若不可用则回退旧配置模式,保证兼容存量部署。 + login_ok = False + if admin_db: + try: + login_ok = admin_db.verify_admin_password(username, password) + if login_ok: + admin_db.mark_login_success(username, request.remote_addr or "") + except Exception as e: + logger.error(f"数据库账号登录校验异常,回退配置模式: {e}") + login_ok = False + + if not login_ok: + login_ok = (username == server.username and password == server.password) + + if login_ok: session['logged_in'] = True session['username'] = username # 存储用户名到session logger.debug(f"Login successful. Session after login: {dict(session)}") @@ -45,3 +61,56 @@ def logout(): session.pop('logged_in', None) session.pop('username', None) # 同时删除username return redirect(url_for('auth.login')) + + +@auth_bp.route('/api/auth/change_password', methods=['POST']) +@login_required +def change_password(): + """修改当前登录管理员密码。 + + 前端请求参数: + { + "old_password": "旧密码", + "new_password": "新密码", + "confirm_password": "确认新密码" + } + """ + server = current_app.dashboard_server + admin_db = getattr(server, "admin_account_db", None) + if not admin_db: + return jsonify({"success": False, "error": "账号数据库未初始化,无法修改密码"}), 500 + + payload = request.get_json(silent=True) or {} + old_password = str(payload.get("old_password", "") or "") + new_password = str(payload.get("new_password", "") or "") + confirm_password = str(payload.get("confirm_password", "") or "") + username = str(session.get("username", "") or "").strip() + + if not username: + return jsonify({"success": False, "error": "会话失效,请重新登录"}), 401 + + if not old_password or not new_password or not confirm_password: + return jsonify({"success": False, "error": "请完整填写旧密码与新密码"}), 400 + + if new_password != confirm_password: + return jsonify({"success": False, "error": "两次输入的新密码不一致"}), 400 + + # 密码长度做基础约束,避免过弱口令。 + if len(new_password) < 6: + return jsonify({"success": False, "error": "新密码长度不能少于6位"}), 400 + + if new_password == old_password: + return jsonify({"success": False, "error": "新密码不能与旧密码相同"}), 400 + + try: + if not admin_db.verify_admin_password(username, old_password): + return jsonify({"success": False, "error": "旧密码错误"}), 400 + + updated = admin_db.update_password(username, new_password) + if not updated: + return jsonify({"success": False, "error": "密码更新失败,请稍后重试"}), 500 + + return jsonify({"success": True, "message": "密码修改成功"}) + except Exception as e: + logger.error(f"修改后台密码失败: username={username}, error={e}") + return jsonify({"success": False, "error": "密码修改失败,请检查日志"}), 500 diff --git a/admin/dashboard/server.py b/admin/dashboard/server.py index bfae6c7..e0dcb9d 100644 --- a/admin/dashboard/server.py +++ b/admin/dashboard/server.py @@ -11,6 +11,7 @@ from flask import Flask, send_from_directory from loguru import logger from db.contacts_db import ContactsDBOperator +from db.admin_account_db import AdminAccountDBOperator from db.member_context_db import MemberContextDBOperator from db.message_storage import MessageStorageDB from db.stats_db import StatsDBOperator @@ -46,6 +47,8 @@ class DashboardServer: self.contact_db: ContactsDBOperator = ContactsDBOperator(self.db_manager) self.member_context_db = MemberContextDBOperator(self.db_manager) self.task_db: TaskDBOperator = TaskDBOperator(self.db_manager) + # 后台管理员账号数据层:用于登录鉴权与修改密码。 + self.admin_account_db = AdminAccountDBOperator(self.db_manager) self.system_job_db = robot_instance.system_job_db self.system_job_loader = robot_instance.system_job_loader self.plugin_schedule_db = robot_instance.plugin_schedule_db @@ -63,6 +66,20 @@ class DashboardServer: self.member_context_service = getattr(self.member_context_plugin, "service", None) self.LOG.info("使用Robot实例的对象进行初始化") + + # 初始化后台管理员账号表,并将旧配置中的默认账号平滑迁移进数据库。 + try: + table_ok = self.admin_account_db.init_tables() + if not table_ok: + self.LOG.warning("初始化后台账号表失败,将回退旧配置账号模式") + else: + seed_ok = self.admin_account_db.ensure_default_admin(self.username, self.password, "系统管理员") + if seed_ok: + self.LOG.info("后台账号体系初始化完成(数据库账号模式已可用)") + else: + self.LOG.warning("后台账号种子初始化失败,请检查配置中的默认账号信息") + except Exception as e: + self.LOG.error(f"初始化后台账号体系失败,将回退旧配置账号模式: {e}") else: self.LOG.error("未提供Robot实例,Dashboard无法正常工作") raise ValueError("必须提供Robot实例") diff --git a/admin/dashboard/templates/base.html b/admin/dashboard/templates/base.html index 1b578d9..a006b53 100644 --- a/admin/dashboard/templates/base.html +++ b/admin/dashboard/templates/base.html @@ -224,6 +224,18 @@ transition: all .18s ease !important; } + .account-btn { + color: var(--text-soft) !important; + padding: 10px 14px !important; + border-radius: 999px !important; + transition: all .18s ease !important; + } + + .account-btn:hover { + color: var(--primary) !important; + background: var(--primary-soft) !important; + } + .logout-btn:hover { color: var(--primary) !important; background: var(--primary-soft) !important; @@ -699,6 +711,13 @@ display: none; } } + + .password-dialog-tip { + margin-top: 8px; + color: var(--text-faint); + font-size: 12px; + line-height: 1.7; + } @@ -741,7 +760,10 @@
- 管理员已登录 + {{ session.get('username', '管理员') }} 已登录 +
+ + 修改密码 退出 @@ -785,6 +807,35 @@ + + + + + + + + + + + + + +
+ 提示:修改成功后将立即生效,建议使用强密码(字母、数字、符号组合)。 +
+ + 取消 + 确认修改 + +