mirror of
https://github.com/encounter/decomp.me.git
synced 2026-03-30 11:06:27 -07:00
138 lines
4.5 KiB
Python
138 lines
4.5 KiB
Python
from typing import Optional
|
|
|
|
import requests
|
|
from django.conf import settings
|
|
from django.contrib.auth import login
|
|
from django.contrib.auth.models import User
|
|
from django.db import models, transaction
|
|
from django.utils.timezone import now
|
|
from github import Github
|
|
from requests import RequestException
|
|
from rest_framework import status
|
|
from rest_framework.exceptions import APIException
|
|
|
|
from ..middleware import Request
|
|
from .profile import Profile
|
|
from .scratch import Scratch
|
|
|
|
|
|
class BadOAuthCodeException(APIException):
|
|
status_code = status.HTTP_401_UNAUTHORIZED
|
|
default_code = "bad_oauth_code"
|
|
default_detail = "Invalid or expired GitHub OAuth verification code."
|
|
|
|
|
|
class MissingOAuthScopeException(APIException):
|
|
status_code = status.HTTP_400_BAD_REQUEST
|
|
default_code = "missing_oauth_scope"
|
|
|
|
|
|
class MalformedGitHubApiResponseException(APIException):
|
|
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
default_code = "malformed_github_api_response"
|
|
default_detail = "The GitHub API returned an malformed or unexpected response."
|
|
|
|
|
|
class GitHubUser(models.Model):
|
|
user = models.OneToOneField(
|
|
User,
|
|
on_delete=models.CASCADE,
|
|
primary_key=True,
|
|
related_name="github",
|
|
)
|
|
github_id = models.PositiveIntegerField(unique=True, editable=False)
|
|
|
|
class Meta:
|
|
verbose_name = "GitHub user"
|
|
verbose_name_plural = "GitHub users"
|
|
|
|
def __str__(self) -> str:
|
|
return "@" + self.user.username
|
|
|
|
@staticmethod
|
|
@transaction.atomic
|
|
def login(request: Request, oauth_code: str) -> "GitHubUser":
|
|
try:
|
|
response = requests.post(
|
|
"https://github.com/login/oauth/access_token",
|
|
json={
|
|
"client_id": settings.GITHUB_CLIENT_ID,
|
|
"client_secret": settings.GITHUB_CLIENT_SECRET,
|
|
"code": oauth_code,
|
|
},
|
|
headers={"Accept": "application/json"},
|
|
timeout=5,
|
|
)
|
|
response_json = response.json()
|
|
except RequestException as e:
|
|
raise MalformedGitHubApiResponseException(
|
|
f"GitHub API login request failed: {e}."
|
|
)
|
|
except ValueError:
|
|
raise MalformedGitHubApiResponseException(
|
|
"GitHub API returned invalid JSON."
|
|
)
|
|
|
|
error: Optional[str] = response_json.get("error")
|
|
if error == "bad_verification_code":
|
|
raise BadOAuthCodeException()
|
|
elif error:
|
|
raise MalformedGitHubApiResponseException(
|
|
f"GitHub API login sent unknown error '{error}'."
|
|
)
|
|
|
|
try:
|
|
access_token = str(response_json["access_token"])
|
|
except KeyError:
|
|
raise MalformedGitHubApiResponseException()
|
|
|
|
details = Github(access_token).get_user()
|
|
|
|
try:
|
|
gh_user = GitHubUser.objects.get(github_id=details.id)
|
|
except GitHubUser.DoesNotExist:
|
|
gh_user = GitHubUser()
|
|
user = request.user
|
|
|
|
# make a new user if request.user already has a github account attached
|
|
if (
|
|
user.is_anonymous
|
|
or isinstance(user, User)
|
|
and GitHubUser.objects.filter(user=user).exists()
|
|
):
|
|
user = User.objects.create_user(
|
|
username=details.login,
|
|
email=details.email,
|
|
password=None,
|
|
)
|
|
|
|
assert isinstance(user, User)
|
|
|
|
gh_user.user = user
|
|
gh_user.github_id = details.id
|
|
|
|
# If the Github username has changed, update the site's username to match it
|
|
if gh_user.user.username != details.login:
|
|
gh_user.user.username = details.login
|
|
gh_user.user.save(update_fields=["username"])
|
|
|
|
gh_user.save()
|
|
|
|
profile: Profile = (
|
|
Profile.objects.filter(user=gh_user.user).first() or Profile()
|
|
)
|
|
profile.user = gh_user.user
|
|
profile.last_request_date = now()
|
|
profile.save()
|
|
|
|
# If the previous profile was anonymous, give its scratches to the logged-in profile
|
|
if request.profile.is_anonymous() and profile.id != request.profile.id:
|
|
Scratch.objects.filter(owner=request.profile).update(owner=profile)
|
|
request.profile.delete()
|
|
|
|
login(request, gh_user.user)
|
|
request.profile = profile
|
|
request.session["profile_id"] = profile.id
|
|
|
|
return gh_user
|