plain.auth 0.20.1__py3-none-any.whl → 0.20.2__py3-none-any.whl
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.
- plain/auth/CHANGELOG.md +10 -0
- plain/auth/requests.py +4 -8
- plain/auth/sessions.py +15 -8
- plain/auth/templates.py +8 -1
- plain/auth/test.py +8 -2
- plain/auth/utils.py +5 -1
- plain/auth/views.py +8 -10
- {plain_auth-0.20.1.dist-info → plain_auth-0.20.2.dist-info}/METADATA +1 -1
- plain_auth-0.20.2.dist-info/RECORD +14 -0
- plain_auth-0.20.1.dist-info/RECORD +0 -14
- {plain_auth-0.20.1.dist-info → plain_auth-0.20.2.dist-info}/WHEEL +0 -0
- {plain_auth-0.20.1.dist-info → plain_auth-0.20.2.dist-info}/licenses/LICENSE +0 -0
plain/auth/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# plain-auth changelog
|
|
2
2
|
|
|
3
|
+
## [0.20.2](https://github.com/dropseed/plain/releases/plain-auth@0.20.2) (2025-10-06)
|
|
4
|
+
|
|
5
|
+
### What's changed
|
|
6
|
+
|
|
7
|
+
- Added comprehensive type annotations across the entire package for improved IDE support and type checking ([786c1db](https://github.com/dropseed/plain/commit/786c1dbdbd))
|
|
8
|
+
|
|
9
|
+
### Upgrade instructions
|
|
10
|
+
|
|
11
|
+
- No changes required
|
|
12
|
+
|
|
3
13
|
## [0.20.1](https://github.com/dropseed/plain/releases/plain-auth@0.20.1) (2025-10-02)
|
|
4
14
|
|
|
5
15
|
### What's changed
|
plain/auth/requests.py
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
4
|
from weakref import WeakKeyDictionary
|
|
5
5
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
7
7
|
from plain.http import Request
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
_request_users: WeakKeyDictionary[Request, Any | None] = WeakKeyDictionary()
|
|
10
10
|
|
|
11
|
-
User = get_user_model()
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def set_request_user(request: Request, user: User | None) -> None:
|
|
12
|
+
def set_request_user(request: Request, user: Any | None) -> None:
|
|
17
13
|
"""Store the authenticated user for this request."""
|
|
18
14
|
_request_users[request] = user
|
|
19
15
|
|
|
20
16
|
|
|
21
|
-
def get_request_user(request: Request) ->
|
|
17
|
+
def get_request_user(request: Request) -> Any | None:
|
|
22
18
|
"""
|
|
23
19
|
Get the authenticated user for this request, if any.
|
|
24
20
|
|
plain/auth/sessions.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import hmac
|
|
4
|
+
from collections.abc import Generator
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
2
6
|
|
|
3
7
|
from plain.exceptions import ImproperlyConfigured
|
|
4
8
|
from plain.models import models_registry
|
|
@@ -9,18 +13,21 @@ from plain.utils.encoding import force_bytes
|
|
|
9
13
|
|
|
10
14
|
from .requests import get_request_user, set_request_user
|
|
11
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from plain.http import Request
|
|
18
|
+
|
|
12
19
|
USER_ID_SESSION_KEY = "_auth_user_id"
|
|
13
20
|
USER_HASH_SESSION_KEY = "_auth_user_hash"
|
|
14
21
|
|
|
15
22
|
|
|
16
|
-
def get_session_auth_hash(user):
|
|
23
|
+
def get_session_auth_hash(user: Any) -> str:
|
|
17
24
|
"""
|
|
18
25
|
Return an HMAC of the password field.
|
|
19
26
|
"""
|
|
20
27
|
return _get_session_auth_hash(user)
|
|
21
28
|
|
|
22
29
|
|
|
23
|
-
def update_session_auth_hash(request, user):
|
|
30
|
+
def update_session_auth_hash(request: Request, user: Any) -> None:
|
|
24
31
|
"""
|
|
25
32
|
Updating a user's password (for example) logs out all sessions for the user.
|
|
26
33
|
|
|
@@ -36,12 +43,12 @@ def update_session_auth_hash(request, user):
|
|
|
36
43
|
session[USER_HASH_SESSION_KEY] = get_session_auth_hash(user)
|
|
37
44
|
|
|
38
45
|
|
|
39
|
-
def get_session_auth_fallback_hash(user):
|
|
46
|
+
def get_session_auth_fallback_hash(user: Any) -> Generator[str, None, None]:
|
|
40
47
|
for fallback_secret in settings.SECRET_KEY_FALLBACKS:
|
|
41
48
|
yield _get_session_auth_hash(user, secret=fallback_secret)
|
|
42
49
|
|
|
43
50
|
|
|
44
|
-
def _get_session_auth_hash(user, secret=None):
|
|
51
|
+
def _get_session_auth_hash(user: Any, secret: str | None = None) -> str:
|
|
45
52
|
key_salt = "plain.auth.get_session_auth_hash"
|
|
46
53
|
return salted_hmac(
|
|
47
54
|
key_salt,
|
|
@@ -51,7 +58,7 @@ def _get_session_auth_hash(user, secret=None):
|
|
|
51
58
|
).hexdigest()
|
|
52
59
|
|
|
53
60
|
|
|
54
|
-
def login(request, user):
|
|
61
|
+
def login(request: Request, user: Any) -> None:
|
|
55
62
|
"""
|
|
56
63
|
Persist a user id and a backend in the request. This way a user doesn't
|
|
57
64
|
have to reauthenticate on every request. Note that data set during
|
|
@@ -87,7 +94,7 @@ def login(request, user):
|
|
|
87
94
|
set_request_user(request, user)
|
|
88
95
|
|
|
89
96
|
|
|
90
|
-
def logout(request):
|
|
97
|
+
def logout(request: Request) -> None:
|
|
91
98
|
"""
|
|
92
99
|
Remove the authenticated user's ID from the request and flush their session
|
|
93
100
|
data.
|
|
@@ -99,7 +106,7 @@ def logout(request):
|
|
|
99
106
|
set_request_user(request, None)
|
|
100
107
|
|
|
101
108
|
|
|
102
|
-
def get_user_model():
|
|
109
|
+
def get_user_model() -> type[Any]:
|
|
103
110
|
"""
|
|
104
111
|
Return the User model that is active in this project.
|
|
105
112
|
"""
|
|
@@ -115,7 +122,7 @@ def get_user_model():
|
|
|
115
122
|
)
|
|
116
123
|
|
|
117
124
|
|
|
118
|
-
def get_user(request):
|
|
125
|
+
def get_user(request: Request) -> Any | None:
|
|
119
126
|
"""
|
|
120
127
|
Return the user model instance associated with the given request session.
|
|
121
128
|
If no user is retrieved, return None.
|
plain/auth/templates.py
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
1
5
|
from jinja2 import pass_context
|
|
2
6
|
|
|
3
7
|
from plain.templates import register_template_global
|
|
4
8
|
|
|
5
9
|
from .requests import get_request_user
|
|
6
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from jinja2.runtime import Context
|
|
13
|
+
|
|
7
14
|
|
|
8
15
|
@register_template_global
|
|
9
16
|
@pass_context
|
|
10
|
-
def get_current_user(context):
|
|
17
|
+
def get_current_user(context: Context) -> Any | None:
|
|
11
18
|
"""Get the authenticated user for the current request."""
|
|
12
19
|
request = context.get("request")
|
|
13
20
|
assert request is not None, "No request in template context"
|
plain/auth/test.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from http.cookies import SimpleCookie
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
2
5
|
|
|
3
6
|
from plain.http.request import Request
|
|
4
7
|
from plain.runtime import settings
|
|
@@ -8,8 +11,11 @@ from plain.sessions.requests import get_request_session, set_request_session
|
|
|
8
11
|
from .requests import set_request_user
|
|
9
12
|
from .sessions import get_user, login, logout
|
|
10
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from plain.test.client import Client
|
|
16
|
+
|
|
11
17
|
|
|
12
|
-
def login_client(client, user):
|
|
18
|
+
def login_client(client: Client, user: Any) -> None:
|
|
13
19
|
"""Log a user into a test client."""
|
|
14
20
|
request = Request()
|
|
15
21
|
if client.session:
|
|
@@ -32,7 +38,7 @@ def login_client(client, user):
|
|
|
32
38
|
client.cookies[session_cookie].update(cookie_data)
|
|
33
39
|
|
|
34
40
|
|
|
35
|
-
def logout_client(client):
|
|
41
|
+
def logout_client(client: Client) -> None:
|
|
36
42
|
"""Log out a user from a test client."""
|
|
37
43
|
request = Request()
|
|
38
44
|
if client.session:
|
plain/auth/utils.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
1
5
|
from plain.urls import NoReverseMatch, reverse
|
|
2
6
|
from plain.utils.functional import Promise
|
|
3
7
|
|
|
4
8
|
|
|
5
|
-
def resolve_url(to, *args, **kwargs):
|
|
9
|
+
def resolve_url(to: Any, *args: Any, **kwargs: Any) -> str:
|
|
6
10
|
"""
|
|
7
11
|
Return a URL appropriate for the arguments passed.
|
|
8
12
|
|
plain/auth/views.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from functools import cached_property
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
5
|
from urllib.parse import urlparse, urlunparse
|
|
6
6
|
|
|
7
7
|
from plain.exceptions import PermissionDenied
|
|
@@ -23,13 +23,9 @@ from .utils import resolve_url
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
24
|
from plain.http import Request
|
|
25
25
|
|
|
26
|
-
from .sessions import get_user_model
|
|
27
|
-
|
|
28
|
-
User = get_user_model()
|
|
29
|
-
|
|
30
26
|
|
|
31
27
|
class LoginRequired(Exception):
|
|
32
|
-
def __init__(self, login_url=None, redirect_field_name="next"):
|
|
28
|
+
def __init__(self, login_url: str | None = None, redirect_field_name: str = "next"):
|
|
33
29
|
self.login_url = login_url or settings.AUTH_LOGIN_URL
|
|
34
30
|
self.redirect_field_name = redirect_field_name
|
|
35
31
|
|
|
@@ -42,7 +38,7 @@ class AuthViewMixin(SessionViewMixin):
|
|
|
42
38
|
request: Request
|
|
43
39
|
|
|
44
40
|
@cached_property
|
|
45
|
-
def user(self) ->
|
|
41
|
+
def user(self) -> Any | None:
|
|
46
42
|
"""Get the authenticated user for this request."""
|
|
47
43
|
from .requests import get_request_user
|
|
48
44
|
|
|
@@ -116,12 +112,14 @@ class AuthViewMixin(SessionViewMixin):
|
|
|
116
112
|
|
|
117
113
|
|
|
118
114
|
class LogoutView(View):
|
|
119
|
-
def post(self):
|
|
115
|
+
def post(self) -> ResponseRedirect:
|
|
120
116
|
logout(self.request)
|
|
121
117
|
return ResponseRedirect("/")
|
|
122
118
|
|
|
123
119
|
|
|
124
|
-
def redirect_to_login(
|
|
120
|
+
def redirect_to_login(
|
|
121
|
+
next: str, login_url: str | None = None, redirect_field_name: str = "next"
|
|
122
|
+
) -> ResponseRedirect:
|
|
125
123
|
"""
|
|
126
124
|
Redirect the user to the login page, passing the given 'next' page.
|
|
127
125
|
"""
|
|
@@ -133,4 +131,4 @@ def redirect_to_login(next, login_url=None, redirect_field_name="next"):
|
|
|
133
131
|
querystring[redirect_field_name] = next
|
|
134
132
|
login_url_parts[4] = querystring.urlencode(safe="/")
|
|
135
133
|
|
|
136
|
-
return ResponseRedirect(urlunparse(login_url_parts))
|
|
134
|
+
return ResponseRedirect(str(urlunparse(login_url_parts)))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
plain/auth/CHANGELOG.md,sha256=KEwd7C-ZHFlVY2Rv4G8tm_KFU6iPN-3-ZBs-W479_wA,4864
|
|
2
|
+
plain/auth/README.md,sha256=I1SeOyrBnF0GAjD7T0k5OlZ2bSHz9---nElinaobewc,4030
|
|
3
|
+
plain/auth/__init__.py,sha256=CrOsS74CPGN1nPTTfie13mPgdyVLRyZ1YwDPIA77uaA,179
|
|
4
|
+
plain/auth/default_settings.py,sha256=65VzDn3j61OMn78Lg6Zuds4A8QKzJJ_0G9KoFqAOIRo,466
|
|
5
|
+
plain/auth/requests.py,sha256=jUlMTOWFt0qfDF_uVkOqaP9rZiCLrAofsmirCwyRGEE,880
|
|
6
|
+
plain/auth/sessions.py,sha256=xDp1EiB0cV5w3L5XioqO8vs77wnF8cvreExms3-e744,6015
|
|
7
|
+
plain/auth/templates.py,sha256=bOMPfLrf8SSxOSWrKTkqKGaK3aNBTLxe62HJulghutU,556
|
|
8
|
+
plain/auth/test.py,sha256=SHawhwarJEMVfaLkjpiuFVSWZoGIMwhzXreU_T1zvCE,1599
|
|
9
|
+
plain/auth/utils.py,sha256=9kKWh1QqxA8Esct-jBvTCdjBYOHpO_Tg1YeV9WxYmxg,1362
|
|
10
|
+
plain/auth/views.py,sha256=vWLMeykTrA5LtA179uE7w-BY9Kv25r1VScLHooDvZUA,4792
|
|
11
|
+
plain_auth-0.20.2.dist-info/METADATA,sha256=BE1BOkDeM_Nw6Ym8w3VanuGn7uHN8wwnHkmuYhcEVBU,4390
|
|
12
|
+
plain_auth-0.20.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
+
plain_auth-0.20.2.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
|
|
14
|
+
plain_auth-0.20.2.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
plain/auth/CHANGELOG.md,sha256=W74wFZKot-9heq8l_MsoLSDmHLdAe5LSo9qFPliXFS0,4533
|
|
2
|
-
plain/auth/README.md,sha256=I1SeOyrBnF0GAjD7T0k5OlZ2bSHz9---nElinaobewc,4030
|
|
3
|
-
plain/auth/__init__.py,sha256=CrOsS74CPGN1nPTTfie13mPgdyVLRyZ1YwDPIA77uaA,179
|
|
4
|
-
plain/auth/default_settings.py,sha256=65VzDn3j61OMn78Lg6Zuds4A8QKzJJ_0G9KoFqAOIRo,466
|
|
5
|
-
plain/auth/requests.py,sha256=f8QUpqHueoTYnL2ITgRXxLFYLSxra9jW3p9pjFgj0B0,949
|
|
6
|
-
plain/auth/sessions.py,sha256=Pz27K2wFfij0H0nGg_2rq0WJpcgTWW22bG0YvHM-dfc,5679
|
|
7
|
-
plain/auth/templates.py,sha256=MQ9vdxE2fBXN-F043g8EGm93s6HRvEMtGHrFcbAr3Fs,400
|
|
8
|
-
plain/auth/test.py,sha256=bLxQEp1mM4t9b-bSSq2u9_Z07Wkl--7U_mSBb9VV7BU,1428
|
|
9
|
-
plain/auth/utils.py,sha256=eEON0Mo928l-aW5tqBuoTdVke8aP4majxVtAFoLroSE,1280
|
|
10
|
-
plain/auth/views.py,sha256=Gm91sB_Z47h-8SwxCMKVKl57FSjed_AM5fdeaiPmz-E,4761
|
|
11
|
-
plain_auth-0.20.1.dist-info/METADATA,sha256=5E0uGbf7uFA-F0ywFO45xQOLQSCbvYw9nSNVyezRITI,4390
|
|
12
|
-
plain_auth-0.20.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
-
plain_auth-0.20.1.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
|
|
14
|
-
plain_auth-0.20.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|