refreshing and replacing old user data

This commit is contained in:
Corban-Lee Jones 2024-06-01 18:58:43 +01:00
parent 5a486de8f4
commit 25c168a982
8 changed files with 115 additions and 14 deletions

View File

@ -51,7 +51,8 @@ class DiscordAuthenticationBackend(BaseBackend):
if existing_user: if existing_user:
# The previous access token may have expired, so update it. # The previous access token may have expired, so update it.
existing_user.access_token = discord_user_data["access_token"] existing_user.update(discord_user_data)
# existing_user.access_token = discord_user_data["access_token"]
existing_user.save() existing_user.save()
return existing_user return existing_user

View File

@ -46,6 +46,7 @@ class DiscordUserOAuth2Manager(BaseUserManager):
mfa_enabled=user["mfa_enabled"], mfa_enabled=user["mfa_enabled"],
access_token=user["access_token"], access_token=user["access_token"],
token_expires=user["token_expires"], token_expires=user["token_expires"],
refresh_token=user["refresh_token"],
**extra_fields **extra_fields
) )

View File

@ -1,6 +1,7 @@
# Generated by Django 5.0.4 on 2024-05-31 22:41 # Generated by Django 5.0.4 on 2024-05-31 22:41
from django.db import migrations, models from django.db import migrations, models
from django.utils import timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -13,7 +14,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='discorduser', model_name='discorduser',
name='token_expires', name='token_expires',
field=models.DateTimeField(default=0, help_text='when to request a new access token.', verbose_name='token expires'), field=models.DateTimeField(default=timezone.now, help_text='when to request a new access token.', verbose_name='token expires'),
preserve_default=False, preserve_default=False,
), ),
] ]

View File

@ -0,0 +1,19 @@
# Generated by Django 5.0.4 on 2024-06-01 16:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0002_discorduser_token_expires'),
]
operations = [
migrations.AddField(
model_name='discorduser',
name='refresh_token',
field=models.CharField(default='1', help_text='token for the application to request a new access token.', max_length=100, verbose_name='refresh token'),
preserve_default=False,
),
]

View File

@ -1,5 +1,7 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
import logging
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -7,6 +9,8 @@ from django.contrib.auth.models import PermissionsMixin
from .managers import DiscordUserOAuth2Manager from .managers import DiscordUserOAuth2Manager
log = logging.getLogger(__name__)
class DiscordUser(PermissionsMixin): class DiscordUser(PermissionsMixin):
""" """
@ -65,11 +69,15 @@ class DiscordUser(PermissionsMixin):
max_length=100, max_length=100,
help_text=_("token for the application to make api calls on behalf of the user.") help_text=_("token for the application to make api calls on behalf of the user.")
) )
token_expires = models.DateTimeField( token_expires = models.DateTimeField(
_("token expires"), _("token expires"),
help_text=_("when to request a new access token.") help_text=_("when to request a new access token.")
) )
refresh_token = models.CharField(
_("refresh token"),
max_length=100,
help_text=_("token for the application to request a new access token.")
)
# Custom Attributes # Custom Attributes
@ -129,3 +137,23 @@ class DiscordUser(PermissionsMixin):
def get_username(self): def get_username(self):
return self.username return self.username
def update(self, raw: dict):
log.debug("updating user: %s", self.username)
log.debug("raw update data: %s", raw)
log.debug("public flags: %s, %s", raw["public_flags"], type(raw["public_flags"]))
self.username=raw["username"]
self.global_name=raw["global_name"]
self.avatar=raw["avatar"]
self.public_flags=raw["public_flags"]
self.flags=raw["flags"]
self.locale=raw["locale"]
self.mfa_enabled=raw["mfa_enabled"]
self.access_token=raw["access_token"]
self.token_expires=raw["token_expires"]
self.refresh_token=raw["refresh_token"]
self.save(force_update=True)

View File

@ -2,8 +2,10 @@
import logging import logging
import requests import requests
from datetime import timedelta
from django.conf import settings from django.conf import settings
from django.utils import timezone
from django.http import JsonResponse from django.http import JsonResponse
from django.views.generic import View, TemplateView from django.views.generic import View, TemplateView
from django.shortcuts import redirect from django.shortcuts import redirect
@ -31,17 +33,25 @@ class DiscordLoginRedirect(View):
return redirect("auth:login") return redirect("auth:login")
code = request.GET.get("code") code = request.GET.get("code")
access_token, token_expires = self.exchange_code_for_token(code) exchange_data = self.exchange_code(code)
raw_user_data = self.get_raw_user_data(access_token) access_token = exchange_data["access_token"]
raw_user_data["access_token"] = access_token
raw_user_data["token_expires"] = token_expires
# Get raw user data from discord
raw_user_data = self.get_raw_user_data(access_token)
# Add the token and expires datetime to the user data
token_expire_datetime = timezone.now() + timedelta(seconds=exchange_data["expires_in"])
raw_user_data["token_expires"] = token_expire_datetime
raw_user_data["access_token"] = access_token
raw_user_data["refresh_token"] = exchange_data["refresh_token"]
# authenticate (creates user if not exists) and login
discord_user = authenticate(request, discord_user_data=raw_user_data) discord_user = authenticate(request, discord_user_data=raw_user_data)
login(request, discord_user) login(request, discord_user)
return redirect("home:index") return redirect("home:index")
def exchange_code_for_token(self, code: str) -> tuple[str, str]: def exchange_code(self, code: str) -> dict:
""" """
Exchanges the given code for an access token. Exchanges the given code for an access token.
A call is made to the Discord API. A call is made to the Discord API.
@ -59,12 +69,27 @@ class DiscordLoginRedirect(View):
headers=request_data["headers"] headers=request_data["headers"]
) )
resp_json = response.json() return response.json()
log.debug(resp_json)
access_token = resp_json["access_token"]
expires = resp_json["expires"]
return access_token, expires def refresh_token(self, refresh_token: str) -> dict:
"""
Refresh the access token if expired, using the refresh
token. Returns a new access token, expire time and refresh
token.
"""
request_data = settings.DISCORD_REFRESH_TOKEN_REQUEST
request_data["data"]["refresh_token"] = refresh_token
log.debug("request data: %s", request_data)
response = requests.post(
url=f"{settings.DISCORD_API_URL}/oauth2/token",
data=request_data["data"],
headers=request_data["headers"]
)
return response.json()
def get_raw_user_data(self, access_token: str): def get_raw_user_data(self, access_token: str):
""" """

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-05-31 22:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0021_alter_subscription_filters'),
]
operations = [
migrations.AlterField(
model_name='subscription',
name='filters',
field=models.ManyToManyField(blank=True, to='home.filter'),
),
]

View File

@ -137,7 +137,15 @@ DISCORD_CODE_EXCHANGE_REQUEST = {
"client_secret": DISCORD_SECRET, "client_secret": DISCORD_SECRET,
"grant_type": "authorization_code", "grant_type": "authorization_code",
"redirect_uri": env("DISCORD_REDIRECT_URL"), "redirect_uri": env("DISCORD_REDIRECT_URL"),
"scope": " ".join(DISCORD_SCOPES) "scope": " ".join(DISCORD_SCOPES),
"code": ""
}
}
DISCORD_REFRESH_TOKEN_REQUEST = {
"headers": {"Content-Type": "application/x-www-form-urlencoded"},
"data": {
"grant_type": "refresh_token",
"refresh_token": ""
} }
} }
DISCORD_API_URL = env("DISCORD_API_URL") DISCORD_API_URL = env("DISCORD_API_URL")