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:
jungwoo choi
2025-09-05 14:56:02 +09:00
parent abdcc31245
commit 6c21809a24
25 changed files with 2012 additions and 45 deletions

View 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