diff options
| author | 2021-02-19 09:10:38 +0300 | |
|---|---|---|
| committer | 2021-02-19 09:10:38 +0300 | |
| commit | 10a2afbf27b052ba3561709bcda1ae2924b90cd2 (patch) | |
| tree | abda1723ed527486276a824188028a62dab5ded0 /backend | |
| parent | Adds Token Refresh Route (diff) | |
Refreshes User Data On Form Submit
Signed-off-by: Hassan Abouelela <[email protected]>
Diffstat (limited to '')
| -rw-r--r-- | backend/authentication/backend.py | 42 | ||||
| -rw-r--r-- | backend/routes/forms/submit.py | 46 | 
2 files changed, 69 insertions, 19 deletions
| 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 diff --git a/backend/routes/forms/submit.py b/backend/routes/forms/submit.py index d8e6d35..ec9b24f 100644 --- a/backend/routes/forms/submit.py +++ b/backend/routes/forms/submit.py @@ -3,6 +3,7 @@ Submit a form.  """  import binascii +import datetime  import hashlib  import uuid  from typing import Any, Optional @@ -15,7 +16,8 @@ from starlette.background import BackgroundTask  from starlette.requests import Request  from starlette.responses import JSONResponse -from backend.constants import FRONTEND_URL, FormFeatures, HCAPTCHA_API_SECRET +from backend import constants +from backend.authentication.user import User  from backend.models import Form, FormResponse  from backend.route import Route  from backend.validation import AuthorizationHeaders, ErrorMessage, api @@ -56,8 +58,36 @@ class SubmitForm(Route):      )      async def post(self, request: Request) -> JSONResponse:          """Submit a response to the form.""" -        data = await request.json() +        response = await self.submit(request) + +        # Silently try to update user data +        try: +            if hasattr(request.user, User.refresh_data.__name__): +                old = request.user.token +                await request.user.refresh_data() + +                if old != request.user.token: +                    try: +                        expiry = datetime.datetime.fromisoformat( +                            request.user.decoded_token.get("expiry") +                        ) +                    except ValueError: +                        expiry = None + +                    response.set_cookie( +                        "BackendToken", f"JWT {request.user.token}", +                        secure=constants.PRODUCTION, httponly=True, samesite="strict", +                        max_age=(expiry - datetime.datetime.now()).seconds +                    ) +        except httpx.HTTPStatusError: +            pass + +        return response + +    async def submit(self, request: Request) -> JSONResponse: +        """Helper method for handling submission logic.""" +        data = await request.json()          data["timestamp"] = None          if form := await request.state.db.forms.find_one( @@ -68,7 +98,7 @@ class SubmitForm(Route):              response["id"] = str(uuid.uuid4())              response["form_id"] = form.id -            if FormFeatures.DISABLE_ANTISPAM.value not in form.features: +            if constants.FormFeatures.DISABLE_ANTISPAM.value not in form.features:                  ip_hash_ctx = hashlib.md5()                  ip_hash_ctx.update(request.client.host.encode())                  ip_hash = binascii.hexlify(ip_hash_ctx.digest()) @@ -78,7 +108,7 @@ class SubmitForm(Route):                  async with httpx.AsyncClient() as client:                      query_params = { -                        "secret": HCAPTCHA_API_SECRET, +                        "secret": constants.HCAPTCHA_API_SECRET,                          "response": data.get("captcha")                      }                      r = await client.post( @@ -95,11 +125,11 @@ class SubmitForm(Route):                      "captcha_pass": captcha_data["success"]                  } -            if FormFeatures.REQUIRES_LOGIN.value in form.features: +            if constants.FormFeatures.REQUIRES_LOGIN.value in form.features:                  if request.user.is_authenticated:                      response["user"] = request.user.payload -                    if FormFeatures.COLLECT_EMAIL.value in form.features and "email" not in response["user"]:  # noqa +                    if constants.FormFeatures.COLLECT_EMAIL.value in form.features and "email" not in response["user"]:  # noqa                          return JSONResponse({                              "error": "email_required"                          }, status_code=400) @@ -132,7 +162,7 @@ class SubmitForm(Route):              )              send_webhook = None -            if FormFeatures.WEBHOOK_ENABLED.value in form.features: +            if constants.FormFeatures.WEBHOOK_ENABLED.value in form.features:                  send_webhook = BackgroundTask(                      self.send_submission_webhook,                      form=form, @@ -172,7 +202,7 @@ class SubmitForm(Route):          embed = {              "title": "New Form Response",              "description": f"{mention} submitted a response to `{form.name}`.", -            "url": f"{FRONTEND_URL}/path_to_view_form/{response.id}",  # noqa # TODO: Enter Form View URL +            "url": f"{constants.FRONTEND_URL}/path_to_view_form/{response.id}",  # noqa # TODO: Enter Form View URL              "timestamp": response.timestamp,              "color": 7506394,          } | 
