fix: Dedizierter OnlyOffice Download-Endpunkt ohne JWT-Auth

Problem: OnlyOffice konnte Dateien nicht herunterladen weil unser
token_required-Decorator den Request ablehnte - OnlyOffice sendet
eigene Header die mit unserem JWT-System kollidieren.

Loesung: Eigener Endpunkt GET /files/oo-download/<access_key>
- Kein JWT noetig, stattdessen Einmal-Schluessel
- Schluessel wird beim Oeffnen des Editors generiert und in der DB gespeichert
- Schluessel enthaelt file_id + user_id, wird beim Download validiert
- OnlyOffice ruft diesen Endpunkt intern auf (http://minicloud:5000)
- Kein Token in der URL, keine JWT-Konflikte

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker 2026-04-11 22:24:09 +02:00
parent c3c0610750
commit 1f9b87900c
1 changed files with 31 additions and 16 deletions

View File

@ -353,22 +353,11 @@ def onlyoffice_config(file_id):
AppSettings.set(f'oo_callback_{callback_key}', str(file_id)) AppSettings.set(f'oo_callback_{callback_key}', str(file_id))
# Build the config # Build the config
# Internal URL for OnlyOffice to reach our backend (Docker network)
internal_url = os.environ.get('ONLYOFFICE_INTERNAL_URL', 'http://minicloud:5000') internal_url = os.environ.get('ONLYOFFICE_INTERNAL_URL', 'http://minicloud:5000')
# Generate a long-lived token for OnlyOffice file access (24h) # Generate a one-time file access key (no JWT needed, simpler for OnlyOffice)
from app.api.auth import create_access_token file_access_key = _secrets.token_urlsafe(32)
import jwt as pyjwt AppSettings.set(f'oo_file_{file_access_key}', f'{file_id}:{user.id}')
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',
)
config = { config = {
'available': True, 'available': True,
@ -378,7 +367,7 @@ def onlyoffice_config(file_id):
'fileType': ext, 'fileType': ext,
'key': f'{file_id}_{f.checksum or "0"}_{callback_key[:8]}', 'key': f'{file_id}_{f.checksum or "0"}_{callback_key[:8]}',
'title': f.name, '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, 'documentType': doc_type,
'editorConfig': { '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', '') jwt_secret = os.environ.get('JWT_SECRET_KEY', '')
if jwt_secret: if jwt_secret:
import jwt as pyjwt import jwt as pyjwt
@ -402,6 +391,32 @@ def onlyoffice_config(file_id):
return jsonify(config), 200 return jsonify(config), 200
@api_bp.route('/files/onlyoffice-callback', methods=['POST'])
@api_bp.route('/files/oo-download/<access_key>', 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']) @api_bp.route('/files/onlyoffice-callback', methods=['POST'])
def onlyoffice_callback(): def onlyoffice_callback():
"""Callback from OnlyOffice when document is saved.""" """Callback from OnlyOffice when document is saved."""