diff --git a/robosats/middleware.py b/robosats/middleware.py index 5a669ec20..3c527c71c 100644 --- a/robosats/middleware.py +++ b/robosats/middleware.py @@ -5,6 +5,8 @@ from channels.middleware import BaseMiddleware from django.conf import settings from django.contrib.auth.models import AnonymousUser, User, update_last_login +from django.utils.deprecation import MiddlewareMixin +from django.db import IntegrityError from rest_framework.authtoken.models import Token from rest_framework.exceptions import AuthenticationFailed from robohash import Robohash @@ -29,6 +31,24 @@ def __call__(self, request): response = self.get_response(request) return response +class SplitAuthorizationHeaderMiddleware(MiddlewareMixin): + """ + This middleware splits the HTTP_AUTHORIZATION, leaves on it only the `Token ` and creates + two new META headers for both PGP keys. + Given that API calls to a RoboSats API might be made from other host origin, + there is a high chance browsers will not attach cookies and other sensitive information. + Therefore, we are using the `HTTP_AUTHORIZATION` header to also embded the needed robot + pubKey and encPrivKey to create a new robot in the coordinator on the first request. + """ + + def process_request(self, request): + auth_header = request.META.get("HTTP_AUTHORIZATION", "") + split_auth = auth_header.split(" | ") + + if len(split_auth) == 3: + request.META["HTTP_AUTHORIZATION"] = split_auth[0] + request.META["PUBLIC_KEY"] = split_auth[1] + request.META["ENCRYPTED_PRIVATE_KEY"] = split_auth[2] class RobotTokenSHA256AuthenticationMiddleWare: """ @@ -70,9 +90,21 @@ def __call__(self, request): # If we get here the user does not have a robot on this coordinator # Let's create a new user & robot on-the-fly. - # The first ever request to a coordinator must include cookies for the public key (and encrypted priv key as of now). - public_key = request.COOKIES.get("public_key") - encrypted_private_key = request.COOKIES.get("encrypted_private_key", "") + # The first ever request to a coordinator must public key (and encrypted priv key as of now). Either on the + # Authorization header or in the Cookies. + + public_key = "" + encrypted_private_key = "" + + public_key = request.META.get("PUBLIC_KEY", "").replace("Public ", "") + encrypted_private_key = request.META.get( + "ENCRYPTED_PRIVATE_KEY", "" + ).replace("Private ", "") + + # Some legacy (pre-federation) clients will still send keys as cookies + if public_key == "" or encrypted_private_key == "": + public_key = request.COOKIES.get("public_key") + encrypted_private_key = request.COOKIES.get("encrypted_private_key", "") if not public_key or not encrypted_private_key: raise AuthenticationFailed( diff --git a/robosats/settings.py b/robosats/settings.py index b41b72604..a1455f430 100644 --- a/robosats/settings.py +++ b/robosats/settings.py @@ -149,16 +149,18 @@ } + MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "robosats.middleware.DisableCSRFMiddleware", + "robosats.middleware.SplitAuthorizationHeaderMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "robosats.middleware.RobotTokenSHA256AuthenticationMiddleWare", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", ]