feat(config): allow -1 to represent unlimited upload limits
Introduce support for configuring unlimited upload limits by allowing -1 as a valid value for anonymous and user upload MB limits. Changes include: - Added `envMegabytesLimitFloat` and helper functions to parse and validate limits where -1 is allowed. - Updated validation logic to accept -1 for `AnonymousMaxUploadMB`, `AnonymousDailyUploadMB`, and `UserDailyUploadMB`. - Added a test case to verify unlimited upload policy behavior.
This commit is contained in:
@@ -856,11 +856,11 @@ func VerifyPasswordHash(encoded, password string) bool {
|
||||
}
|
||||
|
||||
func validateUserPolicy(policy UserPolicy) error {
|
||||
if policy.MaxUploadMB != nil && *policy.MaxUploadMB < 0 {
|
||||
return fmt.Errorf("max upload override cannot be negative")
|
||||
if policy.MaxUploadMB != nil && *policy.MaxUploadMB < 0 && *policy.MaxUploadMB != -1 {
|
||||
return fmt.Errorf("max upload override must be positive or -1 for unlimited")
|
||||
}
|
||||
if policy.DailyUploadMB != nil && *policy.DailyUploadMB <= 0 {
|
||||
return fmt.Errorf("daily upload override must be positive")
|
||||
if policy.DailyUploadMB != nil && ((*policy.DailyUploadMB < 0 && *policy.DailyUploadMB != -1) || *policy.DailyUploadMB == 0) {
|
||||
return fmt.Errorf("daily upload override must be positive or -1 for unlimited")
|
||||
}
|
||||
if policy.StorageQuotaMB != nil && *policy.StorageQuotaMB < 0 {
|
||||
return fmt.Errorf("storage quota override cannot be negative")
|
||||
|
||||
@@ -205,6 +205,26 @@ func TestAPITokenScopedToOwnerAndDisabledUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserPolicyAllowsNegativeOneForUnlimitedUploadLimits(t *testing.T) {
|
||||
auth := newTestAuthService(t)
|
||||
user, err := auth.CreateBootstrapUser("daniel", "daniel@example.test", "password123")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
||||
}
|
||||
|
||||
unlimited := -1.0
|
||||
if err := auth.SetUserPolicy(user.ID, UserPolicy{MaxUploadMB: &unlimited, DailyUploadMB: &unlimited}); err != nil {
|
||||
t.Fatalf("SetUserPolicy rejected -1 unlimited upload limits: %v", err)
|
||||
}
|
||||
updated, err := auth.UserByID(user.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("UserByID returned error: %v", err)
|
||||
}
|
||||
if updated.Policy.MaxUploadMB == nil || *updated.Policy.MaxUploadMB != -1 || updated.Policy.DailyUploadMB == nil || *updated.Policy.DailyUploadMB != -1 {
|
||||
t.Fatalf("unlimited policy was not persisted: %+v", updated.Policy)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestAuthService(t *testing.T) *AuthService {
|
||||
t.Helper()
|
||||
root := t.TempDir()
|
||||
|
||||
@@ -170,13 +170,13 @@ func (s *SettingsService) UploadPolicy() (UploadPolicySettings, error) {
|
||||
}
|
||||
|
||||
func (s *SettingsService) withDefaultGaps(settings UploadPolicySettings) UploadPolicySettings {
|
||||
if settings.AnonymousMaxUploadMB <= 0 {
|
||||
if settings.AnonymousMaxUploadMB == 0 {
|
||||
settings.AnonymousMaxUploadMB = s.defaults.AnonymousMaxUploadMB
|
||||
}
|
||||
if settings.AnonymousDailyUploadMB <= 0 {
|
||||
if settings.AnonymousDailyUploadMB == 0 {
|
||||
settings.AnonymousDailyUploadMB = s.defaults.AnonymousDailyUploadMB
|
||||
}
|
||||
if settings.UserDailyUploadMB <= 0 {
|
||||
if settings.UserDailyUploadMB == 0 {
|
||||
settings.UserDailyUploadMB = s.defaults.UserDailyUploadMB
|
||||
}
|
||||
if settings.DefaultUserStorageMB <= 0 {
|
||||
@@ -370,14 +370,14 @@ func (s *SettingsService) UsageForIP(ip string, now time.Time) (UsageRecord, err
|
||||
}
|
||||
|
||||
func (s *SettingsService) validate(settings UploadPolicySettings) error {
|
||||
if settings.AnonymousMaxUploadMB <= 0 {
|
||||
return fmt.Errorf("anonymous max upload must be positive")
|
||||
if settings.AnonymousMaxUploadMB < 0 && settings.AnonymousMaxUploadMB != -1 || settings.AnonymousMaxUploadMB == 0 {
|
||||
return fmt.Errorf("anonymous max upload must be positive or -1 for unlimited")
|
||||
}
|
||||
if settings.AnonymousDailyUploadMB <= 0 {
|
||||
return fmt.Errorf("anonymous daily upload must be positive")
|
||||
if settings.AnonymousDailyUploadMB < 0 && settings.AnonymousDailyUploadMB != -1 || settings.AnonymousDailyUploadMB == 0 {
|
||||
return fmt.Errorf("anonymous daily upload must be positive or -1 for unlimited")
|
||||
}
|
||||
if settings.UserDailyUploadMB <= 0 {
|
||||
return fmt.Errorf("user daily upload must be positive")
|
||||
if settings.UserDailyUploadMB < 0 && settings.UserDailyUploadMB != -1 || settings.UserDailyUploadMB == 0 {
|
||||
return fmt.Errorf("user daily upload must be positive or -1 for unlimited")
|
||||
}
|
||||
if settings.DefaultUserStorageMB <= 0 {
|
||||
return fmt.Errorf("default user storage must be positive")
|
||||
@@ -422,6 +422,32 @@ func ParseMegabytesValue(value string) (float64, error) {
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func ParseMegabytesLimitValue(value string) (float64, error) {
|
||||
parsed, err := parseMegabytesNumber(value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if parsed == -1 {
|
||||
return -1, nil
|
||||
}
|
||||
if parsed <= 0 {
|
||||
return 0, fmt.Errorf("megabyte value must be positive or -1 for unlimited")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func parseMegabytesNumber(value string) (float64, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return 0, fmt.Errorf("megabyte value is required")
|
||||
}
|
||||
value = strings.TrimSuffix(value, "MB")
|
||||
value = strings.TrimSuffix(value, "Mb")
|
||||
value = strings.TrimSuffix(value, "mb")
|
||||
value = strings.TrimSpace(value)
|
||||
return strconv.ParseFloat(value, 64)
|
||||
}
|
||||
|
||||
func MegabytesToBytes(value float64) int64 {
|
||||
return int64(value * 1024 * 1024)
|
||||
}
|
||||
@@ -437,6 +463,9 @@ func FormatMegabytesFromBytes(value int64) string {
|
||||
}
|
||||
|
||||
func FormatMegabytesLabel(value float64) string {
|
||||
if value < 0 {
|
||||
return "unlimited"
|
||||
}
|
||||
return strconv.FormatFloat(value, 'f', -1, 64) + " MB"
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,30 @@ func TestSettingsRejectInvalidMegabytes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadPolicyAllowsNegativeOneForUnlimitedUploadLimits(t *testing.T) {
|
||||
settings := newTestSettingsService(t)
|
||||
policy, err := settings.UploadPolicy()
|
||||
if err != nil {
|
||||
t.Fatalf("UploadPolicy returned error: %v", err)
|
||||
}
|
||||
policy.AnonymousMaxUploadMB = -1
|
||||
policy.AnonymousDailyUploadMB = -1
|
||||
policy.UserDailyUploadMB = -1
|
||||
if err := settings.UpdateUploadPolicy(policy); err != nil {
|
||||
t.Fatalf("UpdateUploadPolicy rejected -1 unlimited upload limits: %v", err)
|
||||
}
|
||||
next, err := settings.UploadPolicy()
|
||||
if err != nil {
|
||||
t.Fatalf("UploadPolicy returned error: %v", err)
|
||||
}
|
||||
if next.AnonymousMaxUploadMB != -1 || next.AnonymousDailyUploadMB != -1 || next.UserDailyUploadMB != -1 {
|
||||
t.Fatalf("unlimited upload limits were not persisted: %+v", next)
|
||||
}
|
||||
if got := FormatMegabytesLabel(-1); got != "unlimited" {
|
||||
t.Fatalf("FormatMegabytesLabel(-1) = %q, want unlimited", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDailyUsageAndCleanup(t *testing.T) {
|
||||
settings := newTestSettingsService(t)
|
||||
now := time.Date(2026, 5, 30, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
Reference in New Issue
Block a user