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))
# 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/<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'])
def onlyoffice_callback():
"""Callback from OnlyOffice when document is saved."""