From 98cd80fd042b527b8c60cb7b2a87eb6ad8bb6883 Mon Sep 17 00:00:00 2001 From: Georgios Atheridis Date: Mon, 23 Jan 2023 17:17:29 +0000 Subject: [PATCH] Tokens are now refreshed if they expire 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 | 2 - core/core/settings.py | 3 +- core/core/urls.py | 10 ++--- core/manager/errors.py | 12 ++++++ core/manager/models.py | 5 ++- core/manager/request_clip.py | 82 ++++++++++++++++++++++++++++++------ core/manager/views.py | 46 +++++++++++++++----- 7 files changed, 126 insertions(+), 34 deletions(-) diff --git a/TODO b/TODO index 8520011..8308d21 100644 --- a/TODO +++ b/TODO @@ -5,5 +5,3 @@ - Display clip info more clearly -- Clip comments? - -- Refresh tokens diff --git a/core/core/settings.py b/core/core/settings.py index e9ed006..cac0464 100644 --- a/core/core/settings.py +++ b/core/core/settings.py @@ -146,8 +146,7 @@ SITE_ID = 1 # Provider specific settings SOCIALACCOUNT_PROVIDERS = { "twitch": { - "SCOPE": [ - ], + "SCOPE": [], }, } AUTHENTICATION_BACKENDS = [ diff --git a/core/core/urls.py b/core/core/urls.py index 9c2a4cb..aaa1c1f 100644 --- a/core/core/urls.py +++ b/core/core/urls.py @@ -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/', 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/", show_clips, name="show"), + path("final", final_ranking, name="final"), ] diff --git a/core/manager/errors.py b/core/manager/errors.py index 702232d..7c7cc9b 100644 --- a/core/manager/errors.py +++ b/core/manager/errors.py @@ -34,3 +34,15 @@ class UserNotCreatedClipError(Exception): class ClipTooOldError(Exception): pass + + +class BadRequestError(Exception): + pass + + +class UnauthorizedError(Exception): + pass + + +class NotFoundError(Exception): + pass diff --git a/core/manager/models.py b/core/manager/models.py index bd497b7..064c28a 100644 --- a/core/manager/models.py +++ b/core/manager/models.py @@ -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): diff --git a/core/manager/request_clip.py b/core/manager/request_clip.py index bdfddbc..cf7ccdb 100644 --- a/core/manager/request_clip.py +++ b/core/manager/request_clip.py @@ -17,17 +17,53 @@ along with this program. If not, see . 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 diff --git a/core/manager/views.py b/core/manager/views.py index 5d66eca..f464b27 100644 --- a/core/manager/views.py +++ b/core/manager/views.py @@ -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: -- 2.30.2