"""认证审计日志构造与独立失败事务。""" from __future__ import annotations from typing import Any from flask import request from dms.common.enums import AuditAction, AuditResult, AuditTarget from dms.common.request_context import get_request_id from dms.extensions import db from dms.models import AuditLog, User from dms.security.auth_context import get_auth_context def client_ip() -> str | None: forwarded = request.headers.get("X-Forwarded-For", "") value = forwarded.split(",", 1)[0].strip() if forwarded else request.remote_addr return value[:64] if value else None def auth_audit( *, action: AuditAction, result: AuditResult, user: User | None = None, attempted_username: str | None = None, failure_reason: str | None = None, detail: dict[str, Any] | None = None, ) -> AuditLog: username = user.username if user is not None else attempted_username return AuditLog( user_id=user.id if user is not None else None, username=username, real_name=user.real_name if user is not None else None, organization_id=user.organization_id if user is not None else None, organization_name=user.organization_name if user is not None else None, action_type=action.value, target_type=AuditTarget.AUTH.value, target_id=user.id if user is not None else None, target_name=username, operation_result=result.value, failure_reason=failure_reason, client_ip=client_ip(), user_agent=(request.user_agent.string or "")[:500] or None, request_id=get_request_id(), operation_detail=detail, ) def business_audit( *, action: AuditAction, target: AuditTarget, target_id: int, target_name: str, detail: dict[str, Any] | None = None, ) -> AuditLog: """使用请求级认证快照构造修改类业务成功审计。""" context = get_auth_context() return AuditLog( user_id=context.user_id, username=context.username, real_name=context.real_name, organization_id=context.organization_id, organization_name=context.organization_name, action_type=action.value, target_type=target.value, target_id=target_id, target_name=target_name, operation_result=AuditResult.SUCCESS.value, failure_reason=None, client_ip=client_ip(), user_agent=(request.user_agent.string or "")[:500] or None, request_id=get_request_id(), operation_detail=detail, ) def record_login_failure( *, user: User | None, attempted_username: str, failure_reason: str, ) -> None: """在登录主流程之外用短事务持久化失败审计。""" db.session.rollback() try: db.session.add( auth_audit( action=AuditAction.LOGIN, result=AuditResult.FAILURE, user=user, attempted_username=attempted_username, failure_reason=failure_reason, detail={"username": attempted_username}, ) ) db.session.commit() except Exception: db.session.rollback() raise