package handlers import ( "crypto/sha256" "encoding/hex" "net/http" "time" "warpbox.dev/backend/libs/services" "warpbox.dev/backend/libs/web" ) const adminCookieName = "warpbox_admin" type adminPageData struct { Stats services.AdminStats Boxes []adminBoxView Error string } type adminBoxView struct { ID string CreatedAt string ExpiresAt string FileCount int TotalSizeLabel string DownloadCount int MaxDownloads int Protected bool Expired bool } func (a *App) AdminLogin(w http.ResponseWriter, r *http.Request) { if a.isAdmin(r) { http.Redirect(w, r, "/admin", http.StatusSeeOther) return } a.renderAdminLogin(w, http.StatusOK, "") } func (a *App) AdminLoginPost(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { a.renderAdminLogin(w, http.StatusBadRequest, "Unable to read login form.") return } if a.cfg.AdminToken == "" || r.FormValue("token") != a.cfg.AdminToken { a.logger.Warn("admin login failed", "source", "admin", "severity", "warn", "code", 4301) a.renderAdminLogin(w, http.StatusUnauthorized, "Invalid admin token.") return } http.SetCookie(w, &http.Cookie{ Name: adminCookieName, Value: adminCookieValue(a.cfg.AdminToken), Path: "/admin", HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: r.TLS != nil, Expires: time.Now().Add(12 * time.Hour), }) a.logger.Info("admin login", "source", "admin", "severity", "user_activity", "code", 2301) http.Redirect(w, r, "/admin", http.StatusSeeOther) } func (a *App) AdminLogout(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &http.Cookie{ Name: adminCookieName, Value: "", Path: "/admin", HttpOnly: true, SameSite: http.SameSiteLaxMode, MaxAge: -1, }) http.Redirect(w, r, "/admin/login", http.StatusSeeOther) } func (a *App) AdminDashboard(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) { return } stats, err := a.uploadService.AdminStats() if err != nil { http.Error(w, "unable to load admin stats", http.StatusInternalServerError) return } boxes, err := a.adminBoxes(8) if err != nil { http.Error(w, "unable to load recent boxes", http.StatusInternalServerError) return } a.renderer.Render(w, http.StatusOK, "admin.html", web.PageData{ Title: "Admin overview", Description: "Warpbox admin overview.", Data: adminPageData{ Stats: stats, Boxes: boxes, }, }) } func (a *App) AdminFiles(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) { return } stats, err := a.uploadService.AdminStats() if err != nil { http.Error(w, "unable to load admin stats", http.StatusInternalServerError) return } boxes, err := a.adminBoxes(100) if err != nil { http.Error(w, "unable to load boxes", http.StatusInternalServerError) return } a.renderer.Render(w, http.StatusOK, "admin.html", web.PageData{ Title: "Admin files", Description: "Manage Warpbox uploads.", Data: adminPageData{ Stats: stats, Boxes: boxes, }, }) } func (a *App) AdminDeleteBox(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) { return } boxID := r.PathValue("boxID") if err := a.uploadService.DeleteBox(boxID); err != nil { a.logger.Warn("admin delete failed", "source", "admin", "severity", "warn", "code", 4302, "box_id", boxID, "error", err.Error()) http.Error(w, "unable to delete box", http.StatusInternalServerError) return } http.Redirect(w, r, "/admin/files", http.StatusSeeOther) } func (a *App) renderAdminLogin(w http.ResponseWriter, status int, message string) { a.renderer.Render(w, status, "admin_login.html", web.PageData{ Title: "Admin login", Description: "Sign in to the Warpbox admin console.", Data: adminPageData{ Error: message, }, }) } func (a *App) adminBoxes(limit int) ([]adminBoxView, error) { boxes, err := a.uploadService.AdminBoxes(limit) if err != nil { return nil, err } rows := make([]adminBoxView, 0, len(boxes)) for _, box := range boxes { rows = append(rows, adminBoxView{ ID: box.ID, CreatedAt: box.CreatedAt.Format("Jan 2 15:04"), ExpiresAt: box.ExpiresAt.Format("Jan 2 15:04"), FileCount: box.FileCount, TotalSizeLabel: box.TotalSizeLabel, DownloadCount: box.DownloadCount, MaxDownloads: box.MaxDownloads, Protected: box.Protected, Expired: box.Expired, }) } return rows, nil } func (a *App) requireAdmin(w http.ResponseWriter, r *http.Request) bool { if a.isAdmin(r) { return true } http.Redirect(w, r, "/admin/login", http.StatusSeeOther) return false } func (a *App) isAdmin(r *http.Request) bool { if a.cfg.AdminToken == "" { return false } cookie, err := r.Cookie(adminCookieName) if err != nil { return false } return cookie.Value == adminCookieValue(a.cfg.AdminToken) } func adminCookieValue(token string) string { sum := sha256.Sum256([]byte("warpbox-admin:" + token)) return hex.EncodeToString(sum[:]) }