refactor: drop /web/ URL prefix from browser routes

The /web/ prefix predated the /api/ split; today it just clutters every URL
without disambiguating anything. All 16 browser routes move to the apex
(/cases, /login, /magic, /events, /audio/...). The 6 /api/* routes are
unchanged. A new /->>/cases redirect closes the apex 404.

The open-redirect guard in magic.rs and case_actions.rs flips from a
positive whitelist (starts_with("/web/")) to a deny-list: same-origin path,
not protocol-relative, not under /api/, no \. The /api/ exclusion is now
load-bearing and covered by tests.

Pre-production: no transition redirects.
This commit is contained in:
2026-05-04 18:36:10 +02:00
parent 3d67cbc1c8
commit 2c6062a53e
43 changed files with 313 additions and 266 deletions
+4 -4
View File
@@ -118,7 +118,7 @@ async fn login_with_uppercase_slug_returns_login_failed_page() {
}
/// A malformed magic-link token must not 400 (would leak shape) — it
/// should redirect to /web/login, identical to the unknown-token path.
/// should redirect to /login, identical to the unknown-token path.
#[tokio::test]
async fn magic_consume_with_malformed_token_redirects_to_login() {
let cfg = TestConfig::new()
@@ -131,7 +131,7 @@ async fn magic_consume_with_malformed_token_redirects_to_login() {
let resp = app
.oneshot(
Request::builder()
.uri("/web/magic?token=abc")
.uri("/magic?token=abc")
.body(Body::empty())
.unwrap(),
)
@@ -144,7 +144,7 @@ async fn magic_consume_with_malformed_token_redirects_to_login() {
.expect("redirect must set Location")
.to_str()
.unwrap();
assert_eq!(loc, "/web/login");
assert_eq!(loc, "/login");
}
/// And the same for tokens that pass the length check but contain
@@ -158,7 +158,7 @@ async fn magic_consume_with_non_urlsafe_token_redirects_to_login() {
let app = doctate_server::create_router(cfg);
let bad = "%2E%2E%2Fetc%2Fpasswd%2E%2E%2Fetc"; // url-encoded ./../etc/passwd…
let uri = format!("/web/magic?token={bad}");
let uri = format!("/magic?token={bad}");
let resp = app
.oneshot(Request::builder().uri(uri).body(Body::empty()).unwrap())
.await