Add TOTP authenticator feature (close #8)
This commit is contained in:
parent
fb4895b6aa
commit
01ba58322d
4
www/icons/ic_add.svg
Normal file
4
www/icons/ic_add.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 199 B |
4
www/icons/ic_vpn_key.svg
Normal file
4
www/icons/ic_vpn_key.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M12.65 10C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H17v4h4v-4h2v-4H12.65zM7 14c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 320 B |
70
www/img/nokeys.svg
Normal file
70
www/img/nokeys.svg
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2"
|
||||||
|
viewBox="0 0 403.11274 163.29169"
|
||||||
|
height="163.29169"
|
||||||
|
width="403.11276"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="nokeys.svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1440"
|
||||||
|
inkscape:window-height="842"
|
||||||
|
id="namedview4274"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="1.6847619"
|
||||||
|
inkscape:cx="193.17345"
|
||||||
|
inkscape:cy="81.645645"
|
||||||
|
inkscape:window-x="1024"
|
||||||
|
inkscape:window-y="1024"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g4375"
|
||||||
|
fit-margin-top="20"
|
||||||
|
fit-margin-bottom="20"
|
||||||
|
fit-margin-left="88.539"
|
||||||
|
fit-margin-right="88.539" />
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
style="fill:#ffffff"
|
||||||
|
id="g4375"
|
||||||
|
transform="matrix(10.274307,0,0,10.274307,94.587448,-48.471738)">
|
||||||
|
<path
|
||||||
|
id="path4365"
|
||||||
|
d="M 0,0 24,0 24,24 0,24 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:none" />
|
||||||
|
<path
|
||||||
|
id="path4367"
|
||||||
|
d="m 11.061302,10.664366 c -0.82,-2.3300004 -3.0399999,-4.0000004 -5.6499999,-4.0000004 -3.31,0 -6,2.69 -6,6.0000004 0,3.31 2.69,6 6,6 2.61,0 4.8299999,-1.67 5.6499999,-4 l 4.35,0 0,4 4,0 0,-4 2,0 0,-4 -10.35,0 z m -5.6499999,4 c -1.1,0 -2,-0.9 -2,-2 0,-1.1 0.9,-2 2,-2 1.1,0 2,0.9 2,2 0,1.1 -0.9,2 -2,2 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#000000;fill-opacity:1" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
184
www/js/app.js
184
www/js/app.js
@ -2,7 +2,7 @@ userinfo = null;
|
|||||||
|
|
||||||
document.addEventListener("deviceready", function () {
|
document.addEventListener("deviceready", function () {
|
||||||
if (cordova.platformId == 'android') {
|
if (cordova.platformId == 'android') {
|
||||||
StatusBar.backgroundColorByHexString("#1976d2");
|
StatusBar.backgroundColorByHexString("#1976d2");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable/disable jQuery animations depending on user preference
|
// Enable/disable jQuery animations depending on user preference
|
||||||
@ -10,15 +10,15 @@ document.addEventListener("deviceready", function () {
|
|||||||
|
|
||||||
/* Fade out alerts */
|
/* Fade out alerts */
|
||||||
$(".alert .close").click(function (e) {
|
$(".alert .close").click(function (e) {
|
||||||
$(this).parent().fadeOut("slow");
|
$(this).parent().fadeOut("slow");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (localStorage.getItem("setupcomplete")) {
|
if (localStorage.getItem("setupcomplete")) {
|
||||||
getuserinfo(function () {
|
getuserinfo(function () {
|
||||||
openscreen("home");
|
openscreen("home");
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
openscreen("setup1");
|
openscreen("setup1");
|
||||||
}
|
}
|
||||||
setTimeout(navigator.splashscreen.hide, 1000);
|
setTimeout(navigator.splashscreen.hide, 1000);
|
||||||
}, false);
|
}, false);
|
||||||
@ -32,24 +32,24 @@ document.addEventListener("deviceready", function () {
|
|||||||
function getuserinfo(callback) {
|
function getuserinfo(callback) {
|
||||||
$(".loading-text").text("Loading account...");
|
$(".loading-text").text("Loading account...");
|
||||||
$.post(localStorage.getItem("syncurl"), {
|
$.post(localStorage.getItem("syncurl"), {
|
||||||
username: localStorage.getItem("username"),
|
username: localStorage.getItem("username"),
|
||||||
key: localStorage.getItem("key"),
|
key: localStorage.getItem("key"),
|
||||||
password: localStorage.getItem("password"),
|
password: localStorage.getItem("password"),
|
||||||
action: "user_info"
|
action: "user_info"
|
||||||
}, function (data) {
|
}, function (data) {
|
||||||
if (data.status === 'OK') {
|
if (data.status === 'OK') {
|
||||||
$(".loading-text").text("Loading...");
|
$(".loading-text").text("Loading...");
|
||||||
userinfo = data.info;
|
userinfo = data.info;
|
||||||
if (typeof callback == 'function') {
|
if (typeof callback == 'function') {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
navigator.notification.alert(data.msg, null, "Error", 'Dismiss');
|
navigator.notification.alert(data.msg, null, "Error", 'Dismiss');
|
||||||
openscreen("homeloaderror");
|
openscreen("homeloaderror");
|
||||||
}
|
}
|
||||||
}, "json").fail(function () {
|
}, "json").fail(function () {
|
||||||
navigator.notification.alert("Could not connect to the server. Try again later.", null, "Error", 'Dismiss');
|
navigator.notification.alert("Could not connect to the server. Try again later.", null, "Error", 'Dismiss');
|
||||||
openscreen("homeloaderror");
|
openscreen("homeloaderror");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,38 +61,38 @@ function getuserinfo(callback) {
|
|||||||
*/
|
*/
|
||||||
function openscreen(screenname, effect) {
|
function openscreen(screenname, effect) {
|
||||||
if (effect === 'FADE') {
|
if (effect === 'FADE') {
|
||||||
$('#content-zone').fadeOut(300, function () {
|
$('#content-zone').fadeOut(300, function () {
|
||||||
$('#content-zone').load("views/" + screenname + ".html", function () {
|
$('#content-zone').load("views/" + screenname + ".html", function () {
|
||||||
$('#content-zone').fadeIn(300);
|
$('#content-zone').fadeIn(300);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (effect === 'SLIDE') {
|
} else if (effect === 'SLIDE') {
|
||||||
$('#content-zone').slideToggle('400', function () {
|
$('#content-zone').slideToggle('400', function () {
|
||||||
$('#content-zone').load("views/" + screenname + ".html", function () {
|
$('#content-zone').load("views/" + screenname + ".html", function () {
|
||||||
$('#content-zone').slideToggle('400');
|
$('#content-zone').slideToggle('400');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$('#content-zone').load("views/" + screenname + ".html");
|
$('#content-zone').load("views/" + screenname + ".html");
|
||||||
}
|
}
|
||||||
currentscreen = screenname;
|
currentscreen = screenname;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openfragment(fragment, target, effect) {
|
function openfragment(fragment, target, effect) {
|
||||||
if (effect === 'FADE') {
|
if (effect === 'FADE') {
|
||||||
$(target).fadeOut('slow', function () {
|
$(target).fadeOut('slow', function () {
|
||||||
$(target).load("views/" + fragment + ".html", function () {
|
$(target).load("views/" + fragment + ".html", function () {
|
||||||
$(target).fadeIn('slow');
|
$(target).fadeIn('slow');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (effect === 'SLIDE') {
|
} else if (effect === 'SLIDE') {
|
||||||
$(target).slideToggle('400', function () {
|
$(target).slideToggle('400', function () {
|
||||||
$(target).load("views/" + fragment + ".html", function () {
|
$(target).load("views/" + fragment + ".html", function () {
|
||||||
$(target).slideToggle('400');
|
$(target).slideToggle('400');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$(target).load("views/" + fragment + ".html");
|
$(target).load("views/" + fragment + ".html");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,39 +108,42 @@ function openfragment(fragment, target, effect) {
|
|||||||
function setnavbar(type, title, returnscreen) {
|
function setnavbar(type, title, returnscreen) {
|
||||||
var navbar = $('#navbar-header');
|
var navbar = $('#navbar-header');
|
||||||
if (type == false) {
|
if (type == false) {
|
||||||
$('#navbar').css('display', 'none');
|
$('#navbar').css('display', 'none');
|
||||||
$('#content-zone').css('margin-top', '0px');
|
$('#content-zone').css('margin-top', '0px');
|
||||||
} else {
|
} else {
|
||||||
if (cordova.platformId == 'android') {
|
if (cordova.platformId == 'android') {
|
||||||
StatusBar.backgroundColorByHexString("#1976d2");
|
StatusBar.backgroundColorByHexString("#1976d2");
|
||||||
window.plugins.headerColor.tint("#2196f3");
|
window.plugins.headerColor.tint("#2196f3");
|
||||||
} else {
|
} else {
|
||||||
StatusBar.backgroundColorByHexString("#2196f3");
|
StatusBar.backgroundColorByHexString("#2196f3");
|
||||||
}
|
}
|
||||||
$('#navbar').css('display', 'initial');
|
$('#navbar').css('display', 'initial');
|
||||||
$('#content-zone').css('margin-top', '75px');
|
$('#content-zone').css('margin-top', '75px');
|
||||||
if (returnscreen === undefined) {
|
if (returnscreen === undefined) {
|
||||||
returnscreen = "home";
|
returnscreen = "home";
|
||||||
_returnscreen = null;
|
_returnscreen = null;
|
||||||
} else {
|
} else {
|
||||||
_returnscreen = returnscreen;
|
_returnscreen = returnscreen;
|
||||||
}
|
}
|
||||||
navbar.fadeOut(150, function () {
|
navbar.fadeOut(150, function () {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "home":
|
case "home":
|
||||||
navbar.html('<span class="navbar-brand" style="color: white;">Business</span><span class="navbar-brand pull-right" onclick="openscreen(\'settings\', \'FADE\')"><img src="icons/ic_settings.svg" alt="" /></span>');
|
navbar.html('<span class="navbar-brand" style="color: white;">Business</span><span class="navbar-brand pull-right"><span onclick="openscreen(\'otp\', \'FADE\')"><img src="icons/ic_vpn_key.svg" alt="" /></span> <span onclick="openscreen(\'settings\', \'FADE\')"><img src="icons/ic_settings.svg" alt="" /></span></span>');
|
||||||
break;
|
break;
|
||||||
case "settings":
|
case "settings":
|
||||||
navbar.html('<span class="navbar-brand pull-left" style="color: white;" onclick="openscreen(\'home\', \'FADE\')"><img src="icons/ic_arrow-back.svg" /></span><span class="navbar-brand navbar-title" style="color: white;" onclick="openscreen(\'home\', \'FADE\')">Settings</span>');
|
navbar.html('<span class="navbar-brand pull-left" style="color: white;" onclick="openscreen(\'home\', \'FADE\')"><img src="icons/ic_arrow-back.svg" /></span><span class="navbar-brand navbar-title" style="color: white;" onclick="openscreen(\'home\', \'FADE\')">Settings</span>');
|
||||||
break;
|
break;
|
||||||
case "app":
|
case "otp":
|
||||||
navbar.html('<span class="navbar-brand pull-left" style="color: white;" onclick="openscreen(\'' + returnscreen + '\', \'FADE\')"><img src="icons/ic_arrow-back.svg" /></span><span class="navbar-brand navbar-title" style="color: white;" onclick="openscreen(\'' + returnscreen + '\', \'FADE\')">' + title + '</span>');
|
navbar.html('<span class="navbar-brand pull-left" style="color: white;" onclick="openscreen(\'home\', \'FADE\')"><img src="icons/ic_arrow-back.svg" /></span><span class="navbar-brand navbar-title" style="color: white;" onclick="openscreen(\'home\', \'FADE\')">Auth Keys</span><span class="navbar-brand pull-right" onclick="openscreen(\'addotp\', \'FADE\')"><img src="icons/ic_add.svg" alt="" /></span>');
|
||||||
break;
|
break;
|
||||||
default:
|
case "app":
|
||||||
navbar.html('<span class="navbar-brand" style="color: white;">Business</span>');
|
navbar.html('<span class="navbar-brand pull-left" style="color: white;" onclick="openscreen(\'' + returnscreen + '\', \'FADE\')"><img src="icons/ic_arrow-back.svg" /></span><span class="navbar-brand navbar-title" style="color: white;" onclick="openscreen(\'' + returnscreen + '\', \'FADE\')">' + title + '</span>');
|
||||||
}
|
break;
|
||||||
navbar.fadeIn(150);
|
default:
|
||||||
});
|
navbar.html('<span class="navbar-brand" style="color: white;">Business</span>');
|
||||||
|
}
|
||||||
|
navbar.fadeIn(150);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,11 +160,11 @@ function setnavbar(type, title, returnscreen) {
|
|||||||
*/
|
*/
|
||||||
function openapp(id, api, url, icon, title, injectcode, shownavbar) {
|
function openapp(id, api, url, icon, title, injectcode, shownavbar) {
|
||||||
$('#content-zone').fadeOut(300, function () {
|
$('#content-zone').fadeOut(300, function () {
|
||||||
$('#content-zone').load("views/app.html", function () {
|
$('#content-zone').load("views/app.html", function () {
|
||||||
$('#content-zone').fadeIn(300, function () {
|
$('#content-zone').fadeIn(300, function () {
|
||||||
launchapp(id, api, url, icon, title, injectcode, shownavbar);
|
launchapp(id, api, url, icon, title, injectcode, shownavbar);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,8 +176,8 @@ function openapp(id, api, url, icon, title, injectcode, shownavbar) {
|
|||||||
*/
|
*/
|
||||||
function openmodal(filename, modalselector) {
|
function openmodal(filename, modalselector) {
|
||||||
$('#modal-load-box').load("views/" + filename + ".html", null, function (x) {
|
$('#modal-load-box').load("views/" + filename + ".html", null, function (x) {
|
||||||
$(modalselector).css('z-index', 9999999);
|
$(modalselector).css('z-index', 9999999);
|
||||||
$(modalselector).modal('show');
|
$(modalselector).modal('show');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +194,7 @@ function restartApplication() {
|
|||||||
navigator.splashscreen.show();
|
navigator.splashscreen.show();
|
||||||
// We're doing the timeout so we don't run afoul of server-side rate limiting
|
// We're doing the timeout so we don't run afoul of server-side rate limiting
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
window.location = "index.html";
|
window.location = "index.html";
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,10 +207,15 @@ document.addEventListener("backbutton", function (event) {
|
|||||||
iframe.contentWindow.postMessage("goback", "*");
|
iframe.contentWindow.postMessage("goback", "*");
|
||||||
historyctr--;
|
historyctr--;
|
||||||
} else if (_returnscreen != null) {
|
} else if (_returnscreen != null) {
|
||||||
openscreen(_returnscreen, "FADE");
|
openscreen(_returnscreen, "FADE");
|
||||||
_returnscreen = null;
|
_returnscreen = null;
|
||||||
} else {
|
} else {
|
||||||
openscreen("home", "FADE");
|
openscreen("home", "FADE");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (_returnscreen != null) {
|
||||||
|
openscreen(_returnscreen, "FADE");
|
||||||
|
_returnscreen = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
6
www/js/jsOTP.min.js
vendored
Normal file
6
www/js/jsOTP.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
132
www/views/addotp.html
Normal file
132
www/views/addotp.html
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<br />
|
||||||
|
<div class="panel panel-blue">
|
||||||
|
<div class="panel-body">
|
||||||
|
<p></p>
|
||||||
|
<span class="btn btn-primary btn-lg" onclick="scanCode()" id="scancodebtn">
|
||||||
|
<i class="fa fa-qrcode"></i> Scan QR Code
|
||||||
|
</span>
|
||||||
|
<span class="btn btn-link" onclick="manualshow()" id="manualaddbtn">
|
||||||
|
Manual Entry
|
||||||
|
</span>
|
||||||
|
<div id="manual_add" class="well" style="display: none;">
|
||||||
|
<input type="text" id="key" class="form-control" placeholder="Secret key" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /> <br />
|
||||||
|
<input type="text" id="label" class="form-control" placeholder="Label" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /> <br />
|
||||||
|
<input type="text" id="issuer" class="form-control" placeholder="Issuer" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
|
||||||
|
<br />
|
||||||
|
<div class="btn btn-primary" onclick="manualadd()">
|
||||||
|
Continue
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$("#key").on("keyup", function () {
|
||||||
|
if (window.getSelection().toString() !== '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var text = $('#key').val().replace(/\s+/g, '');
|
||||||
|
var formatted = "";
|
||||||
|
for (var i = 1; i <= text.length; i++) {
|
||||||
|
formatted = formatted + text[i - 1];
|
||||||
|
if (i % 4 == 0 && i > 1 && i < text.length) {
|
||||||
|
// add a space every 5 characters,
|
||||||
|
// unless it's the first character
|
||||||
|
// or the last character
|
||||||
|
formatted = formatted + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('#key').val(formatted.toUpperCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
function manualadd() {
|
||||||
|
var key = $('#key').val().replace(/\s+/g, '');
|
||||||
|
var label = $('#label').val();
|
||||||
|
var issuer = $('#issuer').val();
|
||||||
|
addOTP(key, label, issuer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function manualshow() {
|
||||||
|
$('#manual_add').css('display', 'block');
|
||||||
|
}
|
||||||
|
|
||||||
|
function addOTP(key, label, issuer) {
|
||||||
|
if (key == "") {
|
||||||
|
navigator.notification.alert("Missing secret key.", 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));
|
||||||
|
navigator.notification.alert("2-factor key saved.", null, "Key added", 'Dismiss');
|
||||||
|
openscreen("otp");
|
||||||
|
}
|
||||||
|
|
||||||
|
function scanCode() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addOTP(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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setnavbar("app", "Add Auth Key", "otp");
|
||||||
|
</script>
|
73
www/views/otp.html
Normal file
73
www/views/otp.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar" id="countdown" style="width: 0%;"></div>
|
||||||
|
</div>
|
||||||
|
<div id="nokeys">
|
||||||
|
<div class="app-dock-container">
|
||||||
|
<div class="app-dock" id="app-dock">
|
||||||
|
<div style="margin-top: 50px; text-align: center; font-size: 120%;">
|
||||||
|
<img src="img/nokeys.svg" alt="" style="max-width: 80%; max-height: 33%;" />
|
||||||
|
<br /><br />
|
||||||
|
You haven't added any authentication keys yet. Press the <i class="fa fa-plus"></i> icon to add one.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-group" id="codelist">
|
||||||
|
</div>
|
||||||
|
<script src="js/jsOTP.min.js"></script>
|
||||||
|
<script>
|
||||||
|
setnavbar("otp", "", "home");
|
||||||
|
|
||||||
|
var totp = new jsOTP.totp();
|
||||||
|
|
||||||
|
var ls_text = localStorage.getItem("otp");
|
||||||
|
var keys = [];
|
||||||
|
if (ls_text !== null && ls_text != "") {
|
||||||
|
var keys = JSON.parse(ls_text || "[]");
|
||||||
|
if (keys.length > 0) {
|
||||||
|
$("#nokeys").css("display", "none");
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
$("#codelist").append("<div class=\"list-group-item\" id=\"codeitem_" + i + "\">"
|
||||||
|
+ "<span class=\"pull-right\" style=\"color: red;\" onclick=\"deleteCode(" + i + ")\"><i class=\"fa fa-trash-o\"></i></span>"
|
||||||
|
+ "<p class=\"h6\">" + label + "</p>"
|
||||||
|
+ "<div class=\"h3 code\" style=\"font-weight: bold;\">" + code + "</div>"
|
||||||
|
+ "<p class=\"small\">" + issuer + "</p>"
|
||||||
|
+ "</div>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshCountdown() {
|
||||||
|
var percent = ((30 - ((new Date).getSeconds() % 30)) / 30) * 100;
|
||||||
|
$("#countdown").css("width", percent + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshCodes() {
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var code = totp.getOtp(keys[i]["secret"]);
|
||||||
|
$("#codelist #codeitem_" + i + " .code").text(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCode(index) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
keys.splice(index, 1);
|
||||||
|
localStorage.setItem("otp", JSON.stringify(keys));
|
||||||
|
openscreen("otp");
|
||||||
|
}, "Delete " + keys[index]["label"] + "?");
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
refreshCountdown();
|
||||||
|
refreshCodes();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
refreshCountdown();
|
||||||
|
</script>
|
@ -47,16 +47,16 @@
|
|||||||
setnavbar("settings");
|
setnavbar("settings");
|
||||||
|
|
||||||
function deleteall() {
|
function deleteall() {
|
||||||
navigator.notification.confirm("Really wipe user data? You will need to resync the app with AccountHub to use it again.", function (result) {
|
navigator.notification.confirm("Really wipe user data? You will need to resync the app with AccountHub to use it again. This will not delete 2-factor auth keys.", function (result) {
|
||||||
if (result != 1) {
|
if (result != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Wipe localStorage
|
// Wipe localStorage
|
||||||
localStorage.removeItem("setupcomplete");
|
localStorage.removeItem("setupcomplete");
|
||||||
localStorage.removeItem("username");
|
localStorage.removeItem("username");
|
||||||
|
localStorage.removeItem("password");
|
||||||
localStorage.removeItem("syncurl");
|
localStorage.removeItem("syncurl");
|
||||||
localStorage.removeItem("key");
|
localStorage.removeItem("key");
|
||||||
localStorage.clear();
|
|
||||||
// force-reload app
|
// force-reload app
|
||||||
navigator.notification.alert("Connection data and credentials erased.", function () {
|
navigator.notification.alert("Connection data and credentials erased.", function () {
|
||||||
restartApplication();
|
restartApplication();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user