Skip to content

Commit

Permalink
Fix authorization cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
Reckless-Satoshi committed Aug 21, 2023
1 parent 64c9c1d commit 2163f7b
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 22 deletions.
8 changes: 3 additions & 5 deletions frontend/src/services/api/ApiNativeClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,18 @@ class ApiNativeClient implements ApiClient {
'Content-Type': 'application/json',
};

if (auth != null) {
if (auth != null && auth.keys === undefined) {
headers = {
...headers,
...{
Authorization: `Token ${auth.tokenSHA256}`,
},
};
}

if (auth?.keys != null) {
} else if (auth?.keys != null) {
headers = {
...headers,
...{
Cookie: `public_key=${auth.keys.pubKey};encrypted_private_key=${auth.keys.encPrivKey}`,
Authorization: `Token ${auth.tokenSHA256} | Public ${auth.keys.pubKey} | Private ${auth.keys.encPrivKey}`,
},
};
}
Expand Down
15 changes: 8 additions & 7 deletions frontend/src/services/api/ApiWebClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@ class ApiWebClient implements ApiClient {
'Content-Type': 'application/json',
};

if (auth != null) {
if (auth != null && auth.keys === undefined) {
headers = {
...headers,
...{
Authorization: `Token ${auth.tokenSHA256}`,
},
};
}

// set cookies before sending the request
if (auth?.keys != null) {
systemClient.setCookie('public_key', auth.keys.pubKey);
systemClient.setCookie('encrypted_private_key', auth.keys.encPrivKey);
} else if (auth?.keys != null) {
headers = {
...headers,
...{
Authorization: `Token ${auth.tokenSHA256} | Public ${auth.keys.pubKey} | Private ${auth.keys.encPrivKey}`,
},
};
}

return headers;
Expand Down
62 changes: 55 additions & 7 deletions robosats/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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 rest_framework.authtoken.models import Token
from rest_framework.exceptions import AuthenticationFailed
from robohash import Robohash
Expand All @@ -30,6 +31,27 @@ def __call__(self, 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:
"""
Builds on django-rest-framework Token Authentication.
Expand All @@ -46,7 +68,6 @@ def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):

token_sha256_b91 = request.META.get("HTTP_AUTHORIZATION", "").replace(
"Token ", ""
)
Expand All @@ -70,16 +91,26 @@ 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).
# print(request.META)
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(
"On the first request to a RoboSats coordinator, you must provide as well a valid public and encrypted private PGP keys"
)

(
valid,
bad_keys_context,
Expand Down Expand Up @@ -112,7 +143,6 @@ def __call__(self, request):
# Generate avatar. Does not replace if existing.
image_path = avatar_path.joinpath(nickname + ".webp")
if not image_path.exists():

rh = Robohash(hash)
rh.assemble(roboset="set1", bgset="any") # for backgrounds ON
with open(image_path, "wb") as f:
Expand Down Expand Up @@ -159,3 +189,21 @@ async def __call__(self, scope, receive, send):
scope["user"] if token_key is None else await get_user(token_key)
)
return await super().__call__(scope, receive, send)


# class HeadersRefactorMiddleware:
# def __init__(self, get_response):
# self.get_response = get_response

# def __call__(self, request):
# auth_header = request.META.get("HTTP_AUTHORIZATION", "")
# auth_parts = auth_header.split(" | ")
# if len(auth_parts) == 3:
# request.META["HTTP_AUTHORIZATION"] = auth_parts[0]
# request.META["Public_key"] = auth_parts[1]
# request.META["Encrypted_private_key"] = auth_parts[2]

# print("HEADERS HAVE BEEN REFACTORED!")

# response = self.get_response(request)
# return response
5 changes: 2 additions & 3 deletions robosats/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,9 @@
]

CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True

# Allows Session Cookie to be read by Javascript on Client side.
SESSION_COOKIE_HTTPONLY = False
SESSION_COOKIE_SAMESITE = None
CSRF_COOKIE_SAMESITE = None

# Logging settings
if os.environ.get("LOG_TO_CONSOLE"):
Expand Down Expand Up @@ -158,13 +155,15 @@
"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",
"django.middleware.common.CommonMiddleware",
]


ROOT_URLCONF = "robosats.urls"
IMPORT_EXPORT_USE_TRANSACTIONS = True

Expand Down

0 comments on commit 2163f7b

Please sign in to comment.