Initial version

This commit is contained in:
Jorge Enrique Gómez Gómez 2022-07-28 01:42:30 -05:00
commit 73ef9bcfaa
10 changed files with 158 additions and 0 deletions

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# Sample application for verifying attendance through a unique QR code
To run in a Python virtual environment, using **Flask**:
```bash
mkvenv flask
venv flask
pip install Flask Flask-QRcode Flask-UUID
```
To create an empty `database.db` sqlite file with the table described in
`schema.sql`:
```bash
./init_db.py
```

Binary file not shown.

BIN
database.db Normal file

Binary file not shown.

10
html/index.html Normal file
View File

@ -0,0 +1,10 @@
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Hi!</h1>
<p>OK.</p>
</body>
</html>

12
init_db.py Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env python3
import sqlite3
connection = sqlite3.connect('database.db')
with open('schema.sql') as f:
connection.executescript(f.read())
connection.commit()
connection.close()

60
main.py Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env python3
import uuid
import sqlite3
from datetime import datetime
from flask import Flask, request, render_template
from flask_uuid import FlaskUUID
from flask_qrcode import QRcode
# TODO: use sockets to broadcast when a hit to the expected uuid has been
# received, so that connected clients (the Chromium public browser) can
# refresh the qr code and update the list of users
# https://flask-socketio.readthedocs.io
app = Flask(__name__, static_folder='html')
FlaskUUID(app)
QRcode(app)
def get_db_connection():
conn = sqlite3.connect('database.db')
conn.row_factory = sqlite3.Row
return conn
@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: next_ uuid should be in global state, to verify it when it's received
return render_template("template.html",
next_uuid=str(uuid.uuid1()),
hits=data)
@app.route('/<uuid:id>')
def catch_uuids(id):
ua = request.headers.get('User-Agent')
# TODO: use the Remote-User header that Authelia should set after authentication
# (check if "Authorization" in request.headers, or request.authorization)
time = datetime.now().strftime("%A %Y-%m-%d %H:%M:%S")
error = None
conn = get_db_connection()
existing = conn.execute(
'SELECT * FROM hits WHERE uuid = ?', (str(id),)).fetchone()
# TODO: verify that the uuid was generated by us (otherwise any uuid,
# like one generated by the user, would be accepted)
if existing:
error = 'ALREADY_USED'
else:
data = conn.execute(
'SELECT * FROM hits WHERE user = ? ORDER BY id DESC LIMIT 10', (ua,)
).fetchall()
conn.execute("INSERT INTO hits (uuid, user) VALUES (?, ?)", (str(id), ua))
conn.commit()
conn.close()
return render_template('thanks.html', user=ua, time=time, error=error, hits=data)
if __name__ == '__main__':
app.run()

10
requirements.txt Normal file
View File

@ -0,0 +1,10 @@
click==8.1.3
Flask==2.1.3
Flask-QRcode==3.1.0
Flask-UUID==0.2
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
Pillow==9.2.0
qrcode==7.3.1
Werkzeug==2.2.1

9
schema.sql Normal file
View File

@ -0,0 +1,9 @@
DROP TABLE IF EXISTS hits;
CREATE TABLE hits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
uuid TEXT NOT NULL,
user TEXT NOT NULL
);

18
templates/template.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<h1>QR Code:</h1>
<p>For URL <a href="{{ request.url_root + next_uuid }}">{{ request.url_root + next_uuid }}</a></p>
<img src="{{ qrcode(request.url_root + next_uuid, box_size=12, border=5) }}">
<h1>Last 10 users:</h1>
<li>
{% for hit in hits %}
<ul>{{ hit['created'] }}, {{ hit['user'] }}</ul>
{% endfor %}
</li>
</body>
</html>

23
templates/thanks.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
{% if error %}
<h1>Error</h1>
<p>Code has been used already.</p>
{% else %}
<h1>Thanks</h1>
<p><strong>At {{ time }}</strong>,</p>
<p>Registered (entrance? exit?) for user {{ user }} .</p>
<h2>Last 10 registers for {{ user }}:</h2>
<li>
{% for hit in hits %}
<ul>{{ hit['created'] }}</ul>
{% endfor %}
</li>
{% endif %}
</body>
</html>