|
|
|
/*
|
|
|
|
* 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, "");
|
|
|
|
}
|