baodan/dify-main/api/insurance/auth/routes.py

167 lines
6.3 KiB
Python
Raw Normal View History

"""认证模块 Blueprint。4 个接口:企微登录、账密登录、刷新 Token、退出。"""
from datetime import datetime, timezone
import bcrypt
from flask import Blueprint, request
from insurance.utils.auth import decode_token, generate_token, login_required, get_current_user
from insurance.utils.response import success, error
auth_bp = Blueprint("insurance_auth", __name__)
def _get_user_by_username(username: str):
"""从 wecom_user_mapping 按 username 查询用户。"""
from extensions.ext_database import db
from insurance.db.models import WecomUserMapping
return db.session.query(WecomUserMapping).filter_by(username=username).first()
def _get_user_by_wecom_userid(userid: str):
"""从 wecom_user_mapping 按 wecom_userid 查询用户。"""
from extensions.ext_database import db
from insurance.db.models import WecomUserMapping
return db.session.query(WecomUserMapping).filter_by(wecom_userid=userid).first()
def _sign_token(user) -> dict:
"""签发 JWT 并返回标准响应。"""
token = generate_token(
user_id=str(user.id),
username=user.username or "",
role=user.role or "sales",
department=user.department or "",
)
return success({
"token": token,
"expires_in": 7200,
"user": {
"id": str(user.id),
"username": user.username,
"wecom_userid": user.wecom_userid,
"role": user.role,
"department": user.department,
},
})
def _record_login_log(user_id: str, action: str = "login"):
"""记录登录/登出日志。"""
try:
from extensions.ext_database import db
from insurance.db.models import SystemOperationLog
log = SystemOperationLog(
user_id=user_id,
action=action,
ip=request.remote_addr,
user_agent=request.headers.get("User-Agent", "")[:256],
)
db.session.add(log)
db.session.commit()
except Exception:
pass
# ──────────────────────────────────────────────
# A1.1.2 账密登录
# ──────────────────────────────────────────────
@auth_bp.route("/password-login", methods=["POST"])
def password_login():
"""账密登录POST /api/auth/password-login"""
data = request.get_json(force=True, silent=True) or {}
username = (data.get("username") or "").strip()
password = data.get("password") or ""
# 参数校验
if not username or len(username) < 3 or len(username) > 64:
return error(1001, "参数错误:用户名不能为空")
if not password or len(password) < 8 or len(password) > 128:
return error(1001, "参数错误:密码不能为空")
# 查用户
user = _get_user_by_username(username)
if user is None:
return error(1002, "用户名或密码错误")
# 校验密码
if not user.password_hash:
return error(1002, "用户名或密码错误")
if not bcrypt.checkpw(password.encode("utf-8"), user.password_hash.encode("utf-8")):
return error(1002, "用户名或密码错误")
# 检查状态
if user.status == "disabled":
return error(1004, "账号已被禁用,请联系管理员")
_record_login_log(str(user.id), "login")
return _sign_token(user)
# ──────────────────────────────────────────────
# A1.1.3 刷新 Token
# ──────────────────────────────────────────────
@auth_bp.route("/refresh-token", methods=["POST"])
@login_required
def refresh_token():
"""刷新 TokenPOST /api/auth/refresh-token"""
user = get_current_user()
if not user:
return error(1003, "登录已过期,请重新登录")
# 旧 Token 加入 Redis 黑名单
try:
from extensions.ext_redis import redis_client
from insurance.config import REDIS_TOKEN_BLACKLIST_PREFIX
old_token = request.headers.get("Authorization", "")[7:]
redis_client.setex(f"{REDIS_TOKEN_BLACKLIST_PREFIX}{old_token}", 7200, "1")
except Exception:
pass
# 签发新 Token
from extensions.ext_database import db
from insurance.db.models import WecomUserMapping
db_user = db.session.query(WecomUserMapping).filter_by(id=user.get("user_id")).first()
if not db_user:
return error(1003, "登录已过期,请重新登录")
return _sign_token(db_user)
# ──────────────────────────────────────────────
# A1.1.4 退出登录
# ──────────────────────────────────────────────
@auth_bp.route("/logout", methods=["POST"])
@login_required
def logout():
"""退出登录POST /api/auth/logout"""
user = get_current_user()
# Token 加入 Redis 黑名单
try:
from extensions.ext_redis import redis_client
from insurance.config import REDIS_TOKEN_BLACKLIST_PREFIX
token = request.headers.get("Authorization", "")[7:]
redis_client.setex(f"{REDIS_TOKEN_BLACKLIST_PREFIX}{token}", 7200, "1")
except Exception:
pass
if user:
_record_login_log(user.get("user_id", ""), "logout")
return success()
# ──────────────────────────────────────────────
# A1.1.1 企微登录POST供企微 H5 调用)
# ──────────────────────────────────────────────
@auth_bp.route("/wework-login", methods=["POST"])
def wework_login():
"""企微 OAuth 登录POST /api/auth/wework-login"""
data = request.get_json(force=True, silent=True) or {}
code = (data.get("code") or "").strip()
if not code or len(code) > 512:
return error(1001, "参数错误:企微授权码不能为空")
# 调企微 API 换用户信息(需要企微配置就绪后实现)
# 暂时返回占位
return error(3001, "企微 API 配置中,请使用账密登录")