Files
Task_Reporter/app/modules/auth/services/ad_client.py
egg 3927441103 feat: Add AI report generation with DIFY integration
- Add Users table for display name resolution from AD authentication
- Integrate DIFY AI service for report content generation
- Create docx assembly service with image embedding from MinIO
- Add REST API endpoints for report generation and download
- Add WebSocket notifications for generation progress
- Add frontend UI with progress modal and download functionality
- Add integration tests for report generation flow

Report sections (Traditional Chinese):
- 事件摘要 (Summary)
- 時間軸 (Timeline)
- 參與人員 (Participants)
- 處理過程 (Resolution Process)
- 目前狀態 (Current Status)
- 最終處置結果 (Final Resolution)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 18:32:40 +08:00

112 lines
3.9 KiB
Python

"""AD API client service for authentication
與 Panjit AD API 整合,負責:
- 驗證使用者憑證
- 取得 AD token 和使用者名稱
- 處理 API 連線錯誤
"""
from datetime import datetime, timedelta
import httpx
from typing import Dict
from app.core.config import get_settings
settings = get_settings()
class ADAuthService:
"""Active Directory authentication service"""
def __init__(self):
self.ad_api_url = settings.AD_API_URL
self._client = httpx.AsyncClient(timeout=10.0)
async def authenticate(self, username: str, password: str) -> Dict[str, any]:
"""Authenticate user with AD API
Args:
username: User email (e.g., ymirliu@panjit.com.tw)
password: User password
Returns:
Dict containing:
- token: AD authentication token
- username: Display name from AD
- email: User email address
- office_location: Office location (optional)
- job_title: Job title (optional)
- expires_at: Estimated token expiry datetime
Raises:
httpx.HTTPStatusError: If authentication fails (401, 403)
httpx.RequestError: If AD API is unreachable
"""
payload = {"username": username, "password": password}
try:
response = await self._client.post(
self.ad_api_url, json=payload, headers={"Content-Type": "application/json"}
)
# Raise exception for 4xx/5xx status codes
response.raise_for_status()
data = response.json()
# Extract token and username from response
# Response structure: {"success": true, "data": {"access_token": "...", "userInfo": {"name": "...", "email": "..."}}}
if not data.get("success"):
raise ValueError("Authentication failed")
token_data = data.get("data", {})
ad_token = token_data.get("access_token")
user_info = token_data.get("userInfo", {})
display_name = user_info.get("name") or username
email = user_info.get("email") or username
office_location = user_info.get("officeLocation")
job_title = user_info.get("jobTitle")
if not ad_token:
raise ValueError("No token received from AD API")
# Parse expiry time from response (expiresAt field)
expires_at_str = token_data.get("expiresAt")
if expires_at_str:
# Parse ISO format: "2025-11-16T14:38:37.912Z"
try:
expires_at = datetime.fromisoformat(expires_at_str.replace("Z", "+00:00"))
except:
expires_at = datetime.utcnow() + timedelta(hours=1)
else:
# Fallback: assume 1 hour if not provided
expires_at = datetime.utcnow() + timedelta(hours=1)
return {
"token": ad_token,
"username": display_name,
"email": email,
"office_location": office_location,
"job_title": job_title,
"expires_at": expires_at,
}
except httpx.HTTPStatusError as e:
# Authentication failed (401) or other HTTP errors
if e.response.status_code == 401:
raise ValueError("Invalid credentials") from e
elif e.response.status_code >= 500:
raise ConnectionError("Authentication service error") from e
else:
raise
except httpx.RequestError as e:
# Network error, timeout, etc.
raise ConnectionError("Authentication service unavailable") from e
async def close(self):
"""Close HTTP client"""
await self._client.aclose()
# Singleton instance
ad_auth_service = ADAuthService()