From 7a16a6b129f754a5486c441f2602a8d593edb85f Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Fri, 19 Feb 2021 09:01:38 +0300 Subject: Adds Token Refresh Route Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/routes/auth/authorize.py | 81 +++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 13 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index 975936a..2244152 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -2,17 +2,23 @@ Use a token received from the Discord OAuth2 system to fetch user information. """ +import datetime +from typing import Union + import httpx import jwt from pydantic.fields import Field from pydantic.main import BaseModel from spectree.response import Response +from starlette.authentication import requires from starlette.requests import Request from starlette.responses import JSONResponse +from backend import constants +from backend.authentication.user import User from backend.constants import SECRET_KEY -from backend.route import Route from backend.discord import fetch_bearer_token, fetch_user_details +from backend.route import Route from backend.validation import ErrorMessage, api @@ -21,7 +27,42 @@ class AuthorizeRequest(BaseModel): class AuthorizeResponse(BaseModel): - token: str = Field(description="A JWT token containing the user information") + username: str = Field("Discord display name.") + + +AUTH_FAILURE = JSONResponse({"error": "auth_failure"}, status_code=400) + + +async def process_token(bearer_token: dict) -> Union[AuthorizeResponse, AUTH_FAILURE]: + """Post a bearer token to Discord, and return a JWT and username.""" + interaction_start = datetime.datetime.now() + + try: + user_details = await fetch_user_details(bearer_token["access_token"]) + except httpx.HTTPStatusError: + AUTH_FAILURE.delete_cookie("BackendToken") + return AUTH_FAILURE + + max_age = datetime.timedelta(seconds=int(bearer_token["expires_in"])) + token_expiry = interaction_start + max_age + + data = { + "token": bearer_token["access_token"], + "refresh": bearer_token["refresh_token"], + "user_details": user_details, + "expiry": token_expiry.isoformat() + } + + token = jwt.encode(data, SECRET_KEY, algorithm="HS256") + user = User(token, user_details) + + response = JSONResponse({"username": user.display_name}) + response.set_cookie( + "BackendToken", f"JWT {token}", + secure=constants.PRODUCTION, httponly=True, samesite="strict", + max_age=bearer_token["expires_in"] + ) + return response class AuthorizeRoute(Route): @@ -40,19 +81,33 @@ class AuthorizeRoute(Route): async def post(self, request: Request) -> JSONResponse: """Generate an authorization token.""" data = await request.json() - try: - bearer_token = await fetch_bearer_token(data["token"]) - user_details = await fetch_user_details(bearer_token["access_token"]) + bearer_token = await fetch_bearer_token(data["token"], refresh=False) except httpx.HTTPStatusError: - return JSONResponse({ - "error": "auth_failure" - }, status_code=400) + return AUTH_FAILURE + + return await process_token(bearer_token) - user_details["admin"] = await request.state.db.admins.find_one( - {"_id": user_details["id"]} - ) is not None - token = jwt.encode(user_details, SECRET_KEY, algorithm="HS256") +class TokenRefreshRoute(Route): + """ + Use the refresh code from a JWT to get a new token and generate a new JWT token. + """ + + name = "refresh" + path = "/refresh" + + @requires(["authenticated"]) + @api.validate( + resp=Response(HTTP_200=AuthorizeResponse, HTTP_400=ErrorMessage), + tags=["auth"] + ) + async def post(self, request: Request) -> JSONResponse: + """Refresh an authorization token.""" + try: + token = request.user.decoded_token.get("refresh") + bearer_token = await fetch_bearer_token(token, refresh=True) + except httpx.HTTPStatusError: + return AUTH_FAILURE - return JSONResponse({"token": token}) + return await process_token(bearer_token) -- cgit v1.2.3 From f6b09f5366a0921d12707c444a8bd86e05b7df19 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Fri, 19 Feb 2021 10:13:17 +0300 Subject: Adds Expiry To Authorization Routes Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/routes/auth/authorize.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index 2244152..c6cd86c 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -28,6 +28,7 @@ class AuthorizeRequest(BaseModel): class AuthorizeResponse(BaseModel): username: str = Field("Discord display name.") + expiry: str = Field("ISO formatted timestamp of expiry.") AUTH_FAILURE = JSONResponse({"error": "auth_failure"}, status_code=400) @@ -56,7 +57,11 @@ async def process_token(bearer_token: dict) -> Union[AuthorizeResponse, AUTH_FAI token = jwt.encode(data, SECRET_KEY, algorithm="HS256") user = User(token, user_details) - response = JSONResponse({"username": user.display_name}) + response = JSONResponse({ + "username": user.display_name, + "expiry": token_expiry.isoformat() + }) + response.set_cookie( "BackendToken", f"JWT {token}", secure=constants.PRODUCTION, httponly=True, samesite="strict", -- cgit v1.2.3 From f90d0c7fddb81215b907808b8365f63f42344652 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 21 Feb 2021 01:44:01 +0300 Subject: Dynamically Selects OAuth Redirect URI Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/discord.py | 6 +++--- backend/routes/auth/authorize.py | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/discord.py b/backend/discord.py index 9cdd2c4..8cb602c 100644 --- a/backend/discord.py +++ b/backend/discord.py @@ -2,18 +2,18 @@ import httpx from backend.constants import ( - OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_REDIRECT_URI + OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET ) API_BASE_URL = "https://discord.com/api/v8" -async def fetch_bearer_token(code: str, *, refresh: bool) -> dict: +async def fetch_bearer_token(code: str, redirect: str, *, refresh: bool) -> dict: async with httpx.AsyncClient() as client: data = { "client_id": OAUTH2_CLIENT_ID, "client_secret": OAUTH2_CLIENT_SECRET, - "redirect_uri": OAUTH2_REDIRECT_URI + "redirect_uri": f"{redirect}/callback" } if refresh: diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index c6cd86c..65709ab 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -87,7 +87,8 @@ class AuthorizeRoute(Route): """Generate an authorization token.""" data = await request.json() try: - bearer_token = await fetch_bearer_token(data["token"], refresh=False) + url = request.headers.get("origin") + bearer_token = await fetch_bearer_token(data["token"], url, refresh=False) except httpx.HTTPStatusError: return AUTH_FAILURE @@ -111,7 +112,8 @@ class TokenRefreshRoute(Route): """Refresh an authorization token.""" try: token = request.user.decoded_token.get("refresh") - bearer_token = await fetch_bearer_token(token, refresh=True) + url = request.headers.get("origin") + bearer_token = await fetch_bearer_token(token, url, refresh=True) except httpx.HTTPStatusError: return AUTH_FAILURE -- cgit v1.2.3 From 02154294da8b25bf7dae1b79f170aab888f92797 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sat, 6 Mar 2021 22:42:52 +0300 Subject: Renames Token To `token` Changes the name for the token used to authorize with the backend. Co-authored-by: Joe Banks --- backend/authentication/backend.py | 2 +- backend/routes/auth/authorize.py | 4 ++-- backend/routes/forms/submit.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/authentication/backend.py b/backend/authentication/backend.py index 206d1eb..c7590e9 100644 --- a/backend/authentication/backend.py +++ b/backend/authentication/backend.py @@ -33,7 +33,7 @@ class JWTAuthenticationBackend(authentication.AuthenticationBackend): self, request: Request ) -> t.Optional[tuple[authentication.AuthCredentials, authentication.BaseUser]]: """Handles JWT authentication process.""" - cookie = request.cookies.get("BackendToken") + cookie = request.cookies.get("token") if not cookie: return None diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index 65709ab..98f9887 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -41,7 +41,7 @@ async def process_token(bearer_token: dict) -> Union[AuthorizeResponse, AUTH_FAI try: user_details = await fetch_user_details(bearer_token["access_token"]) except httpx.HTTPStatusError: - AUTH_FAILURE.delete_cookie("BackendToken") + AUTH_FAILURE.delete_cookie("token") return AUTH_FAILURE max_age = datetime.timedelta(seconds=int(bearer_token["expires_in"])) @@ -63,7 +63,7 @@ async def process_token(bearer_token: dict) -> Union[AuthorizeResponse, AUTH_FAI }) response.set_cookie( - "BackendToken", f"JWT {token}", + "token", f"JWT {token}", secure=constants.PRODUCTION, httponly=True, samesite="strict", max_age=bearer_token["expires_in"] ) diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index 4224586..8680b2d 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -75,7 +75,7 @@ class SubmitForm(Route): expiry = None response.set_cookie( - "BackendToken", f"JWT {request.user.token}", + "token", f"JWT {request.user.token}", secure=constants.PRODUCTION, httponly=True, samesite="strict", max_age=(expiry - datetime.datetime.now()).seconds ) -- cgit v1.2.3 From ca730082b523e62595687843a914adad8dbbaccf Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sat, 6 Mar 2021 22:48:19 +0300 Subject: Formats Authorize File Cleans up the authorize file, and the __init__ to maintain the project's code style. Co-authored-by: Joe Banks Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/__init__.py | 1 + backend/routes/auth/authorize.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/__init__.py b/backend/__init__.py index 5c91a65..220b457 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -15,6 +15,7 @@ ORIGINS = [ r"(https://[^.?#]*--pydis-forms\.netlify\.app)", # Netlify Previews r"(https?://[^.?#]*.forms-frontend.pages.dev)", # Cloudflare Previews ] + if not constants.PRODUCTION: # Allow all hosts on non-production deployments ORIGINS.append(r"(.*)") diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index 98f9887..26d8622 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -21,6 +21,8 @@ from backend.discord import fetch_bearer_token, fetch_user_details from backend.route import Route from backend.validation import ErrorMessage, api +AUTH_FAILURE = JSONResponse({"error": "auth_failure"}, status_code=400) + class AuthorizeRequest(BaseModel): token: str = Field(description="The access token received from Discord.") @@ -31,9 +33,6 @@ class AuthorizeResponse(BaseModel): expiry: str = Field("ISO formatted timestamp of expiry.") -AUTH_FAILURE = JSONResponse({"error": "auth_failure"}, status_code=400) - - async def process_token(bearer_token: dict) -> Union[AuthorizeResponse, AUTH_FAILURE]: """Post a bearer token to Discord, and return a JWT and username.""" interaction_start = datetime.datetime.now() -- cgit v1.2.3 From 013ea9006352ed714cbbd561880770062ea3a0e9 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sat, 6 Mar 2021 23:19:08 +0300 Subject: Sets Token Cookie To Same Site To Lax Sets the authorization token cookie's security policy to lax, to allow it to work on the site. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/routes/auth/authorize.py | 2 +- backend/routes/forms/submit.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index 26d8622..e00aef2 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -63,7 +63,7 @@ async def process_token(bearer_token: dict) -> Union[AuthorizeResponse, AUTH_FAI response.set_cookie( "token", f"JWT {token}", - secure=constants.PRODUCTION, httponly=True, samesite="strict", + secure=constants.PRODUCTION, httponly=True, samesite="lax", max_age=bearer_token["expires_in"] ) return response diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index 8680b2d..8803b7c 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -76,7 +76,7 @@ class SubmitForm(Route): response.set_cookie( "token", f"JWT {request.user.token}", - secure=constants.PRODUCTION, httponly=True, samesite="strict", + secure=constants.PRODUCTION, httponly=True, samesite="lax", max_age=(expiry - datetime.datetime.now()).seconds ) -- cgit v1.2.3 From b2ad14a87ab715eb403be68722914ed1c6b51d91 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sat, 6 Mar 2021 23:32:21 +0300 Subject: Revert "Sets Token Cookie To Same Site To Lax" This reverts commit 013ea900 Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/routes/auth/authorize.py | 2 +- backend/routes/forms/submit.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index e00aef2..26d8622 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -63,7 +63,7 @@ async def process_token(bearer_token: dict) -> Union[AuthorizeResponse, AUTH_FAI response.set_cookie( "token", f"JWT {token}", - secure=constants.PRODUCTION, httponly=True, samesite="lax", + secure=constants.PRODUCTION, httponly=True, samesite="strict", max_age=bearer_token["expires_in"] ) return response diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index 8803b7c..8680b2d 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -76,7 +76,7 @@ class SubmitForm(Route): response.set_cookie( "token", f"JWT {request.user.token}", - secure=constants.PRODUCTION, httponly=True, samesite="lax", + secure=constants.PRODUCTION, httponly=True, samesite="strict", max_age=(expiry - datetime.datetime.now()).seconds ) -- cgit v1.2.3 From 5bab39126bb6b764595a4e21b454249c01628588 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Mar 2021 00:07:19 +0300 Subject: Makes Helper To Handle Token SameSite Logic Adds a helper method to allow tokens to work on deploy previews. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/constants.py | 6 +++-- backend/routes/auth/authorize.py | 49 ++++++++++++++++++++++++++++++---------- backend/routes/forms/submit.py | 9 ++++---- 3 files changed, 45 insertions(+), 19 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/constants.py b/backend/constants.py index e1f4a5b..4bb7fd1 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -1,8 +1,9 @@ -from dotenv import load_dotenv -import os import binascii +import os from enum import Enum +from dotenv import load_dotenv + load_dotenv() @@ -12,6 +13,7 @@ MONGO_DATABASE = os.getenv("MONGO_DATABASE", "pydis_forms") SNEKBOX_URL = os.getenv("SNEKBOX_URL", "http://snekbox.default.svc.cluster.local/eval") PRODUCTION = os.getenv("PRODUCTION", "True").lower() != "false" +PRODUCTION_URL = "https://forms.pythondiscord.com/" OAUTH2_CLIENT_ID = os.getenv("OAUTH2_CLIENT_ID") OAUTH2_CLIENT_SECRET = os.getenv("OAUTH2_CLIENT_SECRET") diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index 26d8622..1e773d6 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -10,9 +10,9 @@ import jwt from pydantic.fields import Field from pydantic.main import BaseModel from spectree.response import Response +from starlette import responses from starlette.authentication import requires from starlette.requests import Request -from starlette.responses import JSONResponse from backend import constants from backend.authentication.user import User @@ -21,7 +21,7 @@ from backend.discord import fetch_bearer_token, fetch_user_details from backend.route import Route from backend.validation import ErrorMessage, api -AUTH_FAILURE = JSONResponse({"error": "auth_failure"}, status_code=400) +AUTH_FAILURE = responses.JSONResponse({"error": "auth_failure"}, status_code=400) class AuthorizeRequest(BaseModel): @@ -33,7 +33,7 @@ class AuthorizeResponse(BaseModel): expiry: str = Field("ISO formatted timestamp of expiry.") -async def process_token(bearer_token: dict) -> Union[AuthorizeResponse, AUTH_FAILURE]: +async def process_token(bearer_token: dict, origin: str) -> Union[AuthorizeResponse, AUTH_FAILURE]: """Post a bearer token to Discord, and return a JWT and username.""" interaction_start = datetime.datetime.now() @@ -56,17 +56,42 @@ async def process_token(bearer_token: dict) -> Union[AuthorizeResponse, AUTH_FAI token = jwt.encode(data, SECRET_KEY, algorithm="HS256") user = User(token, user_details) - response = JSONResponse({ + response = responses.JSONResponse({ "username": user.display_name, "expiry": token_expiry.isoformat() }) + await set_response_token(response, origin, token, bearer_token["expires_in"]) + return response + + +async def set_response_token( + response: responses.Response, + origin_url: str, + new_token: str, + expiry: int +) -> None: + """Helper that handles logic for updating a token in a set-cookie response.""" + if origin_url == constants.PRODUCTION_URL: + domain = constants.PRODUCTION_URL + samesite = "strict" + + elif not constants.PRODUCTION: + domain = None + samesite = "strict" + + else: + domain = origin_url + samesite = "None" + response.set_cookie( - "token", f"JWT {token}", - secure=constants.PRODUCTION, httponly=True, samesite="strict", - max_age=bearer_token["expires_in"] + "token", f"JWT {new_token}", + secure=constants.PRODUCTION, + httponly=True, + samesite=samesite, + domain=domain, + max_age=expiry ) - return response class AuthorizeRoute(Route): @@ -82,7 +107,7 @@ class AuthorizeRoute(Route): resp=Response(HTTP_200=AuthorizeResponse, HTTP_400=ErrorMessage), tags=["auth"] ) - async def post(self, request: Request) -> JSONResponse: + async def post(self, request: Request) -> responses.JSONResponse: """Generate an authorization token.""" data = await request.json() try: @@ -91,7 +116,7 @@ class AuthorizeRoute(Route): except httpx.HTTPStatusError: return AUTH_FAILURE - return await process_token(bearer_token) + return await process_token(bearer_token, url) class TokenRefreshRoute(Route): @@ -107,7 +132,7 @@ class TokenRefreshRoute(Route): resp=Response(HTTP_200=AuthorizeResponse, HTTP_400=ErrorMessage), tags=["auth"] ) - async def post(self, request: Request) -> JSONResponse: + async def post(self, request: Request) -> responses.JSONResponse: """Refresh an authorization token.""" try: token = request.user.decoded_token.get("refresh") @@ -116,4 +141,4 @@ class TokenRefreshRoute(Route): except httpx.HTTPStatusError: return AUTH_FAILURE - return await process_token(bearer_token) + return await process_token(bearer_token, url) diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index 8680b2d..975307b 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -20,6 +20,7 @@ from backend import constants from backend.authentication.user import User from backend.models import Form, FormResponse from backend.route import Route +from backend.routes.auth.authorize import set_response_token from backend.routes.forms.unittesting import execute_unittest from backend.validation import ErrorMessage, api @@ -74,11 +75,9 @@ class SubmitForm(Route): except ValueError: expiry = None - response.set_cookie( - "token", f"JWT {request.user.token}", - secure=constants.PRODUCTION, httponly=True, samesite="strict", - max_age=(expiry - datetime.datetime.now()).seconds - ) + origin = request.headers.get("origin") + expiry_seconds = (expiry - datetime.datetime.now()).seconds + await set_response_token(response, origin, request.user.token, expiry_seconds) except httpx.HTTPStatusError: pass -- cgit v1.2.3 From 8811959c6f13cdccb56d4fc72c1d9027e66d63d5 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Mar 2021 00:37:42 +0300 Subject: Fixes Domain URL On Token Cookie Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/routes/auth/authorize.py | 25 +++++++++++++++---------- backend/routes/forms/submit.py | 4 +++- 2 files changed, 18 insertions(+), 11 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index 1e773d6..5742b9b 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -33,7 +33,11 @@ class AuthorizeResponse(BaseModel): expiry: str = Field("ISO formatted timestamp of expiry.") -async def process_token(bearer_token: dict, origin: str) -> Union[AuthorizeResponse, AUTH_FAILURE]: +async def process_token( + bearer_token: dict, + origin_url: str, + request_url: Request.url +) -> Union[AuthorizeResponse, AUTH_FAILURE]: """Post a bearer token to Discord, and return a JWT and username.""" interaction_start = datetime.datetime.now() @@ -61,19 +65,20 @@ async def process_token(bearer_token: dict, origin: str) -> Union[AuthorizeRespo "expiry": token_expiry.isoformat() }) - await set_response_token(response, origin, token, bearer_token["expires_in"]) + await set_response_token(response, origin_url, request_url, token, bearer_token["expires_in"]) return response async def set_response_token( - response: responses.Response, - origin_url: str, - new_token: str, - expiry: int + response: responses.Response, + origin_url: str, + request_url: Request.url, + new_token: str, + expiry: int ) -> None: """Helper that handles logic for updating a token in a set-cookie response.""" if origin_url == constants.PRODUCTION_URL: - domain = constants.PRODUCTION_URL + domain = request_url samesite = "strict" elif not constants.PRODUCTION: @@ -81,7 +86,7 @@ async def set_response_token( samesite = "strict" else: - domain = origin_url + domain = request_url samesite = "None" response.set_cookie( @@ -116,7 +121,7 @@ class AuthorizeRoute(Route): except httpx.HTTPStatusError: return AUTH_FAILURE - return await process_token(bearer_token, url) + return await process_token(bearer_token, url, request.url) class TokenRefreshRoute(Route): @@ -141,4 +146,4 @@ class TokenRefreshRoute(Route): except httpx.HTTPStatusError: return AUTH_FAILURE - return await process_token(bearer_token, url) + return await process_token(bearer_token, url, request.url) diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index 975307b..ae98cfb 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -77,7 +77,9 @@ class SubmitForm(Route): origin = request.headers.get("origin") expiry_seconds = (expiry - datetime.datetime.now()).seconds - await set_response_token(response, origin, request.user.token, expiry_seconds) + await set_response_token( + response, origin, request.url, request.user.token, expiry_seconds + ) except httpx.HTTPStatusError: pass -- cgit v1.2.3 From 311a58b9a998385961a369dfbdc895c915ba28df Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Mar 2021 00:41:53 +0300 Subject: Corrects Domain On Token Cookie Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/routes/auth/authorize.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index 5742b9b..ce7b8bd 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -77,8 +77,9 @@ async def set_response_token( expiry: int ) -> None: """Helper that handles logic for updating a token in a set-cookie response.""" + stripped_domain = request_url.scheme + request_url.netloc if origin_url == constants.PRODUCTION_URL: - domain = request_url + domain = stripped_domain samesite = "strict" elif not constants.PRODUCTION: @@ -86,7 +87,7 @@ async def set_response_token( samesite = "strict" else: - domain = request_url + domain = stripped_domain samesite = "None" response.set_cookie( -- cgit v1.2.3 From 85396769cc8481d1484da369f9c1a2e0c59409f7 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Mar 2021 00:44:19 +0300 Subject: Corrects Domain On Token Cookie Correctly formats the domain set on the cookie used for tokens. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/routes/auth/authorize.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index ce7b8bd..6a27c65 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -77,7 +77,8 @@ async def set_response_token( expiry: int ) -> None: """Helper that handles logic for updating a token in a set-cookie response.""" - stripped_domain = request_url.scheme + request_url.netloc + stripped_domain = f"{request_url.scheme}://{request_url.netloc}/" + if origin_url == constants.PRODUCTION_URL: domain = stripped_domain samesite = "strict" -- cgit v1.2.3 From 8ef22e9bac402f12bb5f6e932ff67fd45b26433b Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Mar 2021 00:55:31 +0300 Subject: Switches Forwarded Protocol Header Traefik forwards https traffic to http, which causes issues with the protocol in a request's URL. This switch uses the protocol header to correctly set the protocol. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/routes/auth/authorize.py | 16 ++++++++-------- backend/routes/forms/submit.py | 5 +---- 2 files changed, 9 insertions(+), 12 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index 6a27c65..e782bcc 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -35,8 +35,7 @@ class AuthorizeResponse(BaseModel): async def process_token( bearer_token: dict, - origin_url: str, - request_url: Request.url + request: Request ) -> Union[AuthorizeResponse, AUTH_FAILURE]: """Post a bearer token to Discord, and return a JWT and username.""" interaction_start = datetime.datetime.now() @@ -65,19 +64,20 @@ async def process_token( "expiry": token_expiry.isoformat() }) - await set_response_token(response, origin_url, request_url, token, bearer_token["expires_in"]) + await set_response_token(response, request, token, bearer_token["expires_in"]) return response async def set_response_token( response: responses.Response, - origin_url: str, - request_url: Request.url, + request: Request, new_token: str, expiry: int ) -> None: """Helper that handles logic for updating a token in a set-cookie response.""" - stripped_domain = f"{request_url.scheme}://{request_url.netloc}/" + origin_url = request.headers.get("origin") + protocol = request.headers.get("X-Forwarded-Proto") or "https" + stripped_domain = f"{protocol}://{request.url.netloc}/" if origin_url == constants.PRODUCTION_URL: domain = stripped_domain @@ -123,7 +123,7 @@ class AuthorizeRoute(Route): except httpx.HTTPStatusError: return AUTH_FAILURE - return await process_token(bearer_token, url, request.url) + return await process_token(bearer_token, request) class TokenRefreshRoute(Route): @@ -148,4 +148,4 @@ class TokenRefreshRoute(Route): except httpx.HTTPStatusError: return AUTH_FAILURE - return await process_token(bearer_token, url, request.url) + return await process_token(bearer_token, request) diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index ae98cfb..2624c98 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -75,11 +75,8 @@ class SubmitForm(Route): except ValueError: expiry = None - origin = request.headers.get("origin") expiry_seconds = (expiry - datetime.datetime.now()).seconds - await set_response_token( - response, origin, request.url, request.user.token, expiry_seconds - ) + await set_response_token(response, request, request.user.token, expiry_seconds) except httpx.HTTPStatusError: pass -- cgit v1.2.3 From 99e82b5ba80c45e0e0800db93f573929ee05feea Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Mar 2021 03:05:08 +0300 Subject: Corrects Token Cookie Domain Removes schema from the token cookie's domain field. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/routes/auth/authorize.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'backend/routes/auth/authorize.py') diff --git a/backend/routes/auth/authorize.py b/backend/routes/auth/authorize.py index e782bcc..d4587f0 100644 --- a/backend/routes/auth/authorize.py +++ b/backend/routes/auth/authorize.py @@ -76,11 +76,9 @@ async def set_response_token( ) -> None: """Helper that handles logic for updating a token in a set-cookie response.""" origin_url = request.headers.get("origin") - protocol = request.headers.get("X-Forwarded-Proto") or "https" - stripped_domain = f"{protocol}://{request.url.netloc}/" if origin_url == constants.PRODUCTION_URL: - domain = stripped_domain + domain = request.url.netloc samesite = "strict" elif not constants.PRODUCTION: @@ -88,7 +86,7 @@ async def set_response_token( samesite = "strict" else: - domain = stripped_domain + domain = request.url.netloc samesite = "None" response.set_cookie( -- cgit v1.2.3