From d7c00945a75a60bc17088ca80d1dd131a170eb04 Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Sat, 27 Oct 2018 00:47:28 -0600 Subject: [PATCH] Add OTP feature to redesign --- www/index.html | 2 + www/js/otp.js | 188 ++++++++++++++++++++++++++++++++++++++ www/old/pages/addotp.html | 149 ------------------------------ www/old/pages/otp.html | 91 ------------------ www/old/pages/setup2.html | 44 --------- www/pages/home.html | 6 ++ www/pages/otp.html | 89 ++++++++++++++++++ www/pages/setup1.html | 2 +- www/routes.js | 7 +- 9 files changed, 292 insertions(+), 286 deletions(-) create mode 100644 www/js/otp.js delete mode 100644 www/old/pages/addotp.html delete mode 100644 www/old/pages/otp.html delete mode 100644 www/old/pages/setup2.html create mode 100644 www/pages/otp.html diff --git a/www/index.html b/www/index.html index eeea886..61eb8d3 100644 --- a/www/index.html +++ b/www/index.html @@ -26,12 +26,14 @@ + + diff --git a/www/js/otp.js b/www/js/otp.js new file mode 100644 index 0000000..601b997 --- /dev/null +++ b/www/js/otp.js @@ -0,0 +1,188 @@ +/* + * 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 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; + $("#countdown").css("width", 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 = $("#addotppopup #label").val(); + var secret = $("#addotppopup #secret").val().replace(/\s+/g, ''); + app.popup.close("#addotppopup"); + addOTPCode(secret, label, ""); +} \ No newline at end of file diff --git a/www/old/pages/addotp.html b/www/old/pages/addotp.html deleted file mode 100644 index bc68b75..0000000 --- a/www/old/pages/addotp.html +++ /dev/null @@ -1,149 +0,0 @@ - -
-
-

- - Scan QR Code - - - Manual Entry - - -
-
- - \ No newline at end of file diff --git a/www/old/pages/otp.html b/www/old/pages/otp.html deleted file mode 100644 index cac410f..0000000 --- a/www/old/pages/otp.html +++ /dev/null @@ -1,91 +0,0 @@ - -
-
-
-
- -
-
-
-
-
- -

-

You haven't added any authentication keys yet. Press to add one.

-
-
-
-
-
-
- - diff --git a/www/old/pages/setup2.html b/www/old/pages/setup2.html deleted file mode 100644 index 21c593e..0000000 --- a/www/old/pages/setup2.html +++ /dev/null @@ -1,44 +0,0 @@ - -
-
-
-

Setup

-
-
-

Almost done! -

- Please enter your password, then press Finish. -

- -
-
Finish
-
-
- - \ No newline at end of file diff --git a/www/pages/home.html b/www/pages/home.html index 3b9d516..f0e8f5d 100644 --- a/www/pages/home.html +++ b/www/pages/home.html @@ -6,6 +6,12 @@ diff --git a/www/pages/otp.html b/www/pages/otp.html new file mode 100644 index 0000000..e4f4b90 --- /dev/null +++ b/www/pages/otp.html @@ -0,0 +1,89 @@ + +
+ + + + + + + +
+ {{#each otpcodes}} +
+
+
+

{{code}}

+ + delete + +
+

{{label}}

+
+
+ {{/each}} +
+ +
\ No newline at end of file diff --git a/www/pages/setup1.html b/www/pages/setup1.html index 5ecb744..826817f 100644 --- a/www/pages/setup1.html +++ b/www/pages/setup1.html @@ -38,7 +38,7 @@
  • - +
    diff --git a/www/routes.js b/www/routes.js index 1aad6a3..28b6230 100644 --- a/www/routes.js +++ b/www/routes.js @@ -25,5 +25,10 @@ var routes = [ path: '/app', url: './pages/app.html', name: 'app' - } + }, + { + path: '/otp', + templateUrl: './pages/otp.html', + name: 'otp' + }, ]; \ No newline at end of file