import { disableTwoFactorSC, generateBackupCodesSQ, setupTwoFactorSC, verifyAndEnableTwoFactorSC, } from "$lib/domains/security/twofa.remote"; import { toast } from "svelte-sonner"; import QRCode from "qrcode"; class TwoFactorViewModel { twoFactorEnabled = $state(false); twoFactorSetupInProgress = $state(false); showingBackupCodes = $state(false); qrCodeUrl = $state(null); twoFactorSecret = $state(null); backupCodes = $state([]); twoFactorVerificationCode = $state(""); isLoading = $state(false); errorMessage = $state(null); async startTwoFactorSetup() { this.isLoading = true; this.errorMessage = null; try { const result = await setupTwoFactorSC({}); if (result?.error || !result?.data?.totpURI) { this.errorMessage = result?.error?.message || "Could not enable 2FA"; toast.error(this.errorMessage, { description: result?.error?.description || "Please try again later", }); return; } const qrCodeDataUrl = await QRCode.toDataURL(result.data.totpURI, { width: 256, margin: 2, color: { dark: "#000000", light: "#FFFFFF" }, }); this.qrCodeUrl = qrCodeDataUrl; this.twoFactorSetupInProgress = true; this.twoFactorSecret = result.data.secret; this.twoFactorVerificationCode = ""; toast("Setup enabled"); } catch (error) { this.errorMessage = "Could not enable 2FA"; toast.error(this.errorMessage, { description: error instanceof Error ? error.message : "Please try again later", }); } finally { this.isLoading = false; } } async completeTwoFactorSetup() { if (!this.twoFactorVerificationCode) { this.errorMessage = "Please enter the verification code from your authenticator app."; return; } this.isLoading = true; this.errorMessage = null; try { const verifyResult = await verifyAndEnableTwoFactorSC({ code: this.twoFactorVerificationCode, }); if (verifyResult?.error) { this.errorMessage = verifyResult.error.message || "Invalid verification code"; toast.error(this.errorMessage, { description: verifyResult.error.description || "Please try again", }); return; } const backupCodesResult = await generateBackupCodesSQ(); if (backupCodesResult?.error || !Array.isArray(backupCodesResult?.data)) { toast.error("2FA enabled, but failed to generate backup codes", { description: "You can generate them later in settings", }); } else { this.backupCodes = backupCodesResult.data; this.showingBackupCodes = true; } this.twoFactorEnabled = true; this.twoFactorSetupInProgress = false; this.twoFactorVerificationCode = ""; toast.success("Two-factor authentication enabled", { description: "Your account is now more secure", }); } catch (error) { this.errorMessage = "Invalid verification code"; toast.error(this.errorMessage, { description: error instanceof Error ? error.message : "Please try again", }); } finally { this.isLoading = false; } } async disableTwoFactor() { this.isLoading = true; this.errorMessage = null; try { const result = await disableTwoFactorSC({ code: "" }); if (result?.error) { this.errorMessage = result.error.message || "Failed to disable 2FA"; toast.error(this.errorMessage, { description: result.error.description || "Please try again later", }); return; } this.twoFactorEnabled = false; this.backupCodes = []; this.qrCodeUrl = null; this.showingBackupCodes = false; this.twoFactorSecret = null; toast.success("Two-factor authentication disabled"); } catch (error) { this.errorMessage = "Failed to disable 2FA"; toast.error(this.errorMessage, { description: error instanceof Error ? error.message : "Please try again later", }); } finally { this.isLoading = false; } } async generateNewBackupCodes() { this.isLoading = true; this.errorMessage = null; try { const result = await generateBackupCodesSQ(); if (result?.error || !Array.isArray(result?.data)) { this.errorMessage = result?.error?.message || "Failed to generate new backup codes"; toast.error(this.errorMessage, { description: result?.error?.description || "Please try again later", }); return; } this.backupCodes = result.data; this.showingBackupCodes = true; toast.success("New backup codes generated", { description: "Your previous backup codes are now invalid", }); } catch (error) { this.errorMessage = "Failed to generate new backup codes"; toast.error(this.errorMessage, { description: error instanceof Error ? error.message : "Please try again later", }); } finally { this.isLoading = false; } } copyAllBackupCodes() { const codesText = this.backupCodes.join("\n"); navigator.clipboard.writeText(codesText); toast.success("All backup codes copied to clipboard"); } downloadBackupCodes() { const codesText = this.backupCodes.join("\n"); const blob = new Blob( [ `Two-Factor Authentication Backup Codes\n\nGenerated: ${new Date().toLocaleString()}\n\n${codesText}\n\nKeep these codes in a safe place. Each code can only be used once.`, ], { type: "text/plain", }, ); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `2fa-backup-codes-${new Date().toISOString().split("T")[0]}.txt`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); toast.success("Backup codes downloaded"); } confirmBackupCodesSaved() { this.showingBackupCodes = false; toast.success("Great! Your backup codes are safely stored"); } cancelSetup() { this.twoFactorSetupInProgress = false; this.twoFactorSecret = null; this.qrCodeUrl = null; this.twoFactorVerificationCode = ""; this.errorMessage = null; this.showingBackupCodes = false; } copyToClipboard(text: string) { navigator.clipboard.writeText(text); } reset() { this.twoFactorEnabled = false; this.twoFactorSetupInProgress = false; this.showingBackupCodes = false; this.qrCodeUrl = null; this.backupCodes = []; this.twoFactorSecret = null; this.twoFactorVerificationCode = ""; this.isLoading = false; this.errorMessage = null; } } export const twofactorVM = new TwoFactorViewModel();