Working more on web server

This commit is contained in:
Martin Asprusten 2025-04-19 16:42:46 +02:00
parent 8652ab2eec
commit 14cdefea69
25 changed files with 306 additions and 30 deletions

8
.gitignore vendored
View File

@ -1,2 +1,10 @@
.idea/*
**/__pycache__/**
WebServer/static/pyodide*
WebServer/static/socket.*
WebServer/static/python_stdlib.zip
WebServer/static/**/*.whl
pyodide.tar.bz2
src/build
src/dist
src/SantaExchange.egg-info/

34
WebServer/server.py Normal file
View File

@ -0,0 +1,34 @@
import json
from collections import defaultdict
from flask import Flask, render_template
from flask_socketio import SocketIO, join_room, leave_room, send
app = Flask(__name__)
socketio = SocketIO(app)
if __name__ == 'main':
socketio.run(app)
@app.route('/')
def return_index_page():
return render_template('index.html')
@app.route('/<exchange>')
def return_exchange_page(exchange):
return render_template('exchange.html')
@socketio.on('join')
def on_join(data):
room = data['room']
join_room(room)
@socketio.on('leave')
def on_leave(data):
room = data['room']
leave_room(room)
@socketio.on('message')
def on_message(data):
room = data['room']
send(data, to=room)

View File

@ -0,0 +1,28 @@
from Crypto.PublicKey.ECC import EccKey
from src.SantaExchange.Message import Message
from src.SantaExchange.MessageHandler import MessageHandler
from src.SantaExchange.SantasBrain import Brain
from src.SantaExchange.UserInterface import UserInterface
class ExchangeClient(MessageHandler, UserInterface):
def __init__(self, send_message_function, receive_user_function, announce_recipient_function):
MessageHandler.__init__(self)
UserInterface.__init__(self)
self.send_message_function = send_message_function
self.receive_user_function = receive_user_function
self.announce_recipient_function = announce_recipient_function
self.brain = Brain(self, self)
def send_message(self, message: Message, signing_key: EccKey):
message_string = message.generate_and_sign(signing_key)
self.send_message_function(message_string)
def receive_user(self, name: str):
self.receive_user_function(name)
def announce_recipient(self, name: str, other_info: str):
self.announce_recipient_function(name, other_info)

View File

@ -0,0 +1,23 @@
import "./pyodide.js";
async function prepare() {
let pyodide = await loadPyodide();
await pyodide.loadPackage("pycryptodome");
await pyodide.loadPackage("./santaexchange-0.1-py3-none-any.whl")
pyodide.runPython(`
class Test:
def calculate(self):
return 15
a = Test()
`);
console.log('Running python function');
console.log(pyodide.globals.get('a').calculate());
}
prepare();
addEventListener('message', e => {
self.postMessage('Loading pyodide');
self.postMessage('Loaded pyodide');
});

View File

@ -0,0 +1,38 @@
body {
background-color: #fff7f7;
color: #000000;
text-align: center;
font-family: 'Courier New';
font-size: 20px;
}
h1 {
color: #d46a64;
-webkit-text-stroke-width: 2px;
-webkit-text-stroke-color: var(--header-stroke-color);
font-size: 80px;
font-family: 'Helvetica';
margin-bottom: 20px;
}
.bodyDiv {
width: 80vw;
margin-left: auto;
margin-right: auto;
}
button {
font-family: 'Courier New';
font-size: 20px;
margin-top: 10px;
margin-bottom: 5px;
}
input {
font-size: 20px;
}
textarea {
width: 30vw;
height: 20vh;
}

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Exchange</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
<script src="{{ url_for('static', filename='socket.io.min.js') }}" integrity="sha384-mkQ3/7FUtcGyoppY6bz/PORYoGqOl7/aSUMn2ymDOJcapfS6PHqxhRTMh1RR0Q6+" crossorigin="anonymous"></script>
</head>
<body>
<h1>Santa exchange</h1>
<label for="own_name">Your name: </label><br /><input type="text" id="own_name" /><br />
<br />
<label for="info_for_santa">Info for your santa (e.g. shipping address or similar):</label><br />
<textarea id="info_for_santa"></textarea><br />
<button id="submit_info_button">Submit your info</button>
<br /><br /><br />
<p id="statusParagraph"></p>
<button>Start with current participants</button>
<p>Current participants:</p>
<div id='participantList'></div>
<!--<script type="text/javascript">
const socket = io();
const client_id = Math.floor(Math.random() * 2**64)
socket.emit('join', {room: window.location.pathname, 'client_id': client_id})
socket.on('message', data => {
// All messages are broadcast to everyone, so discard messages that were actually sent from ourselves
data = JSON.parse(data);
if (data['client_id'] != client_id) {
console.log(data);
console.log(data['message']);
window.pass_message_to_python(JSON.stringify(data['message']));
}
});
</script>-->
<script type="text/javascript">
const exchangeWorker = new Worker("{{ url_for('static', filename='exchange_worker.js') }}", { type: 'module' });
exchangeWorker.onmessage = (e) => {
console.log('Received message:');
console.log(e);
document.getElementById('statusParagraph').innerHTML = e.data;
};
exchangeWorker.postMessage('Nothing');
</script>
</body>
</html>

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DecentraSanta</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"/>
<script type="text/javascript" src="{{ url_for('static', filename='pyodide.js') }}"></script>
</head>
<body>
<div class="bodyDiv">
<h1>DecentraSanta</h1>
<p>Welcome to the decentralised secret santa exchange. This website uses cryptography to distribute secret santas
in such a way that no one but you, not even the server the communication goes through, can know who you're
giving a
gift to.</p>
<p>You can either start a new secret santa exchange, or join an existing one.</p>
<br/>
<button id="start_new" onclick="onNewExchange();">Start new exchange</button>
<br/>
<br/>
<input type="text" id="existing_address" />
<br/>
<button id="join_existing" onclick="onJoinExchange();">Join existing exchange</button>
</div>
<script type="text/javascript">
function onNewExchange() {
const byteArray = new Uint8Array(8);
self.crypto.getRandomValues(byteArray);
let randomHexString = Array.from(byteArray).map(x => x.toString(16)).reduce((a, c) => a + c);
var urlString = window.location.href;
if (!urlString.endsWith('/')) {
urlString = urlString + '/';
}
urlString = urlString + randomHexString;
window.location.href = urlString;
}
function onJoinExchange() {
const existingExchange = document.getElementById('existing_address').value;
var urlString = window.location.href;
if (!urlString.endsWith('/')) {
urlString = urlString + '/';
}
urlString = urlString + existingExchange;
window.location.href = urlString;
}
</script>
</body>
</html>

13
main.py
View File

@ -1,9 +1,9 @@
from Crypto.PublicKey.ECC import EccKey
from classes.Message import Message
from classes.MessageHandler import MessageHandler
from classes.SantasBrain import Brain
from classes.UserInterface import UserInterface
from src.SantaExchange.Message import Message
from src.SantaExchange.MessageHandler import MessageHandler
from src.SantaExchange.SantasBrain import Brain
from src.SantaExchange.UserInterface import UserInterface
participants: list[tuple[MessageHandler, UserInterface]] = []
@ -36,7 +36,7 @@ class TestUserInterface(UserInterface):
def announce_recipient(self, name: str, other_info: str):
print(f'{self.own_name}: Received {name}, {other_info}')
number_of_participants = 20
number_of_participants = 3
for i in range(number_of_participants):
test_message_handler = TestMessageHandler()
@ -48,5 +48,4 @@ for i in range(number_of_participants):
for i in range(number_of_participants):
handler, interface = participants[i]
interface.lets_go()
interface.lets_go()

View File

@ -1 +1,4 @@
pycryptodome>=3.21.0
pycryptodome>=3.21.0
flask>=3.1.0
flask-socketio>=5.5.1
setuptools>=78.1.0

24
setup-server.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
# A lot of this stuff should probably be done with npm, but I'm not a big fan of Node
echo "Checking if pyodide is installed"
if ! test -f ./WebServer/static/pyodide.js; then
if ! test -f ./pyodide.tar.bz2; then
curl -L --output pyodide.tar.bz2 https://github.com/pyodide/pyodide/releases/download/0.27.5/pyodide-0.27.5.tar.bz2
fi
tar -xvf pyodide.tar.bz2 -C ./WebServer/static --strip-components=1 pyodide/pyodide.asm.js pyodide/pyodide.asm.wasm pyodide/pyodide.js pyodide/pyodide-lock.json pyodide/python_stdlib.zip pyodide/pycryptodome-3.20.0-cp35-abi3-pyodide_2024_0_wasm32.whl
fi
echo "Pyodide is installed"
echo "Checking if socket.io is installed"
if ! test -f ./WebServer/static/socket.io.min.js; then
curl -L --output ./WebServer/static/socket.io.min.js https://cdn.socket.io/4.8.1/socket.io.min.js
fi
echo "Socket.io is installed"
echo "Building wheel file of SantaExchange package"
cd src/
python setup.py bdist_wheel
cd ../
cp ./src/dist/santaexchange-0.1-py3-none-any.whl ./WebServer/static/

View File

@ -8,11 +8,11 @@ from json import JSONDecodeError
from Crypto.PublicKey import ECC
from Crypto.PublicKey.ECC import EccKey
from classes.Message import Message
from classes.MessageTypes.Announcement import AnnouncementMessage
from classes.MessageTypes.Introduction import IntroductionMessage
from classes.MessageTypes.Ready import ReadyMessage
from classes.MessageTypes.Shuffle import ShuffleMessage
from src.SantaExchange.Message import Message
from src.SantaExchange.MessageTypes.Announcement import AnnouncementMessage
from src.SantaExchange.MessageTypes.Introduction import IntroductionMessage
from src.SantaExchange.MessageTypes.Ready import ReadyMessage
from src.SantaExchange.MessageTypes.Shuffle import ShuffleMessage
logger = logging.getLogger(__name__)
@ -22,7 +22,7 @@ class MessageHandler:
self.receivers: list[Callable[[Message], None]] = []
def send_message(self, message: Message, signing_key: EccKey):
# Must be implemented by child classes
# Must be implemented by child SantaExchange
pass
def add_message_receiver(self, message_receiver: Callable[[Message], None]):

View File

@ -1,6 +1,6 @@
import base64
from classes.Message import Message
from src.SantaExchange.Message import Message
class AnnouncementMessage(Message):

View File

@ -3,7 +3,7 @@ import base64
from Crypto.PublicKey import ECC
from Crypto.PublicKey.ECC import EccKey
from classes.Message import Message
from src.SantaExchange.Message import Message
class IntroductionMessage(Message):

View File

@ -3,7 +3,7 @@ import base64
from Crypto.PublicKey import ECC
from Crypto.PublicKey.ECC import EccKey
from classes.Message import Message
from src.SantaExchange.Message import Message
class ReadyMessage(Message):

View File

@ -1,6 +1,6 @@
import base64
from classes.Message import Message
from src.SantaExchange.Message import Message
class ShuffleMessage(Message):

View File

@ -14,15 +14,15 @@ from Crypto.Protocol.DH import key_agreement
from Crypto.PublicKey import ECC
from Crypto.PublicKey.ECC import EccKey
from classes import Message
from classes.Crypto.CSPRNG import CSPRNG
from classes.Crypto.CommutativeCipher import CommutativeCipher
from classes.MessageHandler import MessageHandler
from classes.MessageTypes.Announcement import AnnouncementMessage
from classes.MessageTypes.Introduction import IntroductionMessage
from classes.MessageTypes.Ready import ReadyMessage
from classes.MessageTypes.Shuffle import ShuffleMessage
from classes.UserInterface import UserInterface
from src.SantaExchange import Message
from src.SantaExchange.Crypto.CSPRNG import CSPRNG
from src.SantaExchange.Crypto.CommutativeCipher import CommutativeCipher
from src.SantaExchange.MessageHandler import MessageHandler
from src.SantaExchange.MessageTypes.Announcement import AnnouncementMessage
from src.SantaExchange.MessageTypes.Introduction import IntroductionMessage
from src.SantaExchange.MessageTypes.Ready import ReadyMessage
from src.SantaExchange.MessageTypes.Shuffle import ShuffleMessage
from src.SantaExchange.UserInterface import UserInterface
logger = logging.getLogger(__name__)
@ -583,11 +583,11 @@ class Brain:
# If we are the last participant, everything should now be decrypted
if own_index == len(all_participants) - 1:
self.get_anonymous_keys(shuffle_message)
self.get_anonymous_keys(shuffle_message, len(all_participants))
return shuffle_message
def get_anonymous_keys(self, message: ShuffleMessage):
def get_anonymous_keys(self, message: ShuffleMessage, number_of_participants: int):
anonymous_keys = {}
cards = message.get_cards()
for card in cards:
@ -598,6 +598,16 @@ class Brain:
logger.critical(f'Received card {card} could not be decoded as JSON. Secret santa process failed.')
self.process_failed = True
return
# Check that there are as many different cards as there are participants
for i in range(number_of_participants):
if i not in anonymous_keys:
self.process_failed = True
logger.critical(
f'Not all cards seem to have been drawn. It is possible someone is trying to cheat. '
f'Stopping secret santa exchange'
)
return
self.anonymous_keys = anonymous_keys
def build_announcement(self, all_participants: list[str]) -> Optional[AnnouncementMessage]:
@ -614,7 +624,7 @@ class Brain:
if message is None:
return None
self.get_anonymous_keys(message)
self.get_anonymous_keys(message, len(all_participants))
if self.anonymous_keys is None:
return None

View File

8
src/setup.py Normal file
View File

@ -0,0 +1,8 @@
from setuptools import setup, find_packages
from pathlib import Path
setup(
name='SantaExchange',
version="0.1",
packages=find_packages()
)