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) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-11 15:26:04 +02:00
parent e7170b7cc8
commit 35099de2c5
8 changed files with 459 additions and 34 deletions
+88 -1
View File
@@ -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/<int:user_id>', 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():