package server import ( "html/template" "net/http" "net/http/httptest" "net/url" "path/filepath" "strings" "testing" "time" "github.com/gin-gonic/gin" "warpbox/lib/boxstore" "warpbox/lib/config" "warpbox/lib/metastore" ) func TestAccountLoginSuccess(t *testing.T) { app, _ := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) response := postAccountLogin(router, "admin", "secret") if response.Code != http.StatusSeeOther { t.Fatalf("expected login redirect, got %d", response.Code) } if location := response.Header().Get("Location"); location != "/account" { t.Fatalf("expected redirect to /account, got %q", location) } if cookie := findResponseCookie(response, accountSessionCookie); cookie == nil || cookie.Value == "" { t.Fatal("expected account session cookie") } } func TestAccountLoginFailure(t *testing.T) { app, _ := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) response := postAccountLogin(router, "admin", "wrong") if response.Code != http.StatusOK { t.Fatalf("expected failed login to render form, got %d", response.Code) } if cookie := findResponseCookie(response, accountSessionCookie); cookie != nil { t.Fatal("did not expect account session cookie") } if !strings.Contains(response.Body.String(), "not accepted") { t.Fatal("expected login failure message") } } func TestAccountDisabledUserLoginFailure(t *testing.T) { app, user := setupAccountTestApp(t) user.Disabled = true if err := app.store.UpdateUser(user); err != nil { t.Fatalf("UpdateUser returned error: %v", err) } router := setupAccountTestRouter(t, app) response := postAccountLogin(router, "admin", "secret") if response.Code != http.StatusOK { t.Fatalf("expected disabled login to render form, got %d", response.Code) } if cookie := findResponseCookie(response, accountSessionCookie); cookie != nil { t.Fatal("did not expect account session cookie") } if !strings.Contains(response.Body.String(), "not accepted") { t.Fatal("expected login failure message") } } func TestAccountLogoutRequiresCSRF(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } request := httptest.NewRequest(http.MethodPost, "/account/logout", nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusForbidden { t.Fatalf("expected missing CSRF token to be forbidden, got %d", response.Code) } } func TestAccountDashboardRequiresAuth(t *testing.T) { app, _ := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) request := httptest.NewRequest(http.MethodGet, "/account", nil) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected dashboard redirect, got %d", response.Code) } if location := response.Header().Get("Location"); location != "/account/login" { t.Fatalf("expected redirect to /account/login, got %q", location) } } func TestAccountDashboardLoadsForBootstrapAdmin(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } request := httptest.NewRequest(http.MethodGet, "/account", nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("expected dashboard to load, got %d", response.Code) } body := response.Body.String() for _, text := range []string{"Dashboard", "Recent Boxes", "Users"} { if !strings.Contains(body, text) { t.Fatalf("expected dashboard body to contain %q", text) } } } func TestAccountDashboardHidesAdminOnlyLinksForRegularUser(t *testing.T) { app, _ := setupAccountTestApp(t) user, err := app.store.CreateUserWithPassword("maya", "maya@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } request := httptest.NewRequest(http.MethodGet, "/account", nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("expected dashboard to load, got %d", response.Code) } body := response.Body.String() for _, text := range []string{">Users<", ">Settings<"} { if strings.Contains(body, text) { t.Fatalf("expected dashboard body to hide %q", text) } } } func TestAdminEntryRedirectsToAccount(t *testing.T) { app, _ := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) cases := map[string]string{ "/admin/login": "/account/login", "/admin": "/account", } for path, wantLocation := range cases { request := httptest.NewRequest(http.MethodGet, path, nil) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected %s redirect, got %d", path, response.Code) } if location := response.Header().Get("Location"); location != wantLocation { t.Fatalf("expected %s to redirect to %s, got %q", path, wantLocation, location) } } } func setupAccountTestApp(t *testing.T) (*App, metastore.User) { t.Helper() gin.SetMode(gin.TestMode) restoreUploadRoot := boxstore.UploadRoot() t.Cleanup(func() { boxstore.SetUploadRoot(restoreUploadRoot) }) boxstore.SetUploadRoot(t.TempDir()) store, err := metastore.Open(t.TempDir()) if err != nil { t.Fatalf("Open returned error: %v", err) } t.Cleanup(func() { _ = store.Close() }) cfg, err := config.Load() if err != nil { t.Fatalf("Load returned error: %v", err) } cfg.AdminUsername = "admin" cfg.AdminPassword = "secret" cfg.AdminEmail = "admin@example.test" cfg.AdminEnabled = config.AdminEnabledAuto cfg.SessionTTLSeconds = 3600 bootstrap, err := metastore.BootstrapAdmin(cfg, store) if err != nil { t.Fatalf("BootstrapAdmin returned error: %v", err) } if bootstrap.AdminUser == nil { t.Fatal("expected bootstrap admin user") } app := &App{ config: cfg, store: store, adminLoginEnabled: bootstrap.AdminLoginEnabled, } return app, *bootstrap.AdminUser } func setupAccountTestRouter(t *testing.T, app *App) *gin.Engine { t.Helper() router := gin.New() templates, err := template.ParseGlob(filepath.Join("..", "..", "templates", "*.html")) if err != nil { t.Fatalf("ParseGlob returned error: %v", err) } router.SetHTMLTemplate(templates) app.registerAccountRoutes(router) app.registerAdminRoutes(router) return router } func postAccountLogin(router *gin.Engine, username string, password string) *httptest.ResponseRecorder { form := url.Values{} form.Set("username", username) form.Set("password", password) request := httptest.NewRequest(http.MethodPost, "/account/login", strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") response := httptest.NewRecorder() router.ServeHTTP(response, request) return response } func findResponseCookie(response *httptest.ResponseRecorder, name string) *http.Cookie { for _, cookie := range response.Result().Cookies() { if cookie.Name == name { return cookie } } return nil } func TestUsersPagePermissionDeniedForNoPerms(t *testing.T) { app, _ := setupAccountTestApp(t) user, err := app.store.CreateUserWithPassword("viewer", "viewer@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } request := httptest.NewRequest(http.MethodGet, "/account/users", nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusForbidden { t.Fatalf("expected permission denied, got %d", response.Code) } if !strings.Contains(response.Body.String(), "Permission denied") { t.Fatal("expected permission denied message") } } func TestUsersPageLoadsForAdmin(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } request := httptest.NewRequest(http.MethodGet, "/account/users", nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("expected users page to load, got %d", response.Code) } body := response.Body.String() for _, text := range []string{"WarpBox Users", "Create or Invite", "Total users"} { if !strings.Contains(body, text) { t.Fatalf("expected users page body to contain %q", text) } } } func TestUsersPageListFilters(t *testing.T) { app, user := setupAccountTestApp(t) _, err := app.store.CreateUserWithPassword("beta", "beta@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } request := httptest.NewRequest(http.MethodGet, "/account/users?q=beta", nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("expected users page to load, got %d", response.Code) } body := response.Body.String() if !strings.Contains(body, "beta") { t.Fatal("expected filtered list to contain beta") } if !strings.Contains(body, "1 matching user(s)") { t.Fatalf("expected 1 matching user for beta filter, got body: %s", body) } } func TestUserCreation(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("action", "create") form.Set("mode", "create") form.Set("username", "newuser") form.Set("email", "new@example.test") form.Set("password", "password123") request := httptest.NewRequest(http.MethodPost, "/account/users", strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect after create, got %d", response.Code) } created, ok, err := app.store.GetUserByUsername("newuser") if err != nil || !ok { t.Fatal("expected newuser to exist") } if created.Disabled { t.Fatal("expected newuser to be active") } } func TestUserInviteCreation(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("action", "create") form.Set("mode", "invite") form.Set("username", "invited") form.Set("email", "invited@example.test") request := httptest.NewRequest(http.MethodPost, "/account/users", strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect after invite, got %d", response.Code) } created, ok, err := app.store.GetUserByUsername("invited") if err != nil || !ok { t.Fatal("expected invited user to exist") } if !created.Disabled { t.Fatal("expected invited user to be disabled") } if !strings.HasPrefix(created.PasswordHash, "invite/") { t.Fatal("expected invited user to have invite prefix") } } func TestBulkDisableRejectsSelf(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("selected_ids", user.ID) request := httptest.NewRequest(http.MethodPost, "/account/users/bulk/disable", strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } location := response.Header().Get("Location") if !strings.Contains(location, "cannot disable yourself") && !strings.Contains(location, "error=") { t.Fatalf("expected self-disable rejection, got location %q", location) } } func TestBulkDisableProtectsFinalAdmin(t *testing.T) { app, user := setupAccountTestApp(t) adminTag, ok, err := app.store.GetTagByName(metastore.AdminTagName) if err != nil || !ok || adminTag.ID == "" { t.Fatal("expected admin tag") } second, err := app.store.CreateUserWithPassword("admin2", "admin2@example.test", "secret", []string{adminTag.ID}) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } // Admin tries to disable the other admin (not self): should work since self remains. router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("selected_ids", second.ID) request := httptest.NewRequest(http.MethodPost, "/account/users/bulk/disable", strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected success redirect, got %d", response.Code) } location := response.Header().Get("Location") if !strings.Contains(location, "user(s) disabled") { t.Fatalf("expected success message, got %q", location) } // Verify admin2 is disabled, admin1 still active disabledUser, ok, _ := app.store.GetUserByUsername("admin2") if !ok || !disabledUser.Disabled { t.Fatal("expected admin2 to be disabled") } adminUser, ok, _ := app.store.GetUserByUsername("admin") if !ok || adminUser.Disabled { t.Fatal("expected admin to remain active") } // Now try to disable the only remaining admin (self): should be rejected form2 := url.Values{} form2.Set("csrf_token", session.CSRFToken) form2.Set("selected_ids", user.ID) req2 := httptest.NewRequest(http.MethodPost, "/account/users/bulk/disable", strings.NewReader(form2.Encode())) req2.Header.Set("Content-Type", "application/x-www-form-urlencoded") req2.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) resp2 := httptest.NewRecorder() router.ServeHTTP(resp2, req2) if resp2.Code != http.StatusSeeOther { t.Fatalf("expected redirect for self-disable rejection, got %d", resp2.Code) } loc2 := resp2.Header().Get("Location") if !strings.Contains(loc2, "cannot disable yourself") { t.Fatalf("expected self-disable rejection, got %q", loc2) } } func TestUserEditPagePermissionDenied(t *testing.T) { app, _ := setupAccountTestApp(t) regular, err := app.store.CreateUserWithPassword("viewer2", "viewer2@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(regular.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } request := httptest.NewRequest(http.MethodGet, "/account/users/"+regular.ID, nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusForbidden { t.Fatalf("expected 403, got %d", response.Code) } } func TestUserEditPageLoadsForAdmin(t *testing.T) { app, admin := setupAccountTestApp(t) target, err := app.store.CreateUserWithPassword("edittarget", "edittarget@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(admin.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } request := httptest.NewRequest(http.MethodGet, "/account/users/"+target.ID, nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("expected 200, got %d", response.Code) } body := response.Body.String() for _, text := range []string{"edittarget", "Access rights", "Limits", "Setting overrides", "Resolved policy"} { if !strings.Contains(body, text) { t.Fatalf("expected body to contain %q", text) } } } func TestUserEditProfileUpdate(t *testing.T) { app, admin := setupAccountTestApp(t) target, err := app.store.CreateUserWithPassword("origname", "orig@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(admin.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("username", "newname") form.Set("email", "new@example.test") form.Set("admin_note", "test note") form.Set("state", "active") request := httptest.NewRequest(http.MethodPost, "/account/users/"+target.ID, strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } updated, ok, _ := app.store.GetUser(target.ID) if !ok { t.Fatal("user not found after update") } if updated.Username != "newname" { t.Fatalf("expected username newname, got %q", updated.Username) } if updated.AdminNote != "test note" { t.Fatalf("expected admin note, got %q", updated.AdminNote) } } func TestUserEditAccessRightsUpdate(t *testing.T) { app, admin := setupAccountTestApp(t) target, err := app.store.CreateUserWithPassword("perm_target", "perm@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(admin.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("username", target.Username) form.Set("email", target.Email) form.Set("upload_allowed", "1") form.Set("zip_download_allowed", "1") request := httptest.NewRequest(http.MethodPost, "/account/users/"+target.ID, strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } updated, ok, _ := app.store.GetUser(target.ID) if !ok || updated.PermOverrides == nil { t.Fatal("expected perm overrides to be set") } if updated.PermOverrides.UploadAllowed == nil || !*updated.PermOverrides.UploadAllowed { t.Fatal("expected upload_allowed=true") } if updated.PermOverrides.ZipDownloadAllowed == nil || !*updated.PermOverrides.ZipDownloadAllowed { t.Fatal("expected zip_download_allowed=true") } } func TestUserEditLimitsUpdate(t *testing.T) { app, admin := setupAccountTestApp(t) target, err := app.store.CreateUserWithPassword("limits_target", "limits@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(admin.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("username", target.Username) form.Set("email", target.Email) form.Set("max_file_size_bytes", "1073741824") form.Set("max_expiry_seconds", "86400") request := httptest.NewRequest(http.MethodPost, "/account/users/"+target.ID, strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } updated, ok, _ := app.store.GetUser(target.ID) if !ok { t.Fatal("user not found") } if updated.MaxFileSizeBytes == nil || *updated.MaxFileSizeBytes != 1073741824 { t.Fatalf("expected max_file_size_bytes=1073741824, got %v", updated.MaxFileSizeBytes) } if updated.MaxExpirySeconds == nil || *updated.MaxExpirySeconds != 86400 { t.Fatalf("expected max_expiry_seconds=86400, got %v", updated.MaxExpirySeconds) } } func TestUserEditInvalidLimitRejected(t *testing.T) { app, admin := setupAccountTestApp(t) target, err := app.store.CreateUserWithPassword("badlimit", "badlimit@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(admin.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("username", target.Username) form.Set("email", target.Email) form.Set("max_file_size_bytes", "notanumber") request := httptest.NewRequest(http.MethodPost, "/account/users/"+target.ID, strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } location := response.Header().Get("Location") if !strings.Contains(location, "error=") { t.Fatalf("expected error redirect, got %q", location) } } func TestUserEditSelfDisableRejected(t *testing.T) { app, admin := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(admin.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("username", admin.Username) form.Set("email", admin.Email) form.Set("state", "disabled") request := httptest.NewRequest(http.MethodPost, "/account/users/"+admin.ID, strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } location := response.Header().Get("Location") if !strings.Contains(location, "error=") { t.Fatalf("expected error redirect, got %q", location) } unchanged, _, _ := app.store.GetUser(admin.ID) if unchanged.Disabled { t.Fatal("admin should not have been disabled") } } func TestUserEditLastAdminProtected(t *testing.T) { app, admin := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(admin.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } // try to remove admin tag from the only admin via is_admin=0 (unchecked) form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("username", admin.Username) form.Set("email", admin.Email) // is_admin NOT set → wantsAdmin=false → should be blocked request := httptest.NewRequest(http.MethodPost, "/account/users/"+admin.ID, strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } location := response.Header().Get("Location") if !strings.Contains(location, "error=") { t.Fatalf("expected error for last-admin removal, got %q", location) } } func TestUserEditPasswordReset(t *testing.T) { app, admin := setupAccountTestApp(t) target, err := app.store.CreateUserWithPassword("resetme", "resetme@example.test", "oldpass", nil) if err != nil { t.Fatalf("CreateUserWithPassword: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(admin.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) request := httptest.NewRequest(http.MethodPost, "/account/users/"+target.ID+"/password/reset", strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } location := response.Header().Get("Location") if !strings.Contains(location, "success=") { t.Fatalf("expected success redirect, got %q", location) } updated, _, _ := app.store.GetUser(target.ID) if metastore.VerifyPassword(updated.PasswordHash, "oldpass") { t.Fatal("old password should no longer work after reset") } } func TestUserEditRevokeSessions(t *testing.T) { app, admin := setupAccountTestApp(t) target, err := app.store.CreateUserWithPassword("revokeme", "revokeme@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword: %v", err) } targetSession, err := app.store.CreateSession(target.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } router := setupAccountTestRouter(t, app) adminSession, err := app.store.CreateSession(admin.ID, time.Hour) if err != nil { t.Fatalf("CreateSession: %v", err) } form := url.Values{} form.Set("csrf_token", adminSession.CSRFToken) request := httptest.NewRequest(http.MethodPost, "/account/users/"+target.ID+"/sessions/revoke", strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: adminSession.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } _, stillValid, _ := app.store.GetSession(targetSession.Token) if stillValid { t.Fatal("target session should have been revoked") } } func TestBulkRevokeSessions(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } other, err := app.store.CreateUserWithPassword("other", "other@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } if _, err := app.store.CreateSession(other.ID, time.Hour); err != nil { t.Fatalf("CreateSession returned error: %v", err) } form := url.Values{} form.Set("csrf_token", session.CSRFToken) form.Set("selected_ids", other.ID) request := httptest.NewRequest(http.MethodPost, "/account/users/bulk/revoke-sessions", strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected redirect, got %d", response.Code) } location := response.Header().Get("Location") if !strings.Contains(location, "Sessions revoked") { t.Fatalf("expected success message, got %q", location) } }