()
| 173 | } |
| 174 | |
| 175 | export function openTOTPModal() { |
| 176 | let totpModal = document.getElementById("totpModal"); |
| 177 | const isDarkMode = document.body.classList.contains("dark-mode"); |
| 178 | const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; |
| 179 | const modalContentStyles = ` |
| 180 | background: ${isDarkMode ? "#2c2c2c" : "#fff"}; |
| 181 | color: ${isDarkMode ? "#e0e0e0" : "#000"}; |
| 182 | padding: 20px; max-width:400px; width:90%; border-radius:8px; position:relative; |
| 183 | `; |
| 184 | if (!totpModal) { |
| 185 | totpModal = document.createElement("div"); |
| 186 | totpModal.id = "totpModal"; |
| 187 | totpModal.style.cssText = ` |
| 188 | position: fixed; top:0; left:0; width:100vw; height:100vh; |
| 189 | background-color:${overlayBackground}; display:flex; justify-content:center; align-items:center; |
| 190 | z-index:3100; |
| 191 | `; |
| 192 | totpModal.innerHTML = ` |
| 193 | <div class="modal-content" style="${modalContentStyles}"> |
| 194 | <span id="closeTOTPModal" class="editor-close-btn">×</span> |
| 195 | <h3>${t("totp_setup")}</h3> |
| 196 | <p>${t("scan_qr_code")}</p> |
| 197 | <img id="totpQRCodeImage" src="" alt="TOTP QR Code" style="max-width:100%; height:auto; display:block; margin:0 auto;" /> |
| 198 | <br/> |
| 199 | <p>${t("enter_totp_confirmation")}</p> |
| 200 | <input type="text" id="totpConfirmInput" maxlength="6" style="font-size:24px; text-align:center; width:100%; padding:10px;" placeholder="6-digit code" /> |
| 201 | <br/><br/> |
| 202 | <button type="button" id="confirmTOTPBtn" class="btn btn-primary">${t("confirm")}</button> |
| 203 | </div> |
| 204 | `; |
| 205 | document.body.appendChild(totpModal); |
| 206 | loadTOTPQRCode(); |
| 207 | document.getElementById("closeTOTPModal").addEventListener("click", () => closeTOTPModal(true)); |
| 208 | document.getElementById("confirmTOTPBtn").addEventListener("click", async function () { |
| 209 | const code = document.getElementById("totpConfirmInput").value.trim(); |
| 210 | if (code.length !== 6) { showToast(t("please_enter_valid_code")); return; } |
| 211 | const tokenRes = await fetch("/api/auth/token.php", { credentials: "include" }); |
| 212 | if (!tokenRes.ok) { showToast(t("error_verifying_totp_code")); return; } |
| 213 | window.csrfToken = (await tokenRes.json()).csrf_token; |
| 214 | const verifyRes = await fetch("/api/profile/totp_verify.php", { |
| 215 | method: "POST", credentials: "include", |
| 216 | headers: { "Content-Type": "application/json", "X-CSRF-Token": window.csrfToken }, |
| 217 | body: JSON.stringify({ totp_code: code }) |
| 218 | }); |
| 219 | if (!verifyRes.ok) { showToast(t("totp_verification_failed")); return; } |
| 220 | const result = await verifyRes.json(); |
| 221 | if (result.status !== "ok") { showToast(result.message || t("totp_verification_failed")); return; } |
| 222 | showToast(t("totp_enabled_successfully")); |
| 223 | const saveRes = await fetch("/api/profile/totp_saveCode.php", { |
| 224 | method: "POST", credentials: "include", headers: { "X-CSRF-Token": window.csrfToken } |
| 225 | }); |
| 226 | if (!saveRes.ok) { showToast(t("error_generating_recovery_code")); closeTOTPModal(false); return; } |
| 227 | const data = await saveRes.json(); |
| 228 | if (data.status === "ok" && data.recoveryCode) showRecoveryCodeModal(data.recoveryCode); |
| 229 | else showToast(t("error_generating_recovery_code") + ": " + (data.message || t("unknown_error"))); |
| 230 | closeTOTPModal(false); |
| 231 | }); |
| 232 |
no test coverage detected