216 Zeilen
9.3 KiB
HTML
216 Zeilen
9.3 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Benutzerprofil{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.profile-card {
|
|
transition: all 0.3s ease;
|
|
border: none;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.profile-card:hover {
|
|
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
|
}
|
|
.profile-icon {
|
|
font-size: 3rem;
|
|
margin-bottom: 1rem;
|
|
opacity: 0.8;
|
|
}
|
|
.security-badge {
|
|
font-size: 2rem;
|
|
margin-right: 1rem;
|
|
}
|
|
.form-control:focus {
|
|
border-color: #6c757d;
|
|
box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.25);
|
|
}
|
|
.password-strength {
|
|
height: 4px;
|
|
margin-top: 5px;
|
|
border-radius: 2px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.strength-very-weak { background-color: #dc3545; width: 20%; }
|
|
.strength-weak { background-color: #fd7e14; width: 40%; }
|
|
.strength-medium { background-color: #ffc107; width: 60%; }
|
|
.strength-strong { background-color: #28a745; width: 80%; }
|
|
.strength-very-strong { background-color: #0d6efd; width: 100%; }
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container py-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1>👤 Benutzerprofil</h1>
|
|
</div>
|
|
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show" role="alert">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
<!-- User Info Stats -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card profile-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="profile-icon text-primary">👤</div>
|
|
<h5 class="card-title">{{ user.username }}</h5>
|
|
<p class="text-muted mb-0">{{ user.email or 'Keine E-Mail angegeben' }}</p>
|
|
<small class="text-muted">Mitglied seit: {{ user.created_at.strftime('%d.%m.%Y') if user.created_at else 'Unbekannt' }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card profile-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="profile-icon text-info">🔐</div>
|
|
<h5 class="card-title">Sicherheitsstatus</h5>
|
|
{% if user.totp_enabled %}
|
|
<span class="badge bg-success">2FA Aktiv</span>
|
|
{% else %}
|
|
<span class="badge bg-warning text-dark">2FA Inaktiv</span>
|
|
{% endif %}
|
|
<p class="text-muted mb-0 mt-2">
|
|
<small>Letztes Passwort-Update:<br>{{ user.last_password_change.strftime('%d.%m.%Y') if user.last_password_change else 'Noch nie' }}</small>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Password Change Card -->
|
|
<div class="card profile-card mb-4">
|
|
<div class="card-body">
|
|
<h5 class="card-title d-flex align-items-center">
|
|
<span class="security-badge">🔑</span>
|
|
Passwort ändern
|
|
</h5>
|
|
<hr>
|
|
<form method="POST" action="{{ url_for('auth.change_password') }}">
|
|
<div class="mb-3">
|
|
<label for="current_password" class="form-label">Aktuelles Passwort</label>
|
|
<input type="password" class="form-control" id="current_password" name="current_password" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="new_password" class="form-label">Neues Passwort</label>
|
|
<input type="password" class="form-control" id="new_password" name="new_password" required minlength="8">
|
|
<div class="password-strength" id="password-strength"></div>
|
|
<div class="form-text" id="password-help">Mindestens 8 Zeichen</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="confirm_password" class="form-label">Neues Passwort bestätigen</label>
|
|
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
|
|
<div class="invalid-feedback">Passwörter stimmen nicht überein</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">🔄 Passwort ändern</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 2FA Card -->
|
|
<div class="card profile-card mb-4">
|
|
<div class="card-body">
|
|
<h5 class="card-title d-flex align-items-center">
|
|
<span class="security-badge">🔐</span>
|
|
Zwei-Faktor-Authentifizierung (2FA)
|
|
</h5>
|
|
<hr>
|
|
{% if user.totp_enabled %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-1">Status: <span class="badge bg-success">Aktiv</span></h6>
|
|
<p class="text-muted mb-0">Ihr Account ist durch 2FA geschützt</p>
|
|
</div>
|
|
<div class="text-success" style="font-size: 3rem;">✅</div>
|
|
</div>
|
|
<form method="POST" action="{{ url_for('auth.disable_2fa') }}" onsubmit="return confirm('Sind Sie sicher, dass Sie 2FA deaktivieren möchten? Dies verringert die Sicherheit Ihres Accounts.');">
|
|
<div class="mb-3">
|
|
<label for="password" class="form-label">Passwort zur Bestätigung</label>
|
|
<input type="password" class="form-control" id="password" name="password" required placeholder="Ihr aktuelles Passwort">
|
|
</div>
|
|
<button type="submit" class="btn btn-danger">🚫 2FA deaktivieren</button>
|
|
</form>
|
|
{% else %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-1">Status: <span class="badge bg-warning text-dark">Inaktiv</span></h6>
|
|
<p class="text-muted mb-0">Aktivieren Sie 2FA für zusätzliche Sicherheit</p>
|
|
</div>
|
|
<div class="text-warning" style="font-size: 3rem;">⚠️</div>
|
|
</div>
|
|
<p class="text-muted">
|
|
Mit 2FA wird bei jeder Anmeldung zusätzlich ein Code aus Ihrer Authenticator-App benötigt.
|
|
Dies schützt Ihren Account auch bei kompromittiertem Passwort.
|
|
</p>
|
|
<a href="{{ url_for('auth.setup_2fa') }}" class="btn btn-success">✨ 2FA einrichten</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Password strength indicator
|
|
document.getElementById('new_password').addEventListener('input', function(e) {
|
|
const password = e.target.value;
|
|
let strength = 0;
|
|
|
|
if (password.length >= 8) strength++;
|
|
if (password.match(/[a-z]/) && password.match(/[A-Z]/)) strength++;
|
|
if (password.match(/[0-9]/)) strength++;
|
|
if (password.match(/[^a-zA-Z0-9]/)) strength++;
|
|
|
|
const strengthText = ['Sehr schwach', 'Schwach', 'Mittel', 'Stark', 'Sehr stark'];
|
|
const strengthClass = ['strength-very-weak', 'strength-weak', 'strength-medium', 'strength-strong', 'strength-very-strong'];
|
|
const textClass = ['text-danger', 'text-warning', 'text-warning', 'text-success', 'text-primary'];
|
|
|
|
const strengthBar = document.getElementById('password-strength');
|
|
const helpText = document.getElementById('password-help');
|
|
|
|
if (password.length > 0) {
|
|
strengthBar.className = `password-strength ${strengthClass[strength]}`;
|
|
strengthBar.style.display = 'block';
|
|
helpText.textContent = `Stärke: ${strengthText[strength]}`;
|
|
helpText.className = `form-text ${textClass[strength]}`;
|
|
} else {
|
|
strengthBar.style.display = 'none';
|
|
helpText.textContent = 'Mindestens 8 Zeichen';
|
|
helpText.className = 'form-text';
|
|
}
|
|
});
|
|
|
|
// Confirm password validation
|
|
document.getElementById('confirm_password').addEventListener('input', function(e) {
|
|
const password = document.getElementById('new_password').value;
|
|
const confirm = e.target.value;
|
|
|
|
if (confirm.length > 0) {
|
|
if (password !== confirm) {
|
|
e.target.classList.add('is-invalid');
|
|
e.target.setCustomValidity('Passwörter stimmen nicht überein');
|
|
} else {
|
|
e.target.classList.remove('is-invalid');
|
|
e.target.classList.add('is-valid');
|
|
e.target.setCustomValidity('');
|
|
}
|
|
} else {
|
|
e.target.classList.remove('is-invalid', 'is-valid');
|
|
}
|
|
});
|
|
|
|
// Also check when password field changes
|
|
document.getElementById('new_password').addEventListener('input', function(e) {
|
|
const confirm = document.getElementById('confirm_password');
|
|
if (confirm.value.length > 0) {
|
|
confirm.dispatchEvent(new Event('input'));
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |