feat: classic ui account deletion

This commit is contained in:
2026-10-26 02:51:52 +01:00
parent 0beedfbf0a
commit 6aebad1f0f
4 changed files with 98 additions and 8 deletions

View File

@@ -206,11 +206,7 @@ async fn classic_admin_delete_redirects_to_the_member_list() {
let uri = format!("/users/{}/delete", bob.user_id);
let (status, headers, _) = send(&router, classic_post(&uri, &alice.cookie, PASSWORD)).await;
assert_eq!(status, 303);
assert!(
location(&headers).contains("error="),
"non-admin: {headers:?}"
);
assert_eq!(status, 403, "non-admin: {headers:?}");
promote(&pool, &alice.user_id).await;
let (status, headers, _) = send(&router, classic_post(&uri, &alice.cookie, PASSWORD)).await;
@@ -221,3 +217,93 @@ async fn classic_admin_delete_redirects_to_the_member_list() {
let (status, _, _) = send(&router, get(&format!("/users/{}", bob.user_id), None)).await;
assert_eq!(status, 404);
}
#[tokio::test]
async fn classic_admin_delete_requires_the_csrf_token() {
let (router, pool, alice, bob) = two_users().await;
promote(&pool, &alice.user_id).await;
let req = Request::builder()
.method(Method::POST)
.uri(format!("/users/{}/delete", bob.user_id))
.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::COOKIE, &alice.cookie)
.body(Body::from(format!("password={PASSWORD}&_csrf=wrong")))
.unwrap();
let (status, _, _) = send(&router, req).await;
assert_eq!(status, 403);
let (status, _, _) = send(&router, get(&format!("/users/{}", bob.user_id), None)).await;
assert_eq!(status, 200, "target must survive a CSRF rejection");
}
#[tokio::test]
async fn a_non_admin_classic_delete_of_someone_else_is_forbidden() {
let (router, _pool, alice, bob) = two_users().await;
let uri = format!("/users/{}/delete", bob.user_id);
let (status, _, _) = send(&router, classic_post(&uri, &alice.cookie, PASSWORD)).await;
assert_eq!(status, 403);
let (status, _, _) = send(&router, get(&format!("/users/{}", bob.user_id), None)).await;
assert_eq!(status, 200);
}
#[tokio::test]
async fn classic_admin_self_delete_logs_out_unless_last_admin() {
let (router, pool, alice, bob) = two_users().await;
promote(&pool, &alice.user_id).await;
let uri = format!("/users/{}/delete", alice.user_id);
// Sole admin: refused, redirected back with an error, still logged in.
let (status, headers, _) = send(&router, classic_post(&uri, &alice.cookie, PASSWORD)).await;
assert_eq!(status, 303);
assert!(location(&headers).contains("error="), "{headers:?}");
assert!(support::cookie_value(&headers, "token").is_none());
let (status, _, _) = send(&router, get(&format!("/users/{}", alice.user_id), None)).await;
assert_eq!(status, 200);
// With a second admin the self-delete goes through and ends the session.
promote(&pool, &bob.user_id).await;
let (status, headers, _) = send(&router, classic_post(&uri, &alice.cookie, PASSWORD)).await;
assert_eq!(status, 303);
assert_eq!(location(&headers), "/");
assert_eq!(
support::cookie_value(&headers, "token").as_deref(),
Some("")
);
let (status, _, _) = send(&router, get(&format!("/users/{}", alice.user_id), None)).await;
assert_eq!(status, 404);
}
#[tokio::test]
async fn delete_forms_render_only_where_they_apply() {
let (router, pool, alice, bob) = two_users().await;
let own_delete = "action=\"/settings/account/delete\"";
let user_delete = |id: &str| format!("action=\"/users/{id}/delete\"");
let (_, _, body) = send(&router, get("/settings/profile", Some(&alice.cookie))).await;
assert!(body.contains(own_delete), "owner settings page");
// A non-admin viewing someone else, and anonymous viewers, see no form.
let bob_page = format!("/users/{}", bob.user_id);
let (_, _, body) = send(&router, get(&bob_page, Some(&alice.cookie))).await;
assert!(!body.contains(&user_delete(&bob.user_id)), "non-admin");
promote(&pool, &alice.user_id).await;
let (_, _, body) = send(&router, get(&bob_page, Some(&alice.cookie))).await;
assert!(body.contains(&user_delete(&bob.user_id)), "admin on other");
let (_, _, body) = send(
&router,
get(&format!("/users/{}", alice.user_id), Some(&alice.cookie)),
)
.await;
assert!(
!body.contains(&user_delete(&alice.user_id)),
"admin on self"
);
let (_, _, body) = send(
&router,
get(&format!("{bob_page}?embed=true"), Some(&alice.cookie)),
)
.await;
assert!(!body.contains("/delete"), "embed page");
}