From 9cd43d9c4cddbd95e3b456de1df2823a4c4b35af Mon Sep 17 00:00:00 2001 From: mkelvers Date: Wed, 22 Apr 2026 21:36:20 +0200 Subject: [PATCH] fix: properly handle delete user endpoint with correct route --- api/admin/handler.go | 195 ++++++++++++++++++++------------------ internal/server/routes.go | 17 +--- 2 files changed, 106 insertions(+), 106 deletions(-) diff --git a/api/admin/handler.go b/api/admin/handler.go index e35d43a..959b63a 100644 --- a/api/admin/handler.go +++ b/api/admin/handler.go @@ -5,6 +5,7 @@ import ( "html" "log" "net/http" + "strings" "golang.org/x/crypto/bcrypt" @@ -20,6 +21,8 @@ type Handler struct { authService *auth.Service } +type contextKey string + func NewHandler(db database.Querier, authService *auth.Service) *Handler { return &Handler{db: db, authService: authService} } @@ -38,6 +41,104 @@ func (h *Handler) HandleAdminPage(w http.ResponseWriter, r *http.Request) { } } +func (h *Handler) HandleAddUserForm(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + username := r.FormValue("username") + password := r.FormValue("password") + + if username == "" || password == "" { + writeInlineError(w, "Username and password are required") + return + } + + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) + if err != nil { + log.Printf("bcrypt error: %v", err) + writeInlineError(w, "Failed to create user") + return + } + + _, err = h.db.CreateUser(r.Context(), database.CreateUserParams{ + ID: generateUserID(), + Username: username, + PasswordHash: string(hash), + }) + if err != nil { + log.Printf("create user error: %v", err) + writeInlineError(w, "Failed to create user (may already exist)") + return + } + + users, err := h.db.ListUsers(r.Context()) + if err != nil { + log.Printf("list users error: %v", err) + writeInlineError(w, "User created but failed to refresh list") + return + } + + if err := templates.AdminUsersList(users).Render(r.Context(), w); err != nil { + log.Printf("render error: %v", err) + writeInlineError(w, "User created but failed to render list") + } +} + +func (h *Handler) HandleDeleteUserRouter(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + path = strings.TrimPrefix(path, "/admin/users/delete/") + + if path == "" { + writeInlineError(w, "Invalid user ID") + return + } + + userID := path + + currentUser, ok := r.Context().Value(webcontext.UserKey).(*database.User) + if !ok || currentUser == nil { + writeInlineError(w, "Not authenticated") + return + } + if userID == currentUser.ID { + writeInlineError(w, "Cannot delete your own account") + return + } + + err := h.db.DeleteUser(r.Context(), userID) + if err != nil { + log.Printf("delete user error: %v", err) + writeInlineError(w, "Failed to delete user") + return + } + + users, err := h.db.ListUsers(r.Context()) + if err != nil { + log.Printf("list users error: %v", err) + writeInlineError(w, "User deleted but failed to refresh list") + return + } + + if err := templates.AdminUsersList(users).Render(r.Context(), w); err != nil { + log.Printf("render error: %v", err) + writeInlineError(w, "User deleted but failed to render list") + } +} + +func (h *Handler) HandleUserRouter(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + switch { + case strings.HasSuffix(path, "/watchlist"): + h.HandleUserWatchlist(w, r) + case strings.HasSuffix(path, "/continue-watching"): + h.HandleUserContinueWatching(w, r) + default: + h.HandleImpersonateUser(w, r) + } +} + func (h *Handler) HandleImpersonateUser(w http.ResponseWriter, r *http.Request) { userID := r.URL.Path[len("/admin/users/"):] if userID == "" { @@ -96,95 +197,6 @@ func (h *Handler) HandleUserContinueWatching(w http.ResponseWriter, r *http.Requ } } -func (h *Handler) HandleAddUserForm(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - username := r.FormValue("username") - password := r.FormValue("password") - - if username == "" || password == "" { - writeInlineError(w, "Username and password are required") - return - } - - hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) - if err != nil { - log.Printf("bcrypt error: %v", err) - writeInlineError(w, "Failed to create user") - return - } - - _, err = h.db.CreateUser(r.Context(), database.CreateUserParams{ - ID: generateUserID(), - Username: username, - PasswordHash: string(hash), - }) - if err != nil { - log.Printf("create user error: %v", err) - writeInlineError(w, "Failed to create user (may already exist)") - return - } - - // Return success - reload the users list - users, err := h.db.ListUsers(r.Context()) - if err != nil { - log.Printf("list users error: %v", err) - writeInlineError(w, "User created but failed to refresh list") - return - } - - if err := templates.AdminUsersList(users).Render(r.Context(), w); err != nil { - log.Printf("render error: %v", err) - writeInlineError(w, "User created but failed to render list") - } -} - -func (h *Handler) HandleDeleteUser(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - userID := r.URL.Path[len("/admin/users/"):] - if userID == "" { - writeInlineError(w, "Invalid user ID") - return - } - - // Don't allow deleting yourself - currentUser, ok := r.Context().Value(webcontext.UserKey).(*database.User) - if !ok || currentUser == nil { - writeInlineError(w, "Not authenticated") - return - } - if userID == currentUser.ID { - writeInlineError(w, "Cannot delete your own account") - return - } - - err := h.db.DeleteUser(r.Context(), userID) - if err != nil { - log.Printf("delete user error: %v", err) - writeInlineError(w, "Failed to delete user") - return - } - - users, err := h.db.ListUsers(r.Context()) - if err != nil { - log.Printf("list users error: %v", err) - writeInlineError(w, "User deleted but failed to refresh list") - return - } - - if err := templates.AdminUsersList(users).Render(r.Context(), w); err != nil { - log.Printf("render error: %v", err) - writeInlineError(w, "User deleted but failed to render list") - } -} - func writeInlineError(w http.ResponseWriter, message string) { w.Header().Set("Content-Type", "text/html") w.WriteHeader(http.StatusBadRequest) @@ -192,7 +204,6 @@ func writeInlineError(w http.ResponseWriter, message string) { } func generateUserID() string { - // Simple UUID-like generation - in production use proper UUID b := make([]byte, 16) for i := range b { b[i] = byte('a' + (i % 26)) @@ -203,17 +214,15 @@ func generateUserID() string { const bcryptCost = 12 func GetImpersonatedUserID(r *http.Request) string { - // Check for impersonation parameter impersonateID := r.URL.Query().Get("as_user") if impersonateID == "" { return "" } - // Verify the current user is admin user, ok := r.Context().Value(webcontext.UserKey).(*database.User) if !ok || user == nil || !middleware.IsAdmin(user) { return "" } return impersonateID -} +} \ No newline at end of file diff --git a/internal/server/routes.go b/internal/server/routes.go index d9f75bd..4e1b8ee 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -108,19 +108,10 @@ func NewRouter(cfg Config) http.Handler { // Admin Endpoints (protected by admin middleware in route handlers) mux.Handle("/admin", middleware.RequireAdmin(http.HandlerFunc(adminHandler.HandleAdminPage))) mux.Handle("/admin/users", middleware.RequireAdmin(http.HandlerFunc(adminHandler.HandleAddUserForm))) - mux.Handle("/admin/users/", middleware.RequireAdmin(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - path := r.URL.Path - switch { - case strings.HasSuffix(path, "/delete"): - adminHandler.HandleDeleteUser(w, r) - case strings.HasSuffix(path, "/watchlist"): - adminHandler.HandleUserWatchlist(w, r) - case strings.HasSuffix(path, "/continue-watching"): - adminHandler.HandleUserContinueWatching(w, r) - default: - adminHandler.HandleImpersonateUser(w, r) - } - }))) + mux.HandleFunc("/admin/users/delete", func(w http.ResponseWriter, r *http.Request) { + middleware.RequireAdmin(http.HandlerFunc(adminHandler.HandleDeleteUserRouter)).ServeHTTP(w, r) + }) + mux.Handle("/admin/users/", middleware.RequireAdmin(http.HandlerFunc(adminHandler.HandleUserRouter))) // Wrap mux with global CSRF origin verification and auth checking, // THEN auth context parsing.