diff --git a/backend/app/api/office.py b/backend/app/api/office.py index fec5700..7962ce0 100644 --- a/backend/app/api/office.py +++ b/backend/app/api/office.py @@ -353,22 +353,11 @@ def onlyoffice_config(file_id): AppSettings.set(f'oo_callback_{callback_key}', str(file_id)) # Build the config - # Internal URL for OnlyOffice to reach our backend (Docker network) internal_url = os.environ.get('ONLYOFFICE_INTERNAL_URL', 'http://minicloud:5000') - # Generate a long-lived token for OnlyOffice file access (24h) - from app.api.auth import create_access_token - import jwt as pyjwt - oo_file_token = pyjwt.encode( - { - 'user_id': user.id, - 'type': 'access', - 'exp': datetime.now(timezone.utc) + timedelta(hours=24), - 'iat': datetime.now(timezone.utc), - }, - current_app.config['JWT_SECRET_KEY'], - algorithm='HS256', - ) + # Generate a one-time file access key (no JWT needed, simpler for OnlyOffice) + file_access_key = _secrets.token_urlsafe(32) + AppSettings.set(f'oo_file_{file_access_key}', f'{file_id}:{user.id}') config = { 'available': True, @@ -378,7 +367,7 @@ def onlyoffice_config(file_id): 'fileType': ext, 'key': f'{file_id}_{f.checksum or "0"}_{callback_key[:8]}', 'title': f.name, - 'url': f'{internal_url}/api/files/{file_id}/download?token={oo_file_token}', + 'url': f'{internal_url}/api/files/oo-download/{file_access_key}', }, 'documentType': doc_type, 'editorConfig': { @@ -393,7 +382,7 @@ def onlyoffice_config(file_id): }, } - # Sign with JWT if secret is set + # Sign config with JWT for OnlyOffice validation jwt_secret = os.environ.get('JWT_SECRET_KEY', '') if jwt_secret: import jwt as pyjwt @@ -402,6 +391,32 @@ def onlyoffice_config(file_id): return jsonify(config), 200 +@api_bp.route('/files/onlyoffice-callback', methods=['POST']) +@api_bp.route('/files/oo-download/', methods=['GET']) +def oo_download(access_key): + """Dedicated download endpoint for OnlyOffice - no JWT auth, uses one-time key.""" + data = AppSettings.get(f'oo_file_{access_key}', '') + if not data: + return jsonify({'error': 'Ungueltiger Zugangsschluessel'}), 403 + + parts = data.split(':') + if len(parts) != 2: + return jsonify({'error': 'Ungueltiger Zugangsschluessel'}), 403 + + file_id = int(parts[0]) + from app.models.file import File + f = db.session.get(File, file_id) + if not f: + return jsonify({'error': 'Datei nicht gefunden'}), 404 + + filepath = Path(current_app.config['UPLOAD_PATH']) / str(f.owner_id) / f.storage_path + if not filepath.exists(): + return jsonify({'error': 'Datei nicht auf Datentraeger'}), 404 + + return send_file(str(filepath), mimetype=f.mime_type or 'application/octet-stream', + as_attachment=False, download_name=f.name) + + @api_bp.route('/files/onlyoffice-callback', methods=['POST']) def onlyoffice_callback(): """Callback from OnlyOffice when document is saved."""