feat(desktop): diagnostic + manual retry for legacy key migration
The 0.18.1 fix relied on an existing-device + present-stronghold-key match.
That fails for users who:
- had multiple device registrations and only retain the latest device's
private key in the local vault
- had a vault wipe / fresh OS install at some point
- have device rows that vanished server-side but keys still locally
Migration now scans conversation_keys for distinct un-migrated
recipient_device_ids visible to the user (RLS-filtered) and probes the
stronghold for each, regardless of whether the server still lists that
device. Result struct surfaces attempted/migrated/noKey/decryptFail/rpcFail
counters; SecurityCenter shows them via a new "Migration erneut ausführen"
button so users can self-diagnose without DevTools.
Also adds [crypto-migration] console.info breadcrumbs at every decision
point so a single F12 shows what happened.
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { changePin, regenerateRecoveryCode, resetIdentity } from '../lib/userIdentity';
|
||||
import {
|
||||
changePin,
|
||||
type LegacyMigrationReport,
|
||||
regenerateRecoveryCode,
|
||||
resetIdentity,
|
||||
retryLegacyMigration,
|
||||
} from '../lib/userIdentity';
|
||||
import { PinInput } from './PinInput';
|
||||
import { ShieldIcon, SpinnerIcon } from './icons';
|
||||
|
||||
@@ -12,6 +18,17 @@ export function SecurityCenter({ userId }: Props) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [msg, setMsg] = useState<string | null>(null);
|
||||
const [recovery, setRecovery] = useState<string | null>(null);
|
||||
const [migration, setMigration] = useState<LegacyMigrationReport | null>(null);
|
||||
|
||||
async function handleRetryMigration() {
|
||||
setBusy(true); setMsg(null); setMigration(null);
|
||||
try {
|
||||
const report = await retryLegacyMigration(userId);
|
||||
setMigration(report);
|
||||
} catch (err) {
|
||||
setMsg(err instanceof Error ? err.message : String(err));
|
||||
} finally { setBusy(false); }
|
||||
}
|
||||
|
||||
async function handleChangePin() {
|
||||
setBusy(true); setMsg(null);
|
||||
@@ -76,6 +93,39 @@ export function SecurityCenter({ userId }: Props) {
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 className="mb-2 text-xs font-medium uppercase tracking-wide text-fg-muted">Schlüssel-Migration reparieren</h3>
|
||||
<p className="mb-2 text-xs text-fg-muted">
|
||||
Versucht, alte Conversation-Schlüssel erneut für deine neue Identität zu re-wrappen.
|
||||
Sicher zu klicken wenn Nachrichten verschlüsselt bleiben oder du nicht senden kannst.
|
||||
</p>
|
||||
<button type="button" disabled={busy} onClick={() => void handleRetryMigration()}
|
||||
className="rounded-md border border-line bg-surface-3 px-3 py-2 text-sm hover:bg-surface-2"
|
||||
>
|
||||
{busy && <SpinnerIcon className="mr-1 inline h-4 w-4" />}Migration erneut ausführen
|
||||
</button>
|
||||
{migration && (
|
||||
<div className="mt-2 rounded border border-line bg-surface-3 p-3 text-xs text-fg-muted">
|
||||
<div>Geräte (Server): {migration.serverDevices}</div>
|
||||
<div>Lokale Schlüssel im Vault: {migration.strongholdKeysFromServerDevices}
|
||||
{migration.strongholdKeysFromBundleScan > 0 && (
|
||||
<> (+{migration.strongholdKeysFromBundleScan} aus Bundle-Scan)</>
|
||||
)}
|
||||
</div>
|
||||
<div>Versucht: {migration.attempted}, Erfolgreich: <span className="text-emerald-500">{migration.migrated}</span></div>
|
||||
<div>Übersprungen (kein lokaler Schlüssel): {migration.noStrongholdKey}</div>
|
||||
<div>Entschlüsselung gescheitert: {migration.decryptFailed}</div>
|
||||
<div>Server-Fehler: {migration.rpcFailed}</div>
|
||||
{migration.attempted > 0 && migration.migrated === 0 && (
|
||||
<p className="mt-2 text-rose-300">
|
||||
Keine Bundles migriert. Vermutlich hast du den ursprünglichen Geräteschlüssel nicht mehr lokal.
|
||||
Nutze "Identität zurücksetzen" wenn du neu starten willst (alte Chats gehen verloren).
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 className="mb-2 text-xs font-medium uppercase tracking-wide text-rose-400">Identität zurücksetzen</h3>
|
||||
<p className="mb-2 text-xs text-fg-muted">Erstellt einen neuen Schlüssel. Alle alten Chats werden unlesbar.</p>
|
||||
|
||||
Reference in New Issue
Block a user