Files
warpbox/lib/server/account_test.go

859 lines
29 KiB
Go

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)
}
}