Coverage for src/lexigram/auth/__init__.py: 94%
18 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 12:26 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 12:26 +0800
1"""
2lexigram-auth — Authentication and authorisation for the Lexigram platform.
4Canonical import paths
5-----------------------
6User authentication: from lexigram.auth.authn.services import AuthenticationService
7Token refresh: from lexigram.auth.authn.services import AuthenticationService
8 await service.refresh_token(existing_refresh_token)
9JWT tokens: from lexigram.auth.authn.jwt import JWTTokenManager
10RBAC / authorisation: from lexigram.auth.authz.service import AuthorizationService
11Session management: from lexigram.auth.session import SessionManagerImpl
12OAuth: from lexigram.auth.authn.oauth import OAuthService
13Account verification: from lexigram.auth.authn.verification import AccountVerificationService
15All commonly-used symbols are also re-exported from this root package so
16``from lexigram.auth import JWTTokenManager`` always works.
18Key APIs
19--------
20- ``AuthenticationService.refresh_token(refresh_token: str) -> Result[AuthToken, TokenError]``
21 Refresh an expired access token using a refresh token. Returns new access + refresh tokens.
22- ``AuthenticationService.authenticate(user_id, password) -> Result[AuthToken, AuthenticationFailed]``
23 Authenticate a user with credentials.
24- ``AuthorizationService.authorize(user, required_roles) -> Result[True, AuthorizationFailed]``
25 Enforce RBAC policies.
27GuardProtocol decorators
28----------------
29``use_guards`` is intentionally **not** exported from this root package.
30Use ``lexigram.security.guards.use_guards`` (the canonical, general-purpose
31version backed by ``GuardChain``) or the auth-specific decorators
32``require_auth``, ``require_roles``, and ``require_permissions``.
33"""
35from __future__ import annotations
37import importlib.metadata
38from typing import TYPE_CHECKING, Any
40__path__ = __import__("pkgutil").extend_path(__path__, __name__)
42from lexigram.auth.constants import __version__ as __version__
44# =============================================================================
45# Lazy Loading to Avoid Circular Imports
46# =============================================================================
49if TYPE_CHECKING:
50 from lexigram.auth.authn.api_key import (
51 APIKeyAuthenticator,
52 APIKeyConfig,
53 )
54 from lexigram.auth.authn.google_oauth import GoogleOAuthService
55 from lexigram.auth.authn.jwt import JWTTokenManager
56 from lexigram.auth.authn.revocation import PersistentTokenRevocationStore
57 from lexigram.auth.authn.security import (
58 PasswordHasher,
59 PasswordPolicy,
60 )
61 from lexigram.auth.authn.services import (
62 AuthenticationService,
63 LockoutConfig,
64 LoginAttemptTracker,
65 )
66 from lexigram.auth.authn.user_service import UserService
67 from lexigram.auth.authz.guards import (
68 optional_auth,
69 require_auth,
70 require_permissions,
71 require_roles,
72 )
73 from lexigram.auth.config import (
74 AuthConfig,
75 JWTConfig,
76 RBACConfig,
77 )
78 from lexigram.auth.di import (
79 AuthenticationProvider,
80 AuthorizationProvider,
81 GoogleOAuthProvider,
82 )
83 from lexigram.auth.di.bundle_provider import AuthBundleProvider
84 from lexigram.auth.events import (
85 AuthenticationFailed,
86 PasswordChanged,
87 SessionCreated,
88 SessionRevoked,
89 TokenRevoked,
90 UserAuthenticated,
91 UserLockedOut,
92 UserLoggedIn,
93 UserLoggedOut,
94 UserLoginFailed,
95 UserRegistered,
96 )
97 from lexigram.auth.exceptions import (
98 AlreadyVerifiedError,
99 AuthenticationError,
100 AuthError,
101 AuthorizationError,
102 InvalidTokenError,
103 TokenAudienceError,
104 TokenBlacklistedError,
105 TokenError,
106 TokenExpiredError,
107 TokenExpiredVerificationError,
108 TokenInvalidError,
109 TokenNotFoundError,
110 VerificationError,
111 )
112 from lexigram.auth.models import (
113 AuthToken,
114 User,
115 )
116 from lexigram.auth.protocols import TokenValidatorProtocol
117 from lexigram.auth.session.cookie_backend import SessionCookieBackend
118 from lexigram.auth.storage.oauth_identity_store import (
119 MongoDBOAuthIdentityStore,
120 OAuthIdentity,
121 OAuthIdentityStore,
122 SQLAlchemyOAuthIdentityStore,
123 )
124 from lexigram.auth.types import (
125 AuthResult,
126 AuthStatus,
127 RoleDefinition,
128 UserStatus,
129 )
130 from lexigram.contracts.auth import (
131 IdentityResolverProtocol,
132 )
134_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
135 # Session cookie backend
136 "SessionCookieBackend": (
137 "lexigram.auth.session.cookie_backend",
138 "SessionCookieBackend",
139 ),
140 # Module
141 "AuthModule": ("lexigram.auth.module", "AuthModule"),
142 # Config
143 "AuthConfig": ("lexigram.auth.config", "AuthConfig"),
144 "JWTConfig": ("lexigram.auth.config", "JWTConfig"),
145 "RBACConfig": ("lexigram.auth.config", "RBACConfig"),
146 # Providers
147 "AuthBundleProvider": ("lexigram.auth.di.bundle_provider", "AuthBundleProvider"),
148 "AuthenticationProvider": ("lexigram.auth.di", "AuthenticationProvider"),
149 "AuthorizationProvider": ("lexigram.auth.di", "AuthorizationProvider"),
150 "GoogleOAuthProvider": ("lexigram.auth.di", "GoogleOAuthProvider"),
151 # Services
152 "AuthenticationService": ("lexigram.auth.authn.services", "AuthenticationService"),
153 "LoginAttemptTracker": ("lexigram.auth.authn.services", "LoginAttemptTracker"),
154 "LockoutConfig": ("lexigram.auth.authn.services", "LockoutConfig"),
155 "UserService": ("lexigram.auth.authn.user_service", "UserService"),
156 # API Key AuthenticatorProtocol
157 "APIKeyAuthenticator": ("lexigram.auth.authn.api_key", "APIKeyAuthenticator"),
158 "APIKeyConfig": ("lexigram.auth.authn.api_key", "APIKeyConfig"),
159 # Core Security
160 "JWTTokenManager": ("lexigram.auth.authn.jwt", "JWTTokenManager"),
161 "GoogleOAuthService": ("lexigram.auth.authn.google_oauth", "GoogleOAuthService"),
162 "PasswordHasher": ("lexigram.auth.authn.security", "PasswordHasher"),
163 "PasswordPolicy": ("lexigram.auth.authn.security", "PasswordPolicy"),
164 # Token Revocation
165 "PersistentTokenRevocationStore": (
166 "lexigram.auth.authn.revocation",
167 "PersistentTokenRevocationStore",
168 ),
169 # Types
170 "User": ("lexigram.auth.models", "User"),
171 "AuthToken": ("lexigram.auth.models", "AuthToken"),
172 "AuthResult": ("lexigram.auth.types", "AuthResult"),
173 "AuthStatus": ("lexigram.auth.types", "AuthStatus"),
174 "UserStatus": ("lexigram.auth.types", "UserStatus"),
175 "RoleDefinition": ("lexigram.auth.types", "RoleDefinition"),
176 # OAuth Identity Store
177 "OAuthIdentityStore": (
178 "lexigram.auth.storage.oauth_identity_store",
179 "OAuthIdentityStore",
180 ),
181 "OAuthIdentity": ("lexigram.auth.storage.oauth_identity_store", "OAuthIdentity"),
182 "SQLAlchemyOAuthIdentityStore": (
183 "lexigram.auth.storage.oauth_identity_store",
184 "SQLAlchemyOAuthIdentityStore",
185 ),
186 "MongoDBOAuthIdentityStore": (
187 "lexigram.auth.storage.oauth_identity_store",
188 "MongoDBOAuthIdentityStore",
189 ),
190 # Protocols
191 "IdentityResolverProtocol": (
192 "lexigram.contracts.auth",
193 "IdentityResolverProtocol",
194 ),
195 "OAuthIdentityStoreProtocol": (
196 "lexigram.contracts.auth",
197 "OAuthIdentityStoreProtocol",
198 ),
199 "TokenValidatorProtocol": (
200 "lexigram.auth.protocols",
201 "TokenValidatorProtocol",
202 ),
203 # Exceptions (all from single canonical exceptions.py)
204 "AuthError": ("lexigram.auth.exceptions", "AuthError"),
205 "AuthenticationError": ("lexigram.auth.exceptions", "AuthenticationError"),
206 "AuthorizationError": ("lexigram.auth.exceptions", "AuthorizationError"),
207 "TokenError": ("lexigram.auth.exceptions", "TokenError"),
208 "InvalidTokenError": ("lexigram.auth.exceptions", "InvalidTokenError"),
209 "TokenExpiredError": ("lexigram.auth.exceptions", "TokenExpiredError"),
210 # Leaf token/verification exceptions (merged from former errors.py)
211 "AlreadyVerifiedError": ("lexigram.auth.exceptions", "AlreadyVerifiedError"),
212 "TokenAudienceError": ("lexigram.auth.exceptions", "TokenAudienceError"),
213 "TokenBlacklistedError": ("lexigram.auth.exceptions", "TokenBlacklistedError"),
214 "TokenExpiredVerificationError": (
215 "lexigram.auth.exceptions",
216 "TokenExpiredVerificationError",
217 ),
218 "TokenInvalidError": ("lexigram.auth.exceptions", "TokenInvalidError"),
219 "TokenNotFoundError": ("lexigram.auth.exceptions", "TokenNotFoundError"),
220 "VerificationError": ("lexigram.auth.exceptions", "VerificationError"),
221 # Guards & Dependencies
222 "require_auth": ("lexigram.auth.authz.guards", "require_auth"),
223 "require_roles": ("lexigram.auth.authz.guards", "require_roles"),
224 "require_permissions": ("lexigram.auth.authz.guards", "require_permissions"),
225 "optional_auth": ("lexigram.auth.authz.guards", "optional_auth"),
226 # Domain Events
227 "AuthenticationFailed": ("lexigram.auth.events", "AuthenticationFailed"),
228 "PasswordChanged": ("lexigram.auth.events", "PasswordChanged"),
229 "SessionCreated": ("lexigram.auth.events", "SessionCreated"),
230 "SessionRevoked": ("lexigram.auth.events", "SessionRevoked"),
231 "TokenRevoked": ("lexigram.auth.events", "TokenRevoked"),
232 "UserAuthenticated": ("lexigram.auth.events", "UserAuthenticated"),
233 "UserLoggedIn": ("lexigram.auth.events", "UserLoggedIn"),
234 "UserLoggedOut": ("lexigram.auth.events", "UserLoggedOut"),
235 "UserLoginFailed": ("lexigram.auth.events", "UserLoginFailed"),
236 "UserLockedOut": ("lexigram.auth.events", "UserLockedOut"),
237 "UserRegistered": ("lexigram.auth.events", "UserRegistered"),
238 # Hooks
239 "AuthAuthenticationFailedHook": (
240 "lexigram.auth.hooks",
241 "AuthAuthenticationFailedHook",
242 ),
243 "AuthTokenIssuedHook": ("lexigram.auth.hooks", "AuthTokenIssuedHook"),
244 "AuthTokenRefreshedHook": ("lexigram.auth.hooks", "AuthTokenRefreshedHook"),
245 "AuthTokenRevokedHook": ("lexigram.auth.hooks", "AuthTokenRevokedHook"),
246 "AuthUserAuthenticatedHook": ("lexigram.auth.hooks", "AuthUserAuthenticatedHook"),
247}
250def __getattr__(name: str) -> Any:
251 """Lazy load attributes to avoid circular imports."""
252 if name in _LAZY_IMPORTS:
253 import importlib
255 module_path, attr_name = _LAZY_IMPORTS[name]
256 module = importlib.import_module(module_path)
257 value = getattr(module, attr_name)
258 globals()[name] = value
259 return value
260 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
263def __dir__() -> list[str]:
264 """List available attributes for IDE support."""
265 return list(__all__) + list(_LAZY_IMPORTS.keys())
268__all__ = [
269 "APIKeyAuthenticator",
270 "APIKeyConfig",
271 "AlreadyVerifiedError",
272 "AuthAuthenticationFailedHook",
273 "AuthBundleProvider",
274 "AuthConfig",
275 "AuthError",
276 "AuthModule",
277 "AuthResult",
278 "AuthStatus",
279 "AuthToken",
280 "AuthTokenIssuedHook",
281 "AuthTokenRefreshedHook",
282 "AuthTokenRevokedHook",
283 "AuthUserAuthenticatedHook",
284 "AuthenticationFailed",
285 "AuthenticationProvider",
286 "AuthenticationService",
287 "AuthorizationError",
288 "AuthorizationProvider",
289 "GoogleOAuthProvider",
290 "GoogleOAuthService",
291 "IdentityResolverProtocol",
292 "InvalidTokenError",
293 "JWTConfig",
294 "JWTTokenManager",
295 "LockoutConfig",
296 "LoginAttemptTracker",
297 "MongoDBOAuthIdentityStore",
298 "OAuthIdentity",
299 "OAuthIdentityStore",
300 "PasswordChanged",
301 "PasswordHasher",
302 "PasswordPolicy",
303 "PersistentTokenRevocationStore",
304 "RBACConfig",
305 "RoleDefinition",
306 "SQLAlchemyOAuthIdentityStore",
307 "SessionCookieBackend",
308 "SessionCreated",
309 "SessionRevoked",
310 "TokenAudienceError",
311 "TokenBlacklistedError",
312 "TokenError",
313 "TokenExpiredError",
314 "TokenExpiredVerificationError",
315 "TokenInvalidError",
316 "TokenNotFoundError",
317 "TokenRevoked",
318 "TokenValidatorProtocol",
319 "User",
320 "UserAuthenticated",
321 "UserLockedOut",
322 "UserLoggedIn",
323 "UserLoggedOut",
324 "UserLoginFailed",
325 "UserRegistered",
326 "UserService",
327 "UserStatus",
328 "VerificationError",
329 "optional_auth",
330 "require_auth",
331 "require_permissions",
332 "require_roles",
333]