Tokens are now refreshed if they expire
authorGeorgios Atheridis <georgios@atheridis.org>
Mon, 23 Jan 2023 17:17:29 +0000 (17:17 +0000)
committerGeorgios Atheridis <georgios@atheridis.org>
Mon, 23 Jan 2023 17:17:29 +0000 (17:17 +0000)
Access tokens of the user who makes the request are now used instead
of the first user in the database. Access tokens are now also refreshed
when they make an unauthorised request.

TODO
core/core/settings.py
core/core/urls.py
core/manager/errors.py
core/manager/models.py
core/manager/request_clip.py
core/manager/views.py

diff --git a/TODO b/TODO
index 8520011765dd05398f5a48b155980a95a38cd2a9..8308d21cb63fadbb0392705be4fb33549c22ded9 100644 (file)
--- a/TODO
+++ b/TODO
@@ -5,5 +5,3 @@
 
 - Display clip info more clearly
 -- Clip comments?
-
-- Refresh tokens
index e9ed0069f41f5f8fa53a40fc666903fdb0ebce92..cac04644a1b982c6d0ccb20b8edad8e0f9c87171 100644 (file)
@@ -146,8 +146,7 @@ SITE_ID = 1
 # Provider specific settings
 SOCIALACCOUNT_PROVIDERS = {
     "twitch": {
-        "SCOPE": [
-        ],
+        "SCOPE": [],
     },
 }
 AUTHENTICATION_BACKENDS = [
index 9c2a4cb5ffa1961759dc34c15a7c5ad50be35bed..aaa1c1f47f06e8b8989e6529efc403a5deed532e 100644 (file)
@@ -21,9 +21,9 @@ from manager.views import get_name, show_clips, final_ranking
 
 urlpatterns = [
     path("admin/", admin.site.urls),
-    path('accounts/', include('allauth.urls')),
-    path('', TemplateView.as_view(template_name="manager/index.html"), name='index'),
-    path('input/', get_name, name="input"),
-    path('show/<int:id>', show_clips, name="show"),
-    path('final', final_ranking, name="final"),
+    path("accounts/", include("allauth.urls")),
+    path("", TemplateView.as_view(template_name="manager/index.html"), name="index"),
+    path("input/", get_name, name="input"),
+    path("show/<int:id>", show_clips, name="show"),
+    path("final", final_ranking, name="final"),
 ]
index 702232d823536a7c19de66425d9fce6707e1e698..7c7cc9babed36ffa461397009d566ffa75102709 100644 (file)
@@ -34,3 +34,15 @@ class UserNotCreatedClipError(Exception):
 
 class ClipTooOldError(Exception):
     pass
+
+
+class BadRequestError(Exception):
+    pass
+
+
+class UnauthorizedError(Exception):
+    pass
+
+
+class NotFoundError(Exception):
+    pass
index bd497b7f0e3fd0be00c6f4f7d5433095d45f9d45..064c28a56259cabc71cd6f521d66f7d66b91ae47 100644 (file)
@@ -33,7 +33,10 @@ class AllowedChannel(models.Model):
     reset_data = models.ForeignKey(ResetData, on_delete=models.CASCADE)
 
     class Meta:
-        unique_together = ('broadcaster_id', 'reset_data',)
+        unique_together = (
+            "broadcaster_id",
+            "reset_data",
+        )
 
 
 class Clip(models.Model):
index bdfddbc9590bd16a7120f58a3b6bae69816194a7..cf7ccdb168402b5a261de850b293269d28f18018 100644 (file)
@@ -17,17 +17,53 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 import requests
 import datetime
 import pytz
-from .errors import TooManyClipsError, TooLateError, ChannelNotAllowedError, UserNotCreatedClipError, ClipTooOldError
+from .errors import (
+    TooManyClipsError,
+    TooLateError,
+    ChannelNotAllowedError,
+    UserNotCreatedClipError,
+    ClipTooOldError,
+    BadRequestError,
+    UnauthorizedError,
+    NotFoundError,
+)
 from .models import Clip, ResetData
 from urllib.parse import urlparse
 from allauth.socialaccount.models import SocialApp, SocialToken, SocialAccount
 from django.contrib.auth.models import User
 
 
+def update_tokens(social_app: SocialApp, social_token: SocialToken):
+    token_secret = social_token.token_secret
+    client_id = social_app.client_id
+    client_secret = social_app.secret
+
+    r = requests.post(
+        "https://id.twitch.tv/oauth2/token",
+        headers={
+            "Content-Type": "application/x-www-form-urlencoded",
+        },
+        params={
+            "client_id": client_id,
+            "client_secret": client_secret,
+            "grant_type": "refresh_token",
+            "refresh_token": token_secret,
+        },
+    )
+
+    r.raise_for_status()
+
+    data = r.json()
+    social_token.token = data["access_token"]
+    social_token.token_secret = data["refresh_token"]
+    social_token.save()
+
+
 def request_clip(user, clip: str):
     id = urlparse(clip).path.split("/")[-1]
     user = User.objects.get(id=user.id)
-    reset_data = ResetData.objects.latest('date_time')
+    social_account = SocialAccount.objects.get(user=user)
+    reset_data = ResetData.objects.latest("date_time")
     reset_time = reset_data.date_time
     max_clips = reset_data.max_clips
 
@@ -35,39 +71,59 @@ def request_clip(user, clip: str):
         raise TooLateError
 
     if (
-        len(
-            Clip.objects.filter(account=user).filter(
-                date_added__gt=reset_time
-            )
-        )
+        len(Clip.objects.filter(account=user).filter(date_added__gt=reset_time))
         >= max_clips
     ):
         raise TooManyClipsError
 
     social_app: SocialApp = SocialApp.objects.first()
-    oauth = SocialToken.objects.first().token
+    social_token = SocialToken.objects.get(account=social_account)
+    oauth = social_token.token
     client_id = social_app.client_id
 
     r = requests.get(
         "https://api.twitch.tv/helix/clips",
         headers={
             "Authorization": f"Bearer {oauth}",
-            "Client-Id": f"{client_id}",
+            "Client-Id": client_id,
         },
         params={"id": id},
     )
+    if r.status_code == 401:
+        update_tokens(social_app, social_token)
+
+        oauth = social_token.token
+        client_id = social_app.client_id
+        r = requests.get(
+            "https://api.twitch.tv/helix/clips",
+            headers={
+                "Authorization": f"Bearer {oauth}",
+                "Client-Id": client_id,
+            },
+            params={"id": id},
+        )
 
-    if r.status_code != 200:
-        raise Exception
+    if r.status_code == 400:
+        raise BadRequestError
+    if r.status_code == 404:
+        raise NotFoundError
+    if r.status_code == 401:
+        raise UnauthorizedError
+    r.raise_for_status()
 
-    data = r.json()["data"][0]
+    try:
+        data = r.json()["data"][0]
+    except IndexError:
+        raise NotFoundError
 
     if reset_data.user_created_clip:
         if not SocialAccount.objects.get(user=user).uid == data["creator_id"]:
             raise UserNotCreatedClipError
 
     if reset_data.allowedchannel_set.count() != 0:
-        allowed_channels = reset_data.allowedchannel_set.filter(broadcaster_id=data["broadcaster_id"])
+        allowed_channels = reset_data.allowedchannel_set.filter(
+            broadcaster_id=data["broadcaster_id"]
+        )
         if not allowed_channels.exists():
             raise ChannelNotAllowedError
 
index 5d66eca3a57e245978077eff518b2a65a2584158..f464b274a4d3cc795f45e0ed3320f61bccf1cb27 100644 (file)
@@ -20,13 +20,23 @@ from django.contrib.admin.views.decorators import staff_member_required
 
 from .request_clip import request_clip
 from .forms import ClipForm, RankForm
-from .errors import TooManyClipsError, TooLateError, ChannelNotAllowedError, UserNotCreatedClipError, ClipTooOldError
+from .errors import (
+    TooManyClipsError,
+    TooLateError,
+    ChannelNotAllowedError,
+    UserNotCreatedClipError,
+    ClipTooOldError,
+    BadRequestError,
+    UnauthorizedError,
+    NotFoundError,
+)
 from .models import Clip, ResetData
+import traceback
 
 
 @staff_member_required
 def show_clips(request, id):
-    reset_data = ResetData.objects.latest('date_time')
+    reset_data = ResetData.objects.latest("date_time")
     reset_time = reset_data.date_time
     try:
         video = Clip.objects.filter(
@@ -40,20 +50,28 @@ def show_clips(request, id):
             video.rank = form.cleaned_data["value"]
             video.save()
             return redirect(show_clips, id=id + 1)
-    return render(request, "manager/clip_viewer.html", context={
-        "video": video,
-        "ranks": range(reset_data.ranks, 0, -1),
-    })
+    return render(
+        request,
+        "manager/clip_viewer.html",
+        context={
+            "video": video,
+            "ranks": range(reset_data.ranks, 0, -1),
+        },
+    )
 
 
 @staff_member_required
 def final_ranking(request):
-    reset_data = ResetData.objects.latest('date_time')
+    reset_data = ResetData.objects.latest("date_time")
     reset_time = reset_data.date_time
-    return render(request, "manager/final.html", context={
-        "videos": Clip.objects.filter(date_added__gt=reset_time),
-        "ranks": range(reset_data.ranks, 0, -1),
-    })
+    return render(
+        request,
+        "manager/final.html",
+        context={
+            "videos": Clip.objects.filter(date_added__gt=reset_time),
+            "ranks": range(reset_data.ranks, 0, -1),
+        },
+    )
 
 
 def get_name(request):
@@ -77,6 +95,12 @@ def get_name(request):
                 message = "Sorry, you need to be the one who has created the clip."
             except ClipTooOldError:
                 message = "Sorry, the clip is too old. Please send a newer clip."
+            except BadRequestError:
+                message = "Sorry, a bad request happened on our side."
+            except UnauthorizedError:
+                message = "We couldn't connect to twitch servers. Try logging out and logging back in."
+            except NotFoundError:
+                message = "Sorry, we couldn't find that twitch clip. Make sure to check the url."
             except Exception:
                 message = "Something went wrong."
             else: