Refactor authentication handling in HTTP routes to utilize middleware for user ID extraction and improve download URL encoding
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user