| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- from typing import Any, List
- import logging
- from fastapi import APIRouter, Depends, HTTPException, Query
- from sqlalchemy.orm import Session
- from app.api.v1 import deps
- from app.core import security
- from app.models.user import User
- from app.models.mapping import AppUserMapping
- from app.schemas.token import Token, LoginRequest
- from app.services.hydra_service import hydra_service
- router = APIRouter()
- logger = logging.getLogger(__name__)
- @router.get("/login-request", summary="获取登录请求信息 (OIDC)")
- def get_login_request(challenge: str):
- """
- 从 Hydra 获取登录请求信息。
- 前端调用此接口以检查是否应跳过登录(如果响应中 skip=true)。
- """
- try:
- req = hydra_service.get_login_request(challenge)
- if req.skip:
- logger.info(f"Skipping login for challenge {challenge}, subject: {req.subject}")
- # If Hydra says skip, we just accept it immediately
- return hydra_service.accept_login_request(challenge, subject=req.subject)
- return req
- except Exception as e:
- logger.exception(f"Failed to get login request for challenge: {challenge}")
- raise HTTPException(status_code=400, detail=str(e))
- @router.post("/login/accept", summary="接受登录请求 (OIDC)")
- def accept_login(
- challenge: str,
- login_data: LoginRequest,
- db: Session = Depends(deps.get_db)
- ):
- """
- 用户提交凭据 -> 验证 -> 接受登录请求。
- """
- user = db.query(User).filter(User.mobile == login_data.mobile).first()
- if not user or not security.verify_password(login_data.password, user.password_hash):
- # We don't reject the request immediately to allow retry,
- # but in a strict flow we might. Here we just return 401.
- logger.warning(f"Login failed for user {login_data.mobile}: Invalid credentials")
- raise HTTPException(status_code=401, detail="手机号或密码错误")
-
- if user.status != "ACTIVE":
- logger.warning(f"Login failed for user {login_data.mobile}: User not active")
- raise HTTPException(status_code=400, detail="用户状态不正常")
- try:
- logger.info(f"Accepting login request for user {user.mobile} (ID: {user.id}), challenge: {challenge}")
- return hydra_service.accept_login_request(challenge, subject=str(user.id))
- except Exception as e:
- logger.exception(f"Failed to accept login request for challenge: {challenge}")
- raise HTTPException(status_code=500, detail=str(e))
- @router.get("/consent-request", summary="获取同意请求信息 (OIDC)")
- def get_consent_request(
- challenge: str,
- db: Session = Depends(deps.get_db)
- ):
- """
- 获取同意请求信息。
- 通常自动接受内部用户的同意请求,但需要注入 Claims(映射账户)。
- """
- try:
- req = hydra_service.get_consent_request(challenge)
-
- # If skip is true, Hydra remembers consent.
- # But we still might want to refresh claims?
- # For simplicity, if skip, we accept with old scopes.
- if req.skip:
- logger.info(f"Skipping consent for challenge {challenge}, subject: {req.subject}")
- return hydra_service.accept_consent_request(
- challenge,
- grant_scope=req.requested_scope,
- id_token_claims={} # Hydra might use previous session?
- )
-
- # Auto-accept logic:
- # 1. Identify Client (App)
- # 2. Identify User
- # 3. Find Mapping
- client_id = req.client.client_id
- user_id = int(req.subject) # subject is user.id from login
-
- # Find mapping for this specific app (client_id matches app_id in our DB)
- # Note: In Applications table we used 'app_id' string.
- mapping = db.query(AppUserMapping).join(AppUserMapping.application).filter(
- AppUserMapping.user_id == user_id,
- # Assuming client_id in Hydra == app_id in our DB
- AppUserMapping.application.has(app_id=client_id)
- ).first()
- id_token_claims = {}
- if mapping:
- # We inject the mapped key (e.g. email, username) into the ID Token
- # You can standardize the claim name, e.g. "preferred_username" or "ext_id"
- id_token_claims["preferred_username"] = mapping.mapped_key
- id_token_claims["email"] = mapping.mapped_key # If it's email
- logger.info(f"Injecting claims for user {user_id} in client {client_id}: {mapping.mapped_key}")
- else:
- logger.info(f"No mapping found for user {user_id} in client {client_id}, minimal claims injected.")
-
- # Also inject mobile number if needed
- user = db.query(User).filter(User.id == user_id).first()
- if user:
- id_token_claims["phone_number"] = user.mobile
- return hydra_service.accept_consent_request(
- challenge,
- grant_scope=req.requested_scope,
- id_token_claims=id_token_claims
- )
- except Exception as e:
- logger.exception(f"Failed to process consent request for challenge: {challenge}")
- raise HTTPException(status_code=400, detail=str(e))
|