[+] Added super-secure-token authentication

Now uses firebase complete tokens; it verifies the signature and integrity of the token, the origin of the project the token was issued for, the secret key, and finally the sub before verifying the account.
This commit is contained in:
Charles Le Maux 2024-09-20 15:54:41 +02:00
parent 18acd1e08d
commit 9ad1bdaff9
4 changed files with 50 additions and 37 deletions

View File

@ -107,6 +107,8 @@
document.addEventListener('DOMContentLoaded', () => {
const uploadForm = document.getElementById('uploadForm');
const messageDiv = document.getElementById('message');
const token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUwM2E2ODg3YWU3ZjNkMTAyNzNjNjRiMDU3ZTY1MzE1MWUyOTBiNzIiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiSXR6IFNlbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLOElVZHlzcW5kZkxxNFc5ZWlRNlpjTFpkbUVDX29UNXBVaURGQ2gzY2VDZTZXSGxvWD1zOTYtYyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9zZXJ2aS1lNjcwNSIsImF1ZCI6InNlcnZpLWU2NzA1IiwiYXV0aF90aW1lIjoxNzI2ODM5NTUwLCJ1c2VyX2lkIjoiTXBrYkRNT084UFFkZFFnQjVWZ0JRZFRNV0Y1MyIsInN1YiI6Ik1wa2JETU9POFBRZGRRZ0I1VmdCUWRUTVdGNTMiLCJpYXQiOjE3MjY4Mzk1NTAsImV4cCI6MTcyNjg0MzE1MCwiZW1haWwiOiJ0ZWNobm9wcm9kMjU0NTg1NjVAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZ29vZ2xlLmNvbSI6WyIxMTQ0Mzk0NjEyOTM5OTE1NzU5MTgiXSwiZW1haWwiOlsidGVjaG5vcHJvZDI1NDU4NTY1QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6Imdvb2dsZS5jb20ifX0.Aa3mdAmOzYET_QsGk2-QKxLGhxtXGfyAcTRnnM6cPGx0UJeSoQ-EhMIgK7HDiLVni_eMHbnwMSeEXDHEpsCWosm6e3e96zwMU3GXI1nowcnZ3CYTDH8jDCs2-6_ODomZtT2S1Lp3fD7IoSD4tDGFdo9kZNyuFGApTHhFHNAyHvfBqGL_c0c71Gfh-6ywl5C8nc07YPVbYGJu6GrS28L1vOjRSkl89Xm7o6atf38YWYWwg84QsrugRlF7Nz6yZJf7cjRY5x2guilqxrWVCWhlLiCMqFhe4oIW3BL7s3AfUC6U7DvlTyGwZJoN3fUr7V1Q5xloqSz7dcexRe1YkXXrCA";
// File Upload functionality
uploadForm.addEventListener('submit', async event => {
@ -129,6 +131,7 @@ document.addEventListener('DOMContentLoaded', () => {
try {
const response = await fetch('http://localhost:3000/Upload', {
method: 'POST',
headers: {'SST': token},
body: formData
});
@ -157,7 +160,8 @@ document.addEventListener('DOMContentLoaded', () => {
button.addEventListener('click', async event => {
event.preventDefault();
const action = button.dataset.action;
const token = "MpkbDMOO8PQddQgB5VgBQdTMWF53";
//const token = "dhmNGJYaVzNkKWgMAEOoAjaPWdc2";
const token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUwM2E2ODg3YWU3ZjNkMTAyNzNjNjRiMDU3ZTY1MzE1MWUyOTBiNzIiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiSXR6IFNlbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLOElVZHlzcW5kZkxxNFc5ZWlRNlpjTFpkbUVDX29UNXBVaURGQ2gzY2VDZTZXSGxvWD1zOTYtYyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9zZXJ2aS1lNjcwNSIsImF1ZCI6InNlcnZpLWU2NzA1IiwiYXV0aF90aW1lIjoxNzI2ODM5NTUwLCJ1c2VyX2lkIjoiTXBrYkRNT084UFFkZFFnQjVWZ0JRZFRNV0Y1MyIsInN1YiI6Ik1wa2JETU9POFBRZGRRZ0I1VmdCUWRUTVdGNTMiLCJpYXQiOjE3MjY4Mzk1NTAsImV4cCI6MTcyNjg0MzE1MCwiZW1haWwiOiJ0ZWNobm9wcm9kMjU0NTg1NjVAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZ29vZ2xlLmNvbSI6WyIxMTQ0Mzk0NjEyOTM5OTE1NzU5MTgiXSwiZW1haWwiOlsidGVjaG5vcHJvZDI1NDU4NTY1QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6Imdvb2dsZS5jb20ifX0.Aa3mdAmOzYET_QsGk2-QKxLGhxtXGfyAcTRnnM6cPGx0UJeSoQ-EhMIgK7HDiLVni_eMHbnwMSeEXDHEpsCWosm6e3e96zwMU3GXI1nowcnZ3CYTDH8jDCs2-6_ODomZtT2S1Lp3fD7IoSD4tDGFdo9kZNyuFGApTHhFHNAyHvfBqGL_c0c71Gfh-6ywl5C8nc07YPVbYGJu6GrS28L1vOjRSkl89Xm7o6atf38YWYWwg84QsrugRlF7Nz6yZJf7cjRY5x2guilqxrWVCWhlLiCMqFhe4oIW3BL7s3AfUC6U7DvlTyGwZJoN3fUr7V1Q5xloqSz7dcexRe1YkXXrCA";
const framework = document.getElementById('serverFramework').value;
const subdomain = document.getElementById('subdomain').value;
const email = document.getElementById('accountEmail').value;
@ -172,58 +176,59 @@ document.addEventListener('DOMContentLoaded', () => {
switch(action) {
case 'FetchServers':
data = {token};
data = {};
break;
case 'FetchLogs':
data = {token, name};
data = {name};
break;
case 'FetchPlayersStatus':
data = {token, name};
data = { name};
break;
case 'FetchDirContent':
data = {token, name};
data = { name};
break;
case 'AccountCreate':
data = {email, port, token};
data = {email, port, };
break;
case 'AccountDelete':
data = {subdomain, port, token};
data = {subdomain, port, };
break;
case 'ServerCreate':
data = {port, name, version, token, framework};
data = {port, name, version, framework};
break;
case 'ServerDelete':
data = {port, name, token};
data = {port, name, };
break;
case 'ServerRun':
data = {port, name, token};
data = {port, name, };
break;
case 'ServerStop':
data = {port, name, token};
data = {port, name, };
break;
case 'UpdateProperties':
data = {port, name, props, value, token};
data = {port, name, props, value, };
break;
case 'Command':
data = {port, name, command, token};
data = {port, name, command, };
break;
case 'SetSubdomain':
data = {token, subdomain};
data = {subdomain};
break;
}
sendRequest(action, data)
sendRequest(action, data, token)
.then(response => response.text())
.then(data => alert(`Response: ${data}`))
.catch(error => console.error('Error:', error));
});
});
function sendRequest(endpoint, payload) {
function sendRequest(endpoint, payload, token) {
return fetch(`http://localhost:3000/${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'SST': token,
},
body: JSON.stringify(payload)
});

37
app.py
View File

@ -55,28 +55,25 @@ TODO : replace 53 by the given statement.
'''
def authenticate_request(data: dict):
if 'token' not in data:
raise Exception("Missing 'token' in request body. The API doesn't support anonymous access anymore.")
def authenticate_request(token: str):
valid, user_id = firebase_manager.verify_jwt_token(token)
if not valid:
raise Exception("Invalid JWT token.")
else:
valid, user_id = True, data['token']
if not valid:
raise Exception("Invalid JWT token.")
else:
user = firebase_manager.get_user_from_id(user_id)
if not user:
raise Exception("User not found.")
if not user.email_verified:
raise Exception("Your google account isn't verified yet.")
user = firebase_manager.get_user_from_id(user_id)
if not user:
raise Exception("User not found.")
if not user.email_verified:
raise Exception("Your google account isn't verified yet.")
return user
def parse_and_validate_request(parameters: list[str]) -> Union[list[str], None]:
def parse_and_validate_request(parameters: list[str], token: str) -> Union[list[str], None]:
fn_args = []
data = request.get_json()
if not data:
raise Exception("Empty request body.")
user = authenticate_request(data)
user = authenticate_request(token)
data['user'] = user
for name in parameters:
if name not in data:
@ -115,7 +112,10 @@ def dynamic_route_handler(path):
for param in sig.parameters.values():
parameters.append(param.name)
try:
mapped_parameters = parse_and_validate_request(parameters)
token: str or None = request.headers.get('SST')
if not token:
return generic_response_maker(http.HTTPStatus.BAD_REQUEST, "No provided token.")
mapped_parameters = parse_and_validate_request(parameters, token)
if mapped_parameters is None:
return generic_response_maker(http.HTTPStatus.BAD_REQUEST)
status, message = route_fn(*mapped_parameters)
@ -130,7 +130,7 @@ def dynamic_route_handler(path):
@apiBP.route('/Upload', methods=['POST'])
def upload():
form = request.form
token: str or None = form.get('token')
token: str or None = request.headers.get('SST')
name: str or None = form.get('name')
files: ImmutableMultiDict[str, FileStorage] = request.files
@ -144,8 +144,7 @@ def upload():
if not name:
raise KeyError('name')
data: dict = {'token': token}
user: UserRecord = authenticate_request(data)
user: UserRecord = authenticate_request(token)
user_id: str = user.uid
for _, file in files.items():
@ -200,4 +199,4 @@ if __name__ == '__main__':
scheduler.start()
run_simple('0.0.0.0', 3000, app, use_debugger=False, use_reloader=False)
api_cleanup()
#api_cleanup()

View File

@ -22,13 +22,16 @@ def get_user_from_id(user_id):
def verify_jwt_token(token):
try:
decoded_token = jwt.decode(token, options={"verify_signature": False})
decoded_token = auth.verify_id_token(token, app=app, check_revoked=True)
user_id = decoded_token.get('sub')
return True, user_id
except jwt.ExpiredSignatureError:
return False, None
except jwt.InvalidTokenError:
return False, None
except Exception as e:
log_exception_to_firestore(e, None, {"user": None, "error-step": "auth"})
return False, None
def fetch_port() -> Union[int, None]:

View File

@ -2,6 +2,9 @@ import os
import shutil
from typing import Callable, Union
from firebase_admin import auth
from jwt.api_jws import decode_complete
import firebase_manager
import server_mc_manager
from generic_executor import mc_manager
@ -41,4 +44,7 @@ if __name__ == '__main__':
listdir("/home/hapso/Desktop/Personal/servii-backend/servers/paper"),
"/home/hapso/Desktop/Personal/servii-backend/servers/paper")
'''
token: str = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUwM2E2ODg3YWU3ZjNkMTAyNzNjNjRiMDU3ZTY1MzE1MWUyOTBiNzIiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiSXR6IFNlbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLOElVZHlzcW5kZkxxNFc5ZWlRNlpjTFpkbUVDX29UNXBVaURGQ2gzY2VDZTZXSGxvWD1zOTYtYyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9zZXJ2aS1lNjcwNSIsImF1ZCI6InNlcnZpLWU2NzA1IiwiYXV0aF90aW1lIjoxNzI2ODI1ODEzLCJ1c2VyX2lkIjoiTXBrYkRNT084UFFkZFFnQjVWZ0JRZFRNV0Y1MyIsInN1YiI6Ik1wa2JETU9POFBRZGRRZ0I1VmdCUWRUTVdGNTMiLCJpYXQiOjE3MjY4MjU4MTMsImV4cCI6MTcyNjgyOTQxMywiZW1haWwiOiJ0ZWNobm9wcm9kMjU0NTg1NjVAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZ29vZ2xlLmNvbSI6WyIxMTQ0Mzk0NjEyOTM5OTE1NzU5MTgiXSwiZW1haWwiOlsidGVjaG5vcHJvZDI1NDU4NTY1QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6Imdvb2dsZS5jb20ifX0.K1xpVX3S83b8AIUShE33bcTcN0XaxA1Uh4oe-sjVE51BGbrpHWw5SNhMzBAiaadPf_mET6-85WdmTYTFcXoDhiC5YhXDfu4fsyQq3K-zwi0ZDNOB0A3Xa7kdsCTwYSxb1DAq3zUZSLH6OHq6af1mGFfsH1WmQ9FT34ULgiBV4W1IHH4PtuYIc1kszgNAxU2lJehi2YsCYB2OZ47VohtOpfYtisJzA9er-L9WmtrMKokxTuCXAuhKIZwb0xAr_ZkZSDx8J1uhGPnPPMeID-7cXXg_tcvCv_WSlTXioQ20hG8J4Lq8Xz1ldQmbcdXl_owqty5m3MdIDiDvP8C9Oc_yLg"
decoded = auth.verify_id_token(token, app=firebase_manager.app, check_revoked=True)
print(decoded)
pass