package handlers import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "warpbox.dev/backend/libs/services" ) func TestLoggedInUploadStoresOwnerAndAnonymousUploadDoesNot(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() user, err := app.authService.CreateBootstrapUser("daniel", "daniel@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } _, token, err := app.authService.Login("daniel@example.test", "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "owned.txt", "owned") request.Header.Set("Accept", "application/json") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusCreated { t.Fatalf("owned upload status = %d, body = %s", response.Code, response.Body.String()) } var ownedPayload services.UploadResult if err := json.Unmarshal(response.Body.Bytes(), &ownedPayload); err != nil { t.Fatalf("json.Unmarshal owned returned error: %v", err) } ownedBox, err := app.uploadService.GetBox(ownedPayload.BoxID) if err != nil { t.Fatalf("GetBox owned returned error: %v", err) } if ownedBox.OwnerID != user.ID { t.Fatalf("owned OwnerID = %q, want %q", ownedBox.OwnerID, user.ID) } owned := uploadThroughApp(t, app) anonymous, err := app.uploadService.GetBox(owned.BoxID) if err != nil { t.Fatalf("GetBox anonymous returned error: %v", err) } if anonymous.OwnerID != "" { t.Fatalf("anonymous OwnerID = %q, want empty", anonymous.OwnerID) } boxes, err := app.uploadService.ListBoxes(0) if err != nil { t.Fatalf("ListBoxes returned error: %v", err) } foundOwned := false for _, box := range boxes { if box.OwnerID == user.ID { foundOwned = true } } if !foundOwned { t.Fatalf("logged-in upload did not store owner id %q", user.ID) } } func TestBearerTokenUploadActsAsUser(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() user, err := app.authService.CreateBootstrapUser("daniel", "daniel@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } tokenResult, err := app.authService.CreateAPIToken(user.ID, "cli") if err != nil { t.Fatalf("CreateAPIToken returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "owned.txt", "owned") request.Header.Set("Accept", "application/json") request.Header.Set("Authorization", "Bearer "+tokenResult.Plaintext) response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusCreated { t.Fatalf("token upload status = %d, body = %s", response.Code, response.Body.String()) } var payload services.UploadResult if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil { t.Fatalf("json.Unmarshal returned error: %v", err) } box, err := app.uploadService.GetBox(payload.BoxID) if err != nil { t.Fatalf("GetBox returned error: %v", err) } if box.OwnerID != user.ID { t.Fatalf("OwnerID = %q, want %q", box.OwnerID, user.ID) } // An invalid bearer token is an authentication failure, not an anonymous upload. badRequest := multipartUploadRequest(t, "/api/v1/upload", "file", "x.txt", "x") badRequest.Header.Set("Accept", "application/json") badRequest.Header.Set("Authorization", "Bearer wbx_bogus.secret") badResponse := httptest.NewRecorder() app.Upload(badResponse, badRequest) if badResponse.Code != http.StatusUnauthorized { t.Fatalf("invalid token upload status = %d, body = %s", badResponse.Code, badResponse.Body.String()) } } func TestAnonymousUploadWithoutBearerStillWorks(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() response := httptest.NewRecorder() app.Upload(response, multipartUploadRequest(t, "/api/v1/upload", "file", "anonymous.txt", "anonymous")) if response.Code != http.StatusCreated { t.Fatalf("anonymous upload status = %d, body = %s", response.Code, response.Body.String()) } } func TestDisabledUserBearerTokenCannotUpload(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() user, err := app.authService.CreateBootstrapUser("daniel", "daniel@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } tokenResult, err := app.authService.CreateAPIToken(user.ID, "cli") if err != nil { t.Fatalf("CreateAPIToken returned error: %v", err) } if err := app.authService.DisableUser(user.ID, true); err != nil { t.Fatalf("DisableUser returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "blocked.txt", "blocked") request.Header.Set("Accept", "application/json") request.Header.Set("Authorization", "Bearer "+tokenResult.Plaintext) response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusUnauthorized { t.Fatalf("disabled bearer upload status = %d, body = %s", response.Code, response.Body.String()) } } func TestInviteHandlerCreatesUserAndMarksInviteUsed(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() admin, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } invite, err := app.authService.CreateInvite("friend@example.test", services.UserRoleUser, admin.ID, 0) if err != nil { t.Fatalf("CreateInvite returned error: %v", err) } request := httptest.NewRequest(http.MethodPost, "/invite/"+invite.Token, strings.NewReader("username=friend&password=password123")) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.SetPathValue("token", invite.Token) response := httptest.NewRecorder() app.InvitePost(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("InvitePost status = %d, body = %s", response.Code, response.Body.String()) } if _, err := app.authService.AcceptInvite(invite.Token, "friend", "password123"); err == nil { t.Fatalf("invite token remained reusable") } } func TestNonOwnerCannotManageOwnedBox(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() owner, err := app.authService.CreateBootstrapUser("owner", "owner@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } invite, err := app.authService.CreateInvite("other@example.test", services.UserRoleUser, owner.ID, 0) if err != nil { t.Fatalf("CreateInvite returned error: %v", err) } other, err := app.authService.AcceptInvite(invite.Token, "other", "password123") if err != nil { t.Fatalf("AcceptInvite returned error: %v", err) } result := createOwnedBoxThroughApp(t, app, owner.ID) if err := app.uploadService.RenameOwnedBox(result.BoxID, other.ID, "stolen"); err == nil { t.Fatalf("RenameOwnedBox allowed non-owner") } } func TestAdminUploadBypassesMaxUploadSize(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() _, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } _, token, err := app.authService.Login("admin@example.test", "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "large.txt", strings.Repeat("x", int(app.uploadService.MaxUploadSize())+1)) request.Header.Set("Accept", "application/json") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusCreated { t.Fatalf("admin upload status = %d, body = %s", response.Code, response.Body.String()) } } func TestUnlimitedAnonymousUploadPolicyUsesNegativeOne(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() policy, err := app.settingsService.UploadPolicy() if err != nil { t.Fatalf("UploadPolicy returned error: %v", err) } policy.AnonymousMaxUploadMB = -1 policy.AnonymousDailyUploadMB = -1 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "large.txt", strings.Repeat("x", int(app.uploadService.MaxUploadSize())+1)) request.Header.Set("Accept", "application/json") response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusCreated { t.Fatalf("unlimited anonymous upload status = %d, body = %s", response.Code, response.Body.String()) } } func TestAnonymousUploadDisabled(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() policy := testPolicy(t, app) policy.AnonymousUploadsEnabled = false if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "note.txt", "hello") request.Header.Set("Accept", "application/json") response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusForbidden { t.Fatalf("status = %d, want 403, body = %s", response.Code, response.Body.String()) } } func TestAnonymousUploadLimits(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() policy := testPolicy(t, app) policy.AnonymousMaxUploadMB = 1 policy.AnonymousDailyUploadMB = 0.001 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } large := multipartUploadRequest(t, "/api/v1/upload", "file", "large.txt", strings.Repeat("x", 2*1024*1024)) large.Header.Set("Accept", "application/json") large.RemoteAddr = "192.0.2.10:1234" largeResponse := httptest.NewRecorder() app.Upload(largeResponse, large) if largeResponse.Code != http.StatusRequestEntityTooLarge { t.Fatalf("large status = %d, body = %s", largeResponse.Code, largeResponse.Body.String()) } daily := multipartUploadRequest(t, "/api/v1/upload", "file", "note.txt", strings.Repeat("x", 2048)) daily.Header.Set("Accept", "application/json") daily.RemoteAddr = "192.0.2.10:1234" dailyResponse := httptest.NewRecorder() app.Upload(dailyResponse, daily) if dailyResponse.Code != http.StatusTooManyRequests { t.Fatalf("daily status = %d, body = %s", dailyResponse.Code, dailyResponse.Body.String()) } } func TestSignedInUploadQuotaAndOverride(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() user, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } invite, err := app.authService.CreateInvite("user@example.test", services.UserRoleUser, user.ID, 0) if err != nil { t.Fatalf("CreateInvite returned error: %v", err) } normal, err := app.authService.AcceptInvite(invite.Token, "user", "password123") if err != nil { t.Fatalf("AcceptInvite returned error: %v", err) } _, token, err := app.authService.Login(normal.Email, "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } policy := testPolicy(t, app) policy.DefaultUserStorageMB = 0.001 policy.UserDailyUploadMB = 8 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "quota.txt", strings.Repeat("x", 2048)) request.Header.Set("Accept", "application/json") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusRequestEntityTooLarge { t.Fatalf("quota status = %d, body = %s", response.Code, response.Body.String()) } override := 10.0 if err := app.authService.SetUserStorageQuota(normal.ID, &override); err != nil { t.Fatalf("SetUserStorageQuota returned error: %v", err) } request = multipartUploadRequest(t, "/api/v1/upload", "file", "quota.txt", strings.Repeat("x", 2048)) request.Header.Set("Accept", "application/json") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) response = httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusCreated { t.Fatalf("override status = %d, body = %s", response.Code, response.Body.String()) } } func TestSignedInDailyCap(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() admin, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } invite, err := app.authService.CreateInvite("user@example.test", services.UserRoleUser, admin.ID, 0) if err != nil { t.Fatalf("CreateInvite returned error: %v", err) } user, err := app.authService.AcceptInvite(invite.Token, "user", "password123") if err != nil { t.Fatalf("AcceptInvite returned error: %v", err) } _, token, err := app.authService.Login(user.Email, "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } policy := testPolicy(t, app) policy.UserDailyUploadMB = 0.001 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "daily.txt", strings.Repeat("x", 2048)) request.Header.Set("Accept", "application/json") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusTooManyRequests { t.Fatalf("daily status = %d, body = %s", response.Code, response.Body.String()) } } func TestLayeredUploadLimits(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() policy := testPolicy(t, app) policy.AnonymousDailyBoxes = 1 policy.AnonymousActiveBoxes = 10 policy.AnonymousMaxDays = 3 policy.LocalStorageMaxGB = 0.001 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } first := uploadThroughApp(t, app) if first.BoxID == "" { t.Fatalf("first upload did not return a box id") } secondRequest := multipartUploadRequest(t, "/api/v1/upload", "file", "second.txt", "hello") secondRequest.Header.Set("Accept", "application/json") secondResponse := httptest.NewRecorder() app.Upload(secondResponse, secondRequest) if secondResponse.Code != http.StatusTooManyRequests { t.Fatalf("daily box status = %d, body = %s", secondResponse.Code, secondResponse.Body.String()) } policy.AnonymousDailyBoxes = 10 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } expiryRequest := multipartUploadRequestWithField(t, "/api/v1/upload", "file", "expiry.txt", "hello", "max_days", "30") expiryRequest.Header.Set("Accept", "application/json") expiryResponse := httptest.NewRecorder() app.Upload(expiryResponse, expiryRequest) if expiryResponse.Code != http.StatusRequestEntityTooLarge && expiryResponse.Code != http.StatusTooManyRequests { t.Fatalf("expiry/box status = %d, body = %s", expiryResponse.Code, expiryResponse.Body.String()) } } func TestBatchedUploadAppendBypassesDailyBoxCreationCap(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() policy := testPolicy(t, app) policy.AnonymousDailyBoxes = 1 policy.AnonymousActiveBoxes = 10 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } first := multipartUploadRequest(t, "/api/v1/upload", "file", "first.txt", "hello") first.Header.Set("Accept", "application/json") first.Header.Set(uploadBatchHeader, "sharex-test") firstResponse := httptest.NewRecorder() app.Upload(firstResponse, first) if firstResponse.Code != http.StatusCreated { t.Fatalf("first batched status = %d, body = %s", firstResponse.Code, firstResponse.Body.String()) } second := multipartUploadRequest(t, "/api/v1/upload", "file", "second.txt", "hello") second.Header.Set("Accept", "application/json") second.Header.Set(uploadBatchHeader, "sharex-test") secondResponse := httptest.NewRecorder() app.Upload(secondResponse, second) if secondResponse.Code != http.StatusCreated { t.Fatalf("second batched status = %d, body = %s", secondResponse.Code, secondResponse.Body.String()) } third := multipartUploadRequest(t, "/api/v1/upload", "file", "third.txt", "hello") third.Header.Set("Accept", "application/json") thirdResponse := httptest.NewRecorder() app.Upload(thirdResponse, third) if thirdResponse.Code != http.StatusTooManyRequests { t.Fatalf("non-batched status = %d, body = %s", thirdResponse.Code, thirdResponse.Body.String()) } } func TestBatchedUploadAppendBypassesActiveBoxCreationCap(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() policy := testPolicy(t, app) policy.AnonymousDailyBoxes = 10 policy.AnonymousActiveBoxes = 1 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } first := multipartUploadRequest(t, "/api/v1/upload", "file", "first.txt", "hello") first.Header.Set("Accept", "application/json") first.Header.Set(uploadBatchHeader, "active-cap") firstResponse := httptest.NewRecorder() app.Upload(firstResponse, first) if firstResponse.Code != http.StatusCreated { t.Fatalf("first batched status = %d, body = %s", firstResponse.Code, firstResponse.Body.String()) } second := multipartUploadRequest(t, "/api/v1/upload", "file", "second.txt", "hello") second.Header.Set("Accept", "application/json") second.Header.Set(uploadBatchHeader, "active-cap") secondResponse := httptest.NewRecorder() app.Upload(secondResponse, second) if secondResponse.Code != http.StatusCreated { t.Fatalf("second batched status = %d, body = %s", secondResponse.Code, secondResponse.Body.String()) } third := multipartUploadRequest(t, "/api/v1/upload", "file", "third.txt", "hello") third.Header.Set("Accept", "application/json") thirdResponse := httptest.NewRecorder() app.Upload(thirdResponse, third) if thirdResponse.Code != http.StatusTooManyRequests { t.Fatalf("non-batched status = %d, body = %s", thirdResponse.Code, thirdResponse.Body.String()) } } func TestUserPolicyOverrideChangesUploadEnforcement(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() admin, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } invite, err := app.authService.CreateInvite("user@example.test", services.UserRoleUser, admin.ID, 0) if err != nil { t.Fatalf("CreateInvite returned error: %v", err) } user, err := app.authService.AcceptInvite(invite.Token, "user", "password123") if err != nil { t.Fatalf("AcceptInvite returned error: %v", err) } dailyBoxes := 1 maxDays := 1 if err := app.authService.SetUserPolicy(user.ID, services.UserPolicy{DailyBoxes: &dailyBoxes, MaxDays: &maxDays}); err != nil { t.Fatalf("SetUserPolicy returned error: %v", err) } _, token, err := app.authService.Login(user.Email, "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } first := multipartUploadRequest(t, "/api/v1/upload", "file", "one.txt", "hello") first.Header.Set("Accept", "application/json") first.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) firstResponse := httptest.NewRecorder() app.Upload(firstResponse, first) if firstResponse.Code != http.StatusCreated { t.Fatalf("first status = %d, body = %s", firstResponse.Code, firstResponse.Body.String()) } second := multipartUploadRequest(t, "/api/v1/upload", "file", "two.txt", "hello") second.Header.Set("Accept", "application/json") second.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) secondResponse := httptest.NewRecorder() app.Upload(secondResponse, second) if secondResponse.Code != http.StatusTooManyRequests { t.Fatalf("second status = %d, body = %s", secondResponse.Code, secondResponse.Body.String()) } } func TestLocalStorageCapRejectsUpload(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() policy := testPolicy(t, app) policy.AnonymousMaxUploadMB = 4 policy.AnonymousDailyUploadMB = 8 policy.LocalStorageMaxGB = 0.001 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "large.txt", strings.Repeat("x", 2*1024*1024)) request.Header.Set("Accept", "application/json") response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusRequestEntityTooLarge { t.Fatalf("storage cap status = %d, body = %s", response.Code, response.Body.String()) } } func TestAdminSettingsPostChangesUploadEnforcement(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() _, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } _, token, err := app.authService.Login("admin@example.test", "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } settingsForm := strings.NewReader("anonymous_max_upload_mb=512&anonymous_daily_upload_mb=2048&user_daily_upload_mb=8192&default_user_storage_mb=51200&usage_retention_days=30&csrf_token=test-csrf") settingsRequest := httptest.NewRequest(http.MethodPost, "/admin/settings", settingsForm) settingsRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") settingsRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) settingsRequest.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) settingsResponse := httptest.NewRecorder() app.AdminSettingsPost(settingsResponse, settingsRequest) if settingsResponse.Code != http.StatusSeeOther { t.Fatalf("AdminSettingsPost status = %d, body = %s", settingsResponse.Code, settingsResponse.Body.String()) } uploadRequest := multipartUploadRequest(t, "/api/v1/upload", "file", "note.txt", "hello") uploadRequest.Header.Set("Accept", "application/json") uploadResponse := httptest.NewRecorder() app.Upload(uploadResponse, uploadRequest) if uploadResponse.Code != http.StatusForbidden { t.Fatalf("upload status = %d, want 403, body = %s", uploadResponse.Code, uploadResponse.Body.String()) } } func TestAdminUserQuotaPostChangesEnforcement(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() admin, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } invite, err := app.authService.CreateInvite("user@example.test", services.UserRoleUser, admin.ID, 0) if err != nil { t.Fatalf("CreateInvite returned error: %v", err) } user, err := app.authService.AcceptInvite(invite.Token, "user", "password123") if err != nil { t.Fatalf("AcceptInvite returned error: %v", err) } _, adminToken, err := app.authService.Login(admin.Email, "password123") if err != nil { t.Fatalf("admin Login returned error: %v", err) } quotaRequest := httptest.NewRequest(http.MethodPost, "/admin/users/"+user.ID+"/quota", strings.NewReader("storage_quota_mb=0.001&csrf_token=test-csrf")) quotaRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") quotaRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) quotaRequest.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) quotaRequest.SetPathValue("userID", user.ID) quotaResponse := httptest.NewRecorder() app.AdminUpdateUserQuota(quotaResponse, quotaRequest) if quotaResponse.Code != http.StatusSeeOther { t.Fatalf("AdminUpdateUserQuota status = %d, body = %s", quotaResponse.Code, quotaResponse.Body.String()) } _, userToken, err := app.authService.Login(user.Email, "password123") if err != nil { t.Fatalf("user Login returned error: %v", err) } uploadRequest := multipartUploadRequest(t, "/api/v1/upload", "file", "quota.txt", strings.Repeat("x", 2048)) uploadRequest.Header.Set("Accept", "application/json") uploadRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: userToken}) uploadResponse := httptest.NewRecorder() app.Upload(uploadResponse, uploadRequest) if uploadResponse.Code != http.StatusRequestEntityTooLarge { t.Fatalf("upload status = %d, want 413, body = %s", uploadResponse.Code, uploadResponse.Body.String()) } } func TestHomeReflectsUploadPolicySettings(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() policy := testPolicy(t, app) policy.AnonymousMaxUploadMB = 123 policy.AnonymousDailyUploadMB = 456 if err := app.settingsService.UpdateUploadPolicy(policy); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } request := httptest.NewRequest(http.MethodGet, "/", nil) response := httptest.NewRecorder() app.Home(response, request) if response.Code != http.StatusOK { t.Fatalf("Home status = %d", response.Code) } body := response.Body.String() if !strings.Contains(body, "Max file size: 123 MB") || !strings.Contains(body, "456 MB") { t.Fatalf("home did not reflect policy settings: %s", body) } if !strings.Contains(body, "warpbox.dev · test ·") { t.Fatalf("home footer did not include app version: %s", body) } } func TestAPIDocsHeaderReflectsLoggedInUser(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() _, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } _, token, err := app.authService.Login("admin@example.test", "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } request := httptest.NewRequest(http.MethodGet, "/api", nil) request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) response := httptest.NewRecorder() app.APIDocs(response, request) if response.Code != http.StatusOK { t.Fatalf("APIDocs status = %d", response.Code) } body := response.Body.String() header := body[:strings.Index(body, "API<") || strings.Contains(header, "Health") || strings.Contains(header, "Dashboard") { t.Fatalf("api header did not reflect logged-out state: %s", body) } } func TestAdminOverviewChartsUseZeroAndFullHeights(t *testing.T) { now := time.Now().UTC() today := time.Date(now.Year(), now.Month(), now.Day(), 12, 0, 0, 0, time.UTC) overview := buildAdminOverview([]services.AdminBox{{ ID: "box1", CreatedAt: today, TotalSize: 1024, }}, services.AdminStats{TotalBoxes: 1, TotalFiles: 1, TotalSize: 1024}) for i, bar := range overview.UploadDays { want := 0 if i == len(overview.UploadDays)-1 { want = 100 } if bar.Height != want { t.Fatalf("upload bar %d height = %d, want %d", i, bar.Height, want) } } for i, bar := range overview.StorageDays { want := 0 if i == len(overview.StorageDays)-1 { want = 100 } if bar.Height != want { t.Fatalf("storage bar %d height = %d, want %d", i, bar.Height, want) } } } func TestAdminOverviewRendersBarHeightVariables(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) uploadThroughApp(t, app) request := httptest.NewRequest(http.MethodGet, "/admin", nil) request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) response := httptest.NewRecorder() app.AdminDashboard(response, request) if response.Code != http.StatusOK { t.Fatalf("AdminDashboard status = %d, body = %s", response.Code, response.Body.String()) } body := response.Body.String() if !strings.Contains(body, "--bar-height: 100%") { t.Fatalf("admin overview did not render a full-height bar: %s", body) } if strings.Contains(body, `style="height:`) { t.Fatalf("admin overview still uses fragile percent height styles: %s", body) } } func TestAdminStorageProviderPagesOnlyRenderRelevantFields(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) request := httptest.NewRequest(http.MethodGet, "/admin/storage/new/sftp", nil) request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) response := httptest.NewRecorder() app.AdminNewStorageProvider(response, request) if response.Code != http.StatusOK { t.Fatalf("AdminNewStorageProvider status = %d, body = %s", response.Code, response.Body.String()) } body := response.Body.String() if !strings.Contains(body, "Private key") || !strings.Contains(body, "SSH host key") { t.Fatalf("sftp page did not render sftp fields: %s", body) } for _, unwanted := range []string{"Bucket display name", "WebDAV URL", "Share", "Access key"} { if strings.Contains(body, unwanted) { t.Fatalf("sftp page rendered irrelevant field %q: %s", unwanted, body) } } cfg, err := app.uploadService.Storage().CreateBackend(services.StorageBackendConfig{ Provider: services.StorageProviderSFTP, Name: "NAS", Host: "files.example.test", Username: "warpbox", Password: "secret", }) if err != nil { t.Fatalf("CreateBackend returned error: %v", err) } editRequest := httptest.NewRequest(http.MethodGet, "/admin/storage/"+cfg.ID+"/edit", nil) editRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) editRequest.SetPathValue("backendID", cfg.ID) editResponse := httptest.NewRecorder() app.AdminEditStorageForm(editResponse, editRequest) if editResponse.Code != http.StatusOK { t.Fatalf("AdminEditStorageForm status = %d, body = %s", editResponse.Code, editResponse.Body.String()) } editBody := editResponse.Body.String() if !strings.Contains(editBody, "Immutable provider") || strings.Contains(editBody, "Bucket display name") || strings.Contains(editBody, "WebDAV URL") { t.Fatalf("edit page did not stay provider-specific: %s", editBody) } } func TestAdminStorageEditRejectsProviderMutation(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) cfg, err := app.uploadService.Storage().CreateBackend(services.StorageBackendConfig{ Provider: services.StorageProviderSFTP, Name: "NAS", Host: "files.example.test", Username: "warpbox", Password: "secret", }) if err != nil { t.Fatalf("CreateBackend returned error: %v", err) } form := strings.NewReader("provider=s3&name=Changed&endpoint=https://s3.example.test&bucket=bucket&access_key=access&secret_key=secret&use_ssl=on&csrf_token=test-csrf") request := httptest.NewRequest(http.MethodPost, "/admin/storage/"+cfg.ID+"/edit", form) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) request.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) request.SetPathValue("backendID", cfg.ID) response := httptest.NewRecorder() app.AdminEditStorage(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("AdminEditStorage status = %d, body = %s", response.Code, response.Body.String()) } stored, err := app.uploadService.Storage().BackendConfig(cfg.ID) if err != nil { t.Fatalf("BackendConfig returned error: %v", err) } if stored.Provider != services.StorageProviderSFTP || stored.Type != services.StorageBackendSFTP || stored.Name != "NAS" { t.Fatalf("storage backend mutated despite rejected provider change: %+v", stored) } } func TestAdminStorageJobRoutesRequireAdminAndCSRF(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() unauthorized := httptest.NewRecorder() app.AdminRunStorageCleanup(unauthorized, httptest.NewRequest(http.MethodPost, "/admin/storage/jobs/cleanup", nil)) if unauthorized.Code != http.StatusSeeOther { t.Fatalf("unauthorized cleanup status = %d", unauthorized.Code) } adminToken := createAdminSession(t, app) missingCSRFRequest := httptest.NewRequest(http.MethodPost, "/admin/storage/jobs/cleanup", nil) missingCSRFRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) missingCSRFResponse := httptest.NewRecorder() app.AdminRunStorageCleanup(missingCSRFResponse, missingCSRFRequest) if missingCSRFResponse.Code != http.StatusForbidden { t.Fatalf("missing csrf cleanup status = %d", missingCSRFResponse.Code) } request := httptest.NewRequest(http.MethodPost, "/admin/storage/jobs/cleanup", strings.NewReader("csrf_token=test-csrf")) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) request.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) response := httptest.NewRecorder() app.AdminRunStorageCleanup(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("authorized cleanup status = %d, body = %s", response.Code, response.Body.String()) } } func TestAdminStorageDeleteAction(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) cfg, err := app.uploadService.Storage().CreateBackend(services.StorageBackendConfig{ Provider: services.StorageProviderWebDAV, Name: "DAV", Endpoint: "https://dav.example.test", Username: "warpbox", Password: "secret", RemotePath: "/warpbox", }) if err != nil { t.Fatalf("CreateBackend returned error: %v", err) } deleteRequest := httptest.NewRequest(http.MethodPost, "/admin/storage/"+cfg.ID+"/delete", strings.NewReader("csrf_token=test-csrf")) deleteRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") deleteRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) deleteRequest.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) deleteRequest.SetPathValue("backendID", cfg.ID) deleteResponse := httptest.NewRecorder() app.AdminDeleteStorage(deleteResponse, deleteRequest) if deleteResponse.Code != http.StatusSeeOther { t.Fatalf("AdminDeleteStorage status = %d, body = %s", deleteResponse.Code, deleteResponse.Body.String()) } if _, err := app.uploadService.Storage().BackendConfig(cfg.ID); !errors.Is(err, os.ErrNotExist) { t.Fatalf("BackendConfig after delete = %v, want not exist", err) } } func TestAdminStorageDeleteResetsDefaultsAndUserOverrides(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) user, err := app.authService.UserByEmail("admin@example.test") if err != nil { t.Fatalf("UserByEmail returned error: %v", err) } cfg, err := app.uploadService.Storage().CreateBackend(services.StorageBackendConfig{ Provider: services.StorageProviderWebDAV, Name: "DAV", Endpoint: "https://dav.example.test", Username: "warpbox", Password: "secret", RemotePath: "/warpbox", }) if err != nil { t.Fatalf("CreateBackend returned error: %v", err) } settings, err := app.settingsService.UploadPolicy() if err != nil { t.Fatalf("UploadPolicy returned error: %v", err) } settings.UserStorageBackend = cfg.ID if err := app.settingsService.UpdateUploadPolicy(settings); err != nil { t.Fatalf("UpdateUploadPolicy returned error: %v", err) } if err := app.authService.SetUserStorageBackend(user.ID, cfg.ID); err != nil { t.Fatalf("SetUserStorageBackend returned error: %v", err) } request := httptest.NewRequest(http.MethodPost, "/admin/storage/"+cfg.ID+"/delete", strings.NewReader("csrf_token=test-csrf")) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) request.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) request.SetPathValue("backendID", cfg.ID) response := httptest.NewRecorder() app.AdminDeleteStorage(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("AdminDeleteStorage status = %d, body = %s", response.Code, response.Body.String()) } location := response.Header().Get("Location") if !strings.Contains(location, "Storage+backend+deleted") || !strings.Contains(location, "cleared+1+user+overrides") { t.Fatalf("delete redirect did not include cascade notice: %s", location) } if _, err := app.uploadService.Storage().BackendConfig(cfg.ID); !errors.Is(err, os.ErrNotExist) { t.Fatalf("BackendConfig after delete = %v, want not exist", err) } nextSettings, err := app.settingsService.UploadPolicy() if err != nil { t.Fatalf("UploadPolicy returned error: %v", err) } if nextSettings.UserStorageBackend != services.StorageBackendLocal { t.Fatalf("UserStorageBackend = %q, want local", nextSettings.UserStorageBackend) } nextUser, err := app.authService.UserByID(user.ID) if err != nil { t.Fatalf("UserByID returned error: %v", err) } if nextUser.Policy.StorageBackendID != nil { t.Fatalf("user storage override was not cleared: %+v", nextUser.Policy) } } func TestAdminStorageSpeedTestStartsBackgroundJob(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) if _, err := app.uploadService.Storage().TestBackend(services.StorageBackendLocal); err != nil { t.Fatalf("TestBackend returned error: %v", err) } request := httptest.NewRequest(http.MethodPost, "/admin/storage/local/speed-test", strings.NewReader("mode=custom&custom_file_count=2&custom_file_size_mb=0.001&csrf_token=test-csrf")) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) request.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) request.SetPathValue("backendID", services.StorageBackendLocal) response := httptest.NewRecorder() app.AdminStartStorageSpeedTest(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("AdminStartStorageSpeedTest status = %d, body = %s", response.Code, response.Body.String()) } tests, err := app.uploadService.Storage().ListSpeedTests(services.StorageBackendLocal, 10) if err != nil { t.Fatalf("ListSpeedTests returned error: %v", err) } if len(tests) != 1 { t.Fatalf("speed tests len = %d, want 1", len(tests)) } if tests[0].Mode != services.StorageSpeedModeCustom || tests[0].CustomFileCount != 2 || tests[0].CustomFileSizeMB != 0.001 { t.Fatalf("custom speed test options were not stored: %+v", tests[0]) } location := response.Header().Get("Location") if !strings.Contains(location, "/admin/storage/local/tests") { t.Fatalf("speed test redirect location = %q", location) } } func TestAdminStorageTestingPageRendersHistory(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) if _, err := app.uploadService.Storage().TestBackend(services.StorageBackendLocal); err != nil { t.Fatalf("TestBackend returned error: %v", err) } test, err := app.uploadService.Storage().StartSpeedTest(services.StorageBackendLocal, services.StorageSpeedModeSmall) if err != nil { t.Fatalf("StartSpeedTest returned error: %v", err) } app.uploadService.Storage().RunSpeedTest(context.Background(), test.ID) request := httptest.NewRequest(http.MethodGet, "/admin/storage/local/tests", nil) request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) request.SetPathValue("backendID", services.StorageBackendLocal) response := httptest.NewRecorder() app.AdminStorageTests(response, request) if response.Code != http.StatusOK { t.Fatalf("AdminStorageTests status = %d, body = %s", response.Code, response.Body.String()) } body := response.Body.String() if !strings.Contains(body, "New Test") || !strings.Contains(body, "Many small files") || strings.Contains(body, "storage-test-menu") { t.Fatalf("testing page missing expected page-based controls: %s", body) } jsonRequest := httptest.NewRequest(http.MethodGet, "/admin/storage/local/tests.json", nil) jsonRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) jsonRequest.SetPathValue("backendID", services.StorageBackendLocal) jsonResponse := httptest.NewRecorder() app.AdminStorageTestsJSON(jsonResponse, jsonRequest) if jsonResponse.Code != http.StatusOK { t.Fatalf("AdminStorageTestsJSON status = %d, body = %s", jsonResponse.Code, jsonResponse.Body.String()) } if !strings.Contains(jsonResponse.Body.String(), `"progress":100`) || !strings.Contains(jsonResponse.Body.String(), `"stage":"complete"`) { t.Fatalf("tests json missing progress fields: %s", jsonResponse.Body.String()) } } func TestAdminLogsAndBansPagesRender(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) logDir := filepath.Join(app.cfg.DataDir, "logs") if err := os.MkdirAll(logDir, 0o755); err != nil { t.Fatalf("MkdirAll returned error: %v", err) } logPath := filepath.Join(logDir, "2026-05-31.log") lines := strings.Join([]string{ `{"date":"2026-05-31","time":"12:34:56","source":"user-upload","severity":"user_activity","code":2001,"log":"upload response sent","ip":"127.0.0.1","box_id":"box123"}`, `{"date":"2026-05-31","time":"12:35:56","source":"http","severity":"dev","code":200,"log":"http request","remote_addr":"172.30.0.1:48358","box_id":"box456"}`, `{"date":"2026-05-31","time":"12:36:56","source":"http","severity":"dev","code":200,"log":"http request","method":"GET","path":"/health","ip":"127.0.0.1","user_agent":"Wget"}`, "", }, "\n") if err := os.WriteFile(logPath, []byte(lines), 0o644); err != nil { t.Fatalf("WriteFile returned error: %v", err) } logsRequest := httptest.NewRequest(http.MethodGet, "/admin/logs?q=box123", nil) logsRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) logsResponse := httptest.NewRecorder() app.AdminLogs(logsResponse, logsRequest) if logsResponse.Code != http.StatusOK { t.Fatalf("AdminLogs status = %d, body = %s", logsResponse.Code, logsResponse.Body.String()) } logsBody := logsResponse.Body.String() if !strings.Contains(logsBody, "upload response sent") || !strings.Contains(logsBody, "box123") { t.Fatalf("AdminLogs missing expected log entry: %s", logsBody) } if strings.Contains(logsBody, "172.30.0.1:48358") { t.Fatalf("AdminLogs rendered remote address with port: %s", logsBody) } healthRequest := httptest.NewRequest(http.MethodGet, "/admin/logs", nil) healthRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) healthResponse := httptest.NewRecorder() app.AdminLogs(healthResponse, healthRequest) if healthResponse.Code != http.StatusOK { t.Fatalf("AdminLogs health status = %d, body = %s", healthResponse.Code, healthResponse.Body.String()) } if strings.Contains(healthResponse.Body.String(), "/health") || strings.Contains(healthResponse.Body.String(), "Wget") { t.Fatalf("AdminLogs rendered container health ping: %s", healthResponse.Body.String()) } bansRequest := httptest.NewRequest(http.MethodGet, "/admin/bans", nil) bansRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) bansResponse := httptest.NewRecorder() app.AdminBans(bansResponse, bansRequest) if bansResponse.Code != http.StatusOK { t.Fatalf("AdminBans status = %d, body = %s", bansResponse.Code, bansResponse.Body.String()) } if !strings.Contains(bansResponse.Body.String(), "Manual ban") || !strings.Contains(bansResponse.Body.String(), "Auto-ban settings") { t.Fatalf("AdminBans missing ban controls: %s", bansResponse.Body.String()) } } func TestAdminCanCreateAndUnbanIPBan(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) expiresAt := time.Now().Add(24 * time.Hour).Format("2006-01-02T15:04") request := httptest.NewRequest(http.MethodPost, "/admin/bans", strings.NewReader("target=203.0.113.90&reason=test&expires_at="+expiresAt+"&csrf_token=test-csrf")) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) request.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) response := httptest.NewRecorder() app.AdminCreateBan(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("AdminCreateBan status = %d, body = %s", response.Code, response.Body.String()) } records, err := app.banService.ListBans() if err != nil { t.Fatalf("ListBans returned error: %v", err) } if len(records) != 1 || records[0].Normalized != "203.0.113.90" { t.Fatalf("records = %+v", records) } unbanRequest := httptest.NewRequest(http.MethodPost, "/admin/bans/"+records[0].ID+"/unban", strings.NewReader("csrf_token=test-csrf")) unbanRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") unbanRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) unbanRequest.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) unbanRequest.SetPathValue("banID", records[0].ID) unbanResponse := httptest.NewRecorder() app.AdminUnban(unbanResponse, unbanRequest) if unbanResponse.Code != http.StatusSeeOther { t.Fatalf("AdminUnban status = %d, body = %s", unbanResponse.Code, unbanResponse.Body.String()) } if _, ok, err := app.banService.Match("203.0.113.90", time.Now().UTC()); err != nil || ok { t.Fatalf("unbanned Match = %v, %v", ok, err) } } func TestAdminCanUpdateBanSettingsAndRules(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() adminToken := createAdminSession(t, app) settingsRequest := httptest.NewRequest(http.MethodPost, "/admin/bans/settings", strings.NewReader("auto_ban_enabled=on&auto_ban_duration_hours=48&abuse_window_hours=12&malicious_path_threshold=2&admin_login_failure_threshold=4&user_login_failure_threshold=5&csrf_token=test-csrf")) settingsRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") settingsRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) settingsRequest.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) settingsResponse := httptest.NewRecorder() app.AdminBanSettingsPost(settingsResponse, settingsRequest) if settingsResponse.Code != http.StatusSeeOther { t.Fatalf("AdminBanSettingsPost status = %d, body = %s", settingsResponse.Code, settingsResponse.Body.String()) } settings, err := app.banService.Settings() if err != nil { t.Fatalf("Settings returned error: %v", err) } if !settings.AutoBanEnabled || settings.AutoBanDurationHours != 48 || settings.MaliciousPathThreshold != 2 { t.Fatalf("settings = %+v", settings) } rulesRequest := httptest.NewRequest(http.MethodPost, "/admin/bans/rules", strings.NewReader("patterns=%2Fcustom-one%0A%2Fcustom-two&csrf_token=test-csrf")) rulesRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded") rulesRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken}) rulesRequest.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"}) rulesResponse := httptest.NewRecorder() app.AdminBanRulesPost(rulesResponse, rulesRequest) if rulesResponse.Code != http.StatusSeeOther { t.Fatalf("AdminBanRulesPost status = %d, body = %s", rulesResponse.Code, rulesResponse.Body.String()) } if pattern, err := app.banService.MaliciousPattern("/x/custom-two"); err != nil || pattern != "/custom-two" { t.Fatalf("MaliciousPattern = %q, %v", pattern, err) } } func TestLoginFailuresCreateAutoBanWhenEnabled(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() _, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil { t.Fatalf("CreateBootstrapUser returned error: %v", err) } settings, err := app.banService.Settings() if err != nil { t.Fatalf("Settings returned error: %v", err) } settings.AutoBanEnabled = true settings.UserLoginFailureThreshold = 2 if err := app.banService.UpdateSettings(settings); err != nil { t.Fatalf("UpdateSettings returned error: %v", err) } for i := 0; i < 2; i++ { request := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader("email=admin@example.test&password=wrong")) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.RemoteAddr = "203.0.113.91:1234" response := httptest.NewRecorder() app.LoginPost(response, request) if response.Code != http.StatusUnauthorized { t.Fatalf("LoginPost status = %d", response.Code) } } if _, ok, err := app.banService.Match("203.0.113.91", time.Now().UTC()); err != nil || !ok { t.Fatalf("Match after login failures = %v, %v", ok, err) } } func TestAdminLoginFailuresCreateAutoBanWhenEnabled(t *testing.T) { app, cleanup := newTestApp(t) defer cleanup() settings, err := app.banService.Settings() if err != nil { t.Fatalf("Settings returned error: %v", err) } settings.AutoBanEnabled = true settings.AdminLoginFailureThreshold = 2 if err := app.banService.UpdateSettings(settings); err != nil { t.Fatalf("UpdateSettings returned error: %v", err) } app.cfg.AdminToken = "correct-token" for i := 0; i < 2; i++ { request := httptest.NewRequest(http.MethodPost, "/admin/login", strings.NewReader("token=wrong")) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.RemoteAddr = "203.0.113.92:1234" response := httptest.NewRecorder() app.AdminLoginPost(response, request) if response.Code != http.StatusUnauthorized { t.Fatalf("AdminLoginPost status = %d", response.Code) } } if _, ok, err := app.banService.Match("203.0.113.92", time.Now().UTC()); err != nil || !ok { t.Fatalf("Match after admin login failures = %v, %v", ok, err) } } func createOwnedBoxThroughApp(t *testing.T, app *App, userID string) services.UploadResult { t.Helper() user, err := app.authService.UserByID(userID) if err != nil { t.Fatalf("UserByID returned error: %v", err) } _, token, err := app.authService.Login(user.Email, "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } request := multipartUploadRequest(t, "/api/v1/upload", "file", "owned.txt", "owned") request.Header.Set("Accept", "application/json") request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token}) response := httptest.NewRecorder() app.Upload(response, request) if response.Code != http.StatusCreated { t.Fatalf("upload status = %d, body = %s", response.Code, response.Body.String()) } var payload services.UploadResult if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil { t.Fatalf("json.Unmarshal returned error: %v", err) } return payload } func createAdminSession(t *testing.T, app *App) string { t.Helper() _, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123") if err != nil && !strings.Contains(err.Error(), "registration is closed") { t.Fatalf("CreateBootstrapUser returned error: %v", err) } _, token, err := app.authService.Login("admin@example.test", "password123") if err != nil { t.Fatalf("Login returned error: %v", err) } return token } func testPolicy(t *testing.T, app *App) services.UploadPolicySettings { t.Helper() policy, err := app.settingsService.UploadPolicy() if err != nil { t.Fatalf("UploadPolicy returned error: %v", err) } return policy }