feat: OAuth 2.0 백엔드 시스템 구현 완료
Phase 1 & 2 완료: - 프로젝트 기본 구조 설정 - Docker Compose 환경 구성 (MongoDB, Redis, Backend, Frontend) - FastAPI 기반 OAuth 2.0 백엔드 구현 주요 기능: - JWT 기반 인증 시스템 - 3단계 권한 체계 (System Admin/Group Admin/User) - 사용자 관리 CRUD API - 애플리케이션 관리 CRUD API - OAuth 2.0 Authorization Code Flow - Refresh Token 관리 - 인증 히스토리 추적 API 엔드포인트: - /auth/* - 인증 관련 (register, login, logout, refresh) - /users/* - 사용자 관리 - /applications/* - 애플리케이션 관리 - /oauth/* - OAuth 2.0 플로우 보안 기능: - bcrypt 비밀번호 해싱 - JWT 토큰 인증 - CORS 설정 - Rate limiting 준비 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
164
oauth/backend/app/routers/auth.py
Normal file
164
oauth/backend/app/routers/auth.py
Normal file
@ -0,0 +1,164 @@
|
||||
"""Authentication router"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from typing import Optional
|
||||
from app.models.user import User, UserLogin, Token, TokenRefresh, UserCreate
|
||||
from app.models.auth_history import AuthAction
|
||||
from app.services.auth_service import AuthService
|
||||
from app.services.token_service import TokenService
|
||||
from app.utils.security import decode_token
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["Authentication"])
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
|
||||
"""Get current authenticated user"""
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
payload = decode_token(token)
|
||||
if payload is None or payload.get("type") != "access":
|
||||
raise credentials_exception
|
||||
|
||||
user = await AuthService.get_user_by_id(payload.get("sub"))
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@router.post("/register", response_model=User)
|
||||
async def register(user_data: UserCreate, request: Request):
|
||||
"""Register a new user"""
|
||||
try:
|
||||
user = await AuthService.create_user(user_data)
|
||||
|
||||
# Log registration
|
||||
await AuthService.log_auth_action(
|
||||
user_id=str(user.id),
|
||||
action=AuthAction.REGISTER,
|
||||
ip_address=request.client.host,
|
||||
user_agent=request.headers.get("User-Agent")
|
||||
)
|
||||
|
||||
return user
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
"""Login with username/email and password"""
|
||||
user = await AuthService.authenticate_user(form_data.username, form_data.password)
|
||||
|
||||
if not user:
|
||||
# Log failed login attempt
|
||||
await AuthService.log_auth_action(
|
||||
user_id="unknown",
|
||||
action=AuthAction.FAILED_LOGIN,
|
||||
ip_address=request.client.host,
|
||||
user_agent=request.headers.get("User-Agent"),
|
||||
result="failed",
|
||||
details={"username": form_data.username}
|
||||
)
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Create tokens
|
||||
tokens = await TokenService.create_tokens(
|
||||
str(user.id),
|
||||
{
|
||||
"username": user.username,
|
||||
"role": user.role,
|
||||
"email": user.email
|
||||
}
|
||||
)
|
||||
|
||||
# Log successful login
|
||||
await AuthService.log_auth_action(
|
||||
user_id=str(user.id),
|
||||
action=AuthAction.LOGIN,
|
||||
ip_address=request.client.host,
|
||||
user_agent=request.headers.get("User-Agent")
|
||||
)
|
||||
|
||||
return tokens
|
||||
|
||||
|
||||
@router.post("/refresh", response_model=Token)
|
||||
async def refresh_token(request: Request, token_data: TokenRefresh):
|
||||
"""Refresh access token using refresh token"""
|
||||
user_id = await TokenService.verify_refresh_token(token_data.refresh_token)
|
||||
|
||||
if not user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token"
|
||||
)
|
||||
|
||||
# Get user
|
||||
user = await AuthService.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="User not found"
|
||||
)
|
||||
|
||||
# Revoke old refresh token
|
||||
await TokenService.revoke_refresh_token(token_data.refresh_token)
|
||||
|
||||
# Create new tokens
|
||||
tokens = await TokenService.create_tokens(
|
||||
str(user.id),
|
||||
{
|
||||
"username": user.username,
|
||||
"role": user.role,
|
||||
"email": user.email
|
||||
}
|
||||
)
|
||||
|
||||
# Log token refresh
|
||||
await AuthService.log_auth_action(
|
||||
user_id=str(user.id),
|
||||
action=AuthAction.TOKEN_REFRESH,
|
||||
ip_address=request.client.host,
|
||||
user_agent=request.headers.get("User-Agent")
|
||||
)
|
||||
|
||||
return tokens
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
async def logout(request: Request, current_user: User = Depends(get_current_user)):
|
||||
"""Logout current user"""
|
||||
# Revoke all user tokens
|
||||
await TokenService.revoke_all_user_tokens(str(current_user.id))
|
||||
|
||||
# Log logout
|
||||
await AuthService.log_auth_action(
|
||||
user_id=str(current_user.id),
|
||||
action=AuthAction.LOGOUT,
|
||||
ip_address=request.client.host,
|
||||
user_agent=request.headers.get("User-Agent")
|
||||
)
|
||||
|
||||
return {"message": "Successfully logged out"}
|
||||
|
||||
|
||||
@router.get("/me", response_model=User)
|
||||
async def get_me(current_user: User = Depends(get_current_user)):
|
||||
"""Get current user information"""
|
||||
return current_user
|
||||
Reference in New Issue
Block a user