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

23
app.py
View File

@ -55,11 +55,8 @@ TODO : replace 53 by the given statement.
''' '''
def authenticate_request(data: dict): def authenticate_request(token: str):
if 'token' not in data: valid, user_id = firebase_manager.verify_jwt_token(token)
raise Exception("Missing 'token' in request body. The API doesn't support anonymous access anymore.")
else:
valid, user_id = True, data['token']
if not valid: if not valid:
raise Exception("Invalid JWT token.") raise Exception("Invalid JWT token.")
else: else:
@ -71,12 +68,12 @@ def authenticate_request(data: dict):
return user 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 = [] fn_args = []
data = request.get_json() data = request.get_json()
if not data: if not data:
raise Exception("Empty request body.") raise Exception("Empty request body.")
user = authenticate_request(data) user = authenticate_request(token)
data['user'] = user data['user'] = user
for name in parameters: for name in parameters:
if name not in data: if name not in data:
@ -115,7 +112,10 @@ def dynamic_route_handler(path):
for param in sig.parameters.values(): for param in sig.parameters.values():
parameters.append(param.name) parameters.append(param.name)
try: 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: if mapped_parameters is None:
return generic_response_maker(http.HTTPStatus.BAD_REQUEST) return generic_response_maker(http.HTTPStatus.BAD_REQUEST)
status, message = route_fn(*mapped_parameters) status, message = route_fn(*mapped_parameters)
@ -130,7 +130,7 @@ def dynamic_route_handler(path):
@apiBP.route('/Upload', methods=['POST']) @apiBP.route('/Upload', methods=['POST'])
def upload(): def upload():
form = request.form 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') name: str or None = form.get('name')
files: ImmutableMultiDict[str, FileStorage] = request.files files: ImmutableMultiDict[str, FileStorage] = request.files
@ -144,8 +144,7 @@ def upload():
if not name: if not name:
raise KeyError('name') raise KeyError('name')
data: dict = {'token': token} user: UserRecord = authenticate_request(token)
user: UserRecord = authenticate_request(data)
user_id: str = user.uid user_id: str = user.uid
for _, file in files.items(): for _, file in files.items():
@ -200,4 +199,4 @@ if __name__ == '__main__':
scheduler.start() scheduler.start()
run_simple('0.0.0.0', 3000, app, use_debugger=False, use_reloader=False) 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): def verify_jwt_token(token):
try: 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') user_id = decoded_token.get('sub')
return True, user_id return True, user_id
except jwt.ExpiredSignatureError: except jwt.ExpiredSignatureError:
return False, None return False, None
except jwt.InvalidTokenError: except jwt.InvalidTokenError:
return False, None 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]: def fetch_port() -> Union[int, None]:

View File

@ -2,6 +2,9 @@ import os
import shutil import shutil
from typing import Callable, Union from typing import Callable, Union
from firebase_admin import auth
from jwt.api_jws import decode_complete
import firebase_manager import firebase_manager
import server_mc_manager import server_mc_manager
from generic_executor import mc_manager from generic_executor import mc_manager
@ -41,4 +44,7 @@ if __name__ == '__main__':
listdir("/home/hapso/Desktop/Personal/servii-backend/servers/paper"), listdir("/home/hapso/Desktop/Personal/servii-backend/servers/paper"),
"/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 pass