diff --git a/weed/admin/dash/auth_middleware.go b/weed/admin/dash/auth_middleware.go index 986a30290..87da65659 100644 --- a/weed/admin/dash/auth_middleware.go +++ b/weed/admin/dash/auth_middleware.go @@ -5,6 +5,7 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" + "github.com/seaweedfs/seaweedfs/weed/glog" ) // ShowLogin displays the login page @@ -31,9 +32,16 @@ func (s *AdminServer) HandleLogin(username, password string) gin.HandlerFunc { if loginUsername == username && loginPassword == password { session := sessions.Default(c) + // Clear any existing invalid session data before setting new values + session.Clear() session.Set("authenticated", true) session.Set("username", loginUsername) - session.Save() + if err := session.Save(); err != nil { + // Log the detailed error server-side for diagnostics + glog.Errorf("Failed to save session for user %s: %v", loginUsername, err) + c.Redirect(http.StatusSeeOther, "/login?error=Unable to create session. Please try again or contact administrator.") + return + } c.Redirect(http.StatusSeeOther, "/admin") return @@ -48,6 +56,8 @@ func (s *AdminServer) HandleLogin(username, password string) gin.HandlerFunc { func (s *AdminServer) HandleLogout(c *gin.Context) { session := sessions.Default(c) session.Clear() - session.Save() + if err := session.Save(); err != nil { + glog.Warningf("Failed to save session during logout: %v", err) + } c.Redirect(http.StatusSeeOther, "/login") } diff --git a/weed/admin/handlers/admin_handlers.go b/weed/admin/handlers/admin_handlers.go index 215e2a4e5..b1f465d2e 100644 --- a/weed/admin/handlers/admin_handlers.go +++ b/weed/admin/handlers/admin_handlers.go @@ -48,6 +48,11 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, username, // Health check (no auth required) r.GET("/health", h.HealthCheck) + // Favicon route (no auth required) - redirect to static version + r.GET("/favicon.ico", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "/static/favicon.ico") + }) + if authRequired { // Authentication routes (no auth required) r.GET("/login", h.authHandlers.ShowLogin) diff --git a/weed/command/admin.go b/weed/command/admin.go index 8321aad80..e85b2e431 100644 --- a/weed/command/admin.go +++ b/weed/command/admin.go @@ -191,31 +191,7 @@ func startAdminServer(ctx context.Context, options AdminOptions) error { r := gin.New() r.Use(gin.Logger(), gin.Recovery()) - // Session store - always auto-generate session key - sessionKeyBytes := make([]byte, 32) - _, err := rand.Read(sessionKeyBytes) - if err != nil { - return fmt.Errorf("failed to generate session key: %w", err) - } - store := cookie.NewStore(sessionKeyBytes) - - // Configure session options to ensure cookies are properly saved - store.Options(sessions.Options{ - Path: "/", - MaxAge: 3600 * 24, // 24 hours - }) - - r.Use(sessions.Sessions("admin-session", store)) - - // Static files - serve from embedded filesystem - staticFS, err := admin.GetStaticFS() - if err != nil { - log.Printf("Warning: Failed to load embedded static files: %v", err) - } else { - r.StaticFS("/static", http.FS(staticFS)) - } - - // Create data directory if specified + // Create data directory first if specified (needed for session key storage) var dataDir string if *options.dataDir != "" { // Expand tilde (~) to home directory @@ -236,6 +212,35 @@ func startAdminServer(ctx context.Context, options AdminOptions) error { fmt.Printf("Data directory created/verified: %s\n", dataDir) } + // Detect TLS configuration to set Secure cookie flag + cookieSecure := viper.GetString("https.admin.key") != "" + + // Session store - load or generate session key + sessionKeyBytes, err := loadOrGenerateSessionKey(dataDir) + if err != nil { + return fmt.Errorf("failed to get session key: %w", err) + } + store := cookie.NewStore(sessionKeyBytes) + + // Configure session options to ensure cookies are properly saved + store.Options(sessions.Options{ + Path: "/", + MaxAge: 3600 * 24, // 24 hours + HttpOnly: true, // Prevent JavaScript access + Secure: cookieSecure, // Set based on actual TLS configuration + SameSite: http.SameSiteLaxMode, + }) + + r.Use(sessions.Sessions("admin-session", store)) + + // Static files - serve from embedded filesystem + staticFS, err := admin.GetStaticFS() + if err != nil { + log.Printf("Warning: Failed to load embedded static files: %v", err) + } else { + r.StaticFS("/static", http.FS(staticFS)) + } + // Create admin server adminServer := dash.NewAdminServer(*options.masters, nil, dataDir) @@ -331,6 +336,46 @@ func GetAdminOptions() *AdminOptions { return &AdminOptions{} } +// loadOrGenerateSessionKey loads an existing session key from dataDir or generates a new one +func loadOrGenerateSessionKey(dataDir string) ([]byte, error) { + const sessionKeyLength = 32 + if dataDir == "" { + // No persistence, generate random key + log.Println("No dataDir specified, generating ephemeral session key") + key := make([]byte, sessionKeyLength) + _, err := rand.Read(key) + return key, err + } + + sessionKeyPath := filepath.Join(dataDir, ".session_key") + + // Try to load existing key + if data, err := os.ReadFile(sessionKeyPath); err == nil { + if len(data) == sessionKeyLength { + log.Printf("Loaded persisted session key from %s", sessionKeyPath) + return data, nil + } + log.Printf("Warning: Invalid session key file (expected %d bytes, got %d), generating new key", sessionKeyLength, len(data)) + } else if !os.IsNotExist(err) { + log.Printf("Warning: Failed to read session key from %s: %v. A new key will be generated.", sessionKeyPath, err) + } + + // Generate new key + key := make([]byte, sessionKeyLength) + if _, err := rand.Read(key); err != nil { + return nil, err + } + + // Save key for future use + if err := os.WriteFile(sessionKeyPath, key, 0600); err != nil { + log.Printf("Warning: Failed to persist session key: %v", err) + } else { + log.Printf("Generated and persisted new session key to %s", sessionKeyPath) + } + + return key, nil +} + // expandHomeDir expands the tilde (~) in a path to the user's home directory func expandHomeDir(path string) (string, error) { if path == "" {