package handlers import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "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 must not authenticate as the user. 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.StatusCreated { t.Fatalf("anonymous fallback upload status = %d, body = %s", badResponse.Code, badResponse.Body.String()) } var badPayload services.UploadResult if err := json.Unmarshal(badResponse.Body.Bytes(), &badPayload); err != nil { t.Fatalf("json.Unmarshal returned error: %v", err) } badBox, err := app.uploadService.GetBox(badPayload.BoxID) if err != nil { t.Fatalf("GetBox returned error: %v", err) } if badBox.OwnerID != "" { t.Fatalf("invalid token OwnerID = %q, want empty", badBox.OwnerID) } } 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 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 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) } } 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 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 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 }