From 6322a8b78702e6d6ab43a338b5056b4890d58436 Mon Sep 17 00:00:00 2001 From: thevickypedia Date: Tue, 23 Jan 2024 08:23:08 -0600 Subject: [PATCH] Send credentials as headers instead of form-data --- pystream/models/authenticator.py | 11 ++++--- pystream/requirements.txt | 13 ++++---- pystream/routers/auth.py | 23 ++++++------- pystream/routers/basics.py | 3 -- pystream/templates/index.html | 56 ++++++++++++++++++++++++++++++-- 5 files changed, 78 insertions(+), 28 deletions(-) diff --git a/pystream/models/authenticator.py b/pystream/models/authenticator.py index 5e9f2d6..8260dce 100644 --- a/pystream/models/authenticator.py +++ b/pystream/models/authenticator.py @@ -10,13 +10,16 @@ from pystream.models import config -async def verify_login(username: str, password: str) -> JSONResponse: +async def verify_login(credentials) -> JSONResponse: """Verifies authentication. Returns: JSONResponse: Returns JSON response with content and status code. """ + decoded_auth = base64.b64decode(credentials).decode('utf-8') + auth = bytes(decoded_auth, "utf-8").decode(encoding="unicode_escape") + username, password = auth.split(':') if not username or not password: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -35,8 +38,7 @@ async def verify_login(username: str, password: str) -> JSONResponse: status_code=200, ) - logger.error("Incorrect username or password") - logger.error(__dict__) + logger.error("Incorrect username [%s] or password [%s]", username, password) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", @@ -61,6 +63,5 @@ async def verify_token(token: str): except jwt.InvalidSignatureError as error: logger.error(error) raise go_home - username, password = base64.b64decode(decoded.credentials).decode("UTF-8").split(':') - await verify_login(username, password) + await verify_login(decoded.credentials) await verify_timestamp(decoded.timestamp) diff --git a/pystream/requirements.txt b/pystream/requirements.txt index 28286b3..76aa8e8 100644 --- a/pystream/requirements.txt +++ b/pystream/requirements.txt @@ -1,13 +1,12 @@ aiofiles==23.2.1 -fastapi==0.104.1 +fastapi==0.109.0 itsdangerous==2.1.2 -Jinja2==3.1.2 -opencv-python==4.8.1.78 -pydantic==2.5.2 +Jinja2==3.1.3 +opencv-python==4.9.0.80 +pydantic==2.5.3 pydantic-settings==2.1.0 -pydantic_core==2.14.5 +pyjwt==2.8.0 python-multipart==0.0.6 requests==2.31.0 -uvicorn==0.24.0.post1 +uvicorn==0.27.0 websockets==12.0 -pyjwt==2.8.0 diff --git a/pystream/routers/auth.py b/pystream/routers/auth.py index 2efd497..c9ac1bb 100644 --- a/pystream/routers/auth.py +++ b/pystream/routers/auth.py @@ -1,8 +1,7 @@ import time -import jwt -import base64 -from fastapi import APIRouter, Request, Form, Cookie, status +import jwt +from fastapi import APIRouter, Request, Cookie, status from fastapi.responses import RedirectResponse, JSONResponse from pystream.logger import logger @@ -24,16 +23,18 @@ async def home_page(request: Request, session_token: str = Cookie(None)): @router.post("%s" % config.static.login_endpoint, response_model=None) -async def login(request: Request, username: str = Form(...), password: str = Form(...)): - # todo: change form auth to bearer auth +async def login(request: Request): squire.log_connection(request) - await authenticator.verify_login(username, password) - # fixme: investigate if adding username and password to jwt any good, or switch to token based auth - response = RedirectResponse(config.static.home_endpoint, status_code=status.HTTP_303_SEE_OTHER) - encoded_credentials = base64.b64encode(bytes(f"{username}:{password}", "UTF-8")).decode("UTF-8") - encoded_jwt = jwt.encode(payload={"credentials": encoded_credentials, "timestamp": int(time.time())}, + authorization = request.headers.get('authorization') + await authenticator.verify_login(authorization) + # todo: instead of storing authorization to cookie + # create a db, assign a token to the user and set that token as cookie + # Since JavaScript cannot handle RedirectResponse from FastAPI + # Solution is to revert to Form, but that won't allow header auth and additional customization done by JavaScript + response = JSONResponse(content={"redirect_url": config.static.home_endpoint}, status_code=status.HTTP_200_OK) + encoded_jwt = jwt.encode(payload={"credentials": authorization, "timestamp": int(time.time())}, key=config.env.secret.get_secret_value(), algorithm="HS256") - response.set_cookie("session_token", encoded_jwt) + response.set_cookie("session_token", encoded_jwt, httponly=True) return response diff --git a/pystream/routers/basics.py b/pystream/routers/basics.py index aadc192..d264dc9 100644 --- a/pystream/routers/basics.py +++ b/pystream/routers/basics.py @@ -1,7 +1,5 @@ import os -import requests -import starlette.routing from fastapi import APIRouter, Request from fastapi.responses import FileResponse, RedirectResponse @@ -31,7 +29,6 @@ async def root(request: Request) -> RedirectResponse: Redirects to login page. """ squire.log_connection(request) - # fixme: investigate why url_for(signin) stopped working suddenly return squire.templates.TemplateResponse( name=config.fileio.index, context={"request": request, "signin": config.static.login_endpoint} diff --git a/pystream/templates/index.html b/pystream/templates/index.html index 55e4113..acc8632 100644 --- a/pystream/templates/index.html +++ b/pystream/templates/index.html @@ -8,6 +8,7 @@ + @@ -88,12 +89,12 @@
-
+ - +
@@ -105,4 +106,55 @@ history.pushState(null, document.title, location.href); }); + +