fix: Portal-Passwort-Card im Vertragsdetail wieder sichtbar
Folge-Symptom zum PW-Save-Fix: das Speichern hat funktioniert, aber die "Zugangsdaten"-Card im Read-Only-View hat das Passwort- Feld nicht angezeigt. Ursache: das Frontend nutzte `c.portalPasswordEncrypted` als Truthy-Check, aber sanitizeContract strippt das Feld bewusst aus jeder Response (Pentest Runde 15 - kein verschlüsselter Blob in /contracts/:id). Fix: getContractById hängt jetzt ein virtuelles `hasPortalPassword`- Bool-Flag an die Response. Frontend nutzt das statt portalPasswordEncrypted. Der verschlüsselte Wert bleibt server-seitig; der Klartext kommt weiterhin über GET /contracts/:id/password mit Audit-Log. Live-verifiziert: PUT setzt PW, GET liefert hasPortalPassword:true + portalPasswordEncrypted ist NICHT in der Response. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -177,6 +177,13 @@ export async function getContractById(id: number, decryptPassword = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Virtuelles Bool-Flag, damit das Frontend "PW gesetzt?" weiß, ohne dass
|
||||||
|
// der verschlüsselte Blob in die Response leakt (sanitizeContract strippt
|
||||||
|
// portalPasswordEncrypted bewusst). Pentest Runde 15 – sensitive Feld
|
||||||
|
// raus aus /contracts/:id; UI nutzt jetzt `hasPortalPassword`.
|
||||||
|
(contract as Record<string, unknown>).hasPortalPassword =
|
||||||
|
!!contract.portalPasswordEncrypted;
|
||||||
|
|
||||||
return contract;
|
return contract;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2399,7 +2399,7 @@ export default function ContractDetail() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Portal Credentials */}
|
{/* Portal Credentials */}
|
||||||
{(c.portalUsername || c.stressfreiEmail || c.portalPasswordEncrypted) && (
|
{(c.portalUsername || c.stressfreiEmail || c.hasPortalPassword) && (
|
||||||
<Card className="mb-6" title="Zugangsdaten">
|
<Card className="mb-6" title="Zugangsdaten">
|
||||||
<dl className="grid grid-cols-2 gap-4">
|
<dl className="grid grid-cols-2 gap-4">
|
||||||
{(c.portalUsername || c.stressfreiEmail) && (
|
{(c.portalUsername || c.stressfreiEmail) && (
|
||||||
@@ -2416,7 +2416,7 @@ export default function ContractDetail() {
|
|||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{c.portalPasswordEncrypted && (
|
{c.hasPortalPassword && (
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-sm text-gray-500">Passwort</dt>
|
<dt className="text-sm text-gray-500">Passwort</dt>
|
||||||
<dd className="flex items-center gap-2">
|
<dd className="flex items-center gap-2">
|
||||||
@@ -2435,7 +2435,7 @@ export default function ContractDetail() {
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{/* Auto-Login Button */}
|
{/* Auto-Login Button */}
|
||||||
{c.provider?.portalUrl && (c.portalUsername || c.stressfreiEmail) && c.portalPasswordEncrypted && (
|
{c.provider?.portalUrl && (c.portalUsername || c.stressfreiEmail) && c.hasPortalPassword && (
|
||||||
<div className="mt-4 pt-4 border-t">
|
<div className="mt-4 pt-4 border-t">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAutoLogin}
|
onClick={handleAutoLogin}
|
||||||
|
|||||||
@@ -429,7 +429,10 @@ export interface Contract {
|
|||||||
contractDuration?: ContractDuration;
|
contractDuration?: ContractDuration;
|
||||||
commission?: number;
|
commission?: number;
|
||||||
portalUsername?: string;
|
portalUsername?: string;
|
||||||
portalPasswordEncrypted?: string;
|
/** Backend liefert nur ein Bool-Flag – der verschlüsselte Wert selbst
|
||||||
|
* bleibt server-seitig (sanitizeContract strippt `portalPasswordEncrypted`).
|
||||||
|
* Entschlüsselter Wert kommt über `GET /contracts/:id/password`. */
|
||||||
|
hasPortalPassword?: boolean;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
// Kündigungsdokumente
|
// Kündigungsdokumente
|
||||||
cancellationLetterPath?: string;
|
cancellationLetterPath?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user