mirror of
https://github.com/csd4ni3l/loginween.git
synced 2025-11-05 05:58:10 +01:00
Add missing GRID_SIZE option to example env and decrease to 15, add profile page with username and pattern change, add logout, fix clear buttons submitting, add Posts table for later use, prevent timing attacks during pattern equal check
This commit is contained in:
@@ -2,4 +2,5 @@ HOST="0.0.0.0"
|
||||
PORT=8080
|
||||
DB_FILE="data.db"
|
||||
APP_KEY="changeme"
|
||||
DEBUG_MODE=false
|
||||
DEBUG_MODE=false
|
||||
GRID_SIZE=15
|
||||
100
app.py
100
app.py
@@ -25,10 +25,21 @@ def get_db():
|
||||
db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS Users (
|
||||
username TEXT PRIMARY KEY,
|
||||
pattern TEXT UNIQUE
|
||||
pattern TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS Posts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT,
|
||||
comment TEXT,
|
||||
pattern TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
db.commit()
|
||||
|
||||
return db
|
||||
|
||||
@app.teardown_appcontext
|
||||
@@ -61,7 +72,7 @@ def login():
|
||||
if flask_login.current_user.is_authenticated:
|
||||
return redirect(url_for("main"))
|
||||
|
||||
return render_template("login.jinja2", grid_size=os.getenv("GRID_SIZE", 25))
|
||||
return render_template("login.jinja2", grid_size=os.getenv("GRID_SIZE", 15))
|
||||
|
||||
elif request.method == "POST":
|
||||
username = request.form["username"]
|
||||
@@ -91,7 +102,7 @@ def register():
|
||||
if flask_login.current_user.is_authenticated:
|
||||
return redirect(url_for("main"))
|
||||
|
||||
return render_template("register.jinja2", grid_size=os.getenv("GRID_SIZE", 25))
|
||||
return render_template("register.jinja2", grid_size=os.getenv("GRID_SIZE", 15))
|
||||
|
||||
elif request.method == "POST":
|
||||
username = request.form["username"]
|
||||
@@ -99,7 +110,7 @@ def register():
|
||||
|
||||
cur = get_db().cursor()
|
||||
|
||||
cur.execute("SELECT username from Users WHERE username = ?", (username, ))
|
||||
cur.execute("SELECT pattern from Users WHERE username = ?", (username, ))
|
||||
|
||||
if cur.fetchone():
|
||||
cur.close()
|
||||
@@ -110,5 +121,86 @@ def register():
|
||||
cur.close()
|
||||
|
||||
return redirect(url_for("login"))
|
||||
|
||||
@app.route("/posts")
|
||||
@login_required
|
||||
def posts():
|
||||
username = flask_login.current_user.id
|
||||
return render_template("posts.jinja2", username=username)
|
||||
|
||||
@app.route("/profile")
|
||||
@login_required
|
||||
def profile():
|
||||
username = flask_login.current_user.id
|
||||
return render_template("profile.jinja2", username=username, grid_size=os.getenv("GRID_SIZE", 15), logged_in_account=True)
|
||||
|
||||
@app.route("/profile/<username>")
|
||||
def profile_external(username):
|
||||
return render_template("profile.jinja2", username=username, grid_size=os.getenv("GRID_SIZE", 15), logged_in_account=False)
|
||||
|
||||
@app.route("/change_username", methods=["POST"])
|
||||
@login_required
|
||||
def change_username():
|
||||
username = flask_login.current_user.id
|
||||
|
||||
new_username = request.form["new_username"]
|
||||
|
||||
cur = get_db().cursor()
|
||||
|
||||
cur.execute("UPDATE Users SET username = ? WHERE username = ?", (new_username, username))
|
||||
|
||||
get_db().commit()
|
||||
cur.close()
|
||||
|
||||
flask_login.logout_user()
|
||||
return redirect(url_for("login"))
|
||||
|
||||
@app.route("/change_pattern", methods=["POST"])
|
||||
@login_required
|
||||
def change_pattern():
|
||||
username = flask_login.current_user.id
|
||||
|
||||
current_pattern, new_pattern = request.form["current_pattern"], request.form["new_pattern"]
|
||||
|
||||
cur = get_db().cursor()
|
||||
|
||||
cur.execute("SELECT pattern FROM Users WHERE username = ?", (username,))
|
||||
row = cur.fetchone()
|
||||
|
||||
if not row:
|
||||
cur.close()
|
||||
return Response("No pattern exists? WTF?", 500)
|
||||
|
||||
if not Pattern.from_str(current_pattern) == Pattern.from_json_str(row[0]):
|
||||
cur.close()
|
||||
return Response("Invalid Pattern", 401)
|
||||
|
||||
cur.execute("UPDATE Users SET pattern = ? WHERE username = ?", (Pattern.from_str(new_pattern).to_json_str(), username))
|
||||
get_db().commit()
|
||||
cur.close()
|
||||
|
||||
flask_login.logout_user() # not logout redirect because that might fail and we would be in a weird state
|
||||
return redirect(url_for("login"))
|
||||
|
||||
@app.route("/delete_account")
|
||||
@login_required
|
||||
def delete_account():
|
||||
username = flask_login.current_user.id
|
||||
|
||||
cur = get_db().cursor()
|
||||
|
||||
cur.execute("DELETE FROM Users WHERE username = ?", (username,))
|
||||
|
||||
get_db().commit()
|
||||
cur.close()
|
||||
|
||||
flask_login.logout_user() # not logout redirect because that might fail and we would be in a weird state
|
||||
return redirect(url_for("login"))
|
||||
|
||||
@app.route("/logout")
|
||||
@login_required
|
||||
def logout():
|
||||
flask_login.logout_user()
|
||||
return redirect(url_for("login"))
|
||||
|
||||
app.run(host=os.getenv("HOST", "0.0.0.0"), port=int(os.getenv("PORT", 8080)), debug=os.getenv("DEBUG_MODE", False).lower() == "true")
|
||||
@@ -1,4 +1,6 @@
|
||||
import json
|
||||
import json, random, time
|
||||
|
||||
systemrandom = random.SystemRandom()
|
||||
|
||||
class Pattern():
|
||||
def __init__(self, data: list[tuple]):
|
||||
@@ -19,4 +21,6 @@ class Pattern():
|
||||
if not isinstance(value, Pattern):
|
||||
return False
|
||||
|
||||
time.sleep(systemrandom.uniform(0.001, 0.5)) # prevent timing attacks
|
||||
|
||||
return set(self.data) == set(value.data)
|
||||
@@ -6,6 +6,12 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/posts">Posts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/profile">Profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/logout">Logout</a>
|
||||
</li>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button type="submit" class="btn btn-primary me-2" id="loginBtn">Login</button>
|
||||
<button id="clearBtn" class="btn btn-danger">Clear</button>
|
||||
<button type="button" id="clearBtn" class="btn btn-danger">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -75,9 +75,6 @@ function draw(e) {
|
||||
ctx.fillRect(cellX - CELL_SIZE / 3, cellY - CELL_SIZE / 3, CELL_SIZE, CELL_SIZE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(pixel);
|
||||
}
|
||||
}
|
||||
|
||||
function drawGrid() {
|
||||
|
||||
22
templates/posts.jinja2
Normal file
22
templates/posts.jinja2
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends "base.jinja2" %}
|
||||
|
||||
{% block title %} LoginWeen Posts {% endblock title %}
|
||||
|
||||
{% block nav %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/posts">Posts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/profile">Profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/logout">Logout</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% endblock body %}
|
||||
248
templates/profile.jinja2
Normal file
248
templates/profile.jinja2
Normal file
@@ -0,0 +1,248 @@
|
||||
{% extends "base.jinja2" %}
|
||||
|
||||
{% block title %}Loginween Profile{% endblock %}
|
||||
|
||||
{% block nav %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/posts">Posts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/profile">Profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/logout">Logout</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div class="container my-4">
|
||||
<div class="card shadow-sm bg-dark text-light border-secondary">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Profile Overview {% if not logged_in_account %} of {{ username}} {% endif %}</h2>
|
||||
|
||||
{% if logged_in_account %}
|
||||
<p class="mb-1">Logged in as: {{ username }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if logged_in_account %}
|
||||
<div class="card shadow-sm mt-4 bg-dark text-light border-secondary">
|
||||
<div class="card-body">
|
||||
<h4>Change Username</h4>
|
||||
<form method="POST" action="/change_username">
|
||||
<div class="mb-3">
|
||||
<label for="newusername" class="form-label">New Username</label>
|
||||
<input type="text" class="form-control bg-secondary text-light" id="newusername" name="new_username" placeholder="Enter new username..." required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Update Username</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mt-4 bg-dark text-light border-secondary">
|
||||
<div class="card-body">
|
||||
<h4>Change Pattern</h4>
|
||||
<div class="mt-3"></div>
|
||||
<form id="change_pattern_form" method="POST" action="/change_pattern">
|
||||
<div class="mb-3 d-block">
|
||||
<h5>Current</h5>
|
||||
<input type="hidden" name="current_pattern" id="current_pattern_field">
|
||||
<canvas id="current_pumpkin_canvas" width="600" height="600" class="my-3"></canvas>
|
||||
<div class="buttons">
|
||||
<button type="button" id="currentclearBtn" class="btn btn-danger">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3"></div>
|
||||
<div class="mb-3 d-block">
|
||||
<h5>New</h5>
|
||||
<input type="hidden" name="new_pattern" id="new_pattern_field">
|
||||
<canvas id="new_pumpkin_canvas" width="600" height="600" class="my-3"></canvas>
|
||||
<div class="buttons">
|
||||
<button type="button" id="newclearBtn" class="btn btn-danger">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Update Pattern</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mt-4 border-danger bg-dark text-light">
|
||||
<div class="card-body">
|
||||
<h4 class="text-danger">Danger Zone</h4>
|
||||
<p class="text-muted">These actions cannot be undone!</p>
|
||||
<form method="POST" action="/delete_account" class="d-inline">
|
||||
<button type="submit" class="btn btn-danger me-2" onclick="return confirm('Are you sure you want to delete your account?');">Delete Account</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
const canvas = document.getElementById('current_pumpkin_canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = new Image();
|
||||
img.src = '/static/pumpkin.png';
|
||||
|
||||
const GRID_SIZE = {{ grid_size }};
|
||||
const CELL_SIZE = canvas.width / GRID_SIZE;
|
||||
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
drawGrid();
|
||||
};
|
||||
|
||||
let drawing = false;
|
||||
let currentPattern = [];
|
||||
let savedPattern = null;
|
||||
|
||||
canvas.addEventListener('mousedown', () => { drawing = true; });
|
||||
canvas.addEventListener('mouseup', () => { drawing = false; });
|
||||
canvas.addEventListener('mousemove', draw);
|
||||
|
||||
function draw(e) {
|
||||
if (!drawing) return;
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
var x = e.clientX - rect.left;
|
||||
var y = e.clientY - rect.top;
|
||||
|
||||
var gridX = Math.floor(x / CELL_SIZE);
|
||||
var gridY = Math.floor(y / CELL_SIZE);
|
||||
|
||||
var cellX = gridX * CELL_SIZE + CELL_SIZE / 3;
|
||||
var cellY = gridY * CELL_SIZE + CELL_SIZE / 3;
|
||||
|
||||
var pixel = ctx.getImageData(cellX, cellY, 1, 1).data;
|
||||
|
||||
if (pixel[0] >= 254 && (pixel[1] >= 124 && pixel[1] <= 126)) {
|
||||
var key = `${gridX},${gridY}`;
|
||||
|
||||
if (!currentPattern.includes(key)) {
|
||||
currentPattern.push(key);
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillRect(cellX - CELL_SIZE / 3, cellY - CELL_SIZE / 3, CELL_SIZE, CELL_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawGrid() {
|
||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.6)';
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
for (let i = 0; i <= GRID_SIZE; i++) {
|
||||
const pos = i * CELL_SIZE;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pos, 0);
|
||||
ctx.lineTo(pos, canvas.height);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, pos);
|
||||
ctx.lineTo(canvas.width, pos);
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('currentclearBtn').addEventListener('click', clearCanvas);
|
||||
|
||||
function clearCanvas() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
drawGrid()
|
||||
currentPattern = [];
|
||||
}
|
||||
|
||||
document.getElementById("change_pattern_form").addEventListener('submit', function(event) {
|
||||
document.getElementById('current_pattern_field').value = JSON.stringify(currentPattern);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
const canvas = document.getElementById('new_pumpkin_canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = new Image();
|
||||
img.src = '/static/pumpkin.png';
|
||||
|
||||
const GRID_SIZE = {{ grid_size }};
|
||||
const CELL_SIZE = canvas.width / GRID_SIZE;
|
||||
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
drawGrid();
|
||||
};
|
||||
|
||||
let drawing = false;
|
||||
let currentPattern = [];
|
||||
let savedPattern = null;
|
||||
|
||||
canvas.addEventListener('mousedown', () => { drawing = true; });
|
||||
canvas.addEventListener('mouseup', () => { drawing = false; });
|
||||
canvas.addEventListener('mousemove', draw);
|
||||
|
||||
function draw(e) {
|
||||
if (!drawing) return;
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
var x = e.clientX - rect.left;
|
||||
var y = e.clientY - rect.top;
|
||||
|
||||
var gridX = Math.floor(x / CELL_SIZE);
|
||||
var gridY = Math.floor(y / CELL_SIZE);
|
||||
|
||||
var cellX = gridX * CELL_SIZE + CELL_SIZE / 3;
|
||||
var cellY = gridY * CELL_SIZE + CELL_SIZE / 3;
|
||||
|
||||
var pixel = ctx.getImageData(cellX, cellY, 1, 1).data;
|
||||
|
||||
if (pixel[0] >= 254 && (pixel[1] >= 124 && pixel[1] <= 126)) {
|
||||
var key = `${gridX},${gridY}`;
|
||||
|
||||
if (!currentPattern.includes(key)) {
|
||||
currentPattern.push(key);
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillRect(cellX - CELL_SIZE / 3, cellY - CELL_SIZE / 3, CELL_SIZE, CELL_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawGrid() {
|
||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.6)';
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
for (let i = 0; i <= GRID_SIZE; i++) {
|
||||
const pos = i * CELL_SIZE;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pos, 0);
|
||||
ctx.lineTo(pos, canvas.height);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, pos);
|
||||
ctx.lineTo(canvas.width, pos);
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('newclearBtn').addEventListener('click', clearCanvas);
|
||||
|
||||
function clearCanvas() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
drawGrid()
|
||||
currentPattern = [];
|
||||
}
|
||||
|
||||
document.getElementById("change_pattern_form").addEventListener('submit', function(event) {
|
||||
document.getElementById('new_pattern_field').value = JSON.stringify(currentPattern);
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock body %}
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button type="submit" class="btn btn-primary me-2" id="registerBtn">Register</button>
|
||||
<button id="clearBtn" class="btn btn-danger">Clear</button>
|
||||
<button type="button" id="clearBtn" class="btn btn-danger">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -75,9 +75,6 @@ function draw(e) {
|
||||
ctx.fillRect(cellX - CELL_SIZE / 3, cellY - CELL_SIZE / 3, CELL_SIZE, CELL_SIZE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(pixel);
|
||||
}
|
||||
}
|
||||
|
||||
function drawGrid() {
|
||||
|
||||
Reference in New Issue
Block a user