Files
aiagent/saars/backend/app/admin_login_routes.py
2026-03-07 10:29:17 +08:00

126 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
管理后台登录页:/admin-login表单登录Cookie 保持会话)
"""
from flask import request, redirect, url_for, make_response, Response, current_app
from flask_jwt_extended import create_access_token
from app.models.user import User, Role
from app.utils.auth import check_password
ADMIN_COOKIE_NAME = "admin_token"
ADMIN_COOKIE_MAX_AGE = 86400 * 7 # 7 天
LOGIN_HTML = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>管理后台登录 - Chat Platform Admin</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; background: #f5f5f5; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.card { background: #fff; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,.08); width: 100%; max-width: 360px; }
h1 { margin: 0 0 1.5rem 0; font-size: 1.25rem; color: #333; }
.field { margin-bottom: 1rem; }
label { display: block; margin-bottom: 0.35rem; font-size: 0.875rem; color: #555; }
input[type="text"], input[type="password"] { width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; }
input:focus { outline: none; border-color: #333; }
.error { color: #c00; font-size: 0.875rem; margin-top: 0.5rem; }
button { width: 100%; padding: 0.6rem 1rem; margin-top: 0.5rem; background: #333; color: #fff; border: none; border-radius: 4px; font-size: 1rem; cursor: pointer; }
button:hover { background: #555; }
</style>
</head>
<body>
<div class="card">
<h1>Chat Platform Admin</h1>
<form method="post" action="/admin-login">
<div class="field">
<label for="username">用户名</label>
<input id="username" name="username" type="text" autocomplete="username" required placeholder="admin">
</div>
<div class="field">
<label for="password">密码</label>
<input id="password" name="password" type="password" autocomplete="current-password" required>
</div>
<div class="field">
<button type="submit">登录</button>
</div>
<!-- error will be injected by server -->
</form>
</div>
</body>
</html>
"""
def register_admin_login_routes(flask_app):
"""注册 /admin-login 与 /admin-logout独立路径避免与 Flask-Admin /admin/* 冲突)。"""
@flask_app.route("/admin-login", methods=["GET", "POST"])
def admin_login():
if request.method == "GET":
return Response(LOGIN_HTML, mimetype="text/html; charset=utf-8")
try:
username = (request.form.get("username") or "").strip()
password = request.form.get("password") or ""
if not username or not password:
html = LOGIN_HTML.replace(
"</form>",
'<p class="error">请填写用户名和密码</p></form>',
)
return Response(html, mimetype="text/html; charset=utf-8")
user = User.query.filter_by(username=username).first()
if not user or not check_password(password, user.password_hash):
html = LOGIN_HTML.replace(
"</form>",
'<p class="error">用户名或密码错误</p></form>',
)
return Response(html, mimetype="text/html; charset=utf-8")
if not user.is_active:
html = LOGIN_HTML.replace(
"</form>",
'<p class="error">账号已停用</p></form>',
)
return Response(html, mimetype="text/html; charset=utf-8")
role = Role.query.get(user.role_id) if user.role_id else None
if not role or role.name != "admin":
html = LOGIN_HTML.replace(
"</form>",
'<p class="error">无管理员权限</p></form>',
)
return Response(html, mimetype="text/html; charset=utf-8")
token = create_access_token(identity=user.id)
# 直接重定向到 /admin/,避免 url_for 与 Flask-Admin 版本差异
resp = make_response(redirect("/admin/"))
resp.set_cookie(
ADMIN_COOKIE_NAME,
token,
max_age=ADMIN_COOKIE_MAX_AGE,
path="/admin",
httponly=True,
samesite="Lax",
secure=request.is_secure,
)
return resp
except Exception as e:
current_app.logger.exception("admin_login failed")
err_msg = str(e) if current_app.debug else "登录处理失败,请稍后重试"
html = LOGIN_HTML.replace(
"</form>",
f'<p class="error">{err_msg}</p></form>',
)
return Response(html, mimetype="text/html; charset=utf-8"), 500
@flask_app.route("/admin-logout", methods=["GET"])
def admin_logout():
resp = make_response(redirect(url_for("admin_login")))
resp.delete_cookie(ADMIN_COOKIE_NAME, path="/admin")
return resp
@flask_app.route("/admin/login", methods=["GET"])
def admin_login_legacy():
"""兼容旧链接:/admin/login -> /admin-login"""
return redirect(url_for("admin_login"))