implemented optional authentification app and stricter password and user rules.

Also against brute force.
This commit is contained in:
2026-04-01 10:38:29 +02:00
parent f2de165c55
commit 9cc1a22232
21 changed files with 1124 additions and 170 deletions
+22 -17
View File
@@ -6,24 +6,33 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
<link rel="stylesheet" href="../static/css/dashboard.css">
<link rel="stylesheet" href="{{ url_for('static', filename='css/dashboard.css') }}">
</head>
<body>
<!-- 🔝 Navbar -->
<nav class="navbar">
<div class="nav-left">♛ 8-Queens App</div>
<div class="nav-right">
<a href="{{ url_for('main.dashboard') }}">Dashboard</a>
<a href="{{ url_for('login.logout') }}">Logout</a>
<a href="{{ url_for('login.login_settings') }}">Login settings</a>
<form action="{{ url_for('login.logout') }}" method="post" class="nav-logout-form">
<button class="nav-link-button" type="submit">Logout</button>
</form>
</div>
</nav>
<!-- 📦 Container -->
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-stack">
{% for category, message in messages %}
<p class="flash-message {{ category }}">{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- 👤 User Card -->
<div class="card">
<h2>👋 Willkommen, {{ current_user.username }}</h2>
@@ -31,18 +40,16 @@
<p><strong>ID:</strong> {{ current_user.id }}</p>
<p><strong>Username:</strong> {{ current_user.username }}</p>
{% if current_user.email %}
<p><strong>Email:</strong> {{ current_user.email }}</p>
{% else %}
<p><strong>Email:</strong> Not set</p>
{% endif %}
<p><strong>2FA:</strong> {% if current_user.two_factor_enabled %}Authenticator app enabled{% else %}Disabled{% endif %}</p>
<p><strong>Erstellt:</strong> {{ current_user.created_at }}</p>
</div>
<form action="{{ url_for('login.logout') }}" method="post">
<button class="btn danger" type="submit">Logout</button>
</form>
<div class="inline-actions">
<a class="btn secondary" href="{{ url_for('login.login_settings') }}">Open login settings</a>
<form action="{{ url_for('login.logout') }}" method="post">
<button class="btn danger" type="submit">Logout</button>
</form>
</div>
</div>
<div class="mode-switch">
@@ -51,8 +58,6 @@
<button class="btn secondary" onclick="toggleSidebar()" id="btnSidebar">Board Settings</button>
</div>
<!-- ♟️ Game Card -->
<div class="card game-card">
<h2>♟️ Damen- und Tuerme-Spiel</h2>
<p class="board-size-info">Aktuelle Brettgroesse: <span id="boardSizeValue">8</span> x <span
@@ -165,7 +170,7 @@
<div class="sidebar-backdrop" id="saveBackdrop" onclick="toggleSavePanel(false)"></div>
<div class="sidebar-backdrop" id="savePointsBackdrop" onclick="toggleSavePointsPanel(false)"></div>
<script src="../static/js/dashboard.js"></script>
<script src="{{ url_for('static', filename='js/dashboard.js') }}"></script>
</body>
+31 -19
View File
@@ -5,35 +5,47 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<link rel="stylesheet" href="../static/css/login.css">
<link rel="stylesheet" href="../static/css/login.css">
</link>
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
</head>
<body>
<nav>
<ul>
<li><a href="{{ url_for('login.login') }}"><span>Login</span></a></li>
<li><a href="{{ url_for('login.register') }}"><span>Create account</span></a></li>
<li><a href="{{ url_for('login.login') }}">Login</a></li>
<li><a href="{{ url_for('login.register') }}">Create account</a></li>
</ul>
</nav>
<main>
<h1>Login to your account</h1>
<main class="auth-shell">
<section class="auth-panel">
<h1>Login to your account</h1>
<p class="muted">If two-factor authentication is enabled, enter the current 6-digit code from your authenticator app after the password check.</p>
{% if error %}
<p class="error">{{ error }}</p>
{% endif %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<p class="message {{ category }}">{{ message }}</p>
{% endfor %}
{% endif %}
{% endwith %}
<form action="{{ url_for('login.login') }}" method="post">
<label for="username">Username:</label>
<input type="text" name="username" required />
<label for="password">Password:</label>
<input type="password" name="password" required />
<button class="magnetic" type="submit">Login</button>
</form>
{% if error %}
<p class="message error">{{ error }}</p>
{% endif %}
<form action="{{ url_for('login.login') }}" method="post">
<label for="username">Username</label>
<input id="username" type="text" name="username" maxlength="20" required value="{{ username or '' }}">
<label for="password">Password</label>
<input id="password" type="password" name="password" minlength="8" maxlength="128" required>
<button class="magnetic" type="submit" data-loading-label="Checking login...">Login</button>
</form>
</section>
</main>
<script src="../static/js/login.js" defer></script>
<script src="{{ url_for('static', filename='js/login.js') }}" defer></script>
</body>
</html>
</html>
+123
View File
@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Settings</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" defer></script>
</head>
<body>
<nav>
<ul>
<li><a href="{{ url_for('main.dashboard') }}">Dashboard</a></li>
<li><a href="{{ url_for('login.login_settings') }}">Login settings</a></li>
</ul>
<form action="{{ url_for('login.logout') }}" method="post" class="nav-form">
<button type="submit" class="nav-button">Logout</button>
</form>
</nav>
<main class="auth-shell settings-shell">
<section class="auth-panel settings-panel">
<h1>Login settings</h1>
<p class="muted">Manage authenticator app 2FA and your password without changing the registration flow.</p>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<p class="message {{ category }}">{{ message }}</p>
{% endfor %}
{% endif %}
{% endwith %}
{% if error %}
<p class="message error">{{ error }}</p>
{% endif %}
<div class="settings-grid">
{% if current_user.two_factor_enabled %}
<form action="{{ url_for('login.login_settings') }}" method="post">
<input type="hidden" name="action" value="disable_two_factor">
<h2>Authenticator app 2FA</h2>
<p class="field-hint">2FA is currently enabled for this account.</p>
<p class="field-hint">Use Google Authenticator, Microsoft Authenticator, Authy, or another TOTP app to generate your 6-digit login codes.</p>
<button class="secondary-button" type="submit" data-loading-label="Disabling 2FA...">Disable 2FA</button>
</form>
{% elif pending_two_factor_secret %}
<form action="{{ url_for('login.login_settings') }}" method="post">
<input type="hidden" name="action" value="confirm_two_factor_setup">
<h2>Finish 2FA setup</h2>
<p class="field-hint">Scan this QR code with your authenticator app, then enter the current 6-digit code to confirm.</p>
<div class="qr-panel">
<div class="qr-code" data-qr-value="{{ pending_two_factor_uri }}"></div>
</div>
<p class="field-hint">If scanning does not work, you can still use the manual setup key below.</p>
<label for="two_factor_secret">Manual setup key</label>
<input id="two_factor_secret" type="text" value="{{ pending_two_factor_secret }}" readonly>
<label for="two_factor_uri">Setup URI</label>
<input id="two_factor_uri" type="text" value="{{ pending_two_factor_uri }}" readonly>
<p class="field-hint">This URI is used to generate the QR setup and can be copied as a fallback for compatible apps.</p>
<label for="code">Authenticator code</label>
<input id="code" type="text" name="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required>
<div class="button-row">
<button class="magnetic" type="submit" data-loading-label="Enabling 2FA...">Confirm and enable</button>
</div>
</form>
<form action="{{ url_for('login.login_settings') }}" method="post">
<input type="hidden" name="action" value="cancel_two_factor_setup">
<h2>Cancel setup</h2>
<p class="field-hint">Cancel the current authenticator setup if you want to restart with a new secret.</p>
<button class="secondary-button" type="submit">Cancel setup</button>
</form>
{% else %}
<form action="{{ url_for('login.login_settings') }}" method="post">
<input type="hidden" name="action" value="start_two_factor_setup">
<h2>Authenticator app 2FA</h2>
<p class="field-hint">Protect your account with 6-digit codes from an authenticator app.</p>
<p class="field-hint">After starting setup, you will get a QR code to scan and then confirm once with a live code.</p>
<button class="magnetic" type="submit" data-loading-label="Starting setup...">Start setup</button>
</form>
{% endif %}
<form action="{{ url_for('login.login_settings') }}" method="post">
<input type="hidden" name="action" value="password">
<h2>Change password</h2>
<label for="current_password">Current password</label>
<input id="current_password" type="password" name="current_password" minlength="8" maxlength="128"
required>
<label for="new_password">New password</label>
<input id="new_password" type="password" name="new_password" minlength="8" maxlength="128" required>
<label for="confirm_password">Confirm new password</label>
<input id="confirm_password" type="password" name="confirm_password" minlength="8" maxlength="128"
required>
<button class="magnetic" type="submit" data-loading-label="Updating password...">Update password</button>
</form>
</div>
</section>
</main>
<script src="{{ url_for('static', filename='js/login.js') }}" defer></script>
</body>
</html>
+31 -14
View File
@@ -5,7 +5,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sign Up</title>
<link rel="stylesheet" href="../static/css/login.css">
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
</head>
<body>
@@ -15,21 +15,38 @@
<li><a href="{{ url_for('login.register') }}">Create account</a></li>
</ul>
</nav>
<main>
<h1>Create an account</h1>
{% if error %}
<p class="error">{{ error }}</p>
{% endif %}
<main class="auth-shell">
<section class="auth-panel">
<h1>Create an account</h1>
<p class="muted">Create your account here and enable authenticator app 2FA later in your login settings.</p>
<form action="{{ url_for('login.register') }}" method="post">
<label for="username">Username:</label>
<input type="text" name="username" required />
<label for="password">Password:</label>
<input type="password" name="password" required />
<button type="submit">Sign Up</button>
</form>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<p class="message {{ category }}">{{ message }}</p>
{% endfor %}
{% endif %}
{% endwith %}
{% if error %}
<p class="message error">{{ error }}</p>
{% endif %}
<form action="{{ url_for('login.register') }}" method="post">
<label for="username">Username</label>
<input id="username" type="text" name="username" maxlength="20" required value="{{ username or '' }}">
<label for="password">Password</label>
<input id="password" type="password" name="password" minlength="8" maxlength="128" required>
<p class="field-hint">Use at least 8 characters.</p>
<button class="magnetic" type="submit" data-loading-label="Creating account...">Sign Up</button>
</form>
</section>
</main>
<script src="{{ url_for('static', filename='js/login.js') }}" defer></script>
</body>
</html>
</html>
+49
View File
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verify Login</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
</head>
<body>
<nav>
<ul>
<li><a href="{{ url_for('login.login') }}">Login</a></li>
<li><a href="{{ url_for('login.register') }}">Create account</a></li>
</ul>
</nav>
<main class="auth-shell">
<section class="auth-panel">
<h1>Verify your login</h1>
<p class="muted">Open your authenticator app and enter the current 6-digit code.</p>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<p class="message {{ category }}">{{ message }}</p>
{% endfor %}
{% endif %}
{% endwith %}
{% if error %}
<p class="message error">{{ error }}</p>
{% endif %}
<form action="{{ url_for('login.verify_two_factor') }}" method="post">
<label for="code">Verification code</label>
<input id="code" type="text" name="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required>
<p class="field-hint">Codes refresh automatically every 30 seconds in your authenticator app.</p>
<button class="magnetic" type="submit" data-loading-label="Verifying...">Verify</button>
</form>
</section>
</main>
<script src="{{ url_for('static', filename='js/login.js') }}" defer></script>
</body>
</html>