-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
eab7ab2
commit 6a0379c
Showing
8 changed files
with
371 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import requests | ||
|
||
from gwardia_hub.settings import CLIENT_ID, CLIENT_SECRET, DEBUG, GUILD_ID | ||
|
||
API_ENDPOINT = "https://discord.com/api/v10" | ||
REDIRECT_URI = ( | ||
"http://localhost:8000/auth/discord/code" | ||
if DEBUG | ||
else "https://gwardiahub.fly.dev/auth/discord/code" | ||
) | ||
DISCORD_TOKEN_COOKIE = "discord_access_token" | ||
DISCORD_REFRESH_COOKIE = "discord_refresh_token" | ||
|
||
|
||
class UnauthorizedException(Exception): | ||
pass | ||
|
||
|
||
def code_to_token(code): | ||
data = { | ||
"grant_type": "authorization_code", | ||
"code": code, | ||
"redirect_uri": REDIRECT_URI, | ||
} | ||
headers = {"Content-Type": "application/x-www-form-urlencoded"} | ||
r = requests.post( | ||
f"{API_ENDPOINT}/oauth2/token", | ||
data=data, | ||
headers=headers, | ||
auth=(CLIENT_ID, CLIENT_SECRET), | ||
) | ||
r.raise_for_status() | ||
return r.json() | ||
|
||
|
||
def refresh_token(refresh_token): | ||
data = {"grant_type": "refresh_token", "refresh_token": refresh_token} | ||
headers = {"Content-Type": "application/x-www-form-urlencoded"} | ||
r = requests.post( | ||
f"{API_ENDPOINT}/oauth2/token", | ||
data=data, | ||
headers=headers, | ||
auth=(CLIENT_ID, CLIENT_SECRET), | ||
) | ||
if r.status_code == 401: | ||
raise UnauthorizedException | ||
r.raise_for_status() | ||
return r.json() | ||
|
||
|
||
def fetch_guild_user(token): | ||
headers = { | ||
"Authorization": f"Bearer {token}", | ||
} | ||
r = requests.get( | ||
f"{API_ENDPOINT}/users/@me/guilds/{GUILD_ID}/member", | ||
headers=headers, | ||
) | ||
if r.status_code == 401: | ||
raise UnauthorizedException | ||
r.raise_for_status() | ||
return r.json() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import functools | ||
from django.http import HttpRequest, HttpResponse | ||
from django.shortcuts import redirect | ||
from django.urls import reverse | ||
from core.discord_api import ( | ||
DISCORD_REFRESH_COOKIE, | ||
DISCORD_TOKEN_COOKIE, | ||
UnauthorizedException, | ||
fetch_guild_user, | ||
) | ||
from gwardia_hub.settings import MEMBER_ROLE_ID | ||
|
||
|
||
# Usage | ||
# Add @require_discord_login() to a view to require the user to be logged in via discord | ||
# This adds a second positional argument containing the guild member object from discord api | ||
# https://discord.com/developers/docs/resources/guild#guild-member-object | ||
# Example | ||
# @require_discord_login() | ||
# def index(request, user): | ||
# By defult the user needs the member role to pass, but optionally you can pass a role id to override this behaviour | ||
# E.g. @require_discord_login("123456789") will require the user to have a role with id 123456789 | ||
# Note: Python's way of defining decorators is far from being sane | ||
def require_discord_login(required_role=MEMBER_ROLE_ID): | ||
def decorator(func): | ||
@functools.wraps(func) | ||
def wrapper(*args, **kwargs): | ||
request: HttpRequest = args[0] | ||
token = request.COOKIES.get(DISCORD_TOKEN_COOKIE) | ||
if token is None: | ||
return bounce_redirect("core:discord_refresh", request) | ||
|
||
try: | ||
discord_user = fetch_guild_user(token) | ||
except UnauthorizedException: | ||
return bounce_redirect("core:discord_refresh", request) | ||
|
||
if required_role not in discord_user["roles"]: | ||
return HttpResponse("Unauthorized", status=401) | ||
|
||
return func(*args, discord_user, **kwargs) | ||
|
||
return wrapper | ||
|
||
return decorator | ||
|
||
|
||
# Does a redirect, but also stores the current url, so we can go back to it later using the go_back function | ||
def bounce_redirect(target, request): | ||
request.session["next_url"] = request.build_absolute_uri() | ||
return redirect(target) | ||
|
||
|
||
# Redirects to a stored url if set or else to index page | ||
def go_back(request: HttpRequest): | ||
next_url = request.session.get("next_url") | ||
if next_url is None: | ||
return redirect(reverse("core:index")) | ||
del request.session["next_url"] | ||
return redirect(next_url) | ||
|
||
|
||
def set_cookies(response: HttpResponse, tokens_response): | ||
response.set_cookie( | ||
DISCORD_TOKEN_COOKIE, | ||
tokens_response["access_token"], | ||
max_age=tokens_response["expires_in"], | ||
) | ||
response.set_cookie( | ||
DISCORD_REFRESH_COOKIE, | ||
tokens_response["refresh_token"], | ||
max_age=14 * 24 * 60 * 60, | ||
) | ||
return response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Document</title> | ||
</head> | ||
|
||
<body> | ||
To be replaced by https://github.com/Gwardia-Czapli/GwardiaHub/pull/63 <br> | ||
<a href="{% url 'core:discord_login' %}">Discord</a> | ||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,17 @@ | ||
from django.urls import path | ||
from django.urls import include, path | ||
|
||
from . import views | ||
|
||
login_urls = [ | ||
path("login", views.login, name="login"), | ||
path("discord/login", views.discord_login, name="discord_login"), | ||
path("discord/code", views.discord_code, name="discord_code"), | ||
path("discord/refresh", views.discord_refresh, name="discord_refresh"), | ||
] | ||
|
||
app_name = "core" | ||
urlpatterns = [ | ||
path("", views.index, name="index"), | ||
path("profile/", views.profile, name="profile"), | ||
path("auth/", include(login_urls)), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.