plain.oauth 0.19.0__py3-none-any.whl → 0.20.0__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/oauth/exceptions.py CHANGED
@@ -1,12 +1,16 @@
1
1
  class OAuthError(Exception):
2
2
  """Base class for OAuth errors"""
3
3
 
4
- pass
4
+ message = "An error occurred during the OAuth process."
5
+
6
+
7
+ class OAuthStateMissingError(OAuthError):
8
+ message = "The state parameter is missing. Please try again."
5
9
 
6
10
 
7
11
  class OAuthStateMismatchError(OAuthError):
8
- pass
12
+ message = "The state parameter did not match. Please try again."
9
13
 
10
14
 
11
15
  class OAuthUserAlreadyExistsError(OAuthError):
12
- pass
16
+ message = "A user already exists with this email address. Please log in first and then connect this OAuth provider to the existing account."
@@ -6,7 +6,6 @@ from plain.models import migrations
6
6
 
7
7
  class Migration(migrations.Migration):
8
8
  dependencies = [
9
- ("users", "__first__"),
10
9
  ("plainoauth", "0004_alter_oauthconnection_access_token_and_more"),
11
10
  ]
12
11
 
plain/oauth/providers.py CHANGED
@@ -7,10 +7,11 @@ from plain.auth import login as auth_login
7
7
  from plain.http import HttpRequest, Response, ResponseRedirect
8
8
  from plain.runtime import settings
9
9
  from plain.urls import reverse
10
+ from plain.utils.cache import add_never_cache_headers
10
11
  from plain.utils.crypto import get_random_string
11
12
  from plain.utils.module_loading import import_string
12
13
 
13
- from .exceptions import OAuthError, OAuthStateMismatchError
14
+ from .exceptions import OAuthError, OAuthStateMismatchError, OAuthStateMissingError
14
15
  from .models import OAuthConnection
15
16
 
16
17
  SESSION_STATE_KEY = "plainoauth_state"
@@ -105,7 +106,11 @@ class OAuthProvider:
105
106
  if error := request.query_params.get("error"):
106
107
  raise OAuthError(error)
107
108
 
108
- state = request.query_params["state"]
109
+ try:
110
+ state = request.query_params["state"]
111
+ except KeyError as e:
112
+ raise OAuthStateMissingError() from e
113
+
109
114
  expected_state = request.session.pop(SESSION_STATE_KEY)
110
115
  request.session.save() # Make sure the pop is saved (won't save on an exception)
111
116
  if not secrets.compare_digest(state, expected_state):
@@ -130,7 +135,7 @@ class OAuthProvider:
130
135
  # Sort authorization params for consistency
131
136
  sorted_authorization_params = sorted(authorization_params.items())
132
137
  redirect_url = authorization_url + "?" + urlencode(sorted_authorization_params)
133
- return ResponseRedirect(redirect_url)
138
+ return self.get_redirect_response(redirect_url)
134
139
 
135
140
  def handle_connect_request(
136
141
  self, *, request: HttpRequest, redirect_to: str = ""
@@ -144,7 +149,7 @@ class OAuthProvider:
144
149
  )
145
150
  connection.delete()
146
151
  redirect_url = self.get_disconnect_redirect_url(request=request)
147
- return ResponseRedirect(redirect_url)
152
+ return self.get_redirect_response(redirect_url)
148
153
 
149
154
  def handle_callback_request(self, *, request: HttpRequest) -> Response:
150
155
  self.check_request_state(request=request)
@@ -174,7 +179,7 @@ class OAuthProvider:
174
179
  self.login(request=request, user=user)
175
180
 
176
181
  redirect_url = self.get_login_redirect_url(request=request)
177
- return ResponseRedirect(redirect_url)
182
+ return self.get_redirect_response(redirect_url)
178
183
 
179
184
  def login(self, *, request: HttpRequest, user: Any) -> Response:
180
185
  auth_login(request=request, user=user)
@@ -185,6 +190,15 @@ class OAuthProvider:
185
190
  def get_disconnect_redirect_url(self, *, request: HttpRequest) -> str:
186
191
  return request.data.get("next", "/")
187
192
 
193
+ def get_redirect_response(self, redirect_url: str) -> Response:
194
+ """
195
+ Returns a redirect response to the given URL.
196
+ This is a utility method to ensure consistent redirect handling.
197
+ """
198
+ response = ResponseRedirect(redirect_url)
199
+ add_never_cache_headers(response)
200
+ return response
201
+
188
202
 
189
203
  def get_oauth_provider_instance(*, provider_key: str) -> OAuthProvider:
190
204
  OAUTH_LOGIN_PROVIDERS = getattr(settings, "OAUTH_LOGIN_PROVIDERS", {})
@@ -0,0 +1,8 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="text-center">
5
+ <h1 class="text-lg">OAuth Error</h1>
6
+ <p>{{ oauth_error.message }}</p>
7
+ </div>
8
+ {% endblock %}
plain/oauth/views.py CHANGED
@@ -1,14 +1,11 @@
1
1
  import logging
2
2
 
3
3
  from plain.auth.views import AuthViewMixin
4
- from plain.http import ResponseBadRequest, ResponseRedirect
5
- from plain.templates import Template
6
- from plain.views import View
4
+ from plain.http import ResponseRedirect
5
+ from plain.views import TemplateView, View
7
6
 
8
7
  from .exceptions import (
9
8
  OAuthError,
10
- OAuthStateMismatchError,
11
- OAuthUserAlreadyExistsError,
12
9
  )
13
10
  from .providers import get_oauth_provider_instance
14
11
 
@@ -26,39 +23,29 @@ class OAuthLoginView(View):
26
23
  return provider_instance.handle_login_request(request=request)
27
24
 
28
25
 
29
- class OAuthCallbackView(View):
26
+ class OAuthCallbackView(TemplateView):
30
27
  """
31
28
  The callback view is used for signup, login, and connect.
32
29
  """
33
30
 
31
+ template_name = "oauth/callback.html"
32
+
34
33
  def get(self):
35
- request = self.request
36
34
  provider = self.url_kwargs["provider"]
37
35
  provider_instance = get_oauth_provider_instance(provider_key=provider)
38
36
  try:
39
- return provider_instance.handle_callback_request(request=request)
40
- except OAuthUserAlreadyExistsError:
41
- template = Template("oauth/error.html")
42
- return ResponseBadRequest(
43
- template.render(
44
- {
45
- "oauth_error": "A user already exists with this email address. Please log in first and then connect this OAuth provider to the existing account."
46
- }
47
- )
48
- )
49
- except OAuthStateMismatchError:
50
- template = Template("oauth/error.html")
51
- return ResponseBadRequest(
52
- template.render(
53
- {
54
- "oauth_error": "The state parameter did not match. Please try again."
55
- }
56
- )
57
- )
37
+ return provider_instance.handle_callback_request(request=self.request)
58
38
  except OAuthError as e:
59
39
  logger.exception("OAuth error")
60
- template = Template("oauth/error.html")
61
- return ResponseBadRequest(template.render({"oauth_error": str(e)}))
40
+ self.oauth_error = e
41
+
42
+ # Return a regular template response with the error
43
+ return 400, super().get()
44
+
45
+ def get_template_context(self) -> dict:
46
+ context = super().get_template_context()
47
+ context["oauth_error"] = getattr(self, "oauth_error", None)
48
+ return context
62
49
 
63
50
 
64
51
  class OAuthConnectView(AuthViewMixin, View):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.oauth
3
- Version: 0.19.0
3
+ Version: 0.20.0
4
4
  Summary: OAuth login and API access for Plain.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-Expression: BSD-3-Clause
@@ -3,21 +3,21 @@ plain/oauth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  plain/oauth/admin.py,sha256=rqrGRRUVxOlG9wiuXtndIyQxd1VT67cvFy6qWo7LF-Q,1278
4
4
  plain/oauth/config.py,sha256=0Q4IILBKQbIaxqeL9WRTH5Cka-BO3c3SOj1AdQIAJgc,167
5
5
  plain/oauth/default_settings.py,sha256=dlN1J9vSOjjxPNLp-0qe-cLTqwM4E69ZAx_8lpxMhaM,28
6
- plain/oauth/exceptions.py,sha256=TMGtIGkK3_J4rsEy1oPCmia7BnRSK8N8RMZm4_pNelA,189
6
+ plain/oauth/exceptions.py,sha256=yoZsq8XgzstuwbE2ihoet0nzpw_sVZgDrwUauh6hhUs,546
7
7
  plain/oauth/models.py,sha256=ceSw9AKZMXffE4ZKZLxIbOlSyndTCHL22GCuSF3Hpmk,6880
8
- plain/oauth/providers.py,sha256=M8r_XkKgyCz8YkLyFyNOt35EHThDvwggcDlUoC7ZYwo,7187
8
+ plain/oauth/providers.py,sha256=F5w1ZE0iEF8QZT4cdO6L7v5p2zHxby5_n-f6J0MM12E,7729
9
9
  plain/oauth/urls.py,sha256=FYzpQwhvZdcat8n3f7RyA-1Q21finKb8JEyakSOjXXg,696
10
- plain/oauth/views.py,sha256=dVlFYGsEusD2lr24eVRLVfHfEEreg_1nByepiIOoWLk,3047
10
+ plain/oauth/views.py,sha256=I27UHQ-MgWVFRoeQUIsZZ81u2V8JXGJ6R3m2TO1lc8U,2408
11
11
  plain/oauth/migrations/0001_initial.py,sha256=B9Finbn7ijEIUbkDy_B7UsKQLfMWaXd0Kx3oZrUENWc,1753
12
12
  plain/oauth/migrations/0002_alter_oauthconnection_options_and_more.py,sha256=3Mb0IU9KDRQfog0PjVbzuNv_AxCs7UVHnA0F263AKNo,581
13
13
  plain/oauth/migrations/0003_alter_oauthconnection_access_token_and_more.py,sha256=FyLfwxc2pRzF-CbdRFQRRSQTOCxc9l1womgStygm_lo,629
14
14
  plain/oauth/migrations/0004_alter_oauthconnection_access_token_and_more.py,sha256=ho9CG-lf7OVg1vBnjn7miihoioNmOIz7ObxB2QkPeSo,652
15
- plain/oauth/migrations/0005_alter_oauthconnection_unique_together_and_more.py,sha256=3hmuSvIZhn-haEKOGN3lQzcXD7b_Hy0P8Wa7LOAvJ1k,576
15
+ plain/oauth/migrations/0005_alter_oauthconnection_unique_together_and_more.py,sha256=MdTjX5Awff0QPesdAT5JuToitnbphgIh-nsJnZEAAa0,544
16
16
  plain/oauth/migrations/0006_remove_oauthconnection_unique_oauth_provider_user_id_and_more.py,sha256=UjWRezZoAzjVS70oCRQ38k_w6YJtcd_uSYT4Kc3H1pg,713
17
17
  plain/oauth/migrations/0007_alter_oauthconnection_provider_key_and_more.py,sha256=B_LW6xG1o_uA13tqUs0KniXl1JBNbQu4wMh2pW8rq5I,675
18
18
  plain/oauth/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- plain/oauth/templates/oauth/error.html,sha256=xkkWw57sZ3fz4dDfH30SVtq3okJNnJmrSRPfGxrxjh8,108
20
- plain_oauth-0.19.0.dist-info/METADATA,sha256=nX2C7XH2T6yiXmdkky5STCgWjEkFREIvYmUU-KqeQGU,10304
21
- plain_oauth-0.19.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
- plain_oauth-0.19.0.dist-info/licenses/LICENSE,sha256=cvKM3OlqHx3ijD6e34zsSUkPvzl-ya3Dd63A6EHL94U,1500
23
- plain_oauth-0.19.0.dist-info/RECORD,,
19
+ plain/oauth/templates/oauth/callback.html,sha256=4CJG0oAN0xYjw2IPkjaL7B4hwlf9um9LI4CTu50E-yE,173
20
+ plain_oauth-0.20.0.dist-info/METADATA,sha256=oBC0SBDaGuX3RV27Wfa-VHBymwrHRLqfjoImzOvKfIM,10304
21
+ plain_oauth-0.20.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
+ plain_oauth-0.20.0.dist-info/licenses/LICENSE,sha256=cvKM3OlqHx3ijD6e34zsSUkPvzl-ya3Dd63A6EHL94U,1500
23
+ plain_oauth-0.20.0.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- {% extends "base.html" %}
2
-
3
- {% block content %}
4
- <h1>OAuth Error</h1>
5
- <p>{{ oauth_error }}</p>
6
- {% endblock %}