|
|
@@ -1,7 +1,8 @@
|
|
|
from typing import Optional, List
|
|
|
import json
|
|
|
from datetime import timedelta
|
|
|
-from fastapi import APIRouter, Depends, HTTPException, Body
|
|
|
+import logging
|
|
|
+from fastapi import APIRouter, Depends, HTTPException, Body, Request
|
|
|
from sqlalchemy.orm import Session
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
@@ -29,9 +30,9 @@ from app.services.login_log_service import LoginLogService
|
|
|
from app.services.system_config_service import SystemConfigService
|
|
|
from app.schemas.operation_log import ActionType
|
|
|
from app.schemas.login_log import LoginLogCreate, LoginMethod, AuthType
|
|
|
-from fastapi import Request
|
|
|
|
|
|
router = APIRouter()
|
|
|
+logger = logging.getLogger(__name__)
|
|
|
|
|
|
@router.post("/login", response_model=PasswordLoginResponse, summary="密码登录")
|
|
|
def login_with_password(
|
|
|
@@ -61,15 +62,14 @@ def login_with_password(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "用户未找到"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"平台登录失败: 用户 {req.identifier} 未找到")
|
|
|
raise HTTPException(status_code=404, detail="用户未找到")
|
|
|
|
|
|
log_create.user_id = user.id
|
|
|
|
|
|
is_valid = security.verify_password(req.password, user.password_hash)
|
|
|
if not is_valid:
|
|
|
- import logging
|
|
|
- logger = logging.getLogger(__name__)
|
|
|
- logger.error(f"Platform Login failed for user {user.mobile}")
|
|
|
+ logger.warning(f"平台登录失败: 用户 {user.mobile} 密码错误")
|
|
|
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "密码错误"
|
|
|
@@ -78,6 +78,7 @@ def login_with_password(
|
|
|
raise HTTPException(status_code=401, detail="密码错误")
|
|
|
|
|
|
if user.status != UserStatus.ACTIVE:
|
|
|
+ logger.warning(f"平台登录失败: 用户 {user.mobile} 已被禁用")
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "用户已禁用"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
@@ -95,6 +96,7 @@ def login_with_password(
|
|
|
|
|
|
# Log Success
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.info(f"平台登录成功: 用户 {user.mobile} (ID: {user.id})")
|
|
|
|
|
|
return {
|
|
|
"access_token": access_token,
|
|
|
@@ -117,6 +119,7 @@ def login_with_password(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "应用未找到"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"应用登录失败: 应用ID {req.app_id} 未找到")
|
|
|
raise HTTPException(status_code=404, detail="应用未找到")
|
|
|
|
|
|
# 2. Verify Signature (Optional but recommended for server-side calls)
|
|
|
@@ -132,6 +135,7 @@ def login_with_password(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "签名无效"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"应用登录失败: 应用 {req.app_id} 签名验证失败")
|
|
|
raise HTTPException(status_code=400, detail="签名无效")
|
|
|
|
|
|
# 3. Find User
|
|
|
@@ -158,6 +162,7 @@ def login_with_password(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "用户未找到"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"应用登录失败: 用户 {req.identifier} 在应用 {req.app_id} 中未找到")
|
|
|
raise HTTPException(status_code=404, detail="用户未找到")
|
|
|
|
|
|
log_create.user_id = user.id
|
|
|
@@ -166,16 +171,14 @@ def login_with_password(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "用户已禁用"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"应用登录失败: 用户 {user.mobile} 已被禁用")
|
|
|
raise HTTPException(status_code=400, detail="用户已禁用")
|
|
|
|
|
|
# 4. Verify Password
|
|
|
- import logging
|
|
|
- logger = logging.getLogger(__name__)
|
|
|
-
|
|
|
# DEBUG: Log password verification details
|
|
|
is_valid = security.verify_password(req.password, user.password_hash)
|
|
|
if not is_valid:
|
|
|
- logger.error(f"Password verification failed for user {user.mobile}")
|
|
|
+ logger.warning(f"应用登录失败: 用户 {user.mobile} 密码验证失败 (App: {req.app_id})")
|
|
|
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "密码错误"
|
|
|
@@ -189,6 +192,7 @@ def login_with_password(
|
|
|
# Log Success (AuthType is PASSWORD leading to TICKET generation, keeping PASSWORD is fine or TICKET)
|
|
|
# User requirement: "包括...认证方式". Here the auth method was PASSWORD.
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.info(f"应用登录成功: 用户 {user.mobile} 获取 Ticket (App: {req.app_id})")
|
|
|
|
|
|
return {"ticket": ticket}
|
|
|
|
|
|
@@ -210,6 +214,7 @@ def login_with_sms(
|
|
|
mobile_enabled = SystemConfigService.get_config(db, "sms_login_mobile_enabled")
|
|
|
|
|
|
if pc_enabled != "true" and mobile_enabled != "true":
|
|
|
+ logger.warning("短信登录尝试失败: 短信登录功能未开启")
|
|
|
raise HTTPException(status_code=403, detail="短信登录功能未开启")
|
|
|
|
|
|
# --- Platform Login ---
|
|
|
@@ -231,6 +236,7 @@ def login_with_sms(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "验证码错误或已过期"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"平台短信登录失败: 手机号 {req.mobile} 验证码无效")
|
|
|
raise HTTPException(status_code=400, detail="验证码错误或已过期")
|
|
|
|
|
|
# 2. Find user
|
|
|
@@ -239,6 +245,7 @@ def login_with_sms(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "用户未找到"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"平台短信登录失败: 手机号 {req.mobile} 未注册")
|
|
|
raise HTTPException(status_code=404, detail="用户未找到")
|
|
|
|
|
|
log_create.user_id = user.id
|
|
|
@@ -247,6 +254,7 @@ def login_with_sms(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "用户已禁用"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"平台短信登录失败: 用户 {user.mobile} 已被禁用")
|
|
|
raise HTTPException(status_code=400, detail="用户已禁用")
|
|
|
|
|
|
# 3. Generate JWT Access Token
|
|
|
@@ -261,6 +269,7 @@ def login_with_sms(
|
|
|
|
|
|
# Log Success
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.info(f"平台短信登录成功: 用户 {user.mobile} (ID: {user.id})")
|
|
|
|
|
|
return {
|
|
|
"access_token": access_token,
|
|
|
@@ -283,6 +292,7 @@ def login_with_sms(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "应用未找到"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"应用短信登录失败: 应用ID {req.app_id} 未找到")
|
|
|
raise HTTPException(status_code=404, detail="应用未找到")
|
|
|
|
|
|
# 2. Verify Signature (Optional)
|
|
|
@@ -298,6 +308,7 @@ def login_with_sms(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "签名无效"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"应用短信登录失败: 应用 {req.app_id} 签名无效")
|
|
|
raise HTTPException(status_code=400, detail="签名无效")
|
|
|
|
|
|
# 3. Verify Code
|
|
|
@@ -308,18 +319,17 @@ def login_with_sms(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "验证码错误或已过期"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"应用短信登录失败: 手机号 {req.mobile} 验证码无效")
|
|
|
raise HTTPException(status_code=400, detail="验证码错误或已过期")
|
|
|
|
|
|
# 4. Find User
|
|
|
user = db.query(User).filter(User.mobile == req.mobile, User.is_deleted == 0).first()
|
|
|
|
|
|
- # Check mapping if not found by mobile? No, SMS login implies mobile IS the identity.
|
|
|
- # But maybe the user in UAP has a different mobile? No, SMS is sent to mobile.
|
|
|
-
|
|
|
if not user:
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "用户未找到"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"应用短信登录失败: 手机号 {req.mobile} 未注册")
|
|
|
raise HTTPException(status_code=404, detail="用户未找到")
|
|
|
|
|
|
log_create.user_id = user.id
|
|
|
@@ -328,6 +338,7 @@ def login_with_sms(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "用户已禁用"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"应用短信登录失败: 用户 {user.mobile} 已被禁用")
|
|
|
raise HTTPException(status_code=400, detail="用户已禁用")
|
|
|
|
|
|
# 5. Generate Ticket (Self-Targeting)
|
|
|
@@ -338,6 +349,7 @@ def login_with_sms(
|
|
|
|
|
|
# Log Success
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.info(f"应用短信登录成功: 用户 {user.mobile} 获取 Ticket (App: {req.app_id})")
|
|
|
|
|
|
return {"ticket": ticket}
|
|
|
|
|
|
@@ -361,6 +373,7 @@ def register_user(
|
|
|
|
|
|
existing_user = db.query(User).filter(User.mobile == req.mobile, User.is_deleted == 0).first()
|
|
|
if existing_user:
|
|
|
+ logger.info(f"用户注册失败: 手机号 {req.mobile} 已存在")
|
|
|
raise HTTPException(status_code=400, detail="手机号已注册")
|
|
|
|
|
|
english_name = generate_english_name(req.name)
|
|
|
@@ -376,6 +389,8 @@ def register_user(
|
|
|
db.add(new_user)
|
|
|
db.commit()
|
|
|
db.refresh(new_user)
|
|
|
+
|
|
|
+ logger.info(f"用户注册成功: {req.mobile} (ID: {new_user.id})")
|
|
|
|
|
|
# Auto-login after registration (return token)
|
|
|
access_token = security.create_access_token(new_user.id)
|
|
|
@@ -401,6 +416,7 @@ def admin_reset_password(
|
|
|
|
|
|
# Verify Admin Password
|
|
|
if not security.verify_password(req.admin_password, current_user.password_hash):
|
|
|
+ logger.warning(f"管理员重置密码失败: 管理员 {current_user.mobile} 密码验证错误")
|
|
|
raise HTTPException(status_code=401, detail="管理员密码错误")
|
|
|
|
|
|
target_user = db.query(User).filter(User.id == req.user_id).first()
|
|
|
@@ -423,6 +439,8 @@ def admin_reset_password(
|
|
|
ip_address=get_client_ip(request),
|
|
|
details={}
|
|
|
)
|
|
|
+
|
|
|
+ logger.info(f"管理员重置用户密码成功: 目标用户 {target_user.mobile} (ID: {target_user.id})")
|
|
|
|
|
|
return {"new_password": new_pwd}
|
|
|
|
|
|
@@ -442,10 +460,13 @@ def promote_user(
|
|
|
if not target_user:
|
|
|
raise HTTPException(status_code=404, detail="用户未找到")
|
|
|
|
|
|
+ old_role = target_user.role
|
|
|
target_user.role = req.new_role
|
|
|
db.add(target_user)
|
|
|
db.commit()
|
|
|
|
|
|
+ logger.info(f"用户角色变更: 用户 {target_user.mobile} 从 {old_role} 变更为 {req.new_role} (操作者: {current_user.mobile})")
|
|
|
+
|
|
|
return {"message": "success"}
|
|
|
|
|
|
@router.get("/me/mappings", response_model=MyMappingsResponse, summary="我的映射")
|
|
|
@@ -484,6 +505,7 @@ def change_my_password(
|
|
|
current_user: User = Depends(deps.get_current_active_user),
|
|
|
):
|
|
|
if not security.verify_password(req.old_password, current_user.password_hash):
|
|
|
+ logger.warning(f"用户修改密码失败: 用户 {current_user.mobile} 旧密码验证错误")
|
|
|
raise HTTPException(status_code=400, detail="旧密码错误")
|
|
|
|
|
|
if req.new_password:
|
|
|
@@ -496,6 +518,8 @@ def change_my_password(
|
|
|
db.add(current_user)
|
|
|
db.commit()
|
|
|
|
|
|
+ logger.info(f"用户修改密码成功: {current_user.mobile}")
|
|
|
+
|
|
|
return {"message": "密码修改成功"}
|
|
|
|
|
|
@router.post("/exchange", response_model=TicketExchangeResponse, summary="票据交换")
|
|
|
@@ -509,6 +533,7 @@ def exchange_ticket(
|
|
|
# 1. Verify Source App
|
|
|
source_app = db.query(Application).filter(Application.app_id == req.app_id).first()
|
|
|
if not source_app:
|
|
|
+ logger.warning(f"票据交换失败: 源应用 {req.app_id} 未找到")
|
|
|
raise HTTPException(status_code=404, detail="源应用未找到")
|
|
|
|
|
|
# 2. Verify Signature
|
|
|
@@ -522,6 +547,7 @@ def exchange_ticket(
|
|
|
|
|
|
# Use the stored secret to verify
|
|
|
if not SignatureService.verify_signature(source_app.app_secret, params, req.sign):
|
|
|
+ logger.warning(f"票据交换失败: 源应用 {req.app_id} 签名无效")
|
|
|
raise HTTPException(status_code=400, detail="签名无效")
|
|
|
|
|
|
# 3. Verify User Existence (Optional: Do we trust source app completely? Usually yes if signed.)
|
|
|
@@ -532,6 +558,7 @@ def exchange_ticket(
|
|
|
# If user doesn't exist, we might auto-create OR fail.
|
|
|
# Requirement: "Returns redirect_url".
|
|
|
# For simplicity, if user not found, we cannot map.
|
|
|
+ logger.warning(f"票据交换失败: 用户 {req.user_mobile} 未找到")
|
|
|
raise HTTPException(status_code=404, detail="用户在 UAP 中未找到")
|
|
|
|
|
|
# 4. Generate Ticket for Target App
|
|
|
@@ -541,6 +568,7 @@ def exchange_ticket(
|
|
|
# 5. Get Target App URL
|
|
|
target_app = db.query(Application).filter(Application.app_id == req.target_app_id).first()
|
|
|
if not target_app:
|
|
|
+ logger.warning(f"票据交换失败: 目标应用 {req.target_app_id} 未找到")
|
|
|
raise HTTPException(status_code=404, detail="目标应用未找到")
|
|
|
|
|
|
# Construct redirect URL
|
|
|
@@ -568,6 +596,7 @@ def exchange_ticket(
|
|
|
|
|
|
full_redirect_url = f"{redirect_base}?ticket={ticket}"
|
|
|
|
|
|
+ logger.info(f"票据交换成功: 用户 {req.user_mobile} 从 {req.app_id} -> {req.target_app_id}")
|
|
|
return {
|
|
|
"ticket": ticket,
|
|
|
"redirect_url": full_redirect_url
|
|
|
@@ -604,12 +633,14 @@ def sso_login(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "应用未找到"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"SSO登录失败: 应用 {req.app_id} 未找到")
|
|
|
raise HTTPException(status_code=404, detail="应用未找到")
|
|
|
|
|
|
if app.protocol_type != "SIMPLE_API":
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "协议不支持"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"SSO登录失败: 应用 {req.app_id} 协议类型不支持 ({app.protocol_type})")
|
|
|
raise HTTPException(status_code=400, detail="SSO 登录仅支持简易 API 应用。OIDC 请使用标准流程。")
|
|
|
|
|
|
user = None
|
|
|
@@ -643,12 +674,14 @@ def sso_login(
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "认证失败"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"SSO登录失败: 用户认证失败 (Username: {req.username})")
|
|
|
raise HTTPException(status_code=401, detail="认证失败")
|
|
|
|
|
|
if user.status != "ACTIVE":
|
|
|
log_create.is_success = 0
|
|
|
log_create.failure_reason = "用户已禁用"
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.warning(f"SSO登录失败: 用户 {user.mobile} 已被禁用")
|
|
|
raise HTTPException(status_code=400, detail="用户已禁用")
|
|
|
|
|
|
# 4. Generate Ticket
|
|
|
@@ -656,6 +689,7 @@ def sso_login(
|
|
|
|
|
|
# Log Success
|
|
|
LoginLogService.create_log(db, log_create)
|
|
|
+ logger.info(f"SSO登录成功: 用户 {user.mobile} 获取 Ticket (App: {req.app_id})")
|
|
|
|
|
|
# 5. Get Redirect URL
|
|
|
redirect_base = ""
|
|
|
@@ -672,6 +706,7 @@ def sso_login(
|
|
|
redirect_base = app.redirect_uris.strip()
|
|
|
|
|
|
if not redirect_base:
|
|
|
+ logger.error(f"SSO登录配置错误: 应用 {req.app_id} 未配置回调地址")
|
|
|
raise HTTPException(status_code=400, detail="应用未配置重定向 URI")
|
|
|
|
|
|
full_redirect_url = f"{redirect_base}?ticket={ticket}"
|
|
|
@@ -689,6 +724,7 @@ def validate_ticket(
|
|
|
# 1. Verify App
|
|
|
app = db.query(Application).filter(Application.app_id == req.app_id).first()
|
|
|
if not app:
|
|
|
+ logger.warning(f"票据验证失败: 应用 {req.app_id} 未找到")
|
|
|
raise HTTPException(status_code=404, detail="应用未找到")
|
|
|
|
|
|
# 2. Verify Signature
|
|
|
@@ -699,12 +735,14 @@ def validate_ticket(
|
|
|
"sign": req.sign
|
|
|
}
|
|
|
if not SignatureService.verify_signature(app.app_secret, params, req.sign):
|
|
|
+ logger.warning(f"票据验证失败: 应用 {req.app_id} 签名无效")
|
|
|
raise HTTPException(status_code=400, detail="签名无效")
|
|
|
|
|
|
# 3. Consume Ticket
|
|
|
ticket_data = TicketService.consume_ticket(req.ticket, req.app_id)
|
|
|
|
|
|
if not ticket_data:
|
|
|
+ logger.warning(f"票据验证失败: Ticket 无效或已过期 (App: {req.app_id})")
|
|
|
return {"valid": False}
|
|
|
|
|
|
user_id = ticket_data["user_id"]
|
|
|
@@ -719,6 +757,8 @@ def validate_ticket(
|
|
|
|
|
|
mapped_key = mapping.mapped_key if mapping else None
|
|
|
mapped_email = mapping.mapped_email if mapping else None
|
|
|
+
|
|
|
+ logger.info(f"票据验证成功: 用户 {user.mobile} (App: {req.app_id})")
|
|
|
|
|
|
return {
|
|
|
"valid": True,
|