plain.auth 0.24.0__tar.gz → 0.25.1__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.
Files changed (22) hide show
  1. {plain_auth-0.24.0 → plain_auth-0.25.1}/.gitignore +1 -1
  2. {plain_auth-0.24.0 → plain_auth-0.25.1}/PKG-INFO +90 -14
  3. {plain_auth-0.24.0 → plain_auth-0.25.1}/plain/auth/CHANGELOG.md +20 -0
  4. plain_auth-0.25.1/plain/auth/README.md +250 -0
  5. {plain_auth-0.24.0 → plain_auth-0.25.1}/plain/auth/sessions.py +14 -14
  6. {plain_auth-0.24.0 → plain_auth-0.25.1}/pyproject.toml +1 -1
  7. plain_auth-0.24.0/plain/auth/README.md +0 -174
  8. {plain_auth-0.24.0 → plain_auth-0.25.1}/LICENSE +0 -0
  9. {plain_auth-0.24.0 → plain_auth-0.25.1}/README.md +0 -0
  10. {plain_auth-0.24.0 → plain_auth-0.25.1}/plain/auth/__init__.py +0 -0
  11. {plain_auth-0.24.0 → plain_auth-0.25.1}/plain/auth/default_settings.py +0 -0
  12. {plain_auth-0.24.0 → plain_auth-0.25.1}/plain/auth/requests.py +0 -0
  13. {plain_auth-0.24.0 → plain_auth-0.25.1}/plain/auth/templates.py +0 -0
  14. {plain_auth-0.24.0 → plain_auth-0.25.1}/plain/auth/test.py +0 -0
  15. {plain_auth-0.24.0 → plain_auth-0.25.1}/plain/auth/utils.py +0 -0
  16. {plain_auth-0.24.0 → plain_auth-0.25.1}/plain/auth/views.py +0 -0
  17. {plain_auth-0.24.0 → plain_auth-0.25.1}/tests/app/settings.py +0 -0
  18. {plain_auth-0.24.0 → plain_auth-0.25.1}/tests/app/urls.py +0 -0
  19. {plain_auth-0.24.0 → plain_auth-0.25.1}/tests/app/users/migrations/0001_initial.py +0 -0
  20. {plain_auth-0.24.0 → plain_auth-0.25.1}/tests/app/users/migrations/__init__.py +0 -0
  21. {plain_auth-0.24.0 → plain_auth-0.25.1}/tests/app/users/models.py +0 -0
  22. {plain_auth-0.24.0 → plain_auth-0.25.1}/tests/test_views.py +0 -0
@@ -17,5 +17,5 @@ plain*/tests/.plain
17
17
  .plain
18
18
 
19
19
  .vscode
20
- /.claude
20
+ /.claude/skills/plain-*
21
21
  /.benchmarks
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.auth
3
- Version: 0.24.0
3
+ Version: 0.25.1
4
4
  Summary: Add users to your app and decide what they can access.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-Expression: BSD-3-Clause
@@ -22,14 +22,16 @@ Description-Content-Type: text/markdown
22
22
  - [Login views](#login-views)
23
23
  - [Checking if a user is logged in](#checking-if-a-user-is-logged-in)
24
24
  - [Restricting views](#restricting-views)
25
+ - [Testing with authenticated users](#testing-with-authenticated-users)
26
+ - [Settings](#settings)
27
+ - [FAQs](#faqs)
25
28
  - [Installation](#installation)
26
29
 
27
30
  ## Overview
28
31
 
29
- The `plain.auth` package provides user authentication and authorization for Plain applications. Here's a basic example of checking if a user is logged in:
32
+ The `plain.auth` package handles user authentication and authorization for Plain applications. You can check if a user is logged in like this:
30
33
 
31
34
  ```python
32
- # In a view
33
35
  from plain.auth import get_request_user
34
36
 
35
37
  user = get_request_user(request)
@@ -39,7 +41,7 @@ else:
39
41
  print("You are not logged in.")
40
42
  ```
41
43
 
42
- And restricting a view to logged-in users:
44
+ You can restrict a view to logged-in users using [`AuthViewMixin`](./views.py#AuthViewMixin):
43
45
 
44
46
  ```python
45
47
  from plain.auth.views import AuthViewMixin
@@ -76,7 +78,7 @@ AUTH_LOGIN_URL = "login"
76
78
 
77
79
  ### Creating a user model
78
80
 
79
- Create your own user model using `plain create users` or manually:
81
+ You can create your own user model using `plain create users` or manually:
80
82
 
81
83
  ```python
82
84
  # app/users/models.py
@@ -96,14 +98,13 @@ class User(models.Model):
96
98
 
97
99
  ### Login views
98
100
 
99
- To log users in, you'll need to pair this package with an authentication method:
101
+ To log users in, you need to pair this package with an authentication method:
100
102
 
101
- - `plain-passwords` - Username/password authentication
102
- - `plain-oauth` - OAuth provider authentication
103
- - `plain-passkeys` (TBD) - WebAuthn/passkey authentication
104
- - `plain-passlinks` (TBD) - Magic link authentication
103
+ - [plain.passwords](../../plain-passwords/plain/passwords/README.md) - Username/password authentication
104
+ - [plain.oauth](../../plain-oauth/plain/oauth/README.md) - OAuth provider authentication
105
+ - [plain.loginlink](../../plain-loginlink/plain/loginlink/README.md) - Magic link authentication
105
106
 
106
- Example with password authentication:
107
+ Here's an example with password authentication:
107
108
 
108
109
  ```python
109
110
  # app/urls.py
@@ -124,7 +125,7 @@ urlpatterns = [
124
125
 
125
126
  ## Checking if a user is logged in
126
127
 
127
- In templates, use the `get_current_user()` function:
128
+ In templates, you can use the `get_current_user()` function:
128
129
 
129
130
  ```html
130
131
  {% if get_current_user() %}
@@ -134,7 +135,7 @@ In templates, use the `get_current_user()` function:
134
135
  {% endif %}
135
136
  ```
136
137
 
137
- In Python code, use `get_request_user()`:
138
+ In Python code, use [`get_request_user()`](./requests.py#get_request_user):
138
139
 
139
140
  ```python
140
141
  from plain.auth import get_request_user
@@ -148,7 +149,7 @@ else:
148
149
 
149
150
  ## Restricting views
150
151
 
151
- Use the [`AuthViewMixin`](./views.py#AuthViewMixin) to restrict views to logged-in users, admin users, or custom logic:
152
+ You can use [`AuthViewMixin`](./views.py#AuthViewMixin) to restrict views to logged-in users, admin users, or custom logic:
152
153
 
153
154
  ```python
154
155
  from plain.auth.views import AuthViewMixin
@@ -178,6 +179,81 @@ The [`AuthViewMixin`](./views.py#AuthViewMixin) provides:
178
179
  - `admin_required` - Requires `user.is_admin` to be True
179
180
  - `check_auth()` - Override for custom authorization logic
180
181
 
182
+ ## Testing with authenticated users
183
+
184
+ When writing tests, you can use [`login_client()`](./test.py#login_client) to simulate an authenticated user:
185
+
186
+ ```python
187
+ from plain.auth.test import login_client
188
+ from plain.test import Client
189
+
190
+ from app.users.models import User
191
+
192
+
193
+ def test_profile_view():
194
+ user = User.objects.create(email="test@example.com")
195
+ client = Client()
196
+ login_client(client, user)
197
+
198
+ response = client.get("/profile/")
199
+ assert response.status_code == 200
200
+ ```
201
+
202
+ You can also log out a test user with [`logout_client()`](./test.py#logout_client):
203
+
204
+ ```python
205
+ from plain.auth.test import login_client, logout_client
206
+
207
+ # ... after logging in
208
+ logout_client(client)
209
+ ```
210
+
211
+ ## Settings
212
+
213
+ | Setting | Default | Env var |
214
+ | ------------------------------ | -------------------- | ------------------------------------ |
215
+ | `AUTH_USER_MODEL` | Required | `PLAIN_AUTH_USER_MODEL` |
216
+ | `AUTH_LOGIN_URL` | Required | `PLAIN_AUTH_LOGIN_URL` |
217
+ | `AUTH_USER_SESSION_HASH_FIELD` | `"password"` or `""` | `PLAIN_AUTH_USER_SESSION_HASH_FIELD` |
218
+
219
+ See [`default_settings.py`](./default_settings.py) for more details.
220
+
221
+ ## FAQs
222
+
223
+ #### How do I log in a user programmatically?
224
+
225
+ You can use the [`login()`](./sessions.py#login) function to log in a user:
226
+
227
+ ```python
228
+ from plain.auth.sessions import login
229
+
230
+ login(request, user)
231
+ ```
232
+
233
+ #### How do I log out a user programmatically?
234
+
235
+ You can use the [`logout()`](./sessions.py#logout) function:
236
+
237
+ ```python
238
+ from plain.auth.sessions import logout
239
+
240
+ logout(request)
241
+ ```
242
+
243
+ #### How do I invalidate sessions when a user changes their password?
244
+
245
+ By default, if you have [plain.passwords](../../plain-passwords/plain/passwords/README.md) installed, sessions are automatically invalidated when the `password` field changes. This is controlled by the `AUTH_USER_SESSION_HASH_FIELD` setting. You can change this to a different field name, or set it to an empty string to disable this feature.
246
+
247
+ #### How do I get the user model class?
248
+
249
+ You can use the [`get_user_model()`](./sessions.py#get_user_model) function:
250
+
251
+ ```python
252
+ from plain.auth.sessions import get_user_model
253
+
254
+ User = get_user_model()
255
+ ```
256
+
181
257
  ## Installation
182
258
 
183
259
  Install the `plain.auth` package from [PyPI](https://pypi.org/project/plain.auth/):
@@ -1,5 +1,25 @@
1
1
  # plain-auth changelog
2
2
 
3
+ ## [0.25.1](https://github.com/dropseed/plain/releases/plain-auth@0.25.1) (2026-01-28)
4
+
5
+ ### What's changed
6
+
7
+ - Added Settings section to README ([803fee1ad5](https://github.com/dropseed/plain/commit/803fee1ad5))
8
+
9
+ ### Upgrade instructions
10
+
11
+ - No changes required.
12
+
13
+ ## [0.25.0](https://github.com/dropseed/plain/releases/plain-auth@0.25.0) (2026-01-13)
14
+
15
+ ### What's changed
16
+
17
+ - Improved README documentation with better examples and consistent structure ([da37a78](https://github.com/dropseed/plain/commit/da37a78fbb))
18
+
19
+ ### Upgrade instructions
20
+
21
+ - No changes required
22
+
3
23
  ## [0.24.0](https://github.com/dropseed/plain/releases/plain-auth@0.24.0) (2026-01-13)
4
24
 
5
25
  ### What's changed
@@ -0,0 +1,250 @@
1
+ # plain.auth
2
+
3
+ **Add users to your app and decide what they can access.**
4
+
5
+ - [Overview](#overview)
6
+ - [Authentication setup](#authentication-setup)
7
+ - [Settings configuration](#settings-configuration)
8
+ - [Creating a user model](#creating-a-user-model)
9
+ - [Login views](#login-views)
10
+ - [Checking if a user is logged in](#checking-if-a-user-is-logged-in)
11
+ - [Restricting views](#restricting-views)
12
+ - [Testing with authenticated users](#testing-with-authenticated-users)
13
+ - [Settings](#settings)
14
+ - [FAQs](#faqs)
15
+ - [Installation](#installation)
16
+
17
+ ## Overview
18
+
19
+ The `plain.auth` package handles user authentication and authorization for Plain applications. You can check if a user is logged in like this:
20
+
21
+ ```python
22
+ from plain.auth import get_request_user
23
+
24
+ user = get_request_user(request)
25
+ if user:
26
+ print(f"Hello, {user.email}!")
27
+ else:
28
+ print("You are not logged in.")
29
+ ```
30
+
31
+ You can restrict a view to logged-in users using [`AuthViewMixin`](./views.py#AuthViewMixin):
32
+
33
+ ```python
34
+ from plain.auth.views import AuthViewMixin
35
+ from plain.views import View
36
+
37
+ class ProfileView(AuthViewMixin, View):
38
+ login_required = True
39
+
40
+ def get(self):
41
+ return f"Welcome, {self.user.email}!"
42
+ ```
43
+
44
+ ## Authentication setup
45
+
46
+ ### Settings configuration
47
+
48
+ Configure your authentication settings in `app/settings.py`:
49
+
50
+ ```python
51
+ INSTALLED_PACKAGES = [
52
+ # ...
53
+ "plain.auth",
54
+ "plain.sessions",
55
+ "plain.passwords", # Or another auth method
56
+ ]
57
+
58
+ MIDDLEWARE = [
59
+ "plain.sessions.middleware.SessionMiddleware",
60
+ ]
61
+
62
+ AUTH_USER_MODEL = "users.User"
63
+ AUTH_LOGIN_URL = "login"
64
+ ```
65
+
66
+ ### Creating a user model
67
+
68
+ You can create your own user model using `plain create users` or manually:
69
+
70
+ ```python
71
+ # app/users/models.py
72
+ from plain import models
73
+ from plain.passwords.models import PasswordField
74
+
75
+
76
+ class User(models.Model):
77
+ email = models.EmailField()
78
+ password = PasswordField()
79
+ is_admin = models.BooleanField(default=False)
80
+ created_at = models.DateTimeField(auto_now_add=True)
81
+
82
+ def __str__(self):
83
+ return self.email
84
+ ```
85
+
86
+ ### Login views
87
+
88
+ To log users in, you need to pair this package with an authentication method:
89
+
90
+ - [plain.passwords](../../plain-passwords/plain/passwords/README.md) - Username/password authentication
91
+ - [plain.oauth](../../plain-oauth/plain/oauth/README.md) - OAuth provider authentication
92
+ - [plain.loginlink](../../plain-loginlink/plain/loginlink/README.md) - Magic link authentication
93
+
94
+ Here's an example with password authentication:
95
+
96
+ ```python
97
+ # app/urls.py
98
+ from plain.auth.views import LogoutView
99
+ from plain.urls import path
100
+ from plain.passwords.views import PasswordLoginView
101
+
102
+
103
+ class LoginView(PasswordLoginView):
104
+ template_name = "login.html"
105
+
106
+
107
+ urlpatterns = [
108
+ path("logout/", LogoutView, name="logout"),
109
+ path("login/", LoginView, name="login"),
110
+ ]
111
+ ```
112
+
113
+ ## Checking if a user is logged in
114
+
115
+ In templates, you can use the `get_current_user()` function:
116
+
117
+ ```html
118
+ {% if get_current_user() %}
119
+ <p>Hello, {{ get_current_user().email }}!</p>
120
+ {% else %}
121
+ <p>You are not logged in.</p>
122
+ {% endif %}
123
+ ```
124
+
125
+ In Python code, use [`get_request_user()`](./requests.py#get_request_user):
126
+
127
+ ```python
128
+ from plain.auth import get_request_user
129
+
130
+ user = get_request_user(request)
131
+ if user:
132
+ print(f"Hello, {user.email}!")
133
+ else:
134
+ print("You are not logged in.")
135
+ ```
136
+
137
+ ## Restricting views
138
+
139
+ You can use [`AuthViewMixin`](./views.py#AuthViewMixin) to restrict views to logged-in users, admin users, or custom logic:
140
+
141
+ ```python
142
+ from plain.auth.views import AuthViewMixin
143
+ from plain.http import ForbiddenError403
144
+ from plain.views import View
145
+
146
+
147
+ class LoggedInView(AuthViewMixin, View):
148
+ login_required = True
149
+
150
+
151
+ class AdminOnlyView(AuthViewMixin, View):
152
+ login_required = True
153
+ admin_required = True
154
+
155
+
156
+ class CustomPermissionView(AuthViewMixin, View):
157
+ def check_auth(self):
158
+ super().check_auth()
159
+ if not self.user.is_special:
160
+ raise ForbiddenError403("You're not special!")
161
+ ```
162
+
163
+ The [`AuthViewMixin`](./views.py#AuthViewMixin) provides:
164
+
165
+ - `login_required` - Requires a logged-in user
166
+ - `admin_required` - Requires `user.is_admin` to be True
167
+ - `check_auth()` - Override for custom authorization logic
168
+
169
+ ## Testing with authenticated users
170
+
171
+ When writing tests, you can use [`login_client()`](./test.py#login_client) to simulate an authenticated user:
172
+
173
+ ```python
174
+ from plain.auth.test import login_client
175
+ from plain.test import Client
176
+
177
+ from app.users.models import User
178
+
179
+
180
+ def test_profile_view():
181
+ user = User.objects.create(email="test@example.com")
182
+ client = Client()
183
+ login_client(client, user)
184
+
185
+ response = client.get("/profile/")
186
+ assert response.status_code == 200
187
+ ```
188
+
189
+ You can also log out a test user with [`logout_client()`](./test.py#logout_client):
190
+
191
+ ```python
192
+ from plain.auth.test import login_client, logout_client
193
+
194
+ # ... after logging in
195
+ logout_client(client)
196
+ ```
197
+
198
+ ## Settings
199
+
200
+ | Setting | Default | Env var |
201
+ | ------------------------------ | -------------------- | ------------------------------------ |
202
+ | `AUTH_USER_MODEL` | Required | `PLAIN_AUTH_USER_MODEL` |
203
+ | `AUTH_LOGIN_URL` | Required | `PLAIN_AUTH_LOGIN_URL` |
204
+ | `AUTH_USER_SESSION_HASH_FIELD` | `"password"` or `""` | `PLAIN_AUTH_USER_SESSION_HASH_FIELD` |
205
+
206
+ See [`default_settings.py`](./default_settings.py) for more details.
207
+
208
+ ## FAQs
209
+
210
+ #### How do I log in a user programmatically?
211
+
212
+ You can use the [`login()`](./sessions.py#login) function to log in a user:
213
+
214
+ ```python
215
+ from plain.auth.sessions import login
216
+
217
+ login(request, user)
218
+ ```
219
+
220
+ #### How do I log out a user programmatically?
221
+
222
+ You can use the [`logout()`](./sessions.py#logout) function:
223
+
224
+ ```python
225
+ from plain.auth.sessions import logout
226
+
227
+ logout(request)
228
+ ```
229
+
230
+ #### How do I invalidate sessions when a user changes their password?
231
+
232
+ By default, if you have [plain.passwords](../../plain-passwords/plain/passwords/README.md) installed, sessions are automatically invalidated when the `password` field changes. This is controlled by the `AUTH_USER_SESSION_HASH_FIELD` setting. You can change this to a different field name, or set it to an empty string to disable this feature.
233
+
234
+ #### How do I get the user model class?
235
+
236
+ You can use the [`get_user_model()`](./sessions.py#get_user_model) function:
237
+
238
+ ```python
239
+ from plain.auth.sessions import get_user_model
240
+
241
+ User = get_user_model()
242
+ ```
243
+
244
+ ## Installation
245
+
246
+ Install the `plain.auth` package from [PyPI](https://pypi.org/project/plain.auth/):
247
+
248
+ ```bash
249
+ uv add plain.auth
250
+ ```
@@ -16,8 +16,8 @@ from .requests import get_request_user, set_request_user
16
16
  if TYPE_CHECKING:
17
17
  from plain.http import Request
18
18
 
19
- USER_ID_SESSION_KEY = "_auth_user_id"
20
- USER_HASH_SESSION_KEY = "_auth_user_hash"
19
+ _USER_ID_SESSION_KEY = "_auth_user_id"
20
+ _USER_HASH_SESSION_KEY = "_auth_user_hash"
21
21
 
22
22
 
23
23
  def get_session_auth_hash(user: Any) -> str:
@@ -40,10 +40,10 @@ def update_session_auth_hash(request: Request, user: Any) -> None:
40
40
  session = get_request_session(request)
41
41
  session.cycle_key()
42
42
  if get_request_user(request) == user:
43
- session[USER_HASH_SESSION_KEY] = get_session_auth_hash(user)
43
+ session[_USER_HASH_SESSION_KEY] = get_session_auth_hash(user)
44
44
 
45
45
 
46
- def get_session_auth_fallback_hash(user: Any) -> Generator[str, None, None]:
46
+ def _get_session_auth_fallback_hash(user: Any) -> Generator[str, None, None]:
47
47
  for fallback_secret in settings.SECRET_KEY_FALLBACKS:
48
48
  yield _get_session_auth_hash(user, secret=fallback_secret)
49
49
 
@@ -71,14 +71,14 @@ def login(request: Request, user: Any) -> None:
71
71
  else:
72
72
  session_auth_hash = ""
73
73
 
74
- if USER_ID_SESSION_KEY in session:
75
- if int(session[USER_ID_SESSION_KEY]) != user.id:
74
+ if _USER_ID_SESSION_KEY in session:
75
+ if int(session[_USER_ID_SESSION_KEY]) != user.id:
76
76
  # To avoid reusing another user's session, create a new, empty
77
77
  # session if the existing session corresponds to a different
78
78
  # authenticated user.
79
79
  session.flush()
80
80
  elif session_auth_hash and not hmac.compare_digest(
81
- force_bytes(session.get(USER_HASH_SESSION_KEY, "")),
81
+ force_bytes(session.get(_USER_HASH_SESSION_KEY, "")),
82
82
  force_bytes(session_auth_hash),
83
83
  ):
84
84
  # If the session hash does not match the current hash, reset the
@@ -89,8 +89,8 @@ def login(request: Request, user: Any) -> None:
89
89
  # typically done after user login to prevent session fixation attacks.
90
90
  session.cycle_key()
91
91
 
92
- session[USER_ID_SESSION_KEY] = user.id
93
- session[USER_HASH_SESSION_KEY] = session_auth_hash
92
+ session[_USER_ID_SESSION_KEY] = user.id
93
+ session[_USER_HASH_SESSION_KEY] = session_auth_hash
94
94
  set_request_user(request, user)
95
95
 
96
96
 
@@ -129,12 +129,12 @@ def get_user(request: Request) -> Any | None:
129
129
  """
130
130
  session = get_request_session(request)
131
131
 
132
- if USER_ID_SESSION_KEY not in session:
132
+ if _USER_ID_SESSION_KEY not in session:
133
133
  return None
134
134
 
135
135
  UserModel = get_user_model()
136
136
  try:
137
- user = UserModel.query.get(id=session[USER_ID_SESSION_KEY])
137
+ user = UserModel.query.get(id=session[_USER_ID_SESSION_KEY])
138
138
  except UserModel.DoesNotExist:
139
139
  return None
140
140
 
@@ -145,7 +145,7 @@ def get_user(request: Request) -> Any | None:
145
145
  # If it has changed (i.e. password changed), then the session
146
146
  # is no longer valid and cleared out.
147
147
  if settings.AUTH_USER_SESSION_HASH_FIELD:
148
- session_hash = session.get(USER_HASH_SESSION_KEY)
148
+ session_hash = session.get(_USER_HASH_SESSION_KEY)
149
149
  if not session_hash:
150
150
  session_hash_verified = False
151
151
  else:
@@ -161,10 +161,10 @@ def get_user(request: Request) -> Any | None:
161
161
  hmac.compare_digest(
162
162
  force_bytes(session_hash), force_bytes(fallback_auth_hash)
163
163
  )
164
- for fallback_auth_hash in get_session_auth_fallback_hash(user)
164
+ for fallback_auth_hash in _get_session_auth_fallback_hash(user)
165
165
  ):
166
166
  session.cycle_key()
167
- session[USER_HASH_SESSION_KEY] = session_auth_hash
167
+ session[_USER_HASH_SESSION_KEY] = session_auth_hash
168
168
  else:
169
169
  session.flush()
170
170
  user = None
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain.auth"
3
- version = "0.24.0"
3
+ version = "0.25.1"
4
4
  description = "Add users to your app and decide what they can access."
5
5
  authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
6
6
  readme = "README.md"
@@ -1,174 +0,0 @@
1
- # plain.auth
2
-
3
- **Add users to your app and decide what they can access.**
4
-
5
- - [Overview](#overview)
6
- - [Authentication setup](#authentication-setup)
7
- - [Settings configuration](#settings-configuration)
8
- - [Creating a user model](#creating-a-user-model)
9
- - [Login views](#login-views)
10
- - [Checking if a user is logged in](#checking-if-a-user-is-logged-in)
11
- - [Restricting views](#restricting-views)
12
- - [Installation](#installation)
13
-
14
- ## Overview
15
-
16
- The `plain.auth` package provides user authentication and authorization for Plain applications. Here's a basic example of checking if a user is logged in:
17
-
18
- ```python
19
- # In a view
20
- from plain.auth import get_request_user
21
-
22
- user = get_request_user(request)
23
- if user:
24
- print(f"Hello, {user.email}!")
25
- else:
26
- print("You are not logged in.")
27
- ```
28
-
29
- And restricting a view to logged-in users:
30
-
31
- ```python
32
- from plain.auth.views import AuthViewMixin
33
- from plain.views import View
34
-
35
- class ProfileView(AuthViewMixin, View):
36
- login_required = True
37
-
38
- def get(self):
39
- return f"Welcome, {self.user.email}!"
40
- ```
41
-
42
- ## Authentication setup
43
-
44
- ### Settings configuration
45
-
46
- Configure your authentication settings in `app/settings.py`:
47
-
48
- ```python
49
- INSTALLED_PACKAGES = [
50
- # ...
51
- "plain.auth",
52
- "plain.sessions",
53
- "plain.passwords", # Or another auth method
54
- ]
55
-
56
- MIDDLEWARE = [
57
- "plain.sessions.middleware.SessionMiddleware",
58
- ]
59
-
60
- AUTH_USER_MODEL = "users.User"
61
- AUTH_LOGIN_URL = "login"
62
- ```
63
-
64
- ### Creating a user model
65
-
66
- Create your own user model using `plain create users` or manually:
67
-
68
- ```python
69
- # app/users/models.py
70
- from plain import models
71
- from plain.passwords.models import PasswordField
72
-
73
-
74
- class User(models.Model):
75
- email = models.EmailField()
76
- password = PasswordField()
77
- is_admin = models.BooleanField(default=False)
78
- created_at = models.DateTimeField(auto_now_add=True)
79
-
80
- def __str__(self):
81
- return self.email
82
- ```
83
-
84
- ### Login views
85
-
86
- To log users in, you'll need to pair this package with an authentication method:
87
-
88
- - `plain-passwords` - Username/password authentication
89
- - `plain-oauth` - OAuth provider authentication
90
- - `plain-passkeys` (TBD) - WebAuthn/passkey authentication
91
- - `plain-passlinks` (TBD) - Magic link authentication
92
-
93
- Example with password authentication:
94
-
95
- ```python
96
- # app/urls.py
97
- from plain.auth.views import LogoutView
98
- from plain.urls import path
99
- from plain.passwords.views import PasswordLoginView
100
-
101
-
102
- class LoginView(PasswordLoginView):
103
- template_name = "login.html"
104
-
105
-
106
- urlpatterns = [
107
- path("logout/", LogoutView, name="logout"),
108
- path("login/", LoginView, name="login"),
109
- ]
110
- ```
111
-
112
- ## Checking if a user is logged in
113
-
114
- In templates, use the `get_current_user()` function:
115
-
116
- ```html
117
- {% if get_current_user() %}
118
- <p>Hello, {{ get_current_user().email }}!</p>
119
- {% else %}
120
- <p>You are not logged in.</p>
121
- {% endif %}
122
- ```
123
-
124
- In Python code, use `get_request_user()`:
125
-
126
- ```python
127
- from plain.auth import get_request_user
128
-
129
- user = get_request_user(request)
130
- if user:
131
- print(f"Hello, {user.email}!")
132
- else:
133
- print("You are not logged in.")
134
- ```
135
-
136
- ## Restricting views
137
-
138
- Use the [`AuthViewMixin`](./views.py#AuthViewMixin) to restrict views to logged-in users, admin users, or custom logic:
139
-
140
- ```python
141
- from plain.auth.views import AuthViewMixin
142
- from plain.http import ForbiddenError403
143
- from plain.views import View
144
-
145
-
146
- class LoggedInView(AuthViewMixin, View):
147
- login_required = True
148
-
149
-
150
- class AdminOnlyView(AuthViewMixin, View):
151
- login_required = True
152
- admin_required = True
153
-
154
-
155
- class CustomPermissionView(AuthViewMixin, View):
156
- def check_auth(self):
157
- super().check_auth()
158
- if not self.user.is_special:
159
- raise ForbiddenError403("You're not special!")
160
- ```
161
-
162
- The [`AuthViewMixin`](./views.py#AuthViewMixin) provides:
163
-
164
- - `login_required` - Requires a logged-in user
165
- - `admin_required` - Requires `user.is_admin` to be True
166
- - `check_auth()` - Override for custom authorization logic
167
-
168
- ## Installation
169
-
170
- Install the `plain.auth` package from [PyPI](https://pypi.org/project/plain.auth/):
171
-
172
- ```bash
173
- uv add plain.auth
174
- ```
File without changes
File without changes