feat: Initial commit - Task Reporter incident response system
Complete implementation of the production line incident response system (生產線異常即時反應系統) including: Backend (FastAPI): - User authentication with AD integration and session management - Chat room management (create, list, update, members, roles) - Real-time messaging via WebSocket (typing indicators, reactions) - File storage with MinIO (upload, download, image preview) Frontend (React + Vite): - Authentication flow with token management - Room list with filtering, search, and pagination - Real-time chat interface with WebSocket - File upload with drag-and-drop and image preview - Member management and room settings - Breadcrumb navigation - 53 unit tests (Vitest) Specifications: - authentication: AD auth, sessions, JWT tokens - chat-room: rooms, members, templates - realtime-messaging: WebSocket, messages, reactions - file-storage: MinIO integration, file management - frontend-core: React SPA structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
95
app/modules/auth/router.py
Normal file
95
app/modules/auth/router.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Authentication API endpoints
|
||||
|
||||
提供:
|
||||
- POST /api/auth/login - 使用者登入
|
||||
- POST /api/auth/logout - 使用者登出
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from app.core.database import get_db
|
||||
from app.modules.auth.schemas import LoginRequest, LoginResponse, LogoutResponse, ErrorResponse
|
||||
from app.modules.auth.services.ad_client import ad_auth_service
|
||||
from app.modules.auth.services.encryption import encryption_service
|
||||
from app.modules.auth.services.session_service import session_service
|
||||
from fastapi import Header
|
||||
from typing import Optional
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
||||
|
||||
|
||||
@router.post(
|
||||
"/login",
|
||||
response_model=LoginResponse,
|
||||
responses={
|
||||
401: {"model": ErrorResponse, "description": "Invalid credentials"},
|
||||
503: {"model": ErrorResponse, "description": "Authentication service unavailable"},
|
||||
},
|
||||
)
|
||||
async def login(request: LoginRequest, db: Session = Depends(get_db)):
|
||||
"""使用者登入
|
||||
|
||||
流程:
|
||||
1. 呼叫 AD API 驗證憑證
|
||||
2. 加密密碼(用於自動刷新)
|
||||
3. 生成 internal token (UUID)
|
||||
4. 儲存 session 到資料庫
|
||||
5. 回傳 internal token 和 display_name
|
||||
"""
|
||||
try:
|
||||
# Step 1: Authenticate with AD API
|
||||
ad_result = await ad_auth_service.authenticate(request.username, request.password)
|
||||
|
||||
except ValueError as e:
|
||||
# Invalid credentials
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials"
|
||||
)
|
||||
|
||||
except ConnectionError as e:
|
||||
# AD API unavailable
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Authentication service unavailable",
|
||||
)
|
||||
|
||||
# Step 2: Encrypt password for future auto-refresh
|
||||
encrypted_password = encryption_service.encrypt_password(request.password)
|
||||
|
||||
# Step 3 & 4: Generate internal token and create session
|
||||
user_session = session_service.create_session(
|
||||
db=db,
|
||||
username=request.username,
|
||||
display_name=ad_result["username"],
|
||||
ad_token=ad_result["token"],
|
||||
encrypted_password=encrypted_password,
|
||||
ad_token_expires_at=ad_result["expires_at"],
|
||||
)
|
||||
|
||||
# Step 5: Return internal token to client
|
||||
return LoginResponse(token=user_session.internal_token, display_name=user_session.display_name)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/logout",
|
||||
response_model=LogoutResponse,
|
||||
responses={401: {"model": ErrorResponse, "description": "No authentication token provided"}},
|
||||
)
|
||||
async def logout(authorization: Optional[str] = Header(None), db: Session = Depends(get_db)):
|
||||
"""使用者登出
|
||||
|
||||
刪除 session 記錄,使 token 失效
|
||||
"""
|
||||
if not authorization or not authorization.startswith("Bearer "):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="No authentication token provided"
|
||||
)
|
||||
|
||||
# Extract token
|
||||
internal_token = authorization.replace("Bearer ", "")
|
||||
|
||||
# Find and delete session
|
||||
user_session = session_service.get_session_by_token(db, internal_token)
|
||||
if user_session:
|
||||
session_service.delete_session(db, user_session.id)
|
||||
|
||||
return LogoutResponse(message="Logout successful")
|
||||
Reference in New Issue
Block a user