feat(users): implement comprehensive user listing and control
This commit is contained in:
@@ -243,3 +243,616 @@ func findResponseCookie(response *httptest.ResponseRecorder, name string) *http.
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user