Add JWT token handling to document viewer and related components

This commit is contained in:
Leon Bösche
2026-01-10 05:00:18 +01:00
parent b381a46483
commit 941d8bf736
6 changed files with 42 additions and 26 deletions

View File

@@ -34,6 +34,7 @@ class DocumentViewerBloc
DocumentViewerReady( DocumentViewerReady(
viewUrl: session.viewUrl, viewUrl: session.viewUrl,
caps: session.capabilities, caps: session.capabilities,
token: session.token,
), ),
); );
_expiryTimer = Timer( _expiryTimer = Timer(

View File

@@ -15,11 +15,16 @@ class DocumentViewerLoading extends DocumentViewerState {}
class DocumentViewerReady extends DocumentViewerState { class DocumentViewerReady extends DocumentViewerState {
final Uri viewUrl; final Uri viewUrl;
final DocumentCapabilities caps; 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 @override
List<Object> get props => [viewUrl, caps]; List<Object> get props => [viewUrl, caps, token];
} }
class DocumentViewerError extends DocumentViewerState { class DocumentViewerError extends DocumentViewerState {

View File

@@ -169,19 +169,9 @@ class _DocumentViewerModalState extends State<DocumentViewerModal> {
} }
if (state is DocumentViewerReady) { if (state is DocumentViewerReady) {
if (state.caps.isPdf) { if (state.caps.isPdf) {
return BlocBuilder<SessionBloc, SessionState>(
builder: (context, sessionState) {
String? token;
if (sessionState is SessionActive) {
token = sessionState.token;
}
return SfPdfViewer.network( return SfPdfViewer.network(
state.viewUrl.toString(), state.viewUrl.toString(),
headers: token != null headers: {'Authorization': 'Bearer ${state.token}'},
? {'Authorization': 'Bearer $token'}
: {},
);
},
); );
} else { } else {
return Container( return Container(

View File

@@ -70,14 +70,7 @@ void main() {
act: (bloc) => bloc.add(DocumentOpened(orgId: 'org1', fileId: 'file1')), act: (bloc) => bloc.add(DocumentOpened(orgId: 'org1', fileId: 'file1')),
expect: () => [ expect: () => [
DocumentViewerLoading(), DocumentViewerLoading(),
DocumentViewerReady( DocumentViewerError(message: 'Failed to open document: Server error'),
viewUrl: Uri.parse('https://example.com/view'),
caps: DocumentCapabilities(
canEdit: true,
canAnnotate: false,
isPdf: false,
),
),
], ],
); );
@@ -103,7 +96,15 @@ void main() {
act: (bloc) => bloc.add(DocumentOpened(orgId: 'org1', fileId: 'file1')), act: (bloc) => bloc.add(DocumentOpened(orgId: 'org1', fileId: 'file1')),
expect: () => [ expect: () => [
DocumentViewerLoading(), 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',
),
], ],
); );

View File

@@ -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 // Determine if it's a PDF based on file extension
isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf") isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf")
// Get JWT token from context
token, _ := middleware.GetToken(r.Context())
session := struct { session := struct {
ViewUrl string `json:"viewUrl"` ViewUrl string `json:"viewUrl"`
Token string `json:"token"` Token string `json:"token"`
@@ -382,7 +385,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, audi
ExpiresAt string `json:"expiresAt"` ExpiresAt string `json:"expiresAt"`
}{ }{
ViewUrl: downloadPath, ViewUrl: downloadPath,
Token: userIDStr, // Session token - user is already authenticated via middleware Token: token, // JWT token for authenticating file download
Capabilities: struct { Capabilities: struct {
CanEdit bool `json:"canEdit"` CanEdit bool `json:"canEdit"`
CanAnnotate bool `json:"canAnnotate"` 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 // Determine if it's a PDF based on file extension
isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf") isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf")
// Get JWT token from context
token, _ := middleware.GetToken(r.Context())
session := struct { session := struct {
ViewUrl string `json:"viewUrl"` ViewUrl string `json:"viewUrl"`
Token string `json:"token"` Token string `json:"token"`
@@ -435,7 +441,7 @@ func userViewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB,
ExpiresAt string `json:"expiresAt"` ExpiresAt string `json:"expiresAt"`
}{ }{
ViewUrl: downloadPath, ViewUrl: downloadPath,
Token: userIDStr, // Session token - user is already authenticated via middleware Token: token, // JWT token for authenticating file download
Capabilities: struct { Capabilities: struct {
CanEdit bool `json:"canEdit"` CanEdit bool `json:"canEdit"`
CanAnnotate bool `json:"canAnnotate"` 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) // Check if user can edit (for now, all org members can edit)
readOnly := false readOnly := false
// Get JWT token from context
token, _ := middleware.GetToken(r.Context())
session := struct { session := struct {
EditUrl string `json:"editUrl"` EditUrl string `json:"editUrl"`
Token string `json:"token"`
ReadOnly bool `json:"readOnly"` ReadOnly bool `json:"readOnly"`
ExpiresAt string `json:"expiresAt"` ExpiresAt string `json:"expiresAt"`
}{ }{
EditUrl: collaboraUrl, EditUrl: collaboraUrl,
Token: token, // JWT token for authenticating file access
ReadOnly: readOnly, ReadOnly: readOnly,
ExpiresAt: time.Now().Add(15 * time.Minute).UTC().Format(time.RFC3339), ExpiresAt: time.Now().Add(15 * time.Minute).UTC().Format(time.RFC3339),
} }

View File

@@ -68,6 +68,7 @@ type contextKey string
const ( const (
userKey contextKey = "user" userKey contextKey = "user"
sessionKey contextKey = "session" sessionKey contextKey = "session"
tokenKey contextKey = "token"
orgKey contextKey = "org" orgKey contextKey = "org"
) )
@@ -83,6 +84,12 @@ func GetSession(ctx context.Context) (*database.Session, bool) {
return session, ok 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 // Auth middleware
func Auth(jwtManager *jwt.Manager, db *database.DB) func(http.Handler) http.Handler { func Auth(jwtManager *jwt.Manager, db *database.DB) func(http.Handler) http.Handler {
return func(next 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(r.Context(), userKey, claims.UserID)
ctx = context.WithValue(ctx, sessionKey, session) ctx = context.WithValue(ctx, sessionKey, session)
ctx = context.WithValue(ctx, tokenKey, tokenString)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }