|
|
@@ -20,7 +20,9 @@ from app.schemas.simple_auth import (
|
|
|
from app.services.signature_service import SignatureService
|
|
|
from app.services.ticket_service import TicketService
|
|
|
from app.services.log_service import LogService
|
|
|
+from app.services.login_log_service import LoginLogService
|
|
|
from app.schemas.operation_log import ActionType
|
|
|
+from app.schemas.login_log import LoginLogCreate, LoginMethod, AuthType
|
|
|
from fastapi import Request
|
|
|
|
|
|
router = APIRouter()
|
|
|
@@ -28,6 +30,7 @@ router = APIRouter()
|
|
|
@router.post("/login", response_model=PasswordLoginResponse, summary="密码登录")
|
|
|
def login_with_password(
|
|
|
req: PasswordLoginRequest,
|
|
|
+ request: Request,
|
|
|
db: Session = Depends(deps.get_db),
|
|
|
):
|
|
|
"""
|
|
|
@@ -37,27 +40,49 @@ def login_with_password(
|
|
|
|
|
|
# --- Platform Login ---
|
|
|
if not req.app_id:
|
|
|
+ # Prepare Log
|
|
|
+ log_create = LoginLogCreate(
|
|
|
+ mobile=req.identifier,
|
|
|
+ ip_address=request.client.host,
|
|
|
+ login_method=LoginMethod.UNIFIED_PAGE,
|
|
|
+ auth_type=AuthType.PASSWORD,
|
|
|
+ user_agent=request.headers.get("user-agent")
|
|
|
+ )
|
|
|
+
|
|
|
# Find user by mobile only
|
|
|
user = db.query(User).filter(User.mobile == req.identifier, User.is_deleted == 0).first()
|
|
|
if not user:
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "用户未找到"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
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.error(f"Input password length: {len(req.password)}")
|
|
|
- logger.error(f"Stored hash: {user.password_hash}")
|
|
|
- if req.password.strip() != req.password:
|
|
|
- logger.error("WARNING: Input password has leading/trailing whitespace!")
|
|
|
+
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "密码错误"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
+
|
|
|
raise HTTPException(status_code=401, detail="密码错误")
|
|
|
|
|
|
if user.status != UserStatus.ACTIVE:
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "用户已禁用"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
raise HTTPException(status_code=400, detail="用户已禁用")
|
|
|
|
|
|
# Generate JWT Access Token
|
|
|
access_token = security.create_access_token(user.id)
|
|
|
+
|
|
|
+ # Log Success
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
+
|
|
|
return {
|
|
|
"access_token": access_token,
|
|
|
"token_type": "bearer",
|
|
|
@@ -65,9 +90,20 @@ def login_with_password(
|
|
|
}
|
|
|
|
|
|
# --- App SSO Login ---
|
|
|
+ log_create = LoginLogCreate(
|
|
|
+ mobile=req.identifier,
|
|
|
+ ip_address=request.client.host,
|
|
|
+ login_method=LoginMethod.CUSTOM_PAGE, # 假设应用自定义页面调用此接口
|
|
|
+ auth_type=AuthType.PASSWORD,
|
|
|
+ user_agent=request.headers.get("user-agent")
|
|
|
+ )
|
|
|
+
|
|
|
# 1. Verify App
|
|
|
app = db.query(Application).filter(Application.app_id == req.app_id).first()
|
|
|
if not app:
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "应用未找到"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
raise HTTPException(status_code=404, detail="应用未找到")
|
|
|
|
|
|
# 2. Verify Signature (Optional but recommended for server-side calls)
|
|
|
@@ -80,6 +116,9 @@ def login_with_password(
|
|
|
"sign": req.sign
|
|
|
}
|
|
|
if not SignatureService.verify_signature(app.app_secret, params, req.sign):
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "签名无效"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
raise HTTPException(status_code=400, detail="签名无效")
|
|
|
|
|
|
# 3. Find User
|
|
|
@@ -103,9 +142,17 @@ def login_with_password(
|
|
|
user = db.query(User).filter(User.id == mapping.user_id, User.is_deleted == 0).first()
|
|
|
|
|
|
if not user:
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "用户未找到"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
raise HTTPException(status_code=404, detail="用户未找到")
|
|
|
|
|
|
+ log_create.user_id = user.id
|
|
|
+
|
|
|
if user.status != UserStatus.ACTIVE:
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "用户已禁用"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
raise HTTPException(status_code=400, detail="用户已禁用")
|
|
|
|
|
|
# 4. Verify Password
|
|
|
@@ -116,17 +163,19 @@ def login_with_password(
|
|
|
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.error(f"Input password length: {len(req.password)}")
|
|
|
- logger.error(f"Stored hash: {user.password_hash}")
|
|
|
- # Check for surrounding whitespace
|
|
|
- if req.password.strip() != req.password:
|
|
|
- logger.error("WARNING: Input password has leading/trailing whitespace!")
|
|
|
-
|
|
|
- if not is_valid:
|
|
|
+
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "密码错误"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
+
|
|
|
raise HTTPException(status_code=401, detail="密码错误")
|
|
|
|
|
|
# 5. Generate Ticket (Self-Targeting)
|
|
|
ticket = TicketService.generate_ticket(user.id, req.app_id)
|
|
|
+
|
|
|
+ # 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)
|
|
|
|
|
|
return {"ticket": ticket}
|
|
|
|
|
|
@@ -356,6 +405,7 @@ def exchange_ticket(
|
|
|
@router.post("/sso-login", response_model=SsoLoginResponse, summary="SSO 登录 (简易模式)")
|
|
|
def sso_login(
|
|
|
req: SsoLoginRequest,
|
|
|
+ request: Request,
|
|
|
db: Session = Depends(deps.get_db),
|
|
|
current_user: Optional[User] = Depends(deps.get_current_active_user_optional),
|
|
|
):
|
|
|
@@ -368,10 +418,26 @@ def sso_login(
|
|
|
"""
|
|
|
# 1. Verify App
|
|
|
app = db.query(Application).filter(Application.app_id == req.app_id).first()
|
|
|
+
|
|
|
+ # Prepare Log
|
|
|
+ log_create = LoginLogCreate(
|
|
|
+ ip_address=request.client.host,
|
|
|
+ login_method=LoginMethod.DIRECT_JUMP,
|
|
|
+ auth_type=AuthType.SSO,
|
|
|
+ user_agent=request.headers.get("user-agent"),
|
|
|
+ mobile=req.username
|
|
|
+ )
|
|
|
+
|
|
|
if not app:
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "应用未找到"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
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)
|
|
|
raise HTTPException(status_code=400, detail="SSO 登录仅支持简易 API 应用。OIDC 请使用标准流程。")
|
|
|
|
|
|
user = None
|
|
|
@@ -379,9 +445,13 @@ def sso_login(
|
|
|
# 2. Try Session Login first
|
|
|
if current_user:
|
|
|
user = current_user
|
|
|
+ log_create.user_id = user.id
|
|
|
+ log_create.mobile = user.mobile
|
|
|
+ log_create.auth_type = AuthType.TOKEN # Used existing session
|
|
|
|
|
|
# 3. If no session, try Credentials Login
|
|
|
if not user and req.username and req.password:
|
|
|
+ log_create.auth_type = AuthType.PASSWORD
|
|
|
# Verify User Credentials
|
|
|
user_query = db.query(User).filter(User.mobile == req.username, User.is_deleted == 0).first()
|
|
|
if not user_query:
|
|
|
@@ -395,16 +465,26 @@ def sso_login(
|
|
|
|
|
|
if user_query and security.verify_password(req.password, user_query.password_hash):
|
|
|
user = user_query
|
|
|
+ log_create.user_id = user.id
|
|
|
|
|
|
if not user:
|
|
|
+ log_create.is_success = 0
|
|
|
+ log_create.failure_reason = "认证失败"
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
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)
|
|
|
raise HTTPException(status_code=400, detail="用户已禁用")
|
|
|
|
|
|
# 4. Generate Ticket
|
|
|
ticket = TicketService.generate_ticket(user.id, req.app_id)
|
|
|
|
|
|
+ # Log Success
|
|
|
+ LoginLogService.create_log(db, log_create)
|
|
|
+
|
|
|
# 5. Get Redirect URL
|
|
|
redirect_base = ""
|
|
|
if app.redirect_uris:
|