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

1""" 

2lexigram-auth — Authentication and authorisation for the Lexigram platform. 

3 

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 

14 

15All commonly-used symbols are also re-exported from this root package so 

16``from lexigram.auth import JWTTokenManager`` always works. 

17 

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. 

26 

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""" 

34 

35from __future__ import annotations 

36 

37import importlib.metadata 

38from typing import TYPE_CHECKING, Any 

39 

40__path__ = __import__("pkgutil").extend_path(__path__, __name__) 

41 

42from lexigram.auth.constants import __version__ as __version__ 

43 

44# ============================================================================= 

45# Lazy Loading to Avoid Circular Imports 

46# ============================================================================= 

47 

48 

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 ) 

133 

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} 

248 

249 

250def __getattr__(name: str) -> Any: 

251 """Lazy load attributes to avoid circular imports.""" 

252 if name in _LAZY_IMPORTS: 

253 import importlib 

254 

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}") 

261 

262 

263def __dir__() -> list[str]: 

264 """List available attributes for IDE support.""" 

265 return list(__all__) + list(_LAZY_IMPORTS.keys()) 

266 

267 

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]