alpine-components.js
268 lignes · 7472 octets
/* Alpine components for the CSP build (@alpinejs/csp). Loaded before alpine-csp.min.js so these listeners are registered before Alpine.start() dispatches alpine:init. Method bodies here are real JavaScript: fetch, window and document are allowed. They must not appear in HTML Alpine expressions. */ document.addEventListener("alpine:init", () => { Alpine.data("twoFactorPage", () => ({ code: "", error: "", verifying: false, useBackupCode: false, challengeToken: "", errorInvalid: "", errorNetwork: "", init() { const params = new URLSearchParams(window.location.search); this.challengeToken = params.get("challenge") || ""; this.errorInvalid = this.$el.dataset.errorInvalid || ""; this.errorNetwork = this.$el.dataset.errorNetwork || ""; }, toggleBackup() { this.useBackupCode = !this.useBackupCode; this.code = ""; }, async verify() { if (!this.code || !this.challengeToken) return; this.verifying = true; this.error = ""; try { const res = await fetch("/api/v1/auth/2fa/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ challenge_token: this.challengeToken, code: this.code, }), }); const data = await res.json(); if (res.ok) { if (data.refresh_token) { localStorage.setItem("refresh_token", data.refresh_token); } if (data.user && data.user.must_change_password === true) { window.location.href = "/settings/password?must_change=true"; } else { window.location.href = "/home"; } } else { this.error = data.error || this.errorInvalid; this.code = ""; } } catch (e) { this.error = this.errorNetwork; } this.verifying = false; }, })); Alpine.data("securityPage", () => ({ setupStarted: false, setupComplete: false, setupError: "", qrCode: "", secretBase32: "", verifyCode: "", verifying: false, showBackupCodes: false, backupCodes: [], regenerating: false, showDisableModal: false, disablePassword: "", disableError: "", disabling: false, csrf: "", regenerateConfirm: "", init() { this.csrf = this.$el.dataset.csrf || ""; this.regenerateConfirm = this.$el.dataset.regenerateConfirm || ""; }, async startSetup() { this.setupStarted = true; this.setupError = ""; try { const res = await fetch("/settings/security/totp/enable", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": this.csrf, }, }); const data = await res.json(); if (res.ok) { this.qrCode = data.qr_code_data_uri; this.secretBase32 = data.secret_base32; } else { this.setupError = data.error || "Error"; this.setupStarted = false; } } catch (e) { this.setupError = "Network error"; this.setupStarted = false; } }, async verifyAndEnable() { if (this.verifyCode.length !== 6) return; this.verifying = true; this.setupError = ""; try { const res = await fetch("/settings/security/totp/verify", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": this.csrf, }, body: JSON.stringify({ code: this.verifyCode }), }); const data = await res.json(); if (res.ok) { this.setupComplete = true; this.backupCodes = data.backup_codes; this.showBackupCodes = true; } else { this.setupError = data.error || "Invalid code"; this.verifyCode = ""; } } catch (e) { this.setupError = "Network error"; } this.verifying = false; }, async regenerateBackupCodes() { if (!confirm(this.regenerateConfirm)) return; this.regenerating = true; try { const res = await fetch("/settings/security/backup-codes", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": this.csrf, }, }); const data = await res.json(); if (res.ok) { this.backupCodes = data.backup_codes; this.showBackupCodes = true; } } catch (e) {} this.regenerating = false; }, async disableTotp() { this.disabling = true; this.disableError = ""; try { const res = await fetch("/settings/security/totp/disable", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": this.csrf, }, body: JSON.stringify({ password: this.disablePassword }), }); if (res.ok) { window.location.reload(); } else { const data = await res.json(); this.disableError = data.error || "Error"; } } catch (e) { this.disableError = "Network error"; } this.disabling = false; }, copyBackupCodes() { const text = this.backupCodes.join("\n"); if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text); } else { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); } }, acknowledgeBackupCodes() { this.showBackupCodes = false; window.location.reload(); }, closeDisableModal() { this.showDisableModal = false; this.disablePassword = ""; this.disableError = ""; }, })); Alpine.data("analyticsOptOut", () => ({ storageKey: "", optedOut: false, init() { this.storageKey = this.$el.dataset.storageKey || ""; try { this.optedOut = localStorage.getItem(this.storageKey) !== null; } catch (e) {} }, toggle() { try { if (this.optedOut) { localStorage.removeItem(this.storageKey); this.optedOut = false; } else { localStorage.setItem(this.storageKey, "1"); this.optedOut = true; } } catch (e) {} }, })); Alpine.data("fediversePage", () => ({ saved: false, deleteTarget: null, revokeTarget: null, killDomain: null, init() { this.saved = this.$el.dataset.saved === "true"; this.$watch("saved", (value) => { if (value) { setTimeout(() => { this.saved = false; }, 4000); } }); }, revokeHandle() { if (!this.revokeTarget) return ""; const parts = this.revokeTarget.split("|"); return parts.length > 1 ? parts[1] : ""; }, openKillModal() { if (!this.killDomain) return; const modal = document.getElementById("kill-domain-modal"); if (modal) modal.showModal(); }, closeKillModal() { const modal = document.getElementById("kill-domain-modal"); if (modal) modal.close(); }, })); });
GitRust