diff --git a/main.py b/main.py index bcde793..a9a2ee6 100755 --- a/main.py +++ b/main.py @@ -6,8 +6,10 @@ from datetime import datetime from flask import Flask, request, render_template from flask_uuid import FlaskUUID from flask_qrcode import QRcode -from flask_socketio import SocketIO +from flask_socketio import SocketIO, emit from werkzeug.middleware.proxy_fix import ProxyFix +import hashlib +import json # WIP: use sockets to broadcast when a hit to the expected uuid has been # received, so that connected clients (the Chromium public browser) can @@ -16,7 +18,8 @@ from werkzeug.middleware.proxy_fix import ProxyFix app = Flask(__name__) FlaskUUID(app) -QRcode(app) +#QRcode(app) +qrcode = QRcode(app) app.config['SECRET_KEY'] = '53a8373e7ae652cd38beba15454b1dc4' #app = ProxyFix(app, x_for=1, x_host=1) app.wsgi_app = ProxyFix(app.wsgi_app) @@ -27,23 +30,28 @@ def get_db_connection(): conn.row_factory = sqlite3.Row return conn +def hash_string(string): + salt = 'xpwQDFQ7gvcQ--rgO7l9yA' + return hashlib.md5(salt.encode() + string.encode()).hexdigest() + +def check_hash(string, hashed_string): + salt = 'xpwQDFQ7gvcQ--rgO7l9yA' + return hashed_string == hashlib.md5(salt.encode() + string.encode()).hexdigest() + +def generate_next_url(): + next_uuid = str(uuid.uuid1()) + url = request.url_root + str(next_uuid) + '/' + hash_string(next_uuid) + return url + @app.route('/') def show_qr_and_list(): # TODO: reject direct connections to server; allow access only via proxy - conn = get_db_connection() - data = conn.execute('SELECT * FROM hits ORDER BY id DESC LIMIT 10').fetchall() - conn.close() - # TODO: store next_uuid in a queue, and remove it once it's used. Accept only ids - # from the queue - next_uuid = uuid.uuid1() - return render_template("template.html", - next_uuid=str(next_uuid), - hits=data) + return render_template("template.html") -@app.route('/') -def catch_uuids(id): - ua = request.headers.get('Remote-User') +@app.route('//') +def catch_uuids(id, hashed): + user = request.headers.get('Remote-User') # TODO: Check directly with Authelia using https://auth.agofer.net/api/verify time = datetime.now().strftime("%A %Y-%m-%d %H:%M:%S") error = None @@ -51,31 +59,44 @@ def catch_uuids(id): conn = get_db_connection() existing = conn.execute( 'SELECT * FROM hits WHERE uuid = ?', (str(id),)).fetchone() - if not ua: + if not user: error = 'NO_USERNAME' - elif id.fields[5] != uuid.getnode(): - error = 'DIFFERENT_NODE' elif existing: error = 'ALREADY_USED' + elif not check_hash(str(id), str(hashed)): + error = 'DIFFERENT_NODE' else: - conn.execute("INSERT INTO hits (uuid, user) VALUES (?, ?)", (str(id), ua)) + conn.execute("INSERT INTO hits (uuid, user) VALUES (?, ?)", (str(id), user)) conn.commit() - socketio.emit('qr_used', {'data': (time, ua, str(id))}) + #socketio.emit('qr_used', {'data': (time, user, str(id))}) + url = generate_next_url() + qr = qrcode(url) + socketio.emit('qr_used', {'data': (qr, url, user, time)}) data = conn.execute( - 'SELECT * FROM hits WHERE user = ? ORDER BY id DESC LIMIT 10', (ua,) + 'SELECT * FROM hits WHERE user = ? ORDER BY id DESC LIMIT 10', (user,) ).fetchall() conn.close() - return render_template('thanks.html', user=ua, time=time, error=error, hits=data) + return render_template('thanks.html', user=user, time=time, error=error, hits=data) @socketio.on('message') def handle_message(data): print('received message: ' + data) -@socketio.on('my_event') -def handle_my_custom_event(json): - print('received json: ' + str(json)) +@socketio.on('connection') +def handle_initial_connection(json_data): + url = generate_next_url() + qr = qrcode(url) + conn = get_db_connection() + last_entries = conn.execute('SELECT user, created FROM hits ORDER BY id DESC LIMIT 10') + array_entries = [] + for entry in last_entries: + array_entries.append((entry[0], entry[1])) + conn.close() + emit('initial_qr', {'data': (qr, url, array_entries)}) + #socketio.emit('initial_qr', {'data': (qr,)}) + print('received json: ' + str(json_data)) if __name__ == '__main__': #app.run(host='0.0.0.0') diff --git a/static/loading.gif b/static/loading.gif new file mode 100644 index 0000000..a84d1ca Binary files /dev/null and b/static/loading.gif differ diff --git a/templates/template.html b/templates/template.html index bed9666..30a968e 100644 --- a/templates/template.html +++ b/templates/template.html @@ -7,16 +7,32 @@ $(document).ready(function() { var socket = io(); socket.on('connect', function() { - socket.emit('my_event', {data: 'Connected to server'}); + socket.emit('connection', {data: 'Connected to server'}); }); - socket.on('qr_used', function(msg, cb) { - location.reload(true); - /* - $('#log').append('
  • ' + msg.data[0] + ': ' + msg.data[1] + ' (' + msg.data[2] + ')
  • '); - $('#qrcode').attr('src',src); + socket.on('initial_qr', function(msg, cb) { + $('#qrcode').prop('src', msg.data[0]); + $('#url').prop('href', msg.data[1]).text(msg.data[1]); + $('#log').empty(); + msg.data[2].forEach(function(item) { + $('#log').append('
  • ' + item[0] + ': ' + item[1] + '
  • '); + } + ); if (cb) cb(); + }); + socket.on('qr_used', function(msg, cb) { + /* + location.reload(true); */ + $('#qrcode').prop('src', msg.data[0]).fadeTo('fast', 0.05, function(){$(this).delay(100).fadeTo('slow', 1)}); + $('#url').prop('href', msg.data[1]).text(msg.data[1]); + //$('#log').fadeIn(500, function() { $(this).prepend('
  • ' + msg.data[2] + ': ' + msg.data[3] + '
  • ') }); + var new_entry = '
  • ' + msg.data[2] + ': ' + msg.data[3] + '
  • '; + $(new_entry).hide().prependTo('#log').fadeIn('slow'); + $('#log').fadeIn(500, function() { $(this).prepend() }); + $('#log li:gt(9)' ).remove(); + if (cb) + cb(); }); }); @@ -24,15 +40,21 @@

    QR Code:

    + {#

    For URL {{ request.url_root + next_uuid }}

    + #} +

    For URL __loading...__

    +

    Last 10 users:

    - +
      + {#
        {% for hit in hits %}
      • {{ hit['created'] }}, {{ hit['user'] }}
      • {% endfor %}
      + #}