From ef737429d610522233278ef4758d9b217f7a0e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Sun, 11 Jan 2026 17:52:08 +0100 Subject: [PATCH] FIX: Use Authorization header for PDF viewer instead of query parameter token --- b0esche_cloud/lib/pages/document_viewer.dart | 137 ++++++++++++++++++- go_cloud/internal/http/routes.go | 6 +- 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/b0esche_cloud/lib/pages/document_viewer.dart b/b0esche_cloud/lib/pages/document_viewer.dart index ee4f63c..69f8b5a 100644 --- a/b0esche_cloud/lib/pages/document_viewer.dart +++ b/b0esche_cloud/lib/pages/document_viewer.dart @@ -10,6 +10,138 @@ import '../services/file_service.dart'; import '../injection.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:go_router/go_router.dart'; +import 'dart:io'; + +// Custom HTTP client for SfPdfViewer that injects Bearer token +class AuthorizedHttpClient extends HttpClient { + final String token; + final HttpClient _inner = HttpClient(); + + AuthorizedHttpClient(this.token); + + @override + Future getUrl(Uri url) async { + final request = await _inner.getUrl(url); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future openUrl(String method, Uri url) async { + final request = await _inner.openUrl(method, url); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + set connectionTimeout(Duration? timeout) => + _inner.connectionTimeout = timeout; + + @override + Duration? get connectionTimeout => _inner.connectionTimeout; + + @override + set maxConnectionsPerHost(int? value) => + _inner.maxConnectionsPerHost = value; + + @override + int? get maxConnectionsPerHost => _inner.maxConnectionsPerHost; + + @override + Future delete(String host, + [int port = 0, String requestPath = '/']) async { + final request = await _inner.delete(host, port, requestPath); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future deleteUrl(Uri url) async { + final request = await _inner.deleteUrl(url); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future get(String host, + [int port = 0, String requestPath = '/']) async { + final request = await _inner.get(host, port, requestPath); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future head(String host, + [int port = 0, String requestPath = '/']) async { + final request = await _inner.head(host, port, requestPath); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future headUrl(Uri url) async { + final request = await _inner.headUrl(url); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future patch(String host, + [int port = 0, String requestPath = '/']) async { + final request = await _inner.patch(host, port, requestPath); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future patchUrl(Uri url) async { + final request = await _inner.patchUrl(url); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future post(String host, + [int port = 0, String requestPath = '/']) async { + final request = await _inner.post(host, port, requestPath); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future postUrl(Uri url) async { + final request = await _inner.postUrl(url); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future put(String host, + [int port = 0, String requestPath = '/']) async { + final request = await _inner.put(host, port, requestPath); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + Future putUrl(Uri url) async { + final request = await _inner.putUrl(url); + request.headers.set('Authorization', 'Bearer $token'); + return request; + } + + @override + set badCertificateCallback( + bool Function(X509Certificate, String, int)? callback) => + _inner.badCertificateCallback = callback; + + @override + bool Function(X509Certificate, String, int)? get badCertificateCallback => + _inner.badCertificateCallback; + + @override + void close({bool force = false}) => _inner.close(force: force); +} // Modal version for overlay display class DocumentViewerModal extends StatefulWidget { @@ -180,13 +312,16 @@ class _DocumentViewerModalState extends State { if (state.caps.isPdf) { // Log the URL being used for debugging print('Loading PDF from: ${state.viewUrl}'); - // Token is already included in the URL query parameter + // Create custom HTTP client that injects the Bearer token + final httpClient = AuthorizedHttpClient(state.token); + // Token is passed via Authorization header, not in URL return SfPdfViewer.network( state.viewUrl.toString(), onDocumentLoadFailed: (details) { print('PDF load failed: ${details.error}'); print('Description: ${details.description}'); }, + httpClient: httpClient, ); } else { return Container( diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index 3a7b67f..07185eb 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -459,7 +459,8 @@ func viewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, jwtM errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) return } - downloadPath := fmt.Sprintf("%s://%s/orgs/%s/files/download?path=%s&token=%s", scheme, host, orgID.String(), url.QueryEscape(file.Path), url.QueryEscape(viewerToken)) + // Download URL without token - will use Authorization header instead + downloadPath := fmt.Sprintf("%s://%s/orgs/%s/files/download?path=%s", scheme, host, orgID.String(), url.QueryEscape(file.Path)) // Determine if it's a PDF based on file extension isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf") @@ -542,7 +543,8 @@ func userViewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) return } - downloadPath := fmt.Sprintf("%s://%s/user/files/download?path=%s&token=%s", scheme, host, url.QueryEscape(file.Path), url.QueryEscape(viewerToken)) + // Download URL without token - will use Authorization header instead + downloadPath := fmt.Sprintf("%s://%s/user/files/download?path=%s", scheme, host, url.QueryEscape(file.Path)) // Determine if it's a PDF based on file extension isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf")