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"))