Add admin profile

This commit is contained in:
Jonny Ervine 2025-10-04 17:04:55 +08:00
parent 0c60a4b4d8
commit a993767dc0
3 changed files with 240 additions and 5 deletions

View File

@ -24,9 +24,9 @@ from sqlalchemy import text
from flask_wtf import FlaskForm
from flask_bootstrap import Bootstrap
from flask_basicauth import BasicAuth
from wtforms import StringField, PasswordField, BooleanField
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms import DateField
from wtforms.validators import InputRequired, Email, Length
from wtforms.validators import InputRequired, Email, Length, EqualTo
from forms import motmForm, adminSettingsForm2, goalsAssistsForm, DatabaseSetupForm, PlayerForm, ClubForm, TeamForm, DataImportForm, ClubSelectionForm
from db_config import sql_write, sql_write_static, sql_read, sql_read_static
from sqlalchemy import text
@ -35,9 +35,32 @@ from readSettings import mySettings
from fixture_scraper import FixtureScraper, get_next_hkfc_c_fixture, get_opponent_club_name, get_opponent_club_info, match_opponent_to_club
from club_scraper import ClubScraper, get_hk_hockey_clubs, expand_club_abbreviation
app.config['BASIC_AUTH_USERNAME'] = 'admin'
app.config['BASIC_AUTH_PASSWORD'] = 'letmein'
basic_auth = BasicAuth(app)
# Custom authentication class that uses database
class DatabaseBasicAuth(BasicAuth):
def check_credentials(self, username, password):
try:
sql = text("SELECT password_hash FROM admin_profiles WHERE username = :username")
result = sql_read(sql, {'username': username})
if result:
stored_hash = result[0]['password_hash']
password_hash = hashlib.sha256(password.encode()).hexdigest()
return password_hash == stored_hash
return False
except Exception as e:
print(f"Authentication error: {e}")
return False
app.config['BASIC_AUTH_USERNAME'] = 'admin' # Fallback for compatibility
app.config['BASIC_AUTH_PASSWORD'] = 'letmein' # Fallback for compatibility
basic_auth = DatabaseBasicAuth(app)
# Admin profile form
class AdminProfileForm(FlaskForm):
current_password = PasswordField('Current Password', validators=[InputRequired()])
new_password = PasswordField('New Password', validators=[InputRequired(), Length(min=6)])
confirm_password = PasswordField('Confirm New Password', validators=[InputRequired(), EqualTo('new_password', message='Passwords must match')])
email = StringField('Email', validators=[Email()])
submit = SubmitField('Update Profile')
@app.route('/')
@ -53,6 +76,57 @@ def admin_dashboard():
return render_template('admin_dashboard.html')
@app.route('/admin/profile', methods=['GET', 'POST'])
@basic_auth.required
def admin_profile():
"""Admin profile page for changing password and settings"""
form = AdminProfileForm()
# Get current admin profile
sql = text("SELECT username, email FROM admin_profiles WHERE username = 'admin'")
profile = sql_read(sql)
if profile:
current_email = profile[0]['email']
else:
current_email = ''
if request.method == 'POST' and form.validate_on_submit():
# Verify current password
sql_check = text("SELECT password_hash FROM admin_profiles WHERE username = 'admin'")
result = sql_read(sql_check)
if result:
stored_hash = result[0]['password_hash']
current_password_hash = hashlib.sha256(form.current_password.data.encode()).hexdigest()
if current_password_hash == stored_hash:
# Update password and email
new_password_hash = hashlib.sha256(form.new_password.data.encode()).hexdigest()
sql_update = text("""
UPDATE admin_profiles
SET password_hash = :password_hash, email = :email, updated_at = CURRENT_TIMESTAMP
WHERE username = 'admin'
""")
sql_write(sql_update, {
'password_hash': new_password_hash,
'email': form.email.data
})
flash('Profile updated successfully!', 'success')
return redirect(url_for('admin_profile'))
else:
flash('Current password is incorrect!', 'error')
else:
flash('Admin profile not found!', 'error')
# Pre-populate email field
form.email.data = current_email
return render_template('admin_profile.html', form=form, current_email=current_email)
# ==================== PUBLIC VOTING SECTION ====================
@app.route('/motm/<randomUrlSuffix>')

View File

@ -38,6 +38,7 @@
<div class="mb-3">
<a href="/" class="btn btn-default">Back to Main Page</a>
<a href="/admin/profile" class="btn btn-outline-secondary">Admin Profile</a>
</div>
<!-- Data Management Section -->

View File

@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Profile - HKFC Men's C Team MOTM System</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.profile-section {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 2rem;
margin-bottom: 2rem;
}
.security-notice {
background-color: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 2rem;
}
</style>
</head>
<body>
<div class="container mt-4">
<div class="row">
<div class="col-md-8 mx-auto">
<h1>Admin Profile</h1>
<p class="lead">Manage your admin account settings and password</p>
<div class="mb-3">
<a href="/admin" class="btn btn-outline-primary">Back to Admin Dashboard</a>
</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 'success' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Security Notice -->
<div class="security-notice">
<h5><i class="bi bi-shield-check"></i> Security Notice</h5>
<p class="mb-0">
<strong>Important:</strong> Changing your password will immediately affect access to all admin functions.
Make sure to remember your new password or store it securely.
</p>
</div>
<!-- Profile Form -->
<div class="profile-section">
<h3>Change Password & Settings</h3>
<form method="POST">
{{ form.hidden_tag() }}
<div class="row">
<div class="col-md-6">
<div class="mb-3">
{{ form.current_password.label(class="form-label") }}
{{ form.current_password(class="form-control", placeholder="Enter current password") }}
{% if form.current_password.errors %}
<div class="text-danger">
{% for error in form.current_password.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
{{ form.new_password.label(class="form-label") }}
{{ form.new_password(class="form-control", placeholder="Enter new password (min 6 characters)") }}
{% if form.new_password.errors %}
<div class="text-danger">
{% for error in form.new_password.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
{{ form.confirm_password.label(class="form-label") }}
{{ form.confirm_password(class="form-control", placeholder="Confirm new password") }}
{% if form.confirm_password.errors %}
<div class="text-danger">
{% for error in form.confirm_password.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control", placeholder="admin@example.com") }}
{% if form.email.errors %}
<div class="text-danger">
{% for error in form.email.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Update Profile</button>
<a href="/admin" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
<!-- Current Profile Info -->
<div class="profile-section">
<h3>Current Profile Information</h3>
<div class="row">
<div class="col-md-6">
<p><strong>Username:</strong> admin</p>
<p><strong>Email:</strong> {{ current_email or 'Not set' }}</p>
</div>
<div class="col-md-6">
<p><strong>Account Type:</strong> Administrator</p>
<p><strong>Access Level:</strong> Full Admin Rights</p>
</div>
</div>
</div>
<!-- Password Requirements -->
<div class="profile-section">
<h3>Password Requirements</h3>
<ul class="list-unstyled">
<li><i class="bi bi-check-circle text-success"></i> Minimum 6 characters</li>
<li><i class="bi bi-check-circle text-success"></i> Must match confirmation</li>
<li><i class="bi bi-check-circle text-success"></i> Current password must be correct</li>
</ul>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>