Files
site11/services/oauth/backend/models.py
jungwoo choi 1467766f3d Step 8: OAuth 2.0 인증 시스템 및 프로필 기능 구현
- OAuth 2.0 서비스 구현
  * Authorization Code, Client Credentials, Refresh Token 플로우 지원
  * 애플리케이션 등록 및 관리 기능
  * 토큰 introspection 및 revocation
  * SSO 설정 지원 (Google, GitHub, SAML)
  * 실용적인 스코프 시스템 (user, app, org, api 관리)

- 사용자 프로필 기능 확장
  * 프로필 사진 및 썸네일 필드 추가
  * bio, location, website 등 추가 프로필 정보
  * 이메일 인증 및 계정 활성화 상태 관리
  * UserPublicResponse 모델 추가

- OAuth 스코프 관리
  * picture 스코프 추가 (프로필 사진 접근 제어)
  * 카테고리별 스코프 정리 (기본 인증, 사용자 데이터, 앱 관리, 조직, API)
  * 스코프별 승인 필요 여부 설정

- 인프라 개선
  * Users 서비스 포트 매핑 추가 (8001)
  * OAuth 서비스 Docker 구성 (포트 8003)
  * Kafka 이벤트 통합 (USER_CREATED, USER_UPDATED, USER_DELETED)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-10 17:37:16 +09:00

126 lines
5.7 KiB
Python

from beanie import Document, PydanticObjectId
from pydantic import BaseModel, Field, EmailStr
from typing import Optional, List, Dict
from datetime import datetime
from enum import Enum
class GrantType(str, Enum):
AUTHORIZATION_CODE = "authorization_code"
CLIENT_CREDENTIALS = "client_credentials"
PASSWORD = "password"
REFRESH_TOKEN = "refresh_token"
class ResponseType(str, Enum):
CODE = "code"
TOKEN = "token"
class TokenType(str, Enum):
BEARER = "Bearer"
class OAuthApplication(Document):
"""OAuth 2.0 클라이언트 애플리케이션"""
client_id: str = Field(..., unique=True, description="클라이언트 ID")
client_secret: str = Field(..., description="클라이언트 시크릿 (해시됨)")
name: str = Field(..., description="애플리케이션 이름")
description: Optional[str] = Field(None, description="애플리케이션 설명")
owner_id: str = Field(..., description="애플리케이션 소유자 ID")
redirect_uris: List[str] = Field(default_factory=list, description="허용된 리다이렉트 URI들")
allowed_scopes: List[str] = Field(default_factory=list, description="허용된 스코프들")
grant_types: List[GrantType] = Field(default_factory=lambda: [GrantType.AUTHORIZATION_CODE], description="허용된 grant types")
is_active: bool = Field(default=True, description="활성화 상태")
is_trusted: bool = Field(default=False, description="신뢰할 수 있는 앱 (자동 승인)")
# SSO 설정
sso_enabled: bool = Field(default=False, description="SSO 활성화 여부")
sso_provider: Optional[str] = Field(None, description="SSO 제공자 (google, github, saml 등)")
sso_config: Optional[Dict] = Field(default_factory=dict, description="SSO 설정 (provider별 설정)")
allowed_domains: List[str] = Field(default_factory=list, description="SSO 허용 도메인 (예: @company.com)")
website_url: Optional[str] = Field(None, description="애플리케이션 웹사이트")
logo_url: Optional[str] = Field(None, description="애플리케이션 로고 URL")
privacy_policy_url: Optional[str] = Field(None, description="개인정보 처리방침 URL")
terms_url: Optional[str] = Field(None, description="이용약관 URL")
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
class Settings:
collection = "oauth_applications"
class AuthorizationCode(Document):
"""OAuth 2.0 인증 코드"""
code: str = Field(..., unique=True, description="인증 코드")
client_id: str = Field(..., description="클라이언트 ID")
user_id: str = Field(..., description="사용자 ID")
redirect_uri: str = Field(..., description="리다이렉트 URI")
scopes: List[str] = Field(default_factory=list, description="요청된 스코프")
code_challenge: Optional[str] = Field(None, description="PKCE code challenge")
code_challenge_method: Optional[str] = Field(None, description="PKCE challenge method")
expires_at: datetime = Field(..., description="만료 시간")
used: bool = Field(default=False, description="사용 여부")
used_at: Optional[datetime] = Field(None, description="사용 시간")
created_at: datetime = Field(default_factory=datetime.now)
class Settings:
collection = "authorization_codes"
class AccessToken(Document):
"""OAuth 2.0 액세스 토큰"""
token: str = Field(..., unique=True, description="액세스 토큰")
refresh_token: Optional[str] = Field(None, description="리프레시 토큰")
client_id: str = Field(..., description="클라이언트 ID")
user_id: Optional[str] = Field(None, description="사용자 ID (client credentials flow에서는 없음)")
token_type: TokenType = Field(default=TokenType.BEARER)
scopes: List[str] = Field(default_factory=list, description="부여된 스코프")
expires_at: datetime = Field(..., description="액세스 토큰 만료 시간")
refresh_expires_at: Optional[datetime] = Field(None, description="리프레시 토큰 만료 시간")
revoked: bool = Field(default=False, description="폐기 여부")
revoked_at: Optional[datetime] = Field(None, description="폐기 시간")
created_at: datetime = Field(default_factory=datetime.now)
last_used_at: Optional[datetime] = Field(None, description="마지막 사용 시간")
class Settings:
collection = "access_tokens"
class OAuthScope(Document):
"""OAuth 스코프 정의"""
name: str = Field(..., unique=True, description="스코프 이름 (예: read:profile)")
display_name: str = Field(..., description="표시 이름")
description: str = Field(..., description="스코프 설명")
is_default: bool = Field(default=False, description="기본 스코프 여부")
requires_approval: bool = Field(default=True, description="사용자 승인 필요 여부")
created_at: datetime = Field(default_factory=datetime.now)
class Settings:
collection = "oauth_scopes"
class UserConsent(Document):
"""사용자 동의 기록"""
user_id: str = Field(..., description="사용자 ID")
client_id: str = Field(..., description="클라이언트 ID")
granted_scopes: List[str] = Field(default_factory=list, description="승인된 스코프")
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
expires_at: Optional[datetime] = Field(None, description="동의 만료 시간")
class Settings:
collection = "user_consents"
indexes = [
[("user_id", 1), ("client_id", 1)]
]