35099de2c5
- 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>
199 lines
6.3 KiB
Python
199 lines
6.3 KiB
Python
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'])
|
|
@admin_required
|
|
def list_users():
|
|
users = User.query.order_by(User.created_at.desc()).all()
|
|
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):
|
|
user = db.session.get(User, user_id)
|
|
if not user:
|
|
return jsonify({'error': 'Benutzer nicht gefunden'}), 404
|
|
return jsonify(user.to_dict(include_email=True)), 200
|
|
|
|
|
|
@api_bp.route('/users/<int:user_id>', methods=['PUT'])
|
|
@admin_required
|
|
def update_user(user_id):
|
|
user = db.session.get(User, user_id)
|
|
if not user:
|
|
return jsonify({'error': 'Benutzer nicht gefunden'}), 404
|
|
|
|
data = request.get_json()
|
|
if not data:
|
|
return jsonify({'error': 'Keine Daten gesendet'}), 400
|
|
|
|
if 'email' in data:
|
|
email = data['email'].strip() or None
|
|
if email and email != user.email:
|
|
if User.query.filter_by(email=email).first():
|
|
return jsonify({'error': 'Email bereits vergeben'}), 409
|
|
user.email = email
|
|
|
|
if 'role' in data and data['role'] in ('admin', 'user'):
|
|
if user.role == 'admin' and data['role'] == 'user':
|
|
admin_count = User.query.filter_by(role='admin', is_active=True).count()
|
|
if admin_count <= 1:
|
|
return jsonify({'error': 'Letzter Admin kann nicht herabgestuft werden'}), 400
|
|
user.role = data['role']
|
|
|
|
if 'is_active' in data:
|
|
if user.id == request.current_user.id and not data['is_active']:
|
|
return jsonify({'error': 'Eigenes Konto kann nicht deaktiviert werden'}), 400
|
|
user.is_active = data['is_active']
|
|
|
|
if 'storage_quota_mb' in data:
|
|
user.storage_quota_mb = max(0, int(data['storage_quota_mb']))
|
|
|
|
if 'password' in data and data['password']:
|
|
if len(data['password']) < 8:
|
|
return jsonify({'error': 'Passwort muss mindestens 8 Zeichen lang sein'}), 400
|
|
user.set_password(data['password'])
|
|
|
|
db.session.commit()
|
|
return jsonify(user.to_dict(include_email=True)), 200
|
|
|
|
|
|
@api_bp.route('/users/<int:user_id>', methods=['DELETE'])
|
|
@admin_required
|
|
def delete_user(user_id):
|
|
user = db.session.get(User, user_id)
|
|
if not user:
|
|
return jsonify({'error': 'Benutzer nicht gefunden'}), 404
|
|
|
|
if user.id == request.current_user.id:
|
|
return jsonify({'error': 'Eigenes Konto kann nicht geloescht werden'}), 400
|
|
|
|
if user.role == 'admin':
|
|
admin_count = User.query.filter_by(role='admin', is_active=True).count()
|
|
if admin_count <= 1:
|
|
return jsonify({'error': 'Letzter Admin kann nicht geloescht werden'}), 400
|
|
|
|
db.session.delete(user)
|
|
db.session.commit()
|
|
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():
|
|
data = request.get_json()
|
|
if not data:
|
|
return jsonify({'error': 'Keine Daten gesendet'}), 400
|
|
|
|
current_password = data.get('current_password', '')
|
|
new_password = data.get('new_password', '')
|
|
|
|
if not current_password or not new_password:
|
|
return jsonify({'error': 'Aktuelles und neues Passwort erforderlich'}), 400
|
|
|
|
if len(new_password) < 8:
|
|
return jsonify({'error': 'Neues Passwort muss mindestens 8 Zeichen lang sein'}), 400
|
|
|
|
user = request.current_user
|
|
if not user.check_password(current_password):
|
|
return jsonify({'error': 'Aktuelles Passwort falsch'}), 401
|
|
|
|
user.set_password(new_password)
|
|
db.session.commit()
|
|
|
|
return jsonify({'message': 'Passwort geaendert'}), 200
|