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)] ]