Restrict backend selection to admins
An empty `backend` form field now correctly falls back to the default. A non-empty field submitted by a non-admin user is rejected with a 403 error, preventing potential tampering or issues arising from stale sessions after role changes.
This commit is contained in:
@@ -57,7 +57,8 @@ impl HasCsrfToken for AnalyzeForm {
|
||||
/// `analysis_input_v1.json`; otherwise writes `analysis_input_v{N+1}.json`
|
||||
/// where N is the current latest document version. The worker picks up the
|
||||
/// new input and produces `document_v{version}.md`. Available to all logged-in
|
||||
/// users (no admin check). Race protection via `create_new` on the input file.
|
||||
/// users; the optional `backend` form field is admin-only. Race protection
|
||||
/// via `create_new` on the input file.
|
||||
pub async fn handle_analyze_case(
|
||||
user: AuthenticatedWebUser,
|
||||
State(config): State<Arc<Config>>,
|
||||
@@ -72,6 +73,13 @@ pub async fn handle_analyze_case(
|
||||
));
|
||||
}
|
||||
|
||||
// Backend selection is admin-only. A non-empty field on a non-admin
|
||||
// session indicates tampering or a stale tab after a role downgrade,
|
||||
// and is rejected with 403 instead of silently falling back to default.
|
||||
if !form.backend.is_empty() && !user.is_admin() {
|
||||
return Err(AppError::Forbidden("Backend-Wahl nur für Admins".into()));
|
||||
}
|
||||
|
||||
// Resolve the requested backend. Empty string falls back to the default
|
||||
// backend; an unknown id is rejected as a 400 so the user notices the
|
||||
// misconfiguration instead of silently getting a different model.
|
||||
|
||||
@@ -438,11 +438,18 @@
|
||||
<form method="post" action="/web/cases/{{ case_id }}/analyze">
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input type="hidden" name="return_to" value="/web/cases/{{ case_id }}">
|
||||
<span class="analyze-prompt">Analysieren mit:</span>
|
||||
<button type="submit">Analysieren</button>
|
||||
</form>
|
||||
{% if is_admin %}
|
||||
<form class="admin-only" method="post" action="/web/cases/{{ case_id }}/analyze">
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input type="hidden" name="return_to" value="/web/cases/{{ case_id }}">
|
||||
{% for b in backends %}
|
||||
<button type="submit" name="backend" value="{{ b.id }}">{{ b.label }}</button>
|
||||
<button type="submit" name="backend" value="{{ b.id }}"
|
||||
title="Analysieren mit {{ b.label }}">{{ b.label }}</button>
|
||||
{% endfor %}
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else if llm_missing && !has_document %}
|
||||
<span class="llm-missing">Kein LLM-Backend verfügbar — IONOS_API_KEY setzen.</span>
|
||||
{% endif %} {% if is_admin %}
|
||||
@@ -474,7 +481,34 @@
|
||||
<form method="post" action="/web/cases/{{ case_id }}/analyze">
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input type="hidden" name="return_to" value="/web/cases/{{ case_id }}">
|
||||
<span class="analyze-prompt">Neu analysieren mit:</span>
|
||||
<button
|
||||
type="submit"
|
||||
class="copy-btn"
|
||||
title="Neu analysieren"
|
||||
aria-label="Neu analysieren"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
height="18"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="23 4 23 10 17 10"></polyline>
|
||||
<polyline points="1 20 1 14 7 14"></polyline>
|
||||
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"></path>
|
||||
<path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{% if is_admin %}
|
||||
<form class="admin-only" method="post" action="/web/cases/{{ case_id }}/analyze">
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input type="hidden" name="return_to" value="/web/cases/{{ case_id }}">
|
||||
{% for b in backends %}
|
||||
<button
|
||||
type="submit"
|
||||
@@ -484,6 +518,7 @@
|
||||
>{{ b.label }}</button>
|
||||
{% endfor %}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="doc-actions">
|
||||
@@ -591,6 +626,20 @@
|
||||
<form
|
||||
method="post"
|
||||
action="/web/cases/{{ case_id }}/analyze"
|
||||
>
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input
|
||||
type="hidden"
|
||||
name="return_to"
|
||||
value="/web/cases/{{ case_id }}"
|
||||
/>
|
||||
<button type="submit">Erneut versuchen</button>
|
||||
</form>
|
||||
{% if is_admin %}
|
||||
<form
|
||||
class="admin-only"
|
||||
method="post"
|
||||
action="/web/cases/{{ case_id }}/analyze"
|
||||
>
|
||||
{% call csrf::field(csrf_token) %}
|
||||
<input
|
||||
@@ -604,6 +653,7 @@
|
||||
</button>
|
||||
{% endfor %}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% when None %} {% if analyzing %}
|
||||
<div class="placeholder">Wird analysiert …</div>
|
||||
|
||||
Reference in New Issue
Block a user