fix(2.9.2): Fehlermeldung lesbar machen, Modellliste ins Formularraster

Zwei Befunde aus der Abnahme auf der Testinstanz.

1. Die Ablehnung eines Softwarekeys erschien als "[object Object]".
   Die API verpackt Fehler als { error: { code, message } }; das
   JavaScript las "data.message || data.error" und griff damit auf das
   OBJEKT zu - ausgerechnet an der Stelle, an der die Begruendung stehen
   sollte, warum ein Schluessel abgelehnt wurde. Eine Regel, die ihren
   Grund nicht nennt, ist in der Bedienung dasselbe wie ein Defekt.

   Derselbe Fehler steckte in login.php. Dort waere er schwerer wiegend:
   An der zweiten Stufe ist diese Meldung der einzige Hinweis, warum es
   nicht weitergeht. Beide lesen jetzt beide Formen - die verschachtelte
   und die flache, die weiterhin vorkommt.

2. Die Textbox fuer die Modellliste stand neben statt unter ihrer
   Beschriftung und behielt ihre Eigengroesse. Der umschliessende
   form-group fehlte; genau der setzt im Stylesheet display:flex mit
   column und laesst das Feld die Breite fuellen. Alle anderen
   Textboxen der Oberflaeche haben ihn.

Zusaetzlich docs/HARDWAREKEY.md: Konzept, Beispielcode und Prueflíste
zur gleichwertigen Uebernahme in ein anderes Projekt. Der Schwerpunkt
liegt auf den drei Dingen, die wie eine Pruefung aussehen und keine
sind - cross-platform, response.authenticatorAttachment und ein blosses
x5c -, samt dem Gegentest, ohne den nichts belegt ist.

Kein Eingriff in src/, sql/ oder public/api/ - der Kern ist unveraendert
gegenueber dem Lauf mit 184 von 184 gruenen Testfaellen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Deploymentcenter Bot
2026-09-21 11:37:49 +02:00
co-authored by Claude Opus 5
parent 073d52ab7d
commit 0c20698fd5
3 changed files with 518 additions and 8 deletions
+25 -7
View File
@@ -6930,12 +6930,12 @@ Antwort:
</div>
<?php endif; ?>
<label class="form-label" style="margin-top:1rem;">
Zugelassene Modelle (optional)
</label>
<textarea name="policy_aaguids" class="form-input" rows="3"
style="font-family:monospace; font-size:0.8rem;"
placeholder="leer = jedes Gerät des Herstellers&#10;d7781e5d-e353-46aa-afe2-3ca49f13332a"><?= e($policyAllowlist) ?></textarea>
<div class="form-group" style="margin-top:1rem;">
<label class="form-label">Zugelassene Modelle (optional)</label>
<textarea name="policy_aaguids" class="form-input" rows="3"
style="font-family:monospace; font-size:0.8rem;"
placeholder="leer = jedes Gerät des Herstellers&#10;d7781e5d-e353-46aa-afe2-3ca49f13332a"><?= e($policyAllowlist) ?></textarea>
</div>
<p style="color:var(--text-muted); font-size:0.78rem; margin:0.4rem 0 1rem;">
Eine Gerätekennung je Zeile. <strong>Leer lassen ist der Normalfall:</strong>
Die Wurzelprüfung beantwortet bereits „stammt von diesem Hersteller“. Die Liste
@@ -9210,6 +9210,24 @@ document.addEventListener('DOMContentLoaded', () => {
: (tone === 'ok' ? 'var(--success)' : 'var(--text-muted)');
};
/**
* Holt den lesbaren Text aus einer Fehlerantwort.
*
* Die API verpackt Fehler als { error: { code, message } }. Ein
* "data.message || data.error" griff deshalb auf das OBJEKT zu, und der
* Browser machte daraus "[object Object]" - ausgerechnet an der Stelle,
* an der die Begruendung stehen sollte, warum ein Schluessel abgelehnt
* wurde. Beide Formen zu lesen ist kein Zierrat: Die aeltere, flache
* Form kommt weiterhin vor.
*/
const fehlertext = data => {
const huelle = data && data.error;
if (huelle && typeof huelle === 'object' && huelle.message) { return huelle.message; }
if (data && typeof data.message === 'string') { return data.message; }
if (typeof huelle === 'string') { return huelle; }
return 'Unerwartete Antwort des Servers.';
};
const post = async (action, payload) => {
const response = await fetch('/api/auth/v1/?action=' + action, {
method: 'POST',
@@ -9219,7 +9237,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(data.message || data.error || 'Unerwartete Antwort des Servers.');
throw new Error(fehlertext(data));
}
return data;
};
+17 -1
View File
@@ -513,6 +513,22 @@ $pendingUsername = Auth::pendingUsername();
status.classList.toggle('is-error', !!isError);
};
/**
* Der lesbare Text aus einer Fehlerantwort.
*
* Die API verpackt Fehler als { error: { code, message } }. Wer nur
* data.error liest, zeigt "[object Object]" an - hier waere das
* besonders misslich, denn an dieser Stelle steht der einzige Hinweis,
* warum die Anmeldung nicht weitergeht.
*/
const fehlertext = data => {
const huelle = data && data.error;
if (huelle && typeof huelle === 'object' && huelle.message) { return huelle.message; }
if (data && typeof data.message === 'string') { return data.message; }
if (typeof huelle === 'string') { return huelle; }
return 'Unerwartete Antwort des Servers.';
};
if (!window.PublicKeyCredential) {
setStatus('Dieser Browser kennt keine Hardwarekeys. Bitte den Notfalltoken benutzen.', true);
button.disabled = true;
@@ -528,7 +544,7 @@ $pendingUsername = Auth::pendingUsername();
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(data.message || data.error || 'Unerwartete Antwort des Servers.');
throw new Error(fehlertext(data));
}
return data;
};