From a3cc35c4b7bdc9ef3661dbaf74ed54ab061f8a7a Mon Sep 17 00:00:00 2001 From: Jason Staack Date: Mon, 9 Mar 2026 21:47:50 -0500 Subject: [PATCH] fix(frontend): generate Emergency Kit PDF client-side with actual Secret Key The server-generated PDF had a placeholder for the Secret Key that was never filled in client-side, making the Emergency Kit useless. Users who relied on it could not recover their Secret Key on new devices. Now generates the PDF entirely client-side via browser print dialog, with the real Secret Key embedded. No server round-trip, key never leaves the browser. Co-Authored-By: Claude Opus 4.6 --- .../components/auth/EmergencyKitDialog.tsx | 134 +++++++++++++++--- 1 file changed, 117 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/auth/EmergencyKitDialog.tsx b/frontend/src/components/auth/EmergencyKitDialog.tsx index 633c6b2..b8d58d1 100644 --- a/frontend/src/components/auth/EmergencyKitDialog.tsx +++ b/frontend/src/components/auth/EmergencyKitDialog.tsx @@ -3,7 +3,7 @@ * * Displays the Secret Key (which NEVER touches the server) and provides: * - Copy to clipboard button - * - Download Emergency Kit PDF (server-generated template without Secret Key) + * - Download Emergency Kit PDF (generated client-side with the actual Secret Key) * - Mandatory acknowledgment checkbox before closing * * The Secret Key is only shown once — if the user closes this dialog @@ -21,7 +21,101 @@ import { DialogDescription, DialogFooter, } from '@/components/ui/dialog'; -import { authApi } from '@/lib/api'; + +/** Build a self-contained HTML page for the Emergency Kit with the actual Secret Key. */ +function buildEmergencyKitHTML(email: string, secretKey: string, signinUrl: string, date: string): string { + // Escape HTML entities + const esc = (s: string) => s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + return ` + + + +TOD - Emergency Kit + + + +
+
+ +
+

Emergency Kit

+

TOD Zero-Knowledge Recovery

+
+
+
+
+ Keep this document safe + This Emergency Kit is your only way to recover access if you lose your Secret Key. + Store it in a secure location such as a home safe or safety deposit box. +
+
+
Email Address
+
${esc(email)}
+
+
+
Sign-in URL
+
${esc(signinUrl)}
+
+
+
Secret Key
+
${esc(secretKey)}
+
+
+
Master Password (write by hand)
+
+
+
+
+

Instructions

+
    +
  • This Emergency Kit contains your Secret Key needed to log in on new devices.
  • +
  • Store this document in a safe place — a home safe, safety deposit box, or other secure location.
  • +
  • Do NOT store this document digitally alongside your password.
  • +
  • Consider writing your Master Password on this sheet and storing it securely.
  • +
  • If you lose both your Emergency Kit and forget your Secret Key, your encrypted data cannot be recovered. There is no reset mechanism.
  • +
+
+
+ +
+ +`; +} interface EmergencyKitDialogProps { open: boolean; @@ -63,25 +157,31 @@ export function EmergencyKitDialog({ } }, [secretKey]); - const handleDownloadPDF = useCallback(async () => { + const handleDownloadPDF = useCallback(() => { setDownloading(true); try { - const blob = await authApi.getEmergencyKitPDF(); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'MikroTik-Portal-Emergency-Kit.pdf'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - toast.success('Emergency Kit PDF downloaded'); + const today = new Date().toISOString().split('T')[0]; + const html = buildEmergencyKitHTML(email, secretKey, window.location.origin, today); + const printWindow = window.open('', '_blank'); + if (!printWindow) { + toast.error('Pop-up blocked. Please allow pop-ups and try again.'); + return; + } + printWindow.document.write(html); + printWindow.document.close(); + // Wait for content to render then trigger print dialog (Save as PDF) + printWindow.onload = () => { + printWindow.print(); + }; + // Fallback if onload doesn't fire (some browsers) + setTimeout(() => printWindow.print(), 500); + toast.success('Print dialog opened — choose "Save as PDF" to download'); } catch { - toast.error('Failed to download Emergency Kit PDF'); + toast.error('Failed to generate Emergency Kit'); } finally { setDownloading(false); } - }, []); + }, [email, secretKey]); return ( {}}> @@ -146,8 +246,8 @@ export function EmergencyKitDialog({ {/* Instructions */}
- Write your Secret Key on the Emergency Kit PDF after printing it, or save it - in your password manager. Do NOT store it digitally alongside your password. + The PDF includes your Secret Key. Print it or save it securely. + You can also store the key in your password manager. Do NOT store it alongside your password.
{/* Help toggle */}