diff --git a/b0esche_cloud/lib/pages/document_viewer.dart b/b0esche_cloud/lib/pages/document_viewer.dart index 3c0ae75..d1f81d8 100644 --- a/b0esche_cloud/lib/pages/document_viewer.dart +++ b/b0esche_cloud/lib/pages/document_viewer.dart @@ -403,12 +403,28 @@ class _DocumentViewerModalState extends State { final wopiSession = snapshot.data!; - // Don't build URL with query parameters - pass WOPISrc separately to JavaScript - return _buildWebView(wopiSession.wopisrc); + // Use backend proxy endpoint to serve the Collabora form + final proxyUrl = _buildProxyUrl(token); + return _buildWebView(proxyUrl); }, ); } + String _buildProxyUrl(String token) { + // Build the proxy URL based on whether we're in org or user workspace + String baseUrl = 'https://go.b0esche.cloud'; + String endpoint; + + if (widget.orgId.isNotEmpty && widget.orgId != 'personal') { + endpoint = '/orgs/${widget.orgId}/files/${widget.fileId}/collabora-proxy'; + } else { + endpoint = '/user/files/${widget.fileId}/collabora-proxy'; + } + + // Pass token as query parameter for iframe (which cannot send Authorization header) + return '$baseUrl$endpoint?token=$token'; + } + Future _createWOPISession(String token) async { try { // Use default base URL from backend @@ -448,32 +464,29 @@ class _DocumentViewerModalState extends State { } } - Widget _buildCollaboraIframe(String wopisrc) { - // For Collabora Online, POST the WOPISrc to loleaflet.html - // Use JavaScript to submit the form reliably + Widget _buildCollaboraIframe(String proxyUrl) { + // Load the backend proxy page which handles Collabora form submission final String viewType = - 'collabora-viewer-${DateTime.now().millisecondsSinceEpoch}'; - final String iframeName = 'collabora-iframe-$viewType'; + 'collabora-${DateTime.now().millisecondsSinceEpoch}'; ui.platformViewRegistry.registerViewFactory(viewType, (int viewId) { - // Create the iframe that will receive the form submission + // Create iframe pointing to the proxy endpoint final iframe = html.IFrameElement() - ..name = iframeName ..style.border = 'none' ..style.width = '100%' ..style.height = '100%' ..style.margin = '0' ..style.padding = '0' + ..src = proxyUrl ..setAttribute( 'allow', 'microphone; camera; usb; autoplay; clipboard-read; clipboard-write', ) ..setAttribute( 'sandbox', - 'allow-same-origin allow-scripts allow-popups allow-forms allow-pointer-lock allow-presentation allow-modals allow-downloads', + 'allow-same-origin allow-scripts allow-popups allow-forms', ); - // Create container final container = html.DivElement() ..style.width = '100%' ..style.height = '100%' @@ -482,43 +495,15 @@ class _DocumentViewerModalState extends State { ..style.overflow = 'hidden' ..append(iframe); - // Create JavaScript to submit the form - final jsCode = - ''' -(function() { - var form = document.createElement('form'); - form.method = 'POST'; - form.action = 'https://of.b0esche.cloud/loleaflet/dist/loleaflet.html'; - form.target = '$iframeName'; - form.style.display = 'none'; - - var input = document.createElement('input'); - input.type = 'hidden'; - input.name = 'WOPISrc'; - input.value = '$wopisrc'; - - form.appendChild(input); - document.body.appendChild(form); - form.submit(); -})(); - '''; - - // Create and execute script - final script = html.ScriptElement() - ..type = 'text/javascript' - ..text = jsCode; - - html.document.head!.append(script); - return container; }); return HtmlElementView(viewType: viewType); } - Widget _buildWebView(String wopisrc) { - // Embed Collabora Online in an iframe for web platform - return _buildCollaboraIframe(wopisrc); + Widget _buildWebView(String proxyUrl) { + // Embed Collabora Online via proxy endpoint + return _buildCollaboraIframe(proxyUrl); } @override diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index d68ec39..3265b9c 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -185,6 +185,10 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut r.Post("/user/files/{fileId}/wopi-session", func(w http.ResponseWriter, req *http.Request) { wopiSessionHandler(w, req, db, jwtManager, "https://of.b0esche.cloud") }) + // Collabora form proxy for user files + r.Get("/user/files/{fileId}/collabora-proxy", func(w http.ResponseWriter, req *http.Request) { + collaboraProxyHandler(w, req, db, jwtManager, "https://of.b0esche.cloud") + }) // Org routes r.Get("/orgs", func(w http.ResponseWriter, req *http.Request) { @@ -241,6 +245,10 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut r.With(middleware.Permission(db, auditLogger, permission.DocumentView)).Post("/wopi-session", func(w http.ResponseWriter, req *http.Request) { wopiSessionHandler(w, req, db, jwtManager, "https://of.b0esche.cloud") }) + // Collabora form proxy for org files + r.With(middleware.Permission(db, auditLogger, permission.DocumentView)).Get("/collabora-proxy", func(w http.ResponseWriter, req *http.Request) { + collaboraProxyHandler(w, req, db, jwtManager, "https://of.b0esche.cloud") + }) }) r.Get("/activity", func(w http.ResponseWriter, req *http.Request) { activityHandler(w, req, db) diff --git a/go_cloud/internal/http/wopi_handlers.go b/go_cloud/internal/http/wopi_handlers.go index d722a83..26a1894 100644 --- a/go_cloud/internal/http/wopi_handlers.go +++ b/go_cloud/internal/http/wopi_handlers.go @@ -603,3 +603,65 @@ func wopiSessionHandler(w http.ResponseWriter, r *http.Request, db *database.DB, fmt.Printf("[WOPI-REQUEST] Session created: file=%s user=%s\n", fileID, userID.String()) } +// CollaboraProxyHandler serves an HTML page that POSTs WOPISrc to Collabora +// This avoids CORS issues by having the POST originate from our domain +func collaboraProxyHandler(w http.ResponseWriter, r *http.Request, db *database.DB, jwtManager *jwt.Manager, collaboraURL string) { + fileID := r.PathValue("fileId") + if fileID == "" { + errors.WriteError(w, errors.CodeInvalidArgument, "Missing fileId", http.StatusBadRequest) + return + } + + // Get user from context (from auth middleware) + userIDStr, ok := middleware.GetUserID(r.Context()) + if !ok { + errors.WriteError(w, errors.CodeUnauthenticated, "Not authenticated", http.StatusUnauthorized) + return + } + + userID, err := uuid.Parse(userIDStr) + if err != nil { + errors.WriteError(w, errors.CodeInvalidArgument, "Invalid user ID", http.StatusBadRequest) + return + } + + // Create WOPI session + wopiSrc, accessToken, err := createWOPISession(r.Context(), db, jwtManager, userID, fileID) + if err != nil { + fmt.Printf("[WOPI-ERROR] Failed to create session: %v\n", err) + errors.WriteError(w, errors.CodeInternal, "Failed to create WOPI session", http.StatusInternalServerError) + return + } + + // Return HTML page with auto-submitting form + htmlContent := fmt.Sprintf(` + + + Loading Document... + + + +
+
Loading document in Collabora Online...
+
+ + + +`, collaboraURL, wopiSrc) + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("X-Frame-Options", "SAMEORIGIN") + w.WriteHeader(http.StatusOK) + w.Write([]byte(htmlContent)) + + fmt.Printf("[COLLABORA-PROXY] Served HTML form: file=%s user=%s\n", fileID, userID.String()) +} \ No newline at end of file