Add admin profile
This commit is contained in:
parent
0c60a4b4d8
commit
a993767dc0
@ -24,9 +24,9 @@ from sqlalchemy import text
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask_bootstrap import Bootstrap
|
from flask_bootstrap import Bootstrap
|
||||||
from flask_basicauth import BasicAuth
|
from flask_basicauth import BasicAuth
|
||||||
from wtforms import StringField, PasswordField, BooleanField
|
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
||||||
from wtforms import DateField
|
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 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 db_config import sql_write, sql_write_static, sql_read, sql_read_static
|
||||||
from sqlalchemy import text
|
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 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
|
from club_scraper import ClubScraper, get_hk_hockey_clubs, expand_club_abbreviation
|
||||||
|
|
||||||
app.config['BASIC_AUTH_USERNAME'] = 'admin'
|
# Custom authentication class that uses database
|
||||||
app.config['BASIC_AUTH_PASSWORD'] = 'letmein'
|
class DatabaseBasicAuth(BasicAuth):
|
||||||
basic_auth = BasicAuth(app)
|
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('/')
|
@app.route('/')
|
||||||
@ -53,6 +76,57 @@ def admin_dashboard():
|
|||||||
return render_template('admin_dashboard.html')
|
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 ====================
|
# ==================== PUBLIC VOTING SECTION ====================
|
||||||
|
|
||||||
@app.route('/motm/<randomUrlSuffix>')
|
@app.route('/motm/<randomUrlSuffix>')
|
||||||
|
|||||||
@ -38,6 +38,7 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<a href="/" class="btn btn-default">Back to Main Page</a>
|
<a href="/" class="btn btn-default">Back to Main Page</a>
|
||||||
|
<a href="/admin/profile" class="btn btn-outline-secondary">Admin Profile</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Data Management Section -->
|
<!-- Data Management Section -->
|
||||||
|
|||||||
160
motm_app/templates/admin_profile.html
Normal file
160
motm_app/templates/admin_profile.html
Normal 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>
|
||||||
Loading…
Reference in New Issue
Block a user