L-10: E-16 answers not_found for another user's device (D-156)
A member naming the other user's device now gets the same 404 not_found body as an unknown id, so E-16 no longer reveals whose a device id is. The admin keeps revoking any device except the current one (AC-3.2, §8.4). contracts/openapi.yaml drops the 403 on E-16 and states the rule; web/src/api/schema.d.ts regenerated. The forbidden test becomes a not_found test, plus one asserting the body equals the unknown-id body and that nothing was revoked. Also bumps ctutils 0.4.3 -> 0.4.4 (lockfile only) for RUSTSEC-2026-0385, published 2026-10-23, which failed cargo audit on main. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
@@ -77,9 +77,9 @@ async fn e16_admin_revokes_another_device_which_then_gets_device_revoked() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn e16_forbidden_not_found_and_cannot_revoke_current_device() {
|
||||
async fn e16_not_found_and_cannot_revoke_current_device() {
|
||||
let app = TestApp::household().await;
|
||||
// A member may revoke only their own devices.
|
||||
// A member may revoke only their own devices; another user's device is not_found (D-156).
|
||||
let reply = app
|
||||
.call(
|
||||
Method::DELETE,
|
||||
@@ -88,7 +88,7 @@ async fn e16_forbidden_not_found_and_cannot_revoke_current_device() {
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_error(&reply, 403, "forbidden");
|
||||
assert_error(&reply, 404, "not_found");
|
||||
let reply = app
|
||||
.call(
|
||||
Method::DELETE,
|
||||
@@ -109,6 +109,42 @@ async fn e16_forbidden_not_found_and_cannot_revoke_current_device() {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn e16_member_naming_the_other_users_device_gets_the_unknown_id_answer() {
|
||||
// D-156 (security review L-10): the answer does not tell whether the id belongs to a device.
|
||||
let app = TestApp::household().await;
|
||||
let unknown = app
|
||||
.call(
|
||||
Method::DELETE,
|
||||
&format!("/devices/{UNKNOWN_ID}"),
|
||||
&member_token(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_error(&unknown, 404, "not_found");
|
||||
for id in [ADMIN_DEVICE_ID, ADMIN_OLD_DEVICE_ID] {
|
||||
let reply = app
|
||||
.call(
|
||||
Method::DELETE,
|
||||
&format!("/devices/{id}"),
|
||||
&member_token(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(reply.status, unknown.status);
|
||||
assert_eq!(reply.json, unknown.json);
|
||||
}
|
||||
// Nothing was revoked: the admin's current device still works.
|
||||
let me = app.call(Method::GET, "/me", &admin_token(), None).await;
|
||||
assert_eq!(me.status, 200);
|
||||
let revoked: Option<String> = sqlx::query_scalar("SELECT revoked_at FROM devices WHERE id = ?")
|
||||
.bind(ADMIN_DEVICE_ID)
|
||||
.fetch_one(&app.state.db)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(revoked, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn e16_member_revokes_own_other_device() {
|
||||
let app = TestApp::household().await;
|
||||
|
||||
Reference in New Issue
Block a user