From 602ddc7143658bd838d8b56aa4e6d7140b49229b Mon Sep 17 00:00:00 2001 From: nocci Date: Sun, 4 May 2025 15:41:22 +0200 Subject: [PATCH] now we have a admin panel for the first user --- setup.sh | 142 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 123 insertions(+), 19 deletions(-) diff --git a/setup.sh b/setup.sh index a799d58..dda5de0 100644 --- a/setup.sh +++ b/setup.sh @@ -182,7 +182,8 @@ from flask import ( send_file, jsonify, Markup, - make_response + make_response, + abort ) from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user @@ -230,7 +231,7 @@ logging.getLogger('apscheduler').setLevel(logging.DEBUG) from sqlalchemy.engine import Engine import sqlite3 from sqlalchemy.orm import joinedload - +from functools import wraps @event.listens_for(Engine, "connect") def enable_foreign_keys(dbapi_connection, connection_record): @@ -278,13 +279,20 @@ def translate(key, lang=None, **kwargs): value = translations.get(key) or fallback_translations.get(key) or key return value.format(**kwargs) if isinstance(value, str) else value - - - ## DEBUG Translations if app.debug: print(f"Loaded translations for 'de': {TRANSLATIONS.get('de', {})}") +### Admin decorator +def admin_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if not current_user.is_authenticated or not getattr(current_user, 'is_admin', False): + abort(403) + return f(*args, **kwargs) + return decorated_function + + csrf = CSRFProtect(app) convention = { @@ -363,11 +371,12 @@ def _jinja2_filter_datetime(date, fmt='%d.%m.%Y'): # DB Models class User(UserMixin, db.Model): - __tablename__ = 'users' # Expliziter Tabellenname + __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) password = db.Column(db.String(256), nullable=False) + is_admin = db.Column(db.Boolean, default=False) games = db.relationship( 'Game', back_populates='owner', @@ -526,23 +535,32 @@ def login(): @app.route('/register', methods=['GET', 'POST']) def register(): if not app.config['REGISTRATION_ENABLED']: - flash(translate('No new registrations. They are deactivated!'), 'danger') - return redirect(url_for('login')) - + abort(403) + if request.method == 'POST': username = request.form['username'] - password = generate_password_hash(request.form['password']) - - if User.query.filter_by(username=username).first(): - flash(translate('Username already exists', session.get('lang', 'en')), 'danger') + password = request.form['password'] + + existing_user = User.query.filter_by(username=username).first() + if existing_user: + flash(translate('Username already exists'), 'error') return redirect(url_for('register')) - - new_user = User(username=username, password=password) + + # make the first user admin + is_admin = User.query.count() == 0 + + new_user = User( + username=username, + password=generate_password_hash(password), + is_admin=is_admin + ) + db.session.add(new_user) db.session.commit() login_user(new_user) + flash(translate('Registration successful'), 'success') return redirect(url_for('index')) - + return render_template('register.html') @app.route('/logout') @@ -946,6 +964,40 @@ def redeem_page(token): expires_timestamp=int(expires_utc.timestamp() * 1000), # Millisekunden platform_link='https://store.steampowered.com/account/registerkey?key=' if game.steam_appid else 'https://www.gog.com/redeem') +@app.route('/admin/users') +@login_required +@admin_required +def admin_users(): + users = User.query.all() + return render_template('admin_users.html', users=users) + +@app.route('/admin/users/delete/', methods=['POST']) +@login_required +@admin_required +def admin_delete_user(user_id): + if current_user.id == user_id: + flash(translate('You cannot delete yourself'), 'error') + return redirect(url_for('admin_users')) + + user = User.query.get_or_404(user_id) + db.session.delete(user) + db.session.commit() + flash(translate('User deleted successfully'), 'success') + return redirect(url_for('admin_users')) + +@app.route('/admin/users/reset_password/', methods=['POST']) +@login_required +@admin_required +def admin_reset_password(user_id): + user = User.query.get_or_404(user_id) + new_password = secrets.token_urlsafe(8) + user.password = generate_password_hash(new_password) + db.session.commit() + flash(translate('New password for %(username)s: %(password)s', + username=user.username, password=new_password), 'info') + return redirect(url_for('admin_users')) + + # Apprise Notifications import apprise @@ -1059,7 +1111,7 @@ cat < templates/base.html - + {% if games and games[0].steam_appid %} templates/base.html Logo Game Key Manager -
+
{% if current_user.is_authenticated %} + {% endif %}
@@ -1132,7 +1193,7 @@ cat < templates/base.html {% if messages %}
{% for category, message in messages %} -
+ @@ -1833,6 +1894,49 @@ cat < templates/footer.html HTML_END +# Admin interface +cat < templates/admin_users.html +{% extends "base.html" %} + +{% block content %} +
+

{{ _('User Management') }}

+ + + + + + + + + {% for user in users %} + + + + + {% endfor %} + +
{{ _('Username') }}{{ _('Actions') }}
+ {{ user.username }} + {% if user.is_admin %}Admin{% endif %} + + {% if user.id != current_user.id %} + + + + + +
+ + +
+ {% endif %} +
+
+{% endblock %} + +HTML_END + # CSS cat < static/style.css