Refactor authentication handling in HTTP routes to utilize middleware for user ID extraction and improve download URL encoding

This commit is contained in:
Leon Bösche
2026-01-10 04:48:28 +01:00
parent 5669385616
commit b381a46483
2 changed files with 54 additions and 34 deletions

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'blocs/auth/auth_bloc.dart';
import 'blocs/auth/auth_event.dart';
import 'blocs/session/session_bloc.dart';
import 'blocs/activity/activity_bloc.dart';
import 'services/api_client.dart';
@@ -57,15 +58,22 @@ class _MainAppState extends State<MainApp> {
@override
void initState() {
super.initState();
// Restore session from persistent storage early so ApiClient has token if present
_restoreFuture = SessionBloc.restoreSession(_sessionBloc);
// Configure DI to use HTTP repositories
// Configure DI first
configureDependencies(_sessionBloc);
// Create AuthBloc first
_authBloc = AuthBloc(
apiClient: ApiClient(_sessionBloc),
sessionBloc: _sessionBloc,
);
// Restore session and then check auth
_restoreFuture = SessionBloc.restoreSession(_sessionBloc).then((_) {
// After session is restored, check if we should auto-authenticate
if (mounted) {
_authBloc.add(const CheckAuthRequested());
}
});
}
@override

View File

@@ -7,6 +7,7 @@ import (
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
@@ -243,21 +244,14 @@ func logoutHandler(w http.ResponseWriter, r *http.Request, jwtManager *jwt.Manag
}
func listOrgsHandler(w http.ResponseWriter, r *http.Request, db *database.DB, jwtManager *jwt.Manager) {
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "Bearer ") {
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
claims, _, err := jwtManager.ValidateWithSession(r.Context(), tokenString, db)
if err != nil {
errors.LogError(r, err, "Invalid token")
// User ID is already set by Auth middleware
userIDStr, ok := middleware.GetUserID(r.Context())
if !ok {
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
return
}
userID, _ := uuid.Parse(claims.UserID)
userID, _ := uuid.Parse(userIDStr)
orgs, err := org.ResolveUserOrgs(r.Context(), db, userID)
if err != nil {
errors.LogError(r, err, "Failed to resolve user orgs")
@@ -270,21 +264,14 @@ func listOrgsHandler(w http.ResponseWriter, r *http.Request, db *database.DB, jw
}
func createOrgHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, jwtManager *jwt.Manager) {
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "Bearer ") {
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
claims, _, err := jwtManager.ValidateWithSession(r.Context(), tokenString, db)
if err != nil {
errors.LogError(r, err, "Invalid token")
// User ID is already set by Auth middleware
userIDStr, ok := middleware.GetUserID(r.Context())
if !ok {
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
return
}
userID, _ := uuid.Parse(claims.UserID)
userID, _ := uuid.Parse(userIDStr)
var req struct {
Name string `json:"name"`
@@ -378,14 +365,15 @@ func viewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, audi
// Log activity
db.LogActivity(r.Context(), userID, orgID, &fileId, "view_file", map[string]interface{}{})
// Build download URL - using full path for frontend to fetch with auth headers
viewUrl := fmt.Sprintf("https://go.b0esche.cloud/orgs/%s/files/download?path=%s", orgID.String(), file.Path)
// Build download URL with proper URL encoding
downloadPath := fmt.Sprintf("https://go.b0esche.cloud/orgs/%s/files/download?path=%s", orgID.String(), url.QueryEscape(file.Path))
// Determine if it's a PDF based on file extension
isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf")
session := struct {
ViewUrl string `json:"viewUrl"`
Token string `json:"token"`
Capabilities struct {
CanEdit bool `json:"canEdit"`
CanAnnotate bool `json:"canAnnotate"`
@@ -393,7 +381,8 @@ func viewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, audi
} `json:"capabilities"`
ExpiresAt string `json:"expiresAt"`
}{
ViewUrl: viewUrl,
ViewUrl: downloadPath,
Token: userIDStr, // Session token - user is already authenticated via middleware
Capabilities: struct {
CanEdit bool `json:"canEdit"`
CanAnnotate bool `json:"canAnnotate"`
@@ -429,14 +418,15 @@ func userViewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB,
// Optionally log activity without org id
db.LogActivity(r.Context(), userID, uuid.Nil, &fileId, "view_user_file", map[string]interface{}{})
// Build download URL for user files - using full path for frontend to fetch with auth headers
viewUrl := fmt.Sprintf("https://go.b0esche.cloud/user/files/download?path=%s", file.Path)
// Build download URL with proper URL encoding
downloadPath := fmt.Sprintf("https://go.b0esche.cloud/user/files/download?path=%s", url.QueryEscape(file.Path))
// Determine if it's a PDF based on file extension
isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf")
session := struct {
ViewUrl string `json:"viewUrl"`
Token string `json:"token"`
Capabilities struct {
CanEdit bool `json:"canEdit"`
CanAnnotate bool `json:"canAnnotate"`
@@ -444,7 +434,8 @@ func userViewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB,
} `json:"capabilities"`
ExpiresAt string `json:"expiresAt"`
}{
ViewUrl: viewUrl,
ViewUrl: downloadPath,
Token: userIDStr, // Session token - user is already authenticated via middleware
Capabilities: struct {
CanEdit bool `json:"canEdit"`
CanAnnotate bool `json:"canAnnotate"`
@@ -467,17 +458,38 @@ func editorHandler(w http.ResponseWriter, r *http.Request, db *database.DB, audi
orgID := r.Context().Value("org").(uuid.UUID)
fileId := chi.URLParam(r, "fileId")
// Get file metadata to determine path and type
fileUUID, err := uuid.Parse(fileId)
if err != nil {
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid file ID", http.StatusBadRequest)
return
}
file, err := db.GetFileByID(r.Context(), fileUUID)
if err != nil {
errors.LogError(r, err, "Failed to get file metadata")
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
return
}
// Log activity
db.LogActivity(r.Context(), userID, orgID, &fileId, "edit_file", map[string]interface{}{})
// Build Collabora editor URL - Collabora needs the file download URL as the WOPISrc parameter
editUrl := fmt.Sprintf("https://go.b0esche.cloud/orgs/%s/files/download?path=%s", orgID.String(), url.QueryEscape(file.Path))
collaboraUrl := fmt.Sprintf("https://collabora.b0esche.cloud/lool/dist/mobile/cool.html?WOPISrc=%s", url.QueryEscape(editUrl))
// Check if user can edit (for now, all org members can edit)
readOnly := false
session := struct {
EditUrl string `json:"editUrl"`
ReadOnly bool `json:"readOnly"`
ExpiresAt string `json:"expiresAt"`
}{
EditUrl: "https://edit.example.com/" + fileId,
ReadOnly: false,
ExpiresAt: "2023-01-01T01:00:00Z",
EditUrl: collaboraUrl,
ReadOnly: readOnly,
ExpiresAt: time.Now().Add(15 * time.Minute).UTC().Format(time.RFC3339),
}
w.Header().Set("Content-Type", "application/json")