diff --git a/AstraOS/.env b/AstraOS/.env index c72aa3f..e9eb953 100644 --- a/AstraOS/.env +++ b/AstraOS/.env @@ -5,3 +5,8 @@ SSH_USER='root' SSH_PASS='iwkms@1812:(' VPNAPI_KEY='015ada9472664329affb2f26d7036d47' +# Discord OAuth2 Credentials +DISCORD_CLIENT_ID='1395018660267036712' +DISCORD_CLIENT_SECRET='QVGmwtTDsuE8mh2YlBMqeHndeRRKW9-a' +DISCORD_BOT_TOKEN='MTM5NTAxODY2MDI2NzAzNjcxMg.GJE-k4.W0o09yJZfG3onzyzHLbXu4vBrKPXFS1IVuzhMA' +DISCORD_REDIRECT_URI='http://127.0.0.1:5000/callback/discord' diff --git a/AstraOS/Frontend/HTML/link_discord.html b/AstraOS/Frontend/HTML/link_discord.html new file mode 100644 index 0000000..b851141 --- /dev/null +++ b/AstraOS/Frontend/HTML/link_discord.html @@ -0,0 +1,75 @@ + + + + + + AstraOS — Discord verknüpfen + + + + + +
+ +
+
+

Discord-Konto verknüpfen

+

+ Um die Sicherheit deines Kontos zu gewährleisten und alle Funktionen von AstraOS nutzen zu können, + ist eine Verknüpfung mit deinem Discord-Konto erforderlich. +

+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} + {% endwith %} + + + + Mit Discord verknüpfen + +
+ Abmelden +
+
+
+ + + + + diff --git a/AstraOS/Frontend/HTML/login.html b/AstraOS/Frontend/HTML/login.html index 7ef1865..de3a40a 100644 --- a/AstraOS/Frontend/HTML/login.html +++ b/AstraOS/Frontend/HTML/login.html @@ -111,6 +111,18 @@ Login +
+
+ oder +
+
+ + + + Mit Discord anmelden + + diff --git a/AstraOS/Frontend/HTML/settings.html b/AstraOS/Frontend/HTML/settings.html new file mode 100644 index 0000000..c383051 --- /dev/null +++ b/AstraOS/Frontend/HTML/settings.html @@ -0,0 +1,88 @@ +{% extends "sidebar.html" %} + +{% block title %}AstraOS — Einstellungen{% endblock %} + +{% block content %} +
+
+

Einstellungen

+
+ + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} + {% endwith %} + + +
+

Account-Informationen

+
+
+ +

{{ user.username }}

+

Um deinen Benutzernamen zu ändern, kontaktiere bitte einen Administrator.

+
+
+
+ + +
+

Passwort ändern

+
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+

Discord-Verknüpfung

+ {% if user.discord_id %} +
+ {% if user.discord_avatar %} + Discord Avatar + {% endif %} +
+

{{ user.discord_username }}

+

Erfolgreich verknüpft.

+
+
+
+ + +
+ {% else %} +

Dein Account ist nicht mit Discord verknüpft.

+ + + Mit Discord verknüpfen + + {% endif %} +
+
+{% endblock %} + diff --git a/AstraOS/Frontend/HTML/sidebar.html b/AstraOS/Frontend/HTML/sidebar.html index 933964f..2bd6d44 100644 --- a/AstraOS/Frontend/HTML/sidebar.html +++ b/AstraOS/Frontend/HTML/sidebar.html @@ -159,6 +159,13 @@ Status Page + + Einstellungen + +
diff --git a/AstraOS/astra_os.py b/AstraOS/astra_os.py index e9c46ec..d278fab 100644 --- a/AstraOS/astra_os.py +++ b/AstraOS/astra_os.py @@ -97,7 +97,10 @@ try: 'username': 'admin', 'password': hashed_password, 'role_id': owner_role['_id'], - 'creation_date': datetime.utcnow() + 'creation_date': datetime.utcnow(), + 'discord_id': None, + 'discord_username': None, + 'discord_avatar': None }) print("INFO: Standard-Admin-Benutzer ('admin', Passwort: '1234') mit Rolle 'Owner' wurde erstellt.") if dashboard_collection.count_documents({}) == 0: @@ -145,6 +148,17 @@ def login_required(f): if 'user_id' not in session: flash("Bitte melden Sie sich an, um diese Seite zu sehen.", "warning") return redirect(url_for('login')) + + user = accounts_collection.find_one({'_id': ObjectId(session['user_id'])}) + if not user: + session.clear() + flash("Benutzer nicht gefunden.", "danger") + return redirect(url_for('login')) + + # Discord-Verknüpfung prüfen + if not user.get('discord_id') and request.endpoint not in ['link_discord', 'login_discord', 'callback_discord', 'logout', 'static']: + return redirect(url_for('link_discord')) + session['permissions'] = get_user_permissions(session['user_id']) maintenance_status = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if maintenance_status and maintenance_status.get('enabled', False): @@ -192,6 +206,12 @@ def login(): session['user_id'] = str(user['_id']) session['username'] = user['username'] session['permissions'] = get_user_permissions(str(user['_id'])) + + # Prüfen, ob Discord verknüpft ist + if not user.get('discord_id'): + flash("Bitte verknüpfe deinen Discord-Account, um fortzufahren.", "info") + return redirect(url_for('link_discord')) + flash("Erfolgreich angemeldet!", "success") return redirect(url_for('dashboard')) else: @@ -349,6 +369,7 @@ def user_management(): users_for_table = list(accounts_collection.find()) for user in users_for_table: user['role_name'] = role_map.get(str(user.get('role_id')), 'Unbekannt') + user['discord_linked'] = bool(user.get('discord_id')) return render_template('user_management.html', users_for_modal=users_for_modal, users_for_table=users_for_table, @@ -375,7 +396,10 @@ def add_user(): 'username': username, 'password': hashed_password, 'role_id': ObjectId(role_id), - 'creation_date': datetime.utcnow() + 'creation_date': datetime.utcnow(), + 'discord_id': None, + 'discord_username': None, + 'discord_avatar': None }) if dashboard_collection is not None: dashboard_collection.update_one( @@ -436,6 +460,138 @@ def delete_user(user_id): accounts_collection.delete_one({'_id': ObjectId(user_id)}) flash("Benutzer wurde erfolgreich gelöscht.", "success") return redirect(url_for('user_management')) + +@app.route('/settings', methods=['GET']) +@login_required +def settings(): + user = accounts_collection.find_one({'_id': ObjectId(session['user_id'])}) + return render_template('settings.html', user=user, active_page='settings') + +@app.route('/update_password', methods=['POST']) +@login_required +def update_password(): + user_id = session['user_id'] + current_password = request.form.get('current_password') + new_password = request.form.get('new_password') + confirm_password = request.form.get('confirm_password') + + if not all([current_password, new_password, confirm_password]): + flash("Alle Passwortfelder sind erforderlich.", "danger") + return redirect(url_for('settings')) + + if new_password != confirm_password: + flash("Die neuen Passwörter stimmen nicht überein.", "danger") + return redirect(url_for('settings')) + + user = accounts_collection.find_one({'_id': ObjectId(user_id)}) + if not user or not check_password_hash(user['password'], current_password): + flash("Das aktuelle Passwort ist nicht korrekt.", "danger") + return redirect(url_for('settings')) + + hashed_password = generate_password_hash(new_password) + accounts_collection.update_one({'_id': ObjectId(user_id)}, {'$set': {'password': hashed_password}}) + flash("Dein Passwort wurde erfolgreich geändert.", "success") + return redirect(url_for('settings')) + +@app.route('/unlink_discord', methods=['POST']) +@login_required +def unlink_discord(): + user_id = session['user_id'] + accounts_collection.update_one( + {'_id': ObjectId(user_id)}, + {'$set': {'discord_id': None, 'discord_username': None, 'discord_avatar': None}} + ) + flash("Deine Discord-Verknüpfung wurde entfernt. Bitte verknüpfe dein Konto erneut.", "info") + return redirect(url_for('link_discord')) + +@app.route('/link_discord') +@login_required +def link_discord(): + return render_template('link_discord.html') + +@app.route('/login/discord') +def login_discord(): + client_id = os.environ.get('DISCORD_CLIENT_ID') + redirect_uri = os.environ.get('DISCORD_REDIRECT_URI') + scope = 'identify' + discord_auth_url = ( + f'https://discord.com/api/oauth2/authorize?client_id={client_id}&' + f'redirect_uri={redirect_uri}&response_type=code&scope={scope}' + ) + return redirect(discord_auth_url) + +@app.route('/callback/discord') +def callback_discord(): + code = request.args.get('code') + if not code: + flash("Discord-Authentifizierung fehlgeschlagen.", "danger") + return redirect(url_for('login')) + + client_id = os.environ.get('DISCORD_CLIENT_ID') + client_secret = os.environ.get('DISCORD_CLIENT_SECRET') + redirect_uri = os.environ.get('DISCORD_REDIRECT_URI') + + token_url = 'https://discord.com/api/oauth2/token' + token_data = { + 'client_id': client_id, + 'client_secret': client_secret, + 'grant_type': 'authorization_code', + 'code': code, + 'redirect_uri': redirect_uri, + 'scope': 'identify' + } + headers = {'Content-Type': 'application/x-www-form-urlencoded'} + token_r = requests.post(token_url, data=token_data, headers=headers) + if token_r.status_code != 200: + flash("Fehler beim Abrufen des Tokens von Discord.", "danger") + return redirect(url_for('login')) + + access_token = token_r.json()['access_token'] + user_info_url = 'https://discord.com/api/users/@me' + user_info_r = requests.get(user_info_url, headers={'Authorization': f'Bearer {access_token}'}) + if user_info_r.status_code != 200: + flash("Fehler beim Abrufen der Benutzerinformationen von Discord.", "danger") + return redirect(url_for('login')) + + discord_user = user_info_r.json() + discord_id = discord_user['id'] + discord_username = f"{discord_user['username']}" + discord_avatar = discord_user.get('avatar') + + # Fall 1: Benutzer ist bereits eingeloggt und möchte verknüpfen + if 'user_id' in session: + user_id = session['user_id'] + # Prüfen, ob dieser Discord-Account bereits von einem anderen Benutzer verwendet wird + existing_link = accounts_collection.find_one({'discord_id': discord_id}) + if existing_link and str(existing_link['_id']) != user_id: + flash("Dieser Discord-Account ist bereits mit einem anderen Benutzer verknüpft.", "danger") + return redirect(url_for('settings')) + + accounts_collection.update_one( + {'_id': ObjectId(user_id)}, + {'$set': { + 'discord_id': discord_id, + 'discord_username': discord_username, + 'discord_avatar': discord_avatar + }} + ) + flash("Discord-Account erfolgreich verknüpft!", "success") + return redirect(url_for('dashboard')) + + # Fall 2: Benutzer meldet sich direkt über Discord an + else: + user = accounts_collection.find_one({'discord_id': discord_id}) + if user: + session.permanent = True + session['user_id'] = str(user['_id']) + session['username'] = user['username'] + session['permissions'] = get_user_permissions(str(user['_id'])) + flash("Erfolgreich mit Discord angemeldet!", "success") + return redirect(url_for('dashboard')) + else: + flash("Kein AstraOS-Benutzer mit diesem Discord-Konto gefunden. Bitte melde dich zuerst normal an und verknüpfe deinen Account.", "warning") + return redirect(url_for('login')) + @app.route('/role_management') @login_required @permission_required('view_role_management')