From 10a2afbf27b052ba3561709bcda1ae2924b90cd2 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Fri, 19 Feb 2021 09:10:38 +0300 Subject: Refreshes User Data On Form Submit Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/authentication/backend.py | 42 +++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 11 deletions(-) (limited to 'backend/authentication/backend.py') diff --git a/backend/authentication/backend.py b/backend/authentication/backend.py index f1d2ece..abe7313 100644 --- a/backend/authentication/backend.py +++ b/backend/authentication/backend.py @@ -1,6 +1,6 @@ -import jwt import typing as t +import jwt from starlette import authentication from starlette.requests import Request @@ -13,18 +13,18 @@ class JWTAuthenticationBackend(authentication.AuthenticationBackend): """Custom Starlette authentication backend for JWT.""" @staticmethod - def get_token_from_header(header: str) -> str: - """Parse JWT token from header value.""" + def get_token_from_cookie(cookie: str) -> str: + """Parse JWT token from cookie.""" try: - prefix, token = header.split() + prefix, token = cookie.split() except ValueError: raise authentication.AuthenticationError( - "Unable to split prefix and token from Authorization header." + "Unable to split prefix and token from authorization cookie." ) if prefix.upper() != "JWT": raise authentication.AuthenticationError( - f"Invalid Authorization header prefix '{prefix}'." + f"Invalid authorization cookie prefix '{prefix}'." ) return token @@ -33,11 +33,11 @@ class JWTAuthenticationBackend(authentication.AuthenticationBackend): self, request: Request ) -> t.Optional[tuple[authentication.AuthCredentials, authentication.BaseUser]]: """Handles JWT authentication process.""" - if "Authorization" not in request.headers: + cookie = request.cookies.get("BackendToken") + if not cookie: return None - auth = request.headers["Authorization"] - token = self.get_token_from_header(auth) + token = self.get_token_from_cookie(cookie) try: payload = jwt.decode(token, constants.SECRET_KEY, algorithms=["HS256"]) @@ -46,7 +46,27 @@ class JWTAuthenticationBackend(authentication.AuthenticationBackend): scopes = ["authenticated"] - if payload.get("admin") is True: + if not payload.get("token"): + raise authentication.AuthenticationError("Token is missing from JWT.") + if not payload.get("refresh"): + raise authentication.AuthenticationError( + "Refresh token is missing from JWT." + ) + + try: + user_details = payload.get("user_details") + if not user_details or not user_details.get("id"): + raise authentication.AuthenticationError("Improper user details.") + except Exception: + raise authentication.AuthenticationError("Could not parse user details.") + + admin = await request.state.db.admins.find_one( + {"_id": user_details["id"]} + ) is not None + + if admin: scopes.append("admin") - return authentication.AuthCredentials(scopes), User(token, payload) + user = User(token, user_details) + + return authentication.AuthCredentials(scopes), user -- cgit v1.2.3 From 3c4f7e71cb1ecdfd8d255b02cf44adcd90f32f01 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sat, 20 Feb 2021 03:45:16 +0300 Subject: Centralizes Admin Authentication Sets admin authentication on authenticator to allow the addition and removal of admins without creating a new token. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/authentication/backend.py | 9 ++------- backend/authentication/user.py | 9 +++++++++ backend/routes/forms/form.py | 2 +- backend/routes/forms/submit.py | 1 + 4 files changed, 13 insertions(+), 8 deletions(-) (limited to 'backend/authentication/backend.py') diff --git a/backend/authentication/backend.py b/backend/authentication/backend.py index abe7313..bdff796 100644 --- a/backend/authentication/backend.py +++ b/backend/authentication/backend.py @@ -60,13 +60,8 @@ class JWTAuthenticationBackend(authentication.AuthenticationBackend): except Exception: raise authentication.AuthenticationError("Could not parse user details.") - admin = await request.state.db.admins.find_one( - {"_id": user_details["id"]} - ) is not None - - if admin: - scopes.append("admin") - user = User(token, user_details) + if user.fetch_admin_status(request): + scopes.append("admin") return authentication.AuthCredentials(scopes), user diff --git a/backend/authentication/user.py b/backend/authentication/user.py index a1d78e5..52baa61 100644 --- a/backend/authentication/user.py +++ b/backend/authentication/user.py @@ -2,6 +2,7 @@ import typing as t import jwt from starlette.authentication import BaseUser +from starlette.requests import Request from backend.constants import SECRET_KEY from backend.discord import fetch_user_details @@ -13,6 +14,7 @@ class User(BaseUser): def __init__(self, token: str, payload: dict[str, t.Any]) -> None: self.token = token self.payload = payload + self.admin = False @property def is_authenticated(self) -> bool: @@ -32,6 +34,13 @@ class User(BaseUser): def decoded_token(self) -> dict[str, any]: return jwt.decode(self.token, SECRET_KEY, algorithms=["HS256"]) + def fetch_admin_status(self, request: Request) -> bool: + self.admin = request.state.db.admins.find_one( + {"_id": self.payload["id"]} + ) is not None + + return self.admin + async def refresh_data(self) -> None: """Fetches user data from discord, and updates the instance.""" self.payload = await fetch_user_details(self.decoded_token.get("token")) diff --git a/backend/routes/forms/form.py b/backend/routes/forms/form.py index b6b722e..e3360b1 100644 --- a/backend/routes/forms/form.py +++ b/backend/routes/forms/form.py @@ -26,7 +26,7 @@ class SingleForm(Route): @api.validate(resp=Response(HTTP_200=Form, HTTP_404=ErrorMessage), tags=["forms"]) async def get(self, request: Request) -> JSONResponse: """Returns single form information by ID.""" - admin = request.user.payload["admin"] if request.user.is_authenticated else False # noqa + admin = request.user.admin if request.user.is_authenticated else False filters = { "_id": request.path_params["form_id"] diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index 55a4875..8627a29 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -127,6 +127,7 @@ class SubmitForm(Route): if constants.FormFeatures.REQUIRES_LOGIN.value in form.features: if request.user.is_authenticated: response["user"] = request.user.payload + response["user"]["admin"] = request.user.admin if constants.FormFeatures.COLLECT_EMAIL.value in form.features and "email" not in response["user"]: # noqa return JSONResponse({ -- cgit v1.2.3 From da41f255a06516c1b7b85a587b982535cd7fec54 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Mon, 1 Mar 2021 16:56:02 +0300 Subject: Make Admin Fetch Async Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- backend/authentication/backend.py | 2 +- backend/authentication/user.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'backend/authentication/backend.py') diff --git a/backend/authentication/backend.py b/backend/authentication/backend.py index bdff796..206d1eb 100644 --- a/backend/authentication/backend.py +++ b/backend/authentication/backend.py @@ -61,7 +61,7 @@ class JWTAuthenticationBackend(authentication.AuthenticationBackend): raise authentication.AuthenticationError("Could not parse user details.") user = User(token, user_details) - if user.fetch_admin_status(request): + if await user.fetch_admin_status(request): scopes.append("admin") return authentication.AuthCredentials(scopes), user diff --git a/backend/authentication/user.py b/backend/authentication/user.py index 52baa61..857c2ed 100644 --- a/backend/authentication/user.py +++ b/backend/authentication/user.py @@ -34,8 +34,8 @@ class User(BaseUser): def decoded_token(self) -> dict[str, any]: return jwt.decode(self.token, SECRET_KEY, algorithms=["HS256"]) - def fetch_admin_status(self, request: Request) -> bool: - self.admin = request.state.db.admins.find_one( + async def fetch_admin_status(self, request: Request) -> bool: + self.admin = await request.state.db.admins.find_one( {"_id": self.payload["id"]} ) is not None -- 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/authentication/backend.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