fix: properly handle delete user endpoint with correct route
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"html"
|
"html"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
@@ -20,6 +21,8 @@ type Handler struct {
|
|||||||
authService *auth.Service
|
authService *auth.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
func NewHandler(db database.Querier, authService *auth.Service) *Handler {
|
func NewHandler(db database.Querier, authService *auth.Service) *Handler {
|
||||||
return &Handler{db: db, authService: authService}
|
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) {
|
func (h *Handler) HandleImpersonateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
userID := r.URL.Path[len("/admin/users/"):]
|
userID := r.URL.Path[len("/admin/users/"):]
|
||||||
if userID == "" {
|
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) {
|
func writeInlineError(w http.ResponseWriter, message string) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
@@ -192,7 +204,6 @@ func writeInlineError(w http.ResponseWriter, message string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateUserID() string {
|
func generateUserID() string {
|
||||||
// Simple UUID-like generation - in production use proper UUID
|
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
for i := range b {
|
for i := range b {
|
||||||
b[i] = byte('a' + (i % 26))
|
b[i] = byte('a' + (i % 26))
|
||||||
@@ -203,17 +214,15 @@ func generateUserID() string {
|
|||||||
const bcryptCost = 12
|
const bcryptCost = 12
|
||||||
|
|
||||||
func GetImpersonatedUserID(r *http.Request) string {
|
func GetImpersonatedUserID(r *http.Request) string {
|
||||||
// Check for impersonation parameter
|
|
||||||
impersonateID := r.URL.Query().Get("as_user")
|
impersonateID := r.URL.Query().Get("as_user")
|
||||||
if impersonateID == "" {
|
if impersonateID == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the current user is admin
|
|
||||||
user, ok := r.Context().Value(webcontext.UserKey).(*database.User)
|
user, ok := r.Context().Value(webcontext.UserKey).(*database.User)
|
||||||
if !ok || user == nil || !middleware.IsAdmin(user) {
|
if !ok || user == nil || !middleware.IsAdmin(user) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return impersonateID
|
return impersonateID
|
||||||
}
|
}
|
||||||
@@ -108,19 +108,10 @@ func NewRouter(cfg Config) http.Handler {
|
|||||||
// Admin Endpoints (protected by admin middleware in route handlers)
|
// Admin Endpoints (protected by admin middleware in route handlers)
|
||||||
mux.Handle("/admin", middleware.RequireAdmin(http.HandlerFunc(adminHandler.HandleAdminPage)))
|
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(adminHandler.HandleAddUserForm)))
|
||||||
mux.Handle("/admin/users/", middleware.RequireAdmin(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/admin/users/delete", func(w http.ResponseWriter, r *http.Request) {
|
||||||
path := r.URL.Path
|
middleware.RequireAdmin(http.HandlerFunc(adminHandler.HandleDeleteUserRouter)).ServeHTTP(w, r)
|
||||||
switch {
|
})
|
||||||
case strings.HasSuffix(path, "/delete"):
|
mux.Handle("/admin/users/", middleware.RequireAdmin(http.HandlerFunc(adminHandler.HandleUserRouter)))
|
||||||
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)
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
|
|
||||||
// Wrap mux with global CSRF origin verification and auth checking,
|
// Wrap mux with global CSRF origin verification and auth checking,
|
||||||
// THEN auth context parsing.
|
// THEN auth context parsing.
|
||||||
|
|||||||
Reference in New Issue
Block a user