Use POST when sending to third party oauth
authorMagnus Hagander <magnus@hagander.net>
Wed, 11 Jun 2025 10:29:24 +0000 (12:29 +0200)
committerMagnus Hagander <magnus@hagander.net>
Wed, 11 Jun 2025 18:34:09 +0000 (20:34 +0200)
Instead of prepopulating a GET request that could generate a session,
createa a form with different submit buttons and use that. In the brave
new world of AI bots, nobody cares about robots.txt anymore, so we'd get
hit by a lot of requests specifically for these logins that were then
thrown away because they couldn't log in on the third party site.

media/css/main.css
pgweb/account/oauthclient.py
pgweb/account/urls.py
templates/account/login.html

index 71364f19c525a454e09922f572ad9da92db2fa6b..4c5c0fab1428398c7c645575fbdc2796314b3f03 100644 (file)
@@ -1834,6 +1834,12 @@ th.organisation-logo {
     max-width: 650px;
 }
 
+/* Buttons that are images */
+button.imagebutton {
+    border: 0;
+    padding: 0;
+}
+
 
 /** ALL RESPONSIVE QUERIES HERE */
 /* Small devices (landscape phones, 576px and up)*/
index bd57243e03b6bf202dcd64017ce594e958809349..255233b140e8908b4c5197de1dae3b47604bb191 100644 (file)
@@ -1,6 +1,8 @@
 from django.conf import settings
 from django.contrib.auth import login as django_login
-from django.http import HttpResponse, HttpResponseRedirect
+from django.http import HttpResponse, HttpResponseRedirect, Http404
+from django.views.decorators.http import require_POST, require_GET
+from django.views.decorators.csrf import csrf_exempt
 from django.contrib.auth.models import User
 
 import os
@@ -66,7 +68,10 @@ def _login_oauth(request, provider, authurl, tokenurl, scope, authdatafunc):
     redir = '{0}/account/login/{1}/'.format(settings.SITE_ROOT, provider)
 
     oa = OAuth2Session(client_id, scope=scope, redirect_uri=redir)
-    if 'code' in request.GET:
+    if request.method == 'GET':
+        if 'code' not in request.GET:
+            raise OAuthException("No code provided")
+
         log.info("Completing {0} oauth2 step from {1}".format(provider, get_client_ip(request)))
 
         # Receiving a login request from the provider, so validate data
@@ -284,8 +289,21 @@ def oauth_login_twitter(request):
         _twitter_auth_data)
 
 
+@require_POST
+@csrf_exempt
+def initiate_oauth_login(request):
+    if 'submit' not in request.POST:
+        return HttpResponse("Invalid post", status=400)
+    return _oauth_login_dispatch(request.POST['submit'], request)
+
+
+@require_GET
 @queryparams('code', 'state', 'next', 'oauth_verifier')
 def login_oauth(request, provider):
+    return _oauth_login_dispatch(provider, request)
+
+
+def _oauth_login_dispatch(provider, request):
     fn = 'oauth_login_{0}'.format(provider)
     m = sys.modules[__name__]
     if hasattr(m, fn):
@@ -294,5 +312,7 @@ def login_oauth(request, provider):
         except OAuthException as e:
             return HttpResponse(e)
         except Exception as e:
-            log.error('Exception during OAuth: %s' % e)
+            log.error('Exception during OAuth: {}'.format(e))
             return HttpResponse('An unhandled exception occurred during the authentication process')
+    else:
+        raise Http404()
index 80d0871f55ecc58b877790195e45fe134371aa5a..12ad5955711011f73585ba11b8de2d72eba952e7 100644 (file)
@@ -41,6 +41,7 @@ urlpatterns = [
 
     # Log in, logout, change password etc
     re_path(r'^login/$', pgweb.account.views.login),
+    re_path(r'^login/oauth/$', pgweb.account.oauthclient.initiate_oauth_login),
     re_path(r'^logout/$', pgweb.account.views.logout),
     re_path(r'^changepwd/$', pgweb.account.views.changepwd),
     re_path(r'^changepwd/done/$', pgweb.account.views.change_done),
index a48279ae48b623146fcdcb4511a12321e120408e..7eaa2d55c1e2c01fe637eed251d30c7d9bf8aa55 100644 (file)
@@ -46,9 +46,12 @@ password, you can use the <a href="/account/reset/">password reset</a> form.
 
 {%if oauth_providers%}
 <h2>Third party sign in</h2>
+<form method="post" action="/account/login/oauth/">
+  <input type="hidden" name="next" value="{{next}}" />
 {%for p,d in oauth_providers%}
-<p><a href="/account/login/{{p}}/?next={{next}}"><img src="/media/img/misc/btn_login_{{p}}.png" alt="Sign in with {{p|capfirst}}" /></a></p>
+  <p><button type="submit" name="submit" value="{{p}}" class="imagebutton"><img src="/media/img/misc/btn_login_{{p}}.png" alt="Sign in with {{p|capfirst}}"></button></p>
 {%endfor%}
+</form>
 {%endif%}
 
 {%endblock%}