/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ var totp = new jsOTP.totp(); function parseOTP(jsontext, reload) { var keys = []; if (jsontext !== null && jsontext != "") { var keys = JSON.parse(jsontext || "[]"); if (keys.length > 0) { //$("#nokeys").css("display", "none"); } var tmpldata = []; for (var i = 0; i < keys.length; i++) { var code = totp.getOtp(keys[i]["secret"]); // Escape HTML characters var label = $('
').html(keys[i]["label"]).html(); var issuer = $('
').text(keys[i]["issuer"]).html(); tmpldata.push({ code: code, secret: keys[i]["secret"], label: label, issuer: issuer, index: i }); } router.navigate("/otp", { context: { otpcodes: tmpldata }, reloadCurrent: (reload == true ? true : false) }); setInterval(function () { refreshCountdown(); refreshCodes(); }, 1000); refreshCountdown(); } } function setProgressBar(percent) { if (mainView.router.currentRoute.name == "otp") { app.progressbar.show(percent, app.theme === 'md' ? 'yellow' : 'blue'); } else { app.progressbar.hide(); } } function loadOTPPage(reload) { var ls_text = localStorage.getItem("otp"); if (ls_text === null || ls_text == "") { // Recover from NativeStorage NativeStorage.getItem("otp", function (data) { localStorage.setItem("otp"); parseOTP(data, reload); }, function (error) { parseOTP("[]", reload); }); } else { parseOTP(ls_text, reload); } } function refreshCountdown() { var percent = ((30 - ((new Date).getSeconds() % 30)) / 30) * 100; setProgressBar(percent); } function refreshCodes() { $(".otpcard").each(function () { var code = totp.getOtp($(this).data("secret")); $(".otpcode", this).text(code); }); } function deleteOTPCode(index) { var label = $(".otpcard[data-index=" + index + "] .otplabel").text(); navigator.notification.confirm("Delete auth key? This cannot be undone, so make sure you don't need this key to login anymore!", function (result) { if (result != 1) { return; } var keys = JSON.parse(localStorage.getItem("otp")); keys.splice(index, 1); localStorage.setItem("otp", JSON.stringify(keys)); NativeStorage.setItem("otp", JSON.stringify(keys)); loadOTPPage(true); }, "Delete " + label + "?"); } function addOTPCode(key, label, issuer) { if (key == "") { navigator.notification.alert("Missing secret key.", null, "Error", 'Dismiss'); return; } key = key.toUpperCase(); /* Thanks to https://stackoverflow.com/a/27362880 for the regex */ if (!key.match(/^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?$/)) { navigator.notification.alert("Secret key is not valid base32.", null, "Error", 'Dismiss'); return; } if (label == "") { navigator.notification.alert("Missing label.", null, "Error", 'Dismiss'); return; } var ls_text = localStorage.getItem("otp"); var keys = []; if (ls_text != null && ls_text != "") { keys = JSON.parse(ls_text || "[]"); } keys.push({"secret": key, "label": label, "issuer": issuer}); localStorage.setItem("otp", JSON.stringify(keys)); NativeStorage.setItem("otp", JSON.stringify(keys)); app.toast.create({ text: '2-factor key saved.', closeButton: true, }).open(); loadOTPPage(true); } function scanOTPCode() { try { cordova.plugins.barcodeScanner.scan( function (result) { if (!result.cancelled) { try { var url = decodeURI(result.text); } catch (e) { navigator.notification.alert("Could not decode OTP URI.", null, "Error", 'Dismiss'); return; } if (!url.startsWith("otpauth://")) { navigator.notification.alert("Invalid OTP code. Try again.", null, "Error", 'Dismiss'); return; } if (!url.startsWith("otpauth://totp/")) { navigator.notification.alert("Unsupported key type.", null, "Error", 'Dismiss'); return; } var stripped = url.replace("otpauth://totp/", ""); var params = stripped.split("?")[1].split("&"); var label = stripped.split("?")[0]; var secret = ""; var issuer = ""; for (var i = 0; i < params.length; i++) { var param = params[i].split("="); if (param[0] == "secret") { secret = param[1].toUpperCase(); } else if (param[0] == "issuer") { issuer = param[1]; } else if (param[0] == "algorithm" && param[1].toLowerCase() != "sha1") { navigator.notification.alert("Unsupported hash algorithm.", null, "Error", 'Dismiss'); return; } else if (param[0] == "digits" && param[1] != "6") { navigator.notification.alert("Unsupported digit count.", null, "Error", 'Dismiss'); return; } else if (param[0] == "period" && param[1] != "30") { navigator.notification.alert("Unsupported period.", null, "Error", 'Dismiss'); return; } } try { secret = decodeURIComponent(secret); issuer = decodeURIComponent(issuer); label = decodeURIComponent(label); } catch (e) { navigator.notification.alert("Could not decode OTP URI.", null, "Error", 'Dismiss'); return; } addOTPCode(secret, label, issuer); } }, function (error) { navigator.notification.alert("Scanning failed: " + error, null, "Error", 'Dismiss'); }, { "showFlipCameraButton": false, "prompt": "Scan OTP QR code." } ); } catch (ex) { navigator.notification.alert(ex.message, null, "Error", 'Dismiss'); } } function addManualOTP() { var label = $("#addotp_sheetmodal #label").val(); var secret = $("#addotp_sheetmodal #secret").val().replace(/\s+/g, ''); app.sheet.close("#addotp_sheetmodal"); addOTPCode(secret, label, ""); }