package route import ( "app/shelfly/internal/download" "app/shelfly/internal/library" "app/shelfly/internal/login" "app/shelfly/internal/users" "app/shelfly/renders" "encoding/base64" "fmt" "log" "net/http" "os" "path/filepath" "strings" "time" "golang.org/x/crypto/bcrypt" "app/shelfly/internal/models" "github.com/gorilla/mux" "golang.org/x/net/webdav" "gorm.io/gorm" ) func checkUserCredentials(db *gorm.DB, email string, password string) bool { var user models.User // On cherche l'utilisateur par email result := db.Where("email = ?", email).First(&user) if result.Error != nil { return false } // On vérifie le mot de passe via bcrypt comme dans ton LoginHandler err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) return err == nil } type spaHandler struct { staticPath string indexPath string } // Routes non protégées func checkAuth(authHeader, username, password string) bool { const prefix = "Basic " if !strings.HasPrefix(authHeader, prefix) { return false } decoded, err := base64.StdEncoding.DecodeString(authHeader[len(prefix):]) if err != nil { return false } pair := strings.SplitN(string(decoded), ":", 2) if len(pair) != 2 { return false } return pair[0] == username && pair[1] == password } func RoutesPublic(r *mux.Router, bd *gorm.DB) { // Fichiers statiques (CSS, JS, etc.) staticDir := "./templates/assets/" r.PathPrefix("/templates/assets/").Handler( http.StripPrefix("/templates/assets/", http.FileServer(http.Dir(staticDir))), ) // Page de login r.HandleFunc("/login", renders.Login) r.HandleFunc("/api/login", login.LoginHandler(bd)).Methods("POST") r.HandleFunc("/api/scan/{id}", library.ScanFolder(bd)).Methods("GET") r.HandleFunc("/api/download/stream", renders.HandleJobsStream(bd)) // Génération playlist r.HandleFunc("/playlist.m3u", func(w http.ResponseWriter, r *http.Request) { uploadDir := "/app/upload" w.Header().Set("Content-Type", "audio/x-mpegurl") fmt.Fprintln(w, "#EXTM3U") err := filepath.Walk(uploadDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } relPath, _ := filepath.Rel(uploadDir, path) relPath = filepath.ToSlash(relPath) fileURL := fmt.Sprintf("https://media.canguidev.fr/upload/%s", relPath) fmt.Fprintln(w, fileURL) return nil }) if err != nil { http.Error(w, "Erreur lors de la génération de la playlist", http.StatusInternalServerError) } }) r.PathPrefix("/webdav/").Handler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { authHeader := req.Header.Get("Authorization") if authHeader == "" { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // Authentification HTTP Basic en base de données email, password, ok := req.BasicAuth() if !ok { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } log.Printf("✅ email saisie: %s", email) log.Printf("✅ passw saisie: %s", password) var user models.User result := bd.Where("email = ?", email).First(&user) if result.Error != nil { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) if err != nil { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // Lecture seule if req.Method != "GET" && req.Method != "HEAD" && req.Method != "OPTIONS" && req.Method != "PROPFIND" { http.Error(w, "Read-Only", http.StatusForbidden) return } log.Printf("✅ WebDAV access for user: %s", email) w.Header().Set("DAV", "1,2") w.Header().Set("MS-Author-Via", "DAV") webdavHandler := &webdav.Handler{ Prefix: "/webdav/", FileSystem: webdav.Dir("/app/upload"), LockSystem: webdav.NewMemLS(), } webdavHandler.ServeHTTP(w, req) })) // WebDAV sécurisé // username := "tonuser" // ton login // password := "tonpassword" // ton password // webdavHandler := &webdav.Handler{ // Prefix: "/webdav/", // FileSystem: webdav.Dir("/app/upload"), // LockSystem: webdav.NewMemLS(), // } // r.PathPrefix("/webdav/").Handler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { // // Authentification // auth := req.Header.Get("Authorization") // if auth == "" || !checkAuth(auth, username, password) { // w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) // http.Error(w, "Unauthorized", http.StatusUnauthorized) // return // } // // Protection lecture seule // if req.Method != "GET" && req.Method != "HEAD" && req.Method != "OPTIONS" && req.Method != "PROPFIND" { // http.Error(w, "Read-Only", http.StatusForbidden) // return // } // log.Printf("WebDAV request: %s %s", req.Method, req.URL.Path) // // Headers WebDAV que VLC attend // w.Header().Set("DAV", "1,2") // w.Header().Set("MS-Author-Via", "DAV") // webdavHandler.ServeHTTP(w, req) // })) } // Routes protégées func RoutesProtected(r *mux.Router, bd *gorm.DB) { // Ici on place les vues et API qui doivent être protégées r.HandleFunc("/stream", StreamHandler) r.HandleFunc("/dashboard", renders.Dashboard(bd)) r.HandleFunc("/settings", renders.Settings) r.HandleFunc("/library", renders.Library) r.HandleFunc("/menuLibary", renders.Library) r.HandleFunc("/godownloader/downloads", renders.GoDownload) r.HandleFunc("/godownloader/linkcollectors", renders.GoDownloadLinkCollectors) r.HandleFunc("/godownloader/settings", renders.GoDownloadSetting(bd)) r.HandleFunc("/godownloader/poll-status", renders.PollStatusHandler(bd)) r.HandleFunc("/godownloader/table-refresh", renders.GoDownloadPartialTable(bd)) r.HandleFunc("/godownloader/settings/delete", renders.GoDownloadSettingDelete(bd)) r.HandleFunc("/api/download/add", renders.HandleAddJob(bd)).Methods("POST") r.HandleFunc("/api/download/all", renders.HandleListJobsPartial(bd)).Methods("GET") r.HandleFunc("/downloads", renders.GoDownload2(bd)) r.HandleFunc("/stream/{id}", download.HandleStreamPage()).Methods("GET") r.HandleFunc("/api/download/start/{id}", renders.HandleStartJob(bd)).Methods("POST") r.HandleFunc("/api/download/pause/{id}", renders.HandlePauseJob).Methods("POST") r.HandleFunc("/api/download/resume/{id}", renders.HandleResumeJob(bd)).Methods("POST") r.HandleFunc("/api/download/delete/{id}", renders.HandleDeleteJob(bd)).Methods("DELETE") r.HandleFunc("/api/download/delete-multiple", renders.HandleDeleteMultipleJobs(bd)).Methods("POST") // API user r.HandleFunc("/api/user/create", users.CreateUser(bd)).Methods("POST") r.HandleFunc("/api/user/update/{id}", users.UpdateUser(bd)).Methods("PUT") r.HandleFunc("/api/user/delete/{id}", users.DeleteUser(bd)).Methods("DELETE") r.HandleFunc("/api/user/all/", users.ReadAllUser(bd)).Methods("GET") r.HandleFunc("/api/user/{id}", users.FindUserById(bd)).Methods("GET") // API download r.HandleFunc("/api/pathDownload/create", download.CreateSavePath(bd)).Methods("POST") r.HandleFunc("/api/pathDownload/update/{id}", download.UpdateSavePath(bd)).Methods("PUT") r.HandleFunc("/api/pathDownload/delete/{id}", download.DeleteSavePath(bd)).Methods("DELETE") r.HandleFunc("/api/pathDownload/all/", download.ReadAllSavePath(bd)).Methods("GET") //API Check path r.HandleFunc("/validate-path", download.PathValidationHandler) //API Scan folder } func StreamHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "Le streaming n’est pas supporté par ce serveur", http.StatusInternalServerError) return } ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() // Boucle infinie (ou jusqu'à annulation) for { select { case <-ticker.C: fmt.Fprintf(w, "data:

Message #%d

\n\n") flusher.Flush() case <-r.Context().Done(): // Le client a probablement fermé la connexion log.Println("Client déconnecté") return } } }