Coverage for src/lexigram/auth/types.py: 99%
67 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 00:58 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 00:58 +0800
1"""Type definitions for Lexigram Auth."""
3from __future__ import annotations
5from dataclasses import dataclass
6from enum import StrEnum
7from typing import TYPE_CHECKING, Any
9from lexigram.auth.models import AuthToken
10from lexigram.auth.models.user import User
11from lexigram.contracts.auth.roles import RoleDefinition
12from lexigram.domain import DomainModel
13from lexigram.validation import Field
15if TYPE_CHECKING:
16 from lexigram.contracts.core import HealthStatus
19class AuthStatus(StrEnum):
20 """Authentication status values."""
22 AUTHENTICATED = "authenticated"
23 UNAUTHENTICATED = "unauthenticated"
24 TOKEN_EXPIRED = "token_expired" # noqa: S105 # status name, not a credential
25 TOKEN_INVALID = "token_invalid" # noqa: S105 # status name, not a credential
26 USER_INACTIVE = "user_inactive"
27 USER_NOT_VERIFIED = "user_not_verified"
30class TokenType(StrEnum):
31 """Token type values."""
33 BEARER = "Bearer"
34 BASIC = "Basic"
35 API_KEY = "ApiKey"
38class UserStatus(StrEnum):
39 """User account status values."""
41 ACTIVE = "active"
42 INACTIVE = "inactive"
43 SUSPENDED = "suspended"
44 PENDING_VERIFICATION = "pending_verification"
45 DELETED = "deleted"
48@dataclass(init=False)
49class GuardContext(DomainModel):
50 """Context passed to authorization guards."""
52 user: User | None = Field(default=None, description="Authenticated user")
53 request: Any | None = Field(default=None, description="Raw request object")
54 request_context_user_id: str | None = Field(
55 default=None,
56 description="Authenticated user ID from the normalized request context",
57 )
58 route: str | None = Field(default=None, description="Target route name")
59 method: str | None = Field(default=None, description="HTTP method")
60 path: str | None = Field(default=None, description="Request path")
61 headers: dict[str, str] = Field(default_factory=dict, description="Request headers")
62 params: dict[str, Any] = Field(default_factory=dict, description="Route parameters")
65@dataclass(init=False)
66class AuthResult(DomainModel):
67 """Result of an authentication attempt."""
69 success: bool = Field(description="Whether authentication succeeded")
70 status: AuthStatus = Field(description="Status code of the attempt")
71 user: User | None = Field(default=None, description="Authenticated user instance")
72 token: AuthToken | None = Field(default=None, description="Generated auth token")
73 message: str | None = Field(
74 default=None, description="Optional error/status message"
75 )
78@dataclass(init=False)
79class OAuth2UserInfo(DomainModel):
80 """User information returned by an OAuth2 provider."""
82 provider: str = Field(description="OAuth2 provider name")
83 provider_user_id: str = Field(description="Provider-specific user ID")
84 email: str | None = Field(default=None, description="User email")
85 email_verified: bool = Field(
86 default=False,
87 description="Whether the IdP verified the email address",
88 )
89 username: str | None = Field(default=None, description="Provider username")
90 name: str | None = Field(default=None, description="Display name")
91 avatar_url: str | None = Field(default=None, description="Profile image URL")
92 raw_data: dict[str, Any] | None = Field(
93 default=None, description="Original provider response"
94 )
96 def __post_init__(self) -> None:
97 """Populate ``name`` from ``username`` when ``name`` is not supplied."""
98 if self.username and not self.name:
99 self.name = self.username
102@dataclass(init=False)
103class AuthHealthResult(DomainModel):
104 """Health check result for auth components."""
106 status: HealthStatus = Field(description="Overall component health status")
107 message: str = Field(description="Health status message")
108 users_count: int = Field(default=0, description="Number of users in store")
109 components: dict[str, dict[str, Any]] = Field(
110 default_factory=dict, description="Sub-component health details"
111 )
114@dataclass(frozen=True)
115class TokenPair:
116 """A minimal access + refresh token pair.
118 Returned by rotation operations (e.g.
119 :meth:`~lexigram.auth.authn.jwt.JWTTokenManager.refresh_with_rotation`)
120 as a lightweight, immutable carrier for the two new token strings.
122 Attributes:
123 access: The newly issued access token JWT string.
124 refresh: The newly issued refresh token JWT string.
125 """
127 access: str
128 refresh: str
131__all__ = [
132 "AuthHealthResult",
133 "AuthResult",
134 "AuthStatus",
135 "AuthToken",
136 "GuardContext",
137 "OAuth2UserInfo",
138 "RoleDefinition",
139 "TokenPair",
140 "TokenType",
141 "User",
142 "UserStatus",
143]