simple-module-auth 0.0.37__tar.gz → 0.0.39__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/.gitignore +1 -2
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/PKG-INFO +6 -5
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/README.md +1 -1
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/contracts/provider.py +0 -2
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/contracts/schemas.py +20 -7
- simple_module_auth-0.0.39/auth/locales/de.json +6 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/middleware.py +9 -11
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/state.py +9 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/package.json +1 -1
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/pyproject.toml +6 -4
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/LICENSE +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/__init__.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/_route_match.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/contracts/__init__.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/contracts/resolver.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/contracts/tokens.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/deps.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/locales/en.json +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/locales/es.json +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/module.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/auth/py.typed +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_auth_middleware.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_auth_next_target.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_auth_parse_bearer.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_auth_provider_protocol.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_auth_unknown_paths.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_deps.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_module.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_resolver_registry.py +0 -0
- {simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_user_context.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simple_module_auth
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.39
|
|
4
4
|
Summary: Session-cookie authentication primitives — middleware, login/logout, redirect helpers for simple_module
|
|
5
5
|
Project-URL: Homepage, https://github.com/antosubash/simple_module_python
|
|
6
6
|
Project-URL: Repository, https://github.com/antosubash/simple_module_python
|
|
@@ -21,9 +21,10 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.12
|
|
24
|
-
Requires-Dist: itsdangerous
|
|
25
|
-
Requires-Dist: simple-module-core==0.0.
|
|
26
|
-
Requires-Dist: simple-module-db==0.0.
|
|
24
|
+
Requires-Dist: itsdangerous<3,>=2.2
|
|
25
|
+
Requires-Dist: simple-module-core==0.0.39
|
|
26
|
+
Requires-Dist: simple-module-db==0.0.39
|
|
27
|
+
Requires-Dist: simple-module-hosting==0.0.39
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
28
29
|
|
|
29
30
|
# simple_module_auth
|
|
@@ -41,7 +42,7 @@ pip install simple_module_auth
|
|
|
41
42
|
## What it provides
|
|
42
43
|
|
|
43
44
|
- `UserContext` — the request-scoped principal (`id`, `name`, `email`, `roles`).
|
|
44
|
-
- `AuthProvider` protocol — the swappable-backend contract (`resolve_user`, `get_login_url`, `get_logout_url`, `
|
|
45
|
+
- `AuthProvider` protocol — the swappable-backend contract (`resolve_user`, `get_login_url`, `get_logout_url`, `is_bearer_request`); exactly one provider module registers an implementation on `app.state.auth.auth_provider`.
|
|
45
46
|
- `PrincipalResolver` chain — async `(Request) -> UserContext | None` callables apps append to `app.state.auth.principal_resolvers` (e.g. PAT/bearer-token or API-key auth), consulted after the session path.
|
|
46
47
|
- `AuthMiddleware` — delegates to the provider + resolver chain on every request and populates the request principal.
|
|
47
48
|
- `get_current_user` / `CurrentUser` dependency and the `require_permission(*permissions)` dependency factory.
|
|
@@ -13,7 +13,7 @@ pip install simple_module_auth
|
|
|
13
13
|
## What it provides
|
|
14
14
|
|
|
15
15
|
- `UserContext` — the request-scoped principal (`id`, `name`, `email`, `roles`).
|
|
16
|
-
- `AuthProvider` protocol — the swappable-backend contract (`resolve_user`, `get_login_url`, `get_logout_url`, `
|
|
16
|
+
- `AuthProvider` protocol — the swappable-backend contract (`resolve_user`, `get_login_url`, `get_logout_url`, `is_bearer_request`); exactly one provider module registers an implementation on `app.state.auth.auth_provider`.
|
|
17
17
|
- `PrincipalResolver` chain — async `(Request) -> UserContext | None` callables apps append to `app.state.auth.principal_resolvers` (e.g. PAT/bearer-token or API-key auth), consulted after the session path.
|
|
18
18
|
- `AuthMiddleware` — delegates to the provider + resolver chain on every request and populates the request principal.
|
|
19
19
|
- `get_current_user` / `CurrentUser` dependency and the `require_permission(*permissions)` dependency factory.
|
|
@@ -2,15 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Iterable
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import Any, Protocol
|
|
7
8
|
|
|
8
9
|
from auth.contracts.tokens import AUTH_KIND_SESSION
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
|
|
12
|
+
class NamedRole(Protocol):
|
|
13
|
+
name: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AccountLike(Protocol):
|
|
17
|
+
"""What :meth:`UserContext.from_user` reads from an account row.
|
|
18
|
+
|
|
19
|
+
Structural, so ``auth`` names no provider's model: ``users`` depends on
|
|
20
|
+
``auth``, never the other way round (GH #501).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
id: Any
|
|
24
|
+
email: str
|
|
25
|
+
full_name: str | None
|
|
26
|
+
roles: Iterable[NamedRole]
|
|
14
27
|
|
|
15
28
|
|
|
16
29
|
@dataclass
|
|
@@ -53,12 +66,12 @@ class UserContext:
|
|
|
53
66
|
@classmethod
|
|
54
67
|
def from_user(
|
|
55
68
|
cls,
|
|
56
|
-
user:
|
|
69
|
+
user: AccountLike,
|
|
57
70
|
*,
|
|
58
71
|
auth_kind: str = AUTH_KIND_SESSION,
|
|
59
72
|
scopes: list[str] | None = None,
|
|
60
73
|
) -> UserContext:
|
|
61
|
-
"""Build a UserContext from
|
|
74
|
+
"""Build a UserContext from an :class:`AccountLike` with eagerly-loaded roles.
|
|
62
75
|
|
|
63
76
|
Duck-typed to avoid importing users.models at runtime — any object
|
|
64
77
|
exposing .id, .email, .full_name, .roles[*].name works. The user row
|
|
@@ -10,8 +10,10 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
12
|
|
|
13
|
+
from simple_module_core.app_state import get_openapi_url
|
|
13
14
|
from simple_module_core.redirect_safety import SESSION_NEXT_KEY, safe_next_or_none
|
|
14
15
|
from simple_module_db.listeners import current_user_id
|
|
16
|
+
from simple_module_hosting.public_access import is_public as route_is_public
|
|
15
17
|
from starlette.requests import Request
|
|
16
18
|
from starlette.responses import JSONResponse, RedirectResponse
|
|
17
19
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
@@ -86,19 +88,10 @@ class AuthMiddleware:
|
|
|
86
88
|
any(path.startswith(p) for p in _FRAMEWORK_PUBLIC_PREFIXES)
|
|
87
89
|
or path in _FRAMEWORK_PUBLIC_EXACT
|
|
88
90
|
# Anonymous exactly when the host serves it (dev, or openapi_public).
|
|
89
|
-
or path ==
|
|
91
|
+
or path == get_openapi_url(scope["app"])
|
|
90
92
|
)
|
|
91
|
-
# Module-contributed public routes (register_public_routes hook). Method
|
|
92
|
-
# -aware, so a GET read route can be exempted without opening sibling
|
|
93
|
-
# POST/PATCH mutations under the same prefix.
|
|
94
93
|
if not is_public:
|
|
95
|
-
|
|
96
|
-
is_public = public_routes is not None and public_routes.matches(method, path)
|
|
97
|
-
# Legacy provider-declared paths (prefix-only, method-agnostic). Kept for
|
|
98
|
-
# back-compat with AuthProvider implementations.
|
|
99
|
-
if not is_public:
|
|
100
|
-
prefix_paths, exact_paths = provider.get_public_paths()
|
|
101
|
-
is_public = any(path.startswith(p) for p in prefix_paths) or path in exact_paths
|
|
94
|
+
is_public = route_is_public(scope["app"], method, path)
|
|
102
95
|
|
|
103
96
|
# Contract with the host's RateLimitMiddleware (plain scope key, no import
|
|
104
97
|
# between them): whether *this* middleware treats the request as anonymous-
|
|
@@ -158,6 +151,11 @@ class AuthMiddleware:
|
|
|
158
151
|
|
|
159
152
|
if user_ctx is not None:
|
|
160
153
|
request.state.user = user_ctx
|
|
154
|
+
for guard in auth_state.principal_guards:
|
|
155
|
+
refusal = guard(request)
|
|
156
|
+
if refusal is not None:
|
|
157
|
+
await refusal(scope, receive, send)
|
|
158
|
+
return
|
|
161
159
|
token = current_user_id.set(user_ctx.id)
|
|
162
160
|
try:
|
|
163
161
|
await self.app(scope, receive, send)
|
|
@@ -9,12 +9,16 @@ principal-resolver registry. Apps register additional resolvers from their
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
from collections.abc import Callable
|
|
12
13
|
from dataclasses import dataclass, field
|
|
13
14
|
from typing import TYPE_CHECKING
|
|
14
15
|
|
|
15
16
|
from auth.contracts.resolver import PrincipalResolver
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
19
|
+
from starlette.requests import Request
|
|
20
|
+
from starlette.responses import Response
|
|
21
|
+
|
|
18
22
|
from auth.contracts.provider import AuthProvider
|
|
19
23
|
|
|
20
24
|
|
|
@@ -24,6 +28,11 @@ class AuthState:
|
|
|
24
28
|
|
|
25
29
|
auth_provider: AuthProvider | None = None
|
|
26
30
|
principal_resolvers: list[PrincipalResolver] = field(default_factory=list)
|
|
31
|
+
# Run once a principal has resolved (any credential source), before downstream
|
|
32
|
+
# middleware; anonymous requests never reach them.
|
|
33
|
+
# A guard is a sync callable returning a refusal response or None to continue.
|
|
34
|
+
# An exception propagates (a 500), so a broken guard fails closed, never open.
|
|
35
|
+
principal_guards: list[Callable[[Request], Response | None]] = field(default_factory=list)
|
|
27
36
|
|
|
28
37
|
|
|
29
38
|
__all__ = ["AuthState"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_auth"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.39"
|
|
4
4
|
description = "Session-cookie authentication primitives — middleware, login/logout, redirect helpers for simple_module"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -21,9 +21,10 @@ classifiers = [
|
|
|
21
21
|
"Typing :: Typed",
|
|
22
22
|
]
|
|
23
23
|
dependencies = [
|
|
24
|
-
"itsdangerous>=2.2",
|
|
25
|
-
"simple_module_core==0.0.
|
|
26
|
-
"simple_module_db==0.0.
|
|
24
|
+
"itsdangerous>=2.2,<3",
|
|
25
|
+
"simple_module_core==0.0.39",
|
|
26
|
+
"simple_module_db==0.0.39",
|
|
27
|
+
"simple_module_hosting==0.0.39",
|
|
27
28
|
]
|
|
28
29
|
|
|
29
30
|
[project.entry-points.simple_module]
|
|
@@ -50,3 +51,4 @@ packages = ["auth"]
|
|
|
50
51
|
[tool.uv.sources]
|
|
51
52
|
simple_module_core = { workspace = true }
|
|
52
53
|
simple_module_db = { workspace = true }
|
|
54
|
+
simple_module_hosting = { workspace = true }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_auth-0.0.37 → simple_module_auth-0.0.39}/tests/test_auth_provider_protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|