From 35099de2c5d453a0d55e74209e7501885e003d99 Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Sat, 11 Apr 2026 15:26:04 +0200 Subject: [PATCH] feat: Share-Dialog Fix, User-Sharing, Admin-Benutzerverwaltung, Registrierungs-Toggle - Fix: Share-Dialog oeffnet sich jetzt auch bei bereits geteilten Dateien - Neu: Dateien/Ordner direkt mit anderen Benutzern teilen (Lesen/Schreiben/Admin) - Neu: Benutzersuche im Share-Dialog, bestehende Freigaben anzeigen/entfernen - Neu: Admin kann Benutzer ueber die Weboberflaeche anlegen - Neu: Admin kann Benutzer bearbeiten (Rolle, Quota, aktiv/inaktiv) und loeschen - Neu: Schieberegler fuer oeffentliche Registrierung in den Admin-Einstellungen - Neu: Register-Link auf Login-Seite nur sichtbar wenn Registrierung erlaubt - Neu: Register-Seite leitet um wenn Registrierung deaktiviert - Neu: AppSettings-Model fuer persistente App-Konfiguration - Neu: /api/users/search Endpunkt fuer Benutzersuche in Share-Dialogen Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/api/auth.py | 19 +++ backend/app/api/users.py | 89 ++++++++++++- backend/app/models/__init__.py | 2 + backend/app/models/settings.py | 30 +++++ frontend/src/views/AdminView.vue | 198 +++++++++++++++++++++++++--- frontend/src/views/FilesView.vue | 130 +++++++++++++++--- frontend/src/views/LoginView.vue | 13 +- frontend/src/views/RegisterView.vue | 12 +- 8 files changed, 459 insertions(+), 34 deletions(-) create mode 100644 backend/app/models/settings.py diff --git a/backend/app/api/auth.py b/backend/app/api/auth.py index 8782c00..8aafca0 100644 --- a/backend/app/api/auth.py +++ b/backend/app/api/auth.py @@ -71,8 +71,27 @@ def admin_required(f): return decorated +@api_bp.route('/auth/registration-status', methods=['GET']) +def registration_status(): + """Check if public registration is allowed.""" + from app.models.settings import AppSettings + is_first_user = User.query.count() == 0 + public_registration = AppSettings.get_bool('public_registration', default=False) + return jsonify({ + 'allowed': is_first_user or public_registration, + 'is_first_user': is_first_user, + }), 200 + + @api_bp.route('/auth/register', methods=['POST']) def register(): + from app.models.settings import AppSettings + + # Check if registration is allowed + is_first_user = User.query.count() == 0 + if not is_first_user and not AppSettings.get_bool('public_registration', default=False): + return jsonify({'error': 'Oeffentliche Registrierung ist deaktiviert'}), 403 + data = request.get_json() if not data: return jsonify({'error': 'Keine Daten gesendet'}), 400 diff --git a/backend/app/api/users.py b/backend/app/api/users.py index 2c3b2f0..cd947a8 100644 --- a/backend/app/api/users.py +++ b/backend/app/api/users.py @@ -1,9 +1,12 @@ +import os + from flask import request, jsonify from app.api import api_bp from app.api.auth import admin_required, token_required from app.extensions import db from app.models.user import User +from app.models.settings import AppSettings @api_bp.route('/users', methods=['GET']) @@ -13,6 +16,51 @@ def list_users(): return jsonify([u.to_dict(include_email=True) for u in users]), 200 +@api_bp.route('/users', methods=['POST']) +@admin_required +def create_user(): + """Admin creates a new user.""" + data = request.get_json() + if not data: + return jsonify({'error': 'Keine Daten gesendet'}), 400 + + username = data.get('username', '').strip() + password = data.get('password', '') + email = data.get('email', '').strip() or None + role = data.get('role', 'user') + + if not username or not password: + return jsonify({'error': 'Benutzername und Passwort erforderlich'}), 400 + + if len(username) < 3: + return jsonify({'error': 'Benutzername muss mindestens 3 Zeichen lang sein'}), 400 + + if len(password) < 8: + return jsonify({'error': 'Passwort muss mindestens 8 Zeichen lang sein'}), 400 + + if role not in ('admin', 'user'): + return jsonify({'error': 'Rolle muss "admin" oder "user" sein'}), 400 + + if User.query.filter_by(username=username).first(): + return jsonify({'error': 'Benutzername bereits vergeben'}), 409 + + if email and User.query.filter_by(email=email).first(): + return jsonify({'error': 'Email-Adresse bereits vergeben'}), 409 + + user = User( + username=username, + email=email, + role=role, + master_key_salt=os.urandom(32), + storage_quota_mb=data.get('storage_quota_mb', 5120), + ) + user.set_password(password) + + db.session.add(user) + db.session.commit() + return jsonify(user.to_dict(include_email=True)), 201 + + @api_bp.route('/users/', methods=['GET']) @admin_required def get_user(user_id): @@ -41,7 +89,6 @@ def update_user(user_id): user.email = email if 'role' in data and data['role'] in ('admin', 'user'): - # Prevent removing last admin if user.role == 'admin' and data['role'] == 'user': admin_count = User.query.filter_by(role='admin', is_active=True).count() if admin_count <= 1: @@ -85,6 +132,46 @@ def delete_user(user_id): return jsonify({'message': 'Benutzer geloescht'}), 200 +# --- App Settings --- + +@api_bp.route('/settings', methods=['GET']) +@admin_required +def get_settings(): + return jsonify({ + 'public_registration': AppSettings.get_bool('public_registration', default=False), + }), 200 + + +@api_bp.route('/settings', methods=['PUT']) +@admin_required +def update_settings(): + data = request.get_json() + if 'public_registration' in data: + AppSettings.set('public_registration', str(data['public_registration']).lower()) + return jsonify({'message': 'Einstellungen gespeichert'}), 200 + + +# --- User search (for sharing dialogs) --- + +@api_bp.route('/users/search', methods=['GET']) +@token_required +def search_users(): + """Search users by username - for sharing dialogs.""" + query = request.args.get('q', '').strip() + if len(query) < 2: + return jsonify([]), 200 + + users = User.query.filter( + User.username.ilike(f'%{query}%'), + User.id != request.current_user.id, + User.is_active == True, + ).limit(10).all() + + return jsonify([{'id': u.id, 'username': u.username} for u in users]), 200 + + +# --- Change password (non-admin, own account) --- + @api_bp.route('/auth/change-password', methods=['POST']) @token_required def change_password(): diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 01d4dcf..bf43b50 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -4,6 +4,7 @@ from app.models.calendar import Calendar, CalendarEvent, CalendarShare from app.models.contact import AddressBook, Contact, AddressBookShare from app.models.email_account import EmailAccount from app.models.password_vault import PasswordFolder, PasswordEntry, PasswordShare +from app.models.settings import AppSettings __all__ = [ 'User', @@ -12,4 +13,5 @@ __all__ = [ 'AddressBook', 'Contact', 'AddressBookShare', 'EmailAccount', 'PasswordFolder', 'PasswordEntry', 'PasswordShare', + 'AppSettings', ] diff --git a/backend/app/models/settings.py b/backend/app/models/settings.py new file mode 100644 index 0000000..78e5154 --- /dev/null +++ b/backend/app/models/settings.py @@ -0,0 +1,30 @@ +from app.extensions import db + + +class AppSettings(db.Model): + __tablename__ = 'app_settings' + + key = db.Column(db.String(100), primary_key=True) + value = db.Column(db.Text, nullable=False) + + @staticmethod + def get(key, default=''): + setting = db.session.get(AppSettings, key) + return setting.value if setting else default + + @staticmethod + def set(key, value): + setting = db.session.get(AppSettings, key) + if setting: + setting.value = str(value) + else: + setting = AppSettings(key=key, value=str(value)) + db.session.add(setting) + db.session.commit() + + @staticmethod + def get_bool(key, default=False): + val = AppSettings.get(key, '') + if val == '': + return default + return val.lower() in ('true', '1', 'yes') diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index 0711647..8b84ce9 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -4,12 +4,31 @@

Administration

+
-

Benutzerverwaltung

+

Einstellungen

+
+
+ Oeffentliche Registrierung +

Wenn aktiviert, koennen sich neue Benutzer selbst registrieren. Andernfalls kann nur ein Admin neue Benutzer anlegen.

+
+ +
+
+ + +
+
+

Benutzerverwaltung

+
+ - + + + - +
+ + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
-
- - +
+
+ {{ u.username }} +
+
+
+
-
-