"""인증 모델 (Organization, User, APIKey). Phase 8: 멀티테넌트 지원 """ from dataclasses import dataclass, field from datetime import datetime, UTC from typing import Optional, List import uuid @dataclass class Organization: """조직 (테넌트).""" id: str = field(default_factory=lambda: str(uuid.uuid4())) name: str = "" created_at: datetime = field(default_factory=lambda: datetime.now(UTC)) updated_at: datetime = field(default_factory=lambda: datetime.now(UTC)) is_active: bool = True subscription_tier: str = "free" # "free", "pro", "enterprise" max_users: int = 5 # Free tier 기본값 storage_limit_gb: int = 1 api_quota_monthly: int = 10000 metadata: dict = field(default_factory=dict) def to_dict(self) -> dict: """딕셔너리로 변환.""" return { "id": self.id, "name": self.name, "created_at": self.created_at.isoformat(), "is_active": self.is_active, "subscription_tier": self.subscription_tier, "max_users": self.max_users, "storage_limit_gb": self.storage_limit_gb, "api_quota_monthly": self.api_quota_monthly, } @dataclass class User: """사용자.""" id: str = field(default_factory=lambda: str(uuid.uuid4())) org_id: str = "" # 조직 ID (FK) email: str = "" username: str = "" hashed_password: str = "" role: str = "viewer" # "admin", "editor", "viewer" is_active: bool = True created_at: datetime = field(default_factory=lambda: datetime.now(UTC)) updated_at: datetime = field(default_factory=lambda: datetime.now(UTC)) last_login: Optional[datetime] = None metadata: dict = field(default_factory=dict) def to_dict(self) -> dict: """딕셔너리로 변환.""" return { "id": self.id, "org_id": self.org_id, "email": self.email, "username": self.username, "role": self.role, "is_active": self.is_active, "created_at": self.created_at.isoformat(), "last_login": self.last_login.isoformat() if self.last_login else None, } @dataclass class APIKey: """API 키.""" id: str = field(default_factory=lambda: str(uuid.uuid4())) org_id: str = "" # 조직 ID (FK) key_hash: str = "" # SHA256 해시 (원본은 저장하지 않음) name: str = "" description: str = "" is_active: bool = True created_at: datetime = field(default_factory=lambda: datetime.now(UTC)) last_used: Optional[datetime] = None last_used_ip: Optional[str] = None metadata: dict = field(default_factory=dict) def to_dict(self) -> dict: """딕셔너리로 변환 (해시만 포함).""" return { "id": self.id, "org_id": self.org_id, "name": self.name, "description": self.description, "is_active": self.is_active, "created_at": self.created_at.isoformat(), "last_used": self.last_used.isoformat() if self.last_used else None, } @dataclass class TokenPayload: """JWT 토큰 페이로드.""" user_id: str org_id: str email: str role: str exp: int # Unix timestamp def to_dict(self) -> dict: """딕셔너리로 변환.""" return { "user_id": self.user_id, "org_id": self.org_id, "email": self.email, "role": self.role, "exp": self.exp, } @dataclass class AuthCredentials: """인증 자격증명.""" email: Optional[str] = None password: Optional[str] = None api_key: Optional[str] = None token: Optional[str] = None @dataclass class CurrentUser: """현재 인증된 사용자.""" user_id: str org_id: str email: str username: str role: str is_active: bool def has_permission(self, action: str) -> bool: """사용자가 작업 권한을 가지고 있는지 확인.""" from ont_platform.auth.rbac import RBAC rbac = RBAC() permissions = rbac.get_permissions(self.role) return action in permissions def to_dict(self) -> dict: """딕셔너리로 변환.""" return { "user_id": self.user_id, "org_id": self.org_id, "email": self.email, "username": self.username, "role": self.role, "is_active": self.is_active, }