You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
116 lines
4.1 KiB
116 lines
4.1 KiB
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
from flask import Blueprint, flash, g, redirect, render_template, request, url_for
|
|
|
|
from app.auth import (
|
|
ALLOWED_ACCOUNT_ROLES,
|
|
ACTIVE_ACCOUNT_STATUS,
|
|
is_authenticated,
|
|
login_account,
|
|
logout_account,
|
|
resolve_post_login_redirect,
|
|
)
|
|
from app.extensions import db
|
|
from app.models import Account
|
|
from app.services import account_snapshot, write_audit_log
|
|
|
|
auth_bp = Blueprint("auth", __name__, url_prefix="/auth")
|
|
|
|
|
|
@auth_bp.route("/login", methods=["GET", "POST"])
|
|
def login():
|
|
if is_authenticated():
|
|
return redirect(resolve_post_login_redirect(g.current_account))
|
|
|
|
if request.method == "POST":
|
|
username = request.form.get("username", "").strip()
|
|
password = request.form.get("password", "")
|
|
|
|
account = db.session.execute(
|
|
db.select(Account).where(Account.username == username),
|
|
).scalar_one_or_none()
|
|
|
|
if account is None or account.role not in ALLOWED_ACCOUNT_ROLES:
|
|
write_audit_log(
|
|
action_type="auth_login_failed",
|
|
target_type="account",
|
|
actor_username=username or None,
|
|
target_display_name=username or None,
|
|
after_data={
|
|
"reason": "invalid_credentials",
|
|
"attempted_username": username,
|
|
},
|
|
)
|
|
flash("用户名或密码错误。", "error")
|
|
return render_template("auth/login.html"), 400
|
|
|
|
if account.status != ACTIVE_ACCOUNT_STATUS:
|
|
write_audit_log(
|
|
action_type="auth_login_disabled",
|
|
target_type="account",
|
|
actor=account,
|
|
target_id=account.id,
|
|
target_display_name=account.display_name or account.username,
|
|
after_data={
|
|
"reason": "account_disabled",
|
|
"account": account_snapshot(account),
|
|
},
|
|
)
|
|
flash("账号已停用,无法登录。", "error")
|
|
return render_template("auth/login.html"), 403
|
|
|
|
if not account.check_password(password):
|
|
write_audit_log(
|
|
action_type="auth_login_failed",
|
|
target_type="account",
|
|
actor=account,
|
|
target_id=account.id,
|
|
target_display_name=account.display_name or account.username,
|
|
after_data={
|
|
"reason": "invalid_credentials",
|
|
"attempted_username": username,
|
|
},
|
|
)
|
|
flash("用户名或密码错误。", "error")
|
|
return render_template("auth/login.html"), 400
|
|
|
|
redirect_target = resolve_post_login_redirect(account)
|
|
account.last_login_at = datetime.now(UTC).replace(tzinfo=None)
|
|
login_account(account)
|
|
write_audit_log(
|
|
action_type="auth_login",
|
|
target_type="account",
|
|
actor=account,
|
|
target_id=account.id,
|
|
target_display_name=account.display_name or account.username,
|
|
after_data={
|
|
"account": account_snapshot(account),
|
|
"redirect_to": redirect_target,
|
|
},
|
|
commit=False,
|
|
)
|
|
db.session.commit()
|
|
flash(f"欢迎回来,{account.display_name or account.username}。", "success")
|
|
return redirect(redirect_target)
|
|
|
|
return render_template("auth/login.html")
|
|
|
|
|
|
@auth_bp.route("/logout", methods=["GET", "POST"])
|
|
def logout():
|
|
current_account = g.current_account if is_authenticated() else None
|
|
write_audit_log(
|
|
action_type="auth_logout",
|
|
target_type="account",
|
|
actor=current_account,
|
|
target_id=current_account.id if current_account is not None else None,
|
|
target_display_name=(current_account.display_name or current_account.username) if current_account is not None else None,
|
|
after_data={
|
|
"account": account_snapshot(current_account),
|
|
},
|
|
)
|
|
logout_account()
|
|
flash("您已退出登录。", "success")
|
|
return redirect(url_for("auth.login"))
|
|
|