You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MobileApp/www/js/otp.js

196 lines
7.4 KiB
JavaScript

/*
* 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 = $('<div/>').html(keys[i]["label"]).html();
var issuer = $('<div/>').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, "");
}