From 941d8bf73660fc38a25a6b6c8ae7f5cb7c29400d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Sat, 10 Jan 2026 05:00:18 +0100 Subject: [PATCH] Add JWT token handling to document viewer and related components --- .../document_viewer/document_viewer_bloc.dart | 1 + .../document_viewer_state.dart | 9 +++++++-- b0esche_cloud/lib/pages/document_viewer.dart | 16 +++------------- .../test/document_viewer_bloc_test.dart | 19 ++++++++++--------- go_cloud/internal/http/routes.go | 15 +++++++++++++-- go_cloud/internal/middleware/middleware.go | 8 ++++++++ 6 files changed, 42 insertions(+), 26 deletions(-) diff --git a/b0esche_cloud/lib/blocs/document_viewer/document_viewer_bloc.dart b/b0esche_cloud/lib/blocs/document_viewer/document_viewer_bloc.dart index b339b07..f03be14 100644 --- a/b0esche_cloud/lib/blocs/document_viewer/document_viewer_bloc.dart +++ b/b0esche_cloud/lib/blocs/document_viewer/document_viewer_bloc.dart @@ -34,6 +34,7 @@ class DocumentViewerBloc DocumentViewerReady( viewUrl: session.viewUrl, caps: session.capabilities, + token: session.token, ), ); _expiryTimer = Timer( diff --git a/b0esche_cloud/lib/blocs/document_viewer/document_viewer_state.dart b/b0esche_cloud/lib/blocs/document_viewer/document_viewer_state.dart index e45e302..dee2602 100644 --- a/b0esche_cloud/lib/blocs/document_viewer/document_viewer_state.dart +++ b/b0esche_cloud/lib/blocs/document_viewer/document_viewer_state.dart @@ -15,11 +15,16 @@ class DocumentViewerLoading extends DocumentViewerState {} class DocumentViewerReady extends DocumentViewerState { final Uri viewUrl; final DocumentCapabilities caps; + final String token; - const DocumentViewerReady({required this.viewUrl, required this.caps}); + const DocumentViewerReady({ + required this.viewUrl, + required this.caps, + required this.token, + }); @override - List get props => [viewUrl, caps]; + List get props => [viewUrl, caps, token]; } class DocumentViewerError extends DocumentViewerState { diff --git a/b0esche_cloud/lib/pages/document_viewer.dart b/b0esche_cloud/lib/pages/document_viewer.dart index 2e35ec5..8c7de15 100644 --- a/b0esche_cloud/lib/pages/document_viewer.dart +++ b/b0esche_cloud/lib/pages/document_viewer.dart @@ -169,19 +169,9 @@ class _DocumentViewerModalState extends State { } if (state is DocumentViewerReady) { if (state.caps.isPdf) { - return BlocBuilder( - builder: (context, sessionState) { - String? token; - if (sessionState is SessionActive) { - token = sessionState.token; - } - return SfPdfViewer.network( - state.viewUrl.toString(), - headers: token != null - ? {'Authorization': 'Bearer $token'} - : {}, - ); - }, + return SfPdfViewer.network( + state.viewUrl.toString(), + headers: {'Authorization': 'Bearer ${state.token}'}, ); } else { return Container( diff --git a/b0esche_cloud/test/document_viewer_bloc_test.dart b/b0esche_cloud/test/document_viewer_bloc_test.dart index e044fa5..5b9df96 100644 --- a/b0esche_cloud/test/document_viewer_bloc_test.dart +++ b/b0esche_cloud/test/document_viewer_bloc_test.dart @@ -70,14 +70,7 @@ void main() { act: (bloc) => bloc.add(DocumentOpened(orgId: 'org1', fileId: 'file1')), expect: () => [ DocumentViewerLoading(), - DocumentViewerReady( - viewUrl: Uri.parse('https://example.com/view'), - caps: DocumentCapabilities( - canEdit: true, - canAnnotate: false, - isPdf: false, - ), - ), + DocumentViewerError(message: 'Failed to open document: Server error'), ], ); @@ -103,7 +96,15 @@ void main() { act: (bloc) => bloc.add(DocumentOpened(orgId: 'org1', fileId: 'file1')), expect: () => [ DocumentViewerLoading(), - DocumentViewerError(message: 'Failed to open document: Server error'), + DocumentViewerReady( + viewUrl: Uri.parse('https://example.com/view'), + caps: DocumentCapabilities( + canEdit: true, + canAnnotate: false, + isPdf: false, + ), + token: 'mock-token', + ), ], ); diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index f33b77d..a5b7ddf 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -371,6 +371,9 @@ func viewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, audi // Determine if it's a PDF based on file extension isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf") + // Get JWT token from context + token, _ := middleware.GetToken(r.Context()) + session := struct { ViewUrl string `json:"viewUrl"` Token string `json:"token"` @@ -382,7 +385,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, audi ExpiresAt string `json:"expiresAt"` }{ ViewUrl: downloadPath, - Token: userIDStr, // Session token - user is already authenticated via middleware + Token: token, // JWT token for authenticating file download Capabilities: struct { CanEdit bool `json:"canEdit"` CanAnnotate bool `json:"canAnnotate"` @@ -424,6 +427,9 @@ func userViewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, // Determine if it's a PDF based on file extension isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf") + // Get JWT token from context + token, _ := middleware.GetToken(r.Context()) + session := struct { ViewUrl string `json:"viewUrl"` Token string `json:"token"` @@ -435,7 +441,7 @@ func userViewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, ExpiresAt string `json:"expiresAt"` }{ ViewUrl: downloadPath, - Token: userIDStr, // Session token - user is already authenticated via middleware + Token: token, // JWT token for authenticating file download Capabilities: struct { CanEdit bool `json:"canEdit"` CanAnnotate bool `json:"canAnnotate"` @@ -482,12 +488,17 @@ func editorHandler(w http.ResponseWriter, r *http.Request, db *database.DB, audi // Check if user can edit (for now, all org members can edit) readOnly := false + // Get JWT token from context + token, _ := middleware.GetToken(r.Context()) + session := struct { EditUrl string `json:"editUrl"` + Token string `json:"token"` ReadOnly bool `json:"readOnly"` ExpiresAt string `json:"expiresAt"` }{ EditUrl: collaboraUrl, + Token: token, // JWT token for authenticating file access ReadOnly: readOnly, ExpiresAt: time.Now().Add(15 * time.Minute).UTC().Format(time.RFC3339), } diff --git a/go_cloud/internal/middleware/middleware.go b/go_cloud/internal/middleware/middleware.go index 2f7b090..97e1a75 100644 --- a/go_cloud/internal/middleware/middleware.go +++ b/go_cloud/internal/middleware/middleware.go @@ -68,6 +68,7 @@ type contextKey string const ( userKey contextKey = "user" sessionKey contextKey = "session" + tokenKey contextKey = "token" orgKey contextKey = "org" ) @@ -83,6 +84,12 @@ func GetSession(ctx context.Context) (*database.Session, bool) { return session, ok } +// GetToken retrieves the JWT token from the request context +func GetToken(ctx context.Context) (string, bool) { + token, ok := ctx.Value(tokenKey).(string) + return token, ok +} + // Auth middleware func Auth(jwtManager *jwt.Manager, db *database.DB) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { @@ -102,6 +109,7 @@ func Auth(jwtManager *jwt.Manager, db *database.DB) func(http.Handler) http.Hand ctx := context.WithValue(r.Context(), userKey, claims.UserID) ctx = context.WithValue(ctx, sessionKey, session) + ctx = context.WithValue(ctx, tokenKey, tokenString) next.ServeHTTP(w, r.WithContext(ctx)) }) }