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:
@@ -330,13 +330,13 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div><a class="back" href="/web/cases">← Fälle</a></div>
|
||||
<div><a class="back" href="/cases">← Fälle</a></div>
|
||||
<div class="header-right">
|
||||
{% if is_admin %}<label class="admin-toggle"
|
||||
><input type="checkbox" id="admin-view-toggle" /> Admin
|
||||
view</label
|
||||
>{% endif %}
|
||||
<form method="post" action="/web/logout">
|
||||
<form method="post" action="/logout">
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<button type="submit">Logout</button>
|
||||
</form>
|
||||
@@ -362,7 +362,7 @@
|
||||
<form
|
||||
class="delete-form"
|
||||
method="post"
|
||||
action="/web/cases/{{ case_id }}/reopen"
|
||||
action="/cases/{{ case_id }}/reopen"
|
||||
>
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<button
|
||||
@@ -393,7 +393,7 @@
|
||||
<form
|
||||
class="delete-form"
|
||||
method="post"
|
||||
action="/web/cases/{{ case_id }}/close"
|
||||
action="/cases/{{ case_id }}/close"
|
||||
>
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<button
|
||||
@@ -435,15 +435,15 @@
|
||||
|
||||
<div class="actions">
|
||||
{% if can_analyze && !has_document %}
|
||||
<form method="post" action="/web/cases/{{ case_id }}/analyze">
|
||||
<form method="post" action="/cases/{{ case_id }}/analyze">
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input type="hidden" name="return_to" value="/web/cases/{{ case_id }}">
|
||||
<input type="hidden" name="return_to" value="/cases/{{ case_id }}">
|
||||
<button type="submit">Analysieren</button>
|
||||
</form>
|
||||
{% if is_admin %}
|
||||
<form class="admin-only" method="post" action="/web/cases/{{ case_id }}/analyze">
|
||||
<form class="admin-only" method="post" action="/cases/{{ case_id }}/analyze">
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input type="hidden" name="return_to" value="/web/cases/{{ case_id }}">
|
||||
<input type="hidden" name="return_to" value="/cases/{{ case_id }}">
|
||||
{% for b in backends %}
|
||||
<button type="submit" name="backend" value="{{ b.id }}"
|
||||
title="Analysieren mit {{ b.label }}">{{ b.label }}</button>
|
||||
@@ -456,10 +456,10 @@
|
||||
<form
|
||||
class="admin-only"
|
||||
method="post"
|
||||
action="/web/cases/{{ case_id }}/reset"
|
||||
action="/cases/{{ case_id }}/reset"
|
||||
>
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input type="hidden" name="return_to" value="/web/cases/{{ case_id }}">
|
||||
<input type="hidden" name="return_to" value="/cases/{{ case_id }}">
|
||||
<button type="submit">Reset</button>
|
||||
</form>
|
||||
<button
|
||||
@@ -478,9 +478,9 @@
|
||||
<div id="doc-content" class="doc-content">
|
||||
{% if can_analyze %}
|
||||
<div class="doc-actions-top">
|
||||
<form method="post" action="/web/cases/{{ case_id }}/analyze">
|
||||
<form method="post" action="/cases/{{ case_id }}/analyze">
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input type="hidden" name="return_to" value="/web/cases/{{ case_id }}">
|
||||
<input type="hidden" name="return_to" value="/cases/{{ case_id }}">
|
||||
<button
|
||||
type="submit"
|
||||
class="copy-btn"
|
||||
@@ -506,9 +506,9 @@
|
||||
</button>
|
||||
</form>
|
||||
{% if is_admin %}
|
||||
<form class="admin-only" method="post" action="/web/cases/{{ case_id }}/analyze">
|
||||
<form class="admin-only" method="post" action="/cases/{{ case_id }}/analyze">
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input type="hidden" name="return_to" value="/web/cases/{{ case_id }}">
|
||||
<input type="hidden" name="return_to" value="/cases/{{ case_id }}">
|
||||
{% for b in backends %}
|
||||
<button
|
||||
type="submit"
|
||||
@@ -625,13 +625,13 @@
|
||||
</details>
|
||||
<form
|
||||
method="post"
|
||||
action="/web/cases/{{ case_id }}/analyze"
|
||||
action="/cases/{{ case_id }}/analyze"
|
||||
>
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input
|
||||
type="hidden"
|
||||
name="return_to"
|
||||
value="/web/cases/{{ case_id }}"
|
||||
value="/cases/{{ case_id }}"
|
||||
/>
|
||||
<button type="submit">Erneut versuchen</button>
|
||||
</form>
|
||||
@@ -639,13 +639,13 @@
|
||||
<form
|
||||
class="admin-only"
|
||||
method="post"
|
||||
action="/web/cases/{{ case_id }}/analyze"
|
||||
action="/cases/{{ case_id }}/analyze"
|
||||
>
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input
|
||||
type="hidden"
|
||||
name="return_to"
|
||||
value="/web/cases/{{ case_id }}"
|
||||
value="/cases/{{ case_id }}"
|
||||
/>
|
||||
{% for b in backends %}
|
||||
<button type="submit" name="backend" value="{{ b.id }}">
|
||||
@@ -669,7 +669,7 @@
|
||||
<div>
|
||||
<a
|
||||
class="recordings-link"
|
||||
href="/web/cases/{{ case_id }}/recordings"
|
||||
href="/cases/{{ case_id }}/recordings"
|
||||
>→ Aufnahmen anzeigen ({{ recordings_count }})</a
|
||||
>
|
||||
</div>
|
||||
@@ -730,7 +730,7 @@
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => location.reload(), 300);
|
||||
};
|
||||
const es = new EventSource("/web/events");
|
||||
const es = new EventSource("/events");
|
||||
// Release the socket-pool slot on navigation. Without this, Chrome
|
||||
// keeps the SSE connection "draining" after unload; six rapid nav
|
||||
// cycles exhaust the 6-per-origin pool and stall further requests.
|
||||
|
||||
Reference in New Issue
Block a user