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
+5 -5
View File
@@ -70,7 +70,7 @@ where
}
/// Extracted from the session cookie on web UI requests.
/// On missing/expired session, returns `AppError::Redirect("/web/login")`.
/// On missing/expired session, returns `AppError::Redirect("/login")`.
pub struct AuthenticatedWebUser {
pub slug: String,
pub role: String,
@@ -112,7 +112,7 @@ where
let token = jar
.get(SESSION_COOKIE)
.map(|c| c.value().to_owned())
.ok_or_else(|| AppError::Redirect("/web/login".into()))?;
.ok_or_else(|| AppError::Redirect("/login".into()))?;
// Read lock first to check validity. If expired, upgrade to write and remove.
let expired = {
@@ -121,7 +121,7 @@ where
Some(s) => s.expires_at <= Instant::now(),
None => {
warn!(token_prefix = %token_prefix(&token), "session lookup failed (unknown or already expired)");
return Err(AppError::Redirect("/web/login".into()));
return Err(AppError::Redirect("/login".into()));
}
}
};
@@ -132,13 +132,13 @@ where
if let Some(s) = removed {
warn!(slug = %s.slug, "session expired");
}
return Err(AppError::Redirect("/web/login".into()));
return Err(AppError::Redirect("/login".into()));
}
let r = store.read().await;
let session = r
.get(&token)
.ok_or_else(|| AppError::Redirect("/web/login".into()))?;
.ok_or_else(|| AppError::Redirect("/login".into()))?;
let data_dir = config.data_path.join(&session.slug);
// Look up the TOML user record for the policy fields the session