Skip to content

Commit

Permalink
Send credentials as headers instead of form-data
Browse files Browse the repository at this point in the history
  • Loading branch information
dormant-user committed Jan 23, 2024
1 parent d0f89ee commit 6322a8b
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 28 deletions.
11 changes: 6 additions & 5 deletions pystream/models/authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -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)
13 changes: 6 additions & 7 deletions pystream/requirements.txt
Original file line number Diff line number Diff line change
@@ -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
23 changes: 12 additions & 11 deletions pystream/routers/auth.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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


Expand Down
3 changes: 0 additions & 3 deletions pystream/routers/basics.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import os

import requests
import starlette.routing
from fastapi import APIRouter, Request
from fastapi.responses import FileResponse, RedirectResponse

Expand Down Expand Up @@ -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}
Expand Down
56 changes: 54 additions & 2 deletions pystream/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<meta name="author" content="Vignesh Rao">
<meta name="image" property="og:image" content="https://thevickypedia.com/img/apple-touch-icon.jpg">
<meta content="width=device-width, initial-scale=1" name="viewport">
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<!-- Disables 404 for favicon.ico which is a logo on top of the webpage tab -->
<link rel="shortcut icon" href="#">
<!-- Font Awesome icons -->
Expand Down Expand Up @@ -88,12 +89,12 @@
</div>
<div class="content">
<!-- <form action="{ url_for('signin') }" method="post"> -->
<form action="{{ signin }}" method="post">
<form>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<button type="submit">Sign In</button>
<button type="submit" onclick="submitToAPI(event)">Sign In</button>
</form>
</div>
</div>
Expand All @@ -105,4 +106,55 @@
history.pushState(null, document.title, location.href);
});
</script>
<!-- handle authentication from login page -->
<script>
function submitToAPI(event) {
event.preventDefault();
const username = $("#username").val();
const password = $("#password").val();
if (username === "" || password === "") {
alert("ERROR: Username and password are required to authenticate your request!");
return false;
}
function ConvertStringToHex(str) {
let arr = [];
for (let i = 0; i < str.length; i++) {
arr[i] = ("00" + str.charCodeAt(i).toString(16)).slice(-4);
}
return "\\u" + arr.join("\\u");
}
let hex_user = ConvertStringToHex(username);
let hex_pass = ConvertStringToHex(password);
let origin = window.location.origin
$.ajax({
method: "POST",
url: origin.concat("{{ signin }}"),
headers: {
'accept': 'application/json',
'Authorization': btoa(hex_user + ":" + hex_pass)
},
crossDomain: "true",
contentType: "application/json; charset=utf-8",
success: function (data) {
// Check if the response contains a redirect URL
if (data.redirect_url) {
// Manually handle the redirect
window.location.href = data.redirect_url;
} else {
// Handle success if needed
console.log(data);
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error(`Status: ${textStatus}, Error: ${errorThrown}`);
if (jqXHR.hasOwnProperty("responseJSON")) {
alert(jqXHR.responseJSON.detail);
} else {
alert(errorThrown);
}
}
});
}

</script>
</html>

0 comments on commit 6322a8b

Please sign in to comment.