Files
labs/oauth/backend/app/routers/users.py
jungwoo choi 6c21809a24 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>
2025-09-05 14:56:02 +09:00

156 lines
4.8 KiB
Python

"""Users management router"""
from fastapi import APIRouter, Depends, HTTPException, status, Query
from typing import List, Optional
from bson import ObjectId
from app.models.user import User, UserUpdate, UserRole
from app.routers.auth import get_current_user
from app.utils.database import get_database
from app.utils.security import hash_password
from datetime import datetime
router = APIRouter(prefix="/users", tags=["Users"])
def require_admin(current_user: User = Depends(get_current_user)) -> User:
"""Require admin role"""
if current_user.role not in [UserRole.SYSTEM_ADMIN, UserRole.GROUP_ADMIN]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions"
)
return current_user
@router.get("/", response_model=List[User])
async def get_users(
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100),
current_user: User = Depends(require_admin)
):
"""Get all users (admin only)"""
db = get_database()
users = await db.users.find().skip(skip).limit(limit).to_list(limit)
return [User(**user) for user in users]
@router.get("/{user_id}", response_model=User)
async def get_user(user_id: str, current_user: User = Depends(get_current_user)):
"""Get user by ID"""
# Users can only view their own profile unless they're admin
if str(current_user.id) != user_id and current_user.role not in [UserRole.SYSTEM_ADMIN, UserRole.GROUP_ADMIN]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions"
)
db = get_database()
try:
user = await db.users.find_one({"_id": ObjectId(user_id)})
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
return User(**user)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid user ID"
)
@router.put("/{user_id}", response_model=User)
async def update_user(
user_id: str,
user_update: UserUpdate,
current_user: User = Depends(get_current_user)
):
"""Update user"""
# Users can only update their own profile unless they're admin
if str(current_user.id) != user_id and current_user.role not in [UserRole.SYSTEM_ADMIN, UserRole.GROUP_ADMIN]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions"
)
# Only system admin can change roles
if user_update.role and current_user.role != UserRole.SYSTEM_ADMIN:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only system admin can change user roles"
)
db = get_database()
try:
# Prepare update data
update_data = user_update.model_dump(exclude_unset=True)
# Hash password if provided
if "password" in update_data:
update_data["hashed_password"] = hash_password(update_data.pop("password"))
# Update timestamp
update_data["updated_at"] = datetime.utcnow()
# Update user
result = await db.users.update_one(
{"_id": ObjectId(user_id)},
{"$set": update_data}
)
if result.modified_count == 0:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
# Return updated user
user = await db.users.find_one({"_id": ObjectId(user_id)})
return User(**user)
except Exception as e:
if isinstance(e, HTTPException):
raise
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
@router.delete("/{user_id}")
async def delete_user(
user_id: str,
current_user: User = Depends(require_admin)
):
"""Delete user (admin only)"""
# Prevent self-deletion
if str(current_user.id) == user_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Cannot delete your own account"
)
db = get_database()
try:
result = await db.users.delete_one({"_id": ObjectId(user_id)})
if result.deleted_count == 0:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
return {"message": "User deleted successfully"}
except Exception as e:
if isinstance(e, HTTPException):
raise
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid user ID"
)