Skip to content

Commit 2f11e94

Browse files
committed
Reworking bolt-auth, bolt-passwords, other tweaks
1 parent 2a055dd commit 2f11e94

File tree

26 files changed

+687
-810
lines changed

26 files changed

+687
-810
lines changed

bolt-auth/bolt/auth/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
11
# bolt-auth
22

33
Create users and authenticate them.
4+
5+
## Installation
6+
7+
- install bolt-auth
8+
- install bolt-sessions
9+
- optionally bolt-passwords, etc.
10+
- add bolt.auth to installed packages
11+
12+
```python
13+
INSTALLED_PACKAGES = [
14+
# ...
15+
"bolt.auth",
16+
]
17+
```
18+
19+
```
20+
# settings.py
21+
MIDDLEWARE = [
22+
"bolt.middleware.security.SecurityMiddleware",
23+
"bolt.assets.whitenoise.middleware.WhiteNoiseMiddleware",
24+
"bolt.sessions.middleware.SessionMiddleware", # <-- Add SessionMiddleware
25+
"bolt.middleware.common.CommonMiddleware",
26+
"bolt.csrf.middleware.CsrfViewMiddleware",
27+
"bolt.auth.middleware.AuthenticationMiddleware", # <-- Add AuthenticationMiddleware
28+
"bolt.middleware.clickjacking.XFrameOptionsMiddleware",
29+
]
30+
```

bolt-auth/bolt/auth/__init__.py

+2-128
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,3 @@
1-
from bolt.csrf.middleware import rotate_token
2-
from bolt.exceptions import ImproperlyConfigured
3-
from bolt.packages import packages as bolt_packages
4-
from bolt.runtime import settings
5-
from bolt.utils.crypto import constant_time_compare
1+
from .sessions import get_user, get_user_model, login, logout
62

7-
from .signals import user_logged_in, user_logged_out
8-
9-
USER_ID_SESSION_KEY = "_auth_user_id"
10-
USER_HASH_SESSION_KEY = "_auth_user_hash"
11-
12-
13-
def _get_user_id_from_session(request):
14-
# This value in the session is always serialized to a string, so we need
15-
# to convert it back to Python whenever we access it.
16-
return get_user_model()._meta.pk.to_python(request.session[USER_ID_SESSION_KEY])
17-
18-
19-
def login(request, user):
20-
"""
21-
Persist a user id and a backend in the request. This way a user doesn't
22-
have to reauthenticate on every request. Note that data set during
23-
the anonymous session is retained when the user logs in.
24-
"""
25-
if user.SESSION_HASH_FIELD:
26-
session_auth_hash = user.get_session_auth_hash()
27-
else:
28-
session_auth_hash = ""
29-
30-
if USER_ID_SESSION_KEY in request.session:
31-
if _get_user_id_from_session(request) != user.pk:
32-
# To avoid reusing another user's session, create a new, empty
33-
# session if the existing session corresponds to a different
34-
# authenticated user.
35-
request.session.flush()
36-
elif session_auth_hash and not constant_time_compare(
37-
request.session.get(USER_HASH_SESSION_KEY, ""), session_auth_hash
38-
):
39-
# If the session hash does not match the current hash, reset the
40-
# session. Most likely this means the password was changed.
41-
request.session.flush()
42-
else:
43-
request.session.cycle_key()
44-
45-
request.session[USER_ID_SESSION_KEY] = user._meta.pk.value_to_string(user)
46-
request.session[USER_HASH_SESSION_KEY] = session_auth_hash
47-
if hasattr(request, "user"):
48-
request.user = user
49-
rotate_token(request)
50-
user_logged_in.send(sender=user.__class__, request=request, user=user)
51-
52-
53-
def logout(request):
54-
"""
55-
Remove the authenticated user's ID from the request and flush their session
56-
data.
57-
"""
58-
# Dispatch the signal before the user is logged out so the receivers have a
59-
# chance to find out *who* logged out.
60-
user = getattr(request, "user", None)
61-
user_logged_out.send(sender=user.__class__, request=request, user=user)
62-
request.session.flush()
63-
if hasattr(request, "user"):
64-
request.user = None
65-
66-
67-
def get_user_model():
68-
"""
69-
Return the User model that is active in this project.
70-
"""
71-
try:
72-
return bolt_packages.get_model(settings.AUTH_USER_MODEL, require_ready=False)
73-
except ValueError:
74-
raise ImproperlyConfigured(
75-
"AUTH_USER_MODEL must be of the form 'package_label.model_name'"
76-
)
77-
except LookupError:
78-
raise ImproperlyConfigured(
79-
"AUTH_USER_MODEL refers to model '%s' that has not been installed"
80-
% settings.AUTH_USER_MODEL
81-
)
82-
83-
84-
def get_user(request):
85-
"""
86-
Return the user model instance associated with the given request session.
87-
If no user is retrieved, return None.
88-
"""
89-
if USER_ID_SESSION_KEY not in request.session:
90-
return None
91-
92-
user_id = _get_user_id_from_session(request)
93-
94-
UserModel = get_user_model()
95-
try:
96-
user = UserModel._default_manager.get(pk=user_id)
97-
except UserModel.DoesNotExist:
98-
return None
99-
100-
# If the user models defines a specific field to also hash and compare
101-
# (like password), then we verify that the hash of that field is still
102-
# the same as when the session was created.
103-
#
104-
# If it has changed (i.e. password changed), then the session
105-
# is no longer valid and cleared out.
106-
if user.SESSION_HASH_FIELD:
107-
session_hash = request.session.get(USER_HASH_SESSION_KEY)
108-
if not session_hash:
109-
session_hash_verified = False
110-
else:
111-
session_auth_hash = user.get_session_auth_hash()
112-
session_hash_verified = constant_time_compare(
113-
session_hash, session_auth_hash
114-
)
115-
if not session_hash_verified:
116-
# If the current secret does not verify the session, try
117-
# with the fallback secrets and stop when a matching one is
118-
# found.
119-
if session_hash and any(
120-
constant_time_compare(session_hash, fallback_auth_hash)
121-
for fallback_auth_hash in user.get_session_auth_fallback_hash()
122-
):
123-
request.session.cycle_key()
124-
request.session[USER_HASH_SESSION_KEY] = session_auth_hash
125-
else:
126-
request.session.flush()
127-
user = None
128-
129-
return user
3+
__all__ = ["login", "logout", "get_user_model", "get_user"]

bolt-auth/bolt/auth/config.py

-12
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
1-
from bolt.db.models.query_utils import DeferredAttribute
21
from bolt.packages import PackageConfig
32

4-
from . import get_user_model
5-
from .signals import user_logged_in
6-
73

84
class AuthConfig(PackageConfig):
95
name = "bolt.auth"
10-
11-
def ready(self):
12-
last_login_field = getattr(get_user_model(), "last_login", None)
13-
# Register the handler only if UserModel.last_login is a field.
14-
if isinstance(last_login_field, DeferredAttribute):
15-
from .models import update_last_login
16-
17-
user_logged_in.connect(update_last_login, dispatch_uid="update_last_login")
+10-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
##################
2-
# AUTHENTICATION #
3-
##################
1+
from importlib.util import find_spec
42

53
AUTH_USER_MODEL: str
64

7-
LOGIN_URL = "/accounts/login/"
5+
if find_spec("bolt.passwords"):
6+
# Automatically invalidate sessions on password field change,
7+
# if the bolt-passwords is installed. You can change this value
8+
# if your password field is named differently, or you want
9+
# to use a different field to invalidate sessions.
10+
AUTH_USER_SESSION_HASH_FIELD: str = "password"
11+
else:
12+
AUTH_USER_SESSION_HASH_FIELD: str = ""
813

9-
LOGIN_REDIRECT_URL = "/accounts/profile/"
14+
LOGIN_URL = "login"

bolt-auth/bolt/auth/models.py

-120
This file was deleted.

0 commit comments

Comments
 (0)