Coverage for src/lexigram/auth/module.py: 86%
21 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"""Authentication and authorization module for dependency injection."""
3from __future__ import annotations
5from typing import Any
7from lexigram.contracts.auth import (
8 AuthenticatorProtocol,
9 AuthorizerProtocol,
10 TokenManagerProtocol,
11)
12from lexigram.contracts.auth.protocols import PasswordHasherProtocol
13from lexigram.di.module import DynamicModule, Module, module
16@module(is_global=True)
17class AuthModule(Module):
18 """Full authentication and authorization stack (JWT, OAuth2/OIDC, RBAC, policies).
20 Call :meth:`configure` to configure the auth bundle with an
21 :class:`~lexigram.auth.config.AuthConfig`.
23 Usage::
25 from lexigram.auth.config import AuthConfig
27 @module(
28 imports=[AuthModule.configure(AuthConfig(secret_key="..."))]
29 )
30 class AppModule(Module):
31 pass
32 """
34 @classmethod
35 def configure(
36 cls,
37 config: Any | None = None,
38 initial_roles: dict[str, Any] | None = None,
39 is_global: bool = True,
40 ) -> DynamicModule:
41 """Create an AuthModule with explicit configuration.
43 Args:
44 config: :class:`~lexigram.auth.config.AuthConfig` or ``None``
45 for framework defaults (development-only ephemeral secrets).
46 initial_roles: Optional RBAC role seed forwarded to the
47 authorization sub-provider.
49 Returns:
50 A :class:`~lexigram.di.module.DynamicModule` descriptor.
51 """
52 from lexigram.auth.admin.contributor import AuthAdminContributor
53 from lexigram.auth.admin.handlers.active_sessions import (
54 ActiveSessionsWidgetHandler,
55 )
56 from lexigram.auth.admin.handlers.failed_logins import FailedLoginsWidgetHandler
57 from lexigram.auth.admin.handlers.token_refresh_rate import (
58 TokenRefreshRateWidgetHandler,
59 )
60 from lexigram.auth.di.bundle_provider import AuthBundleProvider
62 return DynamicModule(
63 module=cls,
64 providers=[AuthBundleProvider(config=config, initial_roles=initial_roles)],
65 exports=[
66 AuthenticatorProtocol,
67 AuthorizerProtocol,
68 TokenManagerProtocol,
69 PasswordHasherProtocol,
70 AuthAdminContributor,
71 ActiveSessionsWidgetHandler,
72 FailedLoginsWidgetHandler,
73 TokenRefreshRateWidgetHandler,
74 ],
75 is_global=is_global,
76 )
78 @classmethod
79 def stub(cls, config: Any = None) -> DynamicModule:
80 """Return an in-memory AuthModule suitable for unit and integration testing.
82 Uses ephemeral in-memory storage with a fixed test secret key.
83 No external token services, databases, or OAuth providers are
84 configured.
86 Args:
87 config: Optional test configuration override.
89 Returns:
90 A DynamicModule backed by in-memory auth storage.
91 """
92 from lexigram.auth.config import AuthConfig, JWTConfig
93 from lexigram.auth.di.bundle_provider import AuthBundleProvider
95 return DynamicModule(
96 module=cls,
97 providers=[
98 AuthBundleProvider(
99 config=AuthConfig(
100 secret_key="test-secret-key-for-testing-only", # noqa: S106 # in-memory test bootstrap
101 token=JWTConfig(
102 secret_key="test-secret-key-for-testing-only", # noqa: S106 # in-memory test bootstrap
103 ),
104 ),
105 initial_roles=None,
106 )
107 ],
108 exports=[
109 AuthenticatorProtocol,
110 AuthorizerProtocol,
111 TokenManagerProtocol,
112 PasswordHasherProtocol,
113 ],
114 )
117__all__ = ["AuthModule"]