"""Data Transfer Objects for gnexus-gauth."""
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
@dataclass(frozen=True)
class TokenSet:
"""OAuth token response."""
access_token: str
refresh_token: str | None
token_type: str
expires_in: int
expires_at: datetime | None = None
refresh_expires_in: int | None = None
scopes: list[str] = field(default_factory=list)
raw_payload: dict[str, Any] = field(default_factory=dict, repr=False)
@dataclass(frozen=True)
class ClientAccess:
"""Client-level access data for a user."""
client_id: str
access_status: str = "granted"
role_ids: list[str] = field(default_factory=list)
permission_ids: list[str] = field(default_factory=list)
@dataclass(frozen=True)
class AuthenticatedUser:
"""Normalized authenticated user payload from runtime API."""
user_id: str
email: str
email_verified: bool
system_role: str | None = None
status: str | None = None
profile: dict[str, Any] = field(default_factory=dict)
client_access_list: list[ClientAccess] = field(default_factory=list)
raw_payload: dict[str, Any] = field(default_factory=dict, repr=False)
@property
def avatar_url(self) -> str | None:
"""Direct URL to the user's avatar, or None if not set."""
return self.profile.get("avatar_url")
@property
def avatar_variants(self) -> dict[str, str]:
"""Map of size variant names to their URLs."""
variants = self.profile.get("avatar_variants")
return variants if isinstance(variants, dict) else {}
@dataclass(frozen=True)
class AuthorizationRequest:
"""Authorization URL request with PKCE parameters."""
authorization_url: str
state: str
pkce_verifier: str
pkce_challenge: str
scopes: list[str] = field(default_factory=list)
return_to: str | None = None
@dataclass(frozen=True)
class VerifiedWebhook:
"""Result of successful webhook signature verification."""
raw_body: str
normalized_headers: dict[str, str]
signature_id: str
verified_at: datetime
@dataclass(frozen=True)
class WebhookEvent:
"""Parsed webhook event."""
event_id: str | None
event_type: str
occurred_at: datetime | None = None
target_identifiers: dict[str, Any] = field(default_factory=dict)
actor_identifiers: dict[str, Any] = field(default_factory=dict)
metadata: dict[str, Any] = field(default_factory=dict)
raw_payload: dict[str, Any] = field(default_factory=dict, repr=False)