package handlers import ( "context" "crypto/sha256" "encoding/hex" "net/http" "net/url" "strconv" "time" "warpbox.dev/backend/libs/services" "warpbox.dev/backend/libs/web" ) const adminCookieName = "warpbox_admin" type adminPageData struct { Stats services.AdminStats Boxes []adminBoxView Users []adminUserView Settings services.UploadPolicySettings Storage []services.StorageBackendView UserEdit adminUserEditView Section string PageTitle string LastInviteURL string Error string } type adminBoxView struct { ID string Owner string CreatedAt string ExpiresAt string FileCount int TotalSizeLabel string DownloadCount int MaxDownloads int Protected bool Expired bool } type adminUserView struct { ID string Username string Email string Role string Status string StorageUsed string StorageQuota string DailyUsed string StorageBackend string CreatedAt string } type adminUserEditView struct { ID string Username string Email string Role string Status string StorageUsed string DailyUsed string EffectiveStorage string EffectiveDaily string EffectiveMaxDays int EffectiveDailyBoxes int EffectiveActiveBoxes int EffectiveBackend string MaxUploadMB string DailyUploadMB string StorageQuotaMB string MaxDays string DailyBoxes string ActiveBoxes string ShortWindowRequests string StorageBackendID string } 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, r, http.StatusOK, "") } func (a *App) AdminLoginPost(w http.ResponseWriter, r *http.Request) { if !a.rateLimiter.Allow("admin-login:"+uploadClientIP(r), 10, time.Minute, time.Now().UTC()) { a.renderAdminLogin(w, r, http.StatusTooManyRequests, "Too many admin login attempts.") return } if err := r.ParseForm(); err != nil { a.renderAdminLogin(w, r, 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, r, 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) { if !a.validateCSRF(w, r) { return } a.clearUserSessionCookie(w) 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.renderPage(w, r, http.StatusOK, "admin.html", web.PageData{ Title: "Admin overview", Description: "Warpbox admin overview.", CurrentUser: a.currentPublicUser(r), Data: adminPageData{ Stats: stats, Boxes: boxes, Section: "overview", PageTitle: "Admin overview", }, }) } 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.renderPage(w, r, http.StatusOK, "admin.html", web.PageData{ Title: "Admin files", Description: "Manage Warpbox uploads.", CurrentUser: a.currentPublicUser(r), Data: adminPageData{ Stats: stats, Boxes: boxes, Section: "files", PageTitle: "Admin files", }, }) } func (a *App) AdminUsers(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 } users, err := a.authService.ListUsers() if err != nil { http.Error(w, "unable to load users", http.StatusInternalServerError) return } rows := make([]adminUserView, 0, len(users)) settings, err := a.settingsService.UploadPolicy() if err != nil { http.Error(w, "unable to load settings", http.StatusInternalServerError) return } for _, user := range users { storageUsed, _ := a.uploadService.UserActiveStorageUsed(user.ID) usage, _ := a.settingsService.UsageForUser(user.ID, time.Now().UTC()) policy := a.settingsService.EffectivePolicyForUser(settings, user) quota := "unlimited" if policy.StorageQuotaSet { quota = formatMB(policy.StorageQuotaMB) } rows = append(rows, adminUserView{ ID: user.ID, Username: user.Username, Email: user.Email, Role: user.Role, Status: user.Status, StorageUsed: services.FormatMegabytesFromBytes(storageUsed), StorageQuota: quota, DailyUsed: services.FormatMegabytesFromBytes(usage.UploadedBytes), StorageBackend: policy.StorageBackendID, CreatedAt: user.CreatedAt.Format("Jan 2 15:04"), }) } a.renderPage(w, r, http.StatusOK, "admin_users.html", web.PageData{ Title: "Admin users", Description: "Manage Warpbox users and invites.", CurrentUser: a.currentPublicUser(r), Data: adminPageData{ Stats: stats, Users: rows, Section: "users", PageTitle: "Users", LastInviteURL: r.URL.Query().Get("invite"), }, }) } func (a *App) AdminEditUser(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) { return } user, err := a.authService.UserByID(r.PathValue("userID")) if err != nil { http.NotFound(w, r) return } settings, err := a.settingsService.UploadPolicy() if err != nil { http.Error(w, "unable to load settings", http.StatusInternalServerError) return } storage, err := a.storageBackendViews() if err != nil { http.Error(w, "unable to load storage", http.StatusInternalServerError) return } edit, err := a.adminUserEdit(user, settings) if err != nil { http.Error(w, "unable to load user policy", http.StatusInternalServerError) return } a.renderPage(w, r, http.StatusOK, "admin_user_edit.html", web.PageData{ Title: "Edit user", Description: "Edit a Warpbox user.", CurrentUser: a.currentPublicUser(r), Data: adminPageData{ UserEdit: edit, Storage: storage, Section: "users", PageTitle: "Edit user", LastInviteURL: r.URL.Query().Get("invite"), Error: r.URL.Query().Get("error"), }, }) } func (a *App) AdminSettings(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) { return } settings, err := a.settingsService.UploadPolicy() if err != nil { http.Error(w, "unable to load settings", http.StatusInternalServerError) return } storage, err := a.storageBackendViews() if err != nil { http.Error(w, "unable to load storage", http.StatusInternalServerError) return } a.renderPage(w, r, http.StatusOK, "admin_settings.html", web.PageData{ Title: "Admin settings", Description: "Manage Warpbox upload policy.", CurrentUser: a.currentPublicUser(r), Data: adminPageData{ Settings: settings, Storage: storage, Section: "settings", PageTitle: "Settings", }, }) } func (a *App) AdminSettingsPost(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } if err := r.ParseForm(); err != nil { http.Redirect(w, r, "/admin/settings", http.StatusSeeOther) return } settings, err := a.settingsService.UploadPolicy() if err != nil { http.Error(w, "unable to load settings", http.StatusInternalServerError) return } settings.AnonymousUploadsEnabled = r.FormValue("anonymous_uploads_enabled") == "on" if value := parsePositiveInt(r.FormValue("usage_retention_days")); value > 0 { settings.UsageRetentionDays = value } if value := parsePositiveFloat(r.FormValue("local_storage_max_gb")); value > 0 { settings.LocalStorageMaxGB = value } if value := parsePositiveInt(r.FormValue("anonymous_max_days")); value > 0 { settings.AnonymousMaxDays = value } if value := parsePositiveInt(r.FormValue("user_max_days")); value > 0 { settings.UserMaxDays = value } if value := parsePositiveInt(r.FormValue("anonymous_daily_boxes")); value > 0 { settings.AnonymousDailyBoxes = value } if value := parsePositiveInt(r.FormValue("user_daily_boxes")); value > 0 { settings.UserDailyBoxes = value } if value := parsePositiveInt(r.FormValue("anonymous_active_boxes")); value > 0 { settings.AnonymousActiveBoxes = value } if value := parsePositiveInt(r.FormValue("user_active_boxes")); value > 0 { settings.UserActiveBoxes = value } if value := parsePositiveInt(r.FormValue("short_window_requests")); value > 0 { settings.ShortWindowRequests = value } if value := parsePositiveInt(r.FormValue("short_window_seconds")); value > 0 { settings.ShortWindowSeconds = value } if value := r.FormValue("anonymous_storage_backend"); value != "" { settings.AnonymousStorageBackend = value } if value := r.FormValue("user_storage_backend"); value != "" { settings.UserStorageBackend = value } if settings.AnonymousMaxUploadMB, err = services.ParseMegabytesValue(r.FormValue("anonymous_max_upload_mb")); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if settings.AnonymousDailyUploadMB, err = services.ParseMegabytesValue(r.FormValue("anonymous_daily_upload_mb")); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if settings.UserDailyUploadMB, err = services.ParseMegabytesValue(r.FormValue("user_daily_upload_mb")); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if settings.DefaultUserStorageMB, err = services.ParseMegabytesValue(r.FormValue("default_user_storage_mb")); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if settings.UsageRetentionDays <= 0 { http.Error(w, "usage retention days must be positive", http.StatusBadRequest) return } if _, err := a.uploadService.Storage().BackendConfig(settings.AnonymousStorageBackend); err != nil { http.Error(w, "anonymous storage backend not found", http.StatusBadRequest) return } if _, err := a.uploadService.Storage().BackendConfig(settings.UserStorageBackend); err != nil { http.Error(w, "user storage backend not found", http.StatusBadRequest) return } if err := a.settingsService.UpdateUploadPolicy(settings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } http.Redirect(w, r, "/admin/settings", http.StatusSeeOther) } func (a *App) AdminStorage(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) { return } settings, err := a.settingsService.UploadPolicy() if err != nil { http.Error(w, "unable to load settings", http.StatusInternalServerError) return } views, err := a.storageBackendViews() if err != nil { http.Error(w, "unable to load storage", http.StatusInternalServerError) return } a.renderPage(w, r, http.StatusOK, "admin_storage.html", web.PageData{ Title: "Admin storage", Description: "Manage Warpbox storage backends.", CurrentUser: a.currentPublicUser(r), Data: adminPageData{ Settings: settings, Storage: views, Section: "storage", PageTitle: "Storage", Error: r.URL.Query().Get("error"), }, }) } func (a *App) AdminCreateS3Storage(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } if err := r.ParseForm(); err != nil { http.Redirect(w, r, "/admin/storage", http.StatusSeeOther) return } _, err := a.uploadService.Storage().CreateS3Backend(services.StorageBackendConfig{ Provider: r.FormValue("provider"), Name: r.FormValue("name"), Endpoint: r.FormValue("endpoint"), Region: r.FormValue("region"), Bucket: r.FormValue("bucket"), AccessKey: r.FormValue("access_key"), SecretKey: r.FormValue("secret_key"), UseSSL: r.FormValue("use_ssl") == "on", PathStyle: r.FormValue("path_style") == "on", Host: r.FormValue("host"), Port: parsePositiveInt(r.FormValue("port")), Username: r.FormValue("username"), Password: r.FormValue("password"), PrivateKey: r.FormValue("private_key"), HostKey: r.FormValue("host_key"), RemotePath: r.FormValue("remote_path"), Share: r.FormValue("share"), Domain: r.FormValue("domain"), }) if err != nil { http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther) return } http.Redirect(w, r, "/admin/storage", http.StatusSeeOther) } func (a *App) AdminEditStorage(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } if err := r.ParseForm(); err != nil { http.Redirect(w, r, "/admin/storage", http.StatusSeeOther) return } _, err := a.uploadService.Storage().UpdateS3Backend(r.PathValue("backendID"), services.StorageBackendConfig{ Provider: r.FormValue("provider"), Name: r.FormValue("name"), Endpoint: r.FormValue("endpoint"), Region: r.FormValue("region"), Bucket: r.FormValue("bucket"), AccessKey: r.FormValue("access_key"), SecretKey: r.FormValue("secret_key"), UseSSL: r.FormValue("use_ssl") == "on", PathStyle: r.FormValue("path_style") == "on", Host: r.FormValue("host"), Port: parsePositiveInt(r.FormValue("port")), Username: r.FormValue("username"), Password: r.FormValue("password"), PrivateKey: r.FormValue("private_key"), HostKey: r.FormValue("host_key"), RemotePath: r.FormValue("remote_path"), Share: r.FormValue("share"), Domain: r.FormValue("domain"), }) if err != nil { http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther) return } http.Redirect(w, r, "/admin/storage", http.StatusSeeOther) } func (a *App) AdminTestStorage(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } if _, err := a.uploadService.Storage().TestBackend(r.PathValue("backendID")); err != nil { http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther) return } http.Redirect(w, r, "/admin/storage", http.StatusSeeOther) } func (a *App) AdminDisableStorage(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } id := r.PathValue("backendID") inUse, _ := a.storageBackendInUse(id) if err := a.uploadService.Storage().DisableBackend(id, inUse); err != nil { http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther) return } http.Redirect(w, r, "/admin/storage", http.StatusSeeOther) } func (a *App) AdminDeleteStorage(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } id := r.PathValue("backendID") inUse, _ := a.storageBackendInUse(id) if err := a.uploadService.Storage().DeleteBackend(id, inUse); err != nil { http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther) return } http.Redirect(w, r, "/admin/storage", http.StatusSeeOther) } func (a *App) AdminUpdateUserQuota(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } if err := r.ParseForm(); err != nil { http.Redirect(w, r, "/admin/users", http.StatusSeeOther) return } var quota *float64 if r.FormValue("storage_quota_mb") != "" { parsed, err := services.ParseMegabytesValue(r.FormValue("storage_quota_mb")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } quota = &parsed } if err := a.authService.SetUserStorageQuota(r.PathValue("userID"), quota); err != nil { http.Error(w, "unable to update quota", http.StatusInternalServerError) return } http.Redirect(w, r, "/admin/users", http.StatusSeeOther) } func (a *App) AdminUpdateUserPolicy(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } if err := r.ParseForm(); err != nil { http.Redirect(w, r, "/admin/users", http.StatusSeeOther) return } policy := services.UserPolicy{ MaxUploadMB: optionalMB(r.FormValue("max_upload_mb")), DailyUploadMB: optionalMB(r.FormValue("daily_upload_mb")), StorageQuotaMB: optionalMBAllowZero(r.FormValue("storage_quota_mb")), MaxDays: optionalInt(r.FormValue("max_days")), DailyBoxes: optionalInt(r.FormValue("daily_boxes")), ActiveBoxes: optionalInt(r.FormValue("active_boxes")), ShortWindowRequests: optionalInt(r.FormValue("short_window_requests")), } if backendID := r.FormValue("storage_backend_id"); backendID != "" { if _, err := a.uploadService.Storage().BackendConfig(backendID); err != nil { http.Error(w, "storage backend not found", http.StatusBadRequest) return } policy.StorageBackendID = &backendID } if err := a.authService.SetUserPolicy(r.PathValue("userID"), policy); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } http.Redirect(w, r, "/admin/users", http.StatusSeeOther) } func (a *App) AdminUpdateUser(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } if err := r.ParseForm(); err != nil { http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit", http.StatusSeeOther) return } policy := services.UserPolicy{ MaxUploadMB: optionalMB(r.FormValue("max_upload_mb")), DailyUploadMB: optionalMB(r.FormValue("daily_upload_mb")), StorageQuotaMB: optionalMBAllowZero(r.FormValue("storage_quota_mb")), MaxDays: optionalInt(r.FormValue("max_days")), DailyBoxes: optionalInt(r.FormValue("daily_boxes")), ActiveBoxes: optionalInt(r.FormValue("active_boxes")), ShortWindowRequests: optionalInt(r.FormValue("short_window_requests")), } if backendID := r.FormValue("storage_backend_id"); backendID != "" { if _, err := a.uploadService.Storage().BackendConfig(backendID); err != nil { http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit?error="+url.QueryEscape("storage backend not found"), http.StatusSeeOther) return } policy.StorageBackendID = &backendID } if _, err := a.authService.UpdateUserAdminFields( r.PathValue("userID"), r.FormValue("username"), r.FormValue("email"), r.FormValue("role"), r.FormValue("status"), policy, ); err != nil { http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit?error="+url.QueryEscape(err.Error()), http.StatusSeeOther) return } http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit", http.StatusSeeOther) } func (a *App) AdminUpdateUserStorage(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } if err := r.ParseForm(); err != nil { http.Redirect(w, r, "/admin/users", http.StatusSeeOther) return } if backendID := r.FormValue("storage_backend_id"); backendID != "" { if _, err := a.uploadService.Storage().BackendConfig(backendID); err != nil { http.Error(w, "storage backend not found", http.StatusBadRequest) return } } if err := a.authService.SetUserStorageBackend(r.PathValue("userID"), r.FormValue("storage_backend_id")); err != nil { http.Error(w, "unable to update user storage", http.StatusInternalServerError) return } http.Redirect(w, r, "/admin/users", http.StatusSeeOther) } func (a *App) AdminCreateInvite(w http.ResponseWriter, r *http.Request) { admin, ok := a.requireAdminUser(w, r) if !ok || !a.validateCSRF(w, r) { return } if err := r.ParseForm(); err != nil { http.Redirect(w, r, "/admin/users", http.StatusSeeOther) return } result, err := a.authService.CreateInvite(r.FormValue("email"), r.FormValue("role"), admin.ID, 7*24*time.Hour) if err != nil { http.Redirect(w, r, "/admin/users", http.StatusSeeOther) return } a.logger.Info("invite created", "source", "admin", "severity", "user_activity", "code", 2404, "admin_id", admin.ID) http.Redirect(w, r, "/admin/users?invite="+url.QueryEscape(result.URL), http.StatusSeeOther) } func (a *App) AdminDisableUser(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) { return } disabled := r.URL.Query().Get("disabled") != "false" if err := a.authService.DisableUser(r.PathValue("userID"), disabled); err != nil { http.Error(w, "unable to update user", http.StatusInternalServerError) return } http.Redirect(w, r, "/admin/users", http.StatusSeeOther) } func (a *App) AdminResetUser(w http.ResponseWriter, r *http.Request) { admin, ok := a.requireAdminUser(w, r) if !ok || !a.validateCSRF(w, r) { return } result, err := a.authService.CreatePasswordResetInvite(r.PathValue("userID"), admin.ID) if err != nil { http.Error(w, "unable to create reset link", http.StatusInternalServerError) return } if r.URL.Query().Get("next") == "edit" { http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit?invite="+url.QueryEscape(result.URL), http.StatusSeeOther) return } http.Redirect(w, r, "/admin/users?invite="+url.QueryEscape(result.URL), http.StatusSeeOther) } func (a *App) AdminDeleteBox(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) || !a.validateCSRF(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) AdminViewBox(w http.ResponseWriter, r *http.Request) { if !a.requireAdmin(w, r) { return } box, err := a.uploadService.GetBox(r.PathValue("boxID")) if err != nil { http.NotFound(w, r) return } if a.uploadService.IsProtected(box) { http.SetCookie(w, &http.Cookie{ Name: unlockCookieName(box.ID), Value: a.uploadService.UnlockToken(box), Path: "/d/" + box.ID, HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: r.TLS != nil, Expires: box.ExpiresAt, }) a.logger.Info("admin bypassed box password", "source", "admin", "severity", "user_activity", "code", 2302, "box_id", box.ID) } http.Redirect(w, r, "/d/"+box.ID, http.StatusSeeOther) } func (a *App) renderAdminLogin(w http.ResponseWriter, r *http.Request, status int, message string) { a.renderPage(w, r, 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 { owner := "Anonymous" if box.OwnerID != "" { if user, err := a.authService.UserByID(box.OwnerID); err == nil { owner = user.Email } else { owner = "User" } } rows = append(rows, adminBoxView{ ID: box.ID, Owner: owner, 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 user, ok := a.currentUser(r); ok && user.Role == services.UserRoleAdmin { return true } if a.cfg.AdminToken == "" { return false } cookie, err := r.Cookie(adminCookieName) if err != nil { return false } return cookie.Value == adminCookieValue(a.cfg.AdminToken) } func (a *App) requireAdminUser(w http.ResponseWriter, r *http.Request) (services.User, bool) { user, ok := a.currentUser(r) if ok && user.Role == services.UserRoleAdmin { return user, true } if a.cfg.AdminToken != "" && a.isAdmin(r) { return services.User{ID: "env-admin", Role: services.UserRoleAdmin, Status: services.UserStatusActive}, true } http.Redirect(w, r, "/login?next="+r.URL.Path, http.StatusSeeOther) return services.User{}, false } func (a *App) currentPublicUser(r *http.Request) any { if user, ok := a.currentUser(r); ok { return a.authService.PublicUser(user) } return nil } func adminCookieValue(token string) string { sum := sha256.Sum256([]byte("warpbox-admin:" + token)) return hex.EncodeToString(sum[:]) } func parsePositiveInt(value string) int { parsed, err := strconv.Atoi(value) if err != nil { return 0 } return parsed } func parsePositiveFloat(value string) float64 { parsed, err := strconv.ParseFloat(value, 64) if err != nil { return 0 } return parsed } func optionalMB(value string) *float64 { if value == "" { return nil } parsed, err := services.ParseMegabytesValue(value) if err != nil { return nil } return &parsed } func optionalMBAllowZero(value string) *float64 { if value == "" { return nil } parsed, err := strconv.ParseFloat(value, 64) if err != nil || parsed < 0 { return nil } return &parsed } func optionalInt(value string) *int { if value == "" { return nil } parsed, err := strconv.Atoi(value) if err != nil || parsed <= 0 { return nil } return &parsed } func formatMB(value float64) string { return strconv.FormatFloat(value, 'f', -1, 64) + " MB" } func (a *App) storageBackendViews() ([]services.StorageBackendView, error) { configs, err := a.uploadService.Storage().ListBackendConfigs() if err != nil { return nil, err } views := make([]services.StorageBackendView, 0, len(configs)) for _, cfg := range configs { var usage int64 if backend, err := a.uploadService.Storage().BackendConfig(cfg.ID); err == nil && backend.Enabled { if concrete, err := a.uploadService.Storage().Backend(cfg.ID); err == nil { usage, _ = concrete.Usage(context.Background()) } } inUse, _ := a.storageBackendInUse(cfg.ID) views = append(views, services.StorageBackendView{ Config: cfg, UsageBytes: usage, UsageLabel: services.FormatMegabytesFromBytes(usage), InUse: inUse, }) } return views, nil } func (a *App) adminUserEdit(user services.User, settings services.UploadPolicySettings) (adminUserEditView, error) { storageUsed, err := a.uploadService.UserActiveStorageUsed(user.ID) if err != nil { return adminUserEditView{}, err } usage, err := a.settingsService.UsageForUser(user.ID, time.Now().UTC()) if err != nil { return adminUserEditView{}, err } effective := a.settingsService.EffectivePolicyForUser(settings, user) view := adminUserEditView{ ID: user.ID, Username: user.Username, Email: user.Email, Role: user.Role, Status: user.Status, StorageUsed: services.FormatMegabytesFromBytes(storageUsed), DailyUsed: services.FormatMegabytesFromBytes(usage.UploadedBytes), EffectiveDaily: services.FormatMegabytesLabel(effective.DailyUploadMB), EffectiveMaxDays: effective.MaxDays, EffectiveDailyBoxes: effective.DailyBoxes, EffectiveActiveBoxes: effective.ActiveBoxes, EffectiveBackend: effective.StorageBackendID, MaxUploadMB: floatPtrString(user.Policy.MaxUploadMB), DailyUploadMB: floatPtrString(user.Policy.DailyUploadMB), StorageQuotaMB: floatPtrString(user.Policy.StorageQuotaMB), MaxDays: intPtrString(user.Policy.MaxDays), DailyBoxes: intPtrString(user.Policy.DailyBoxes), ActiveBoxes: intPtrString(user.Policy.ActiveBoxes), ShortWindowRequests: intPtrString(user.Policy.ShortWindowRequests), StorageBackendID: stringPtrString(user.Policy.StorageBackendID), } if effective.StorageQuotaSet { view.EffectiveStorage = services.FormatMegabytesLabel(effective.StorageQuotaMB) } else { view.EffectiveStorage = "unlimited" } return view, nil } func (a *App) storageBackendInUse(id string) (bool, error) { settings, err := a.settingsService.UploadPolicy() if err != nil { return false, err } if settings.AnonymousStorageBackend == id || settings.UserStorageBackend == id { return true, nil } boxes, err := a.uploadService.ListBoxes(0) if err != nil { return false, err } for _, box := range boxes { if a.uploadService.BoxStorageBackendID(box) == id { return true, nil } } users, err := a.authService.ListUsers() if err != nil { return false, err } for _, user := range users { if user.Policy.StorageBackendID != nil && *user.Policy.StorageBackendID == id { return true, nil } } return false, nil } func floatPtrString(value *float64) string { if value == nil { return "" } return strconv.FormatFloat(*value, 'f', -1, 64) } func intPtrString(value *int) string { if value == nil { return "" } return strconv.Itoa(*value) } func stringPtrString(value *string) string { if value == nil { return "" } return *value }