From 116c33a7dc91efb24cc566da42740427b98d46aa Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Sat, 11 Apr 2026 18:39:38 +0200 Subject: [PATCH] feat: Share-Links mit Berechtigungen (Lesen / Lesen+Schreiben) Share-Links haben jetzt ein permission-Feld (read/write): - read (Standard): Nur Download erlaubt, kein Upload, kein Aendern - write: Download + Upload in Ordner erlaubt Backend-Absicherung: - POST /share//upload prueft permission == 'write', gibt 403 bei read-only Links zurueck - GET /share//info gibt permission + upload_allowed zurueck - ShareLink-Model hat neues permission-Feld (default: 'read') Frontend Share-Dialog: - Dropdown "Berechtigung" beim Erstellen von Links (Nur Lesen / Lesen+Hochladen) - Bestehende Links zeigen Berechtigungslevel an Frontend ShareView: - Upload-Zone nur sichtbar wenn upload_allowed == true - Bei read-only Links: kein Drag & Drop, kein Upload-Button Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/api/files.py | 16 ++++++++++++++-- backend/app/models/file.py | 1 + frontend/src/views/FilesView.vue | 12 ++++++++++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/backend/app/api/files.py b/backend/app/api/files.py index b7c8768..b3ac8ab 100644 --- a/backend/app/api/files.py +++ b/backend/app/api/files.py @@ -419,6 +419,10 @@ def create_share_link(file_id): password = data.get('password') expires_at = data.get('expires_at') max_downloads = data.get('max_downloads') + permission = data.get('permission', 'read') + + if permission not in ('read', 'write'): + return jsonify({'error': 'Berechtigung muss "read" oder "write" sein'}), 400 token = secrets.token_urlsafe(32) password_hash = None @@ -435,6 +439,7 @@ def create_share_link(file_id): link = ShareLink( file_id=file_id, token=token, + permission=permission, password_hash=password_hash, expires_at=exp_dt, created_by=user.id, @@ -446,6 +451,7 @@ def create_share_link(file_id): return jsonify({ 'token': token, 'url': f'/share/{token}', + 'permission': permission, 'expires_at': exp_dt.isoformat() if exp_dt else None, 'has_password': bool(password), }), 201 @@ -463,6 +469,7 @@ def list_share_links(file_id): return jsonify([{ 'id': l.id, 'token': l.token, + 'permission': l.permission, 'has_password': bool(l.password_hash), 'expires_at': l.expires_at.isoformat() if l.expires_at else None, 'download_count': l.download_count, @@ -490,7 +497,8 @@ def share_info(token): 'size': f.size, 'mime_type': f.mime_type, 'has_password': bool(link.password_hash), - 'upload_allowed': f.is_folder, + 'permission': link.permission, + 'upload_allowed': f.is_folder and link.permission == 'write', }), 200 @@ -560,7 +568,7 @@ def share_download(token): @api_bp.route('/share//upload', methods=['POST']) def share_upload(token): - """Upload a file via a share link (only if the shared item is a folder).""" + """Upload a file via a share link (only if the shared item is a folder with write permission).""" link = ShareLink.query.filter_by(token=token).first() if not link: return jsonify({'error': 'Link nicht gefunden'}), 404 @@ -568,6 +576,10 @@ def share_upload(token): if link.is_expired(): return jsonify({'error': 'Link abgelaufen'}), 410 + # Check write permission + if link.permission != 'write': + return jsonify({'error': 'Dieser Link erlaubt nur Lesen'}), 403 + # Check password if set if link.password_hash: password = request.form.get('password', '') or request.headers.get('X-Share-Password', '') diff --git a/backend/app/models/file.py b/backend/app/models/file.py index a545818..3509969 100644 --- a/backend/app/models/file.py +++ b/backend/app/models/file.py @@ -62,6 +62,7 @@ class ShareLink(db.Model): id = db.Column(db.Integer, primary_key=True) file_id = db.Column(db.Integer, db.ForeignKey('files.id'), nullable=False, index=True) token = db.Column(db.String(64), unique=True, nullable=False, index=True) + permission = db.Column(db.String(20), default='read', nullable=False) # 'read' or 'write' password_hash = db.Column(db.String(255), nullable=True) expires_at = db.Column(db.DateTime, nullable=True) created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) diff --git a/frontend/src/views/FilesView.vue b/frontend/src/views/FilesView.vue index 6914bc9..4b44523 100644 --- a/frontend/src/views/FilesView.vue +++ b/frontend/src/views/FilesView.vue @@ -162,6 +162,10 @@