mirror of https://github.com/espruino/BangleApps
267 lines
9.0 KiB
HTML
267 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
|
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
|
<style type="text/css">
|
|
body{font-family:sans-serif}
|
|
body div{display:none}
|
|
body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr div#tokenqr{display:block}
|
|
#tokens th,#tokens td{padding:5px}
|
|
#tokens tr:nth-child(odd){background-color:#ccc}
|
|
#tokens tr:nth-child(even){background-color:#eee}
|
|
#qr-canvas{margin:auto;width:calc(100%-20px);max-width:400px}
|
|
#advbtn,#scan,#tokenqr table{text-align:center}
|
|
#edittoken tbody#adv{display:none}
|
|
#edittoken.showadv tbody#adv{display:table-row-group}
|
|
#advbtn button:before,#advbtn button:after{content:"\25bc"}
|
|
#edittoken.showadv #advbtn button:before,#edittoken.showadv #advbtn button:after{content:"\25b2"}
|
|
button{height:3em}
|
|
</style>
|
|
<!-- https://rawgit.com/sitepoint-editors/jsqrcode/master/src/qr_packed.js -->
|
|
<script src="qr_packed.js"></script>
|
|
<!-- https://davidshimjs.github.io/qrcodejs/ -->
|
|
<script src="../../core/lib/qrcode.min.js"></script>
|
|
<script type="text/javascript">
|
|
var tokens=[];
|
|
function base32clean(val, nows) {
|
|
var ret = val.replaceAll(/\s+/g, " ");
|
|
ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, "");
|
|
if (nows) {
|
|
ret = ret.replaceAll(/\s+/g, "");
|
|
}
|
|
return ret;
|
|
}
|
|
function saveEdit(id, forget) {
|
|
const parseIntValue = function(v, def) {
|
|
let ret = parseInt(v);
|
|
return (ret > 0) ? ret : def;
|
|
};
|
|
if (forget) {
|
|
tokens.splice(id, 1);
|
|
} else {
|
|
let fe = document.forms["edittoken"].elements;
|
|
let t = {
|
|
"algorithm":fe["algorithm"].value,
|
|
"digits":parseIntValue(fe["digits"].value,6),
|
|
"period":parseIntValue(fe["period"].value,30),
|
|
"secret":base32clean(fe["secret"].value, false),
|
|
"label":fe["label"].value
|
|
};
|
|
tokens[parseInt(fe["tokenid"].value)] = t;
|
|
}
|
|
updateTokens();
|
|
}
|
|
function showQrCode() {
|
|
var fe = document.forms["edittoken"].elements;
|
|
var url = "otpauth://totp/";
|
|
url += encodeURIComponent(fe["label"].value);
|
|
url += "?secret=" + base32clean(fe["secret"].value, true);
|
|
if (parseInt(fe["period"].value) != 30) {
|
|
url += "&period=" + fe["period"].value;
|
|
}
|
|
if (parseInt(fe["digits"].value) != 6) {
|
|
url += "&digits=" + fe["digits"].value;
|
|
}
|
|
if (fe["algorithm"].value != "SHA1") {
|
|
url += "&algorithm=" + fe["algorithm"].value;
|
|
}
|
|
tokenqr.clear();
|
|
tokenqr.makeCode(url);
|
|
document.body.className = "showqr";
|
|
}
|
|
function editToken(dir,id) {
|
|
const selectMarkup = function(name, ary, cur) {
|
|
var ret = "<select name=\"" + name + "\">";
|
|
for (let i = 0; i < ary.length; i++) {
|
|
ret += "<option" + ((ary[i] == cur) ? " selected=selected" : "") + ">" + ary[i] + "</option>";
|
|
}
|
|
return ret + "</select>";
|
|
};
|
|
scanning=false;
|
|
var markup = "<form id=\"edittoken\"><input type=\"hidden\" name=\"tokenid\" value=\"" + id + "\"><table>";
|
|
markup += "<tr><td>Name:</td><td><input name=\"label\" type=\"text\" value=\"" + tokens[id].label + "\"></td></tr>";
|
|
markup += "<tr><td>Secret:</td><td><input name=\"secret\" type=\"text\" value=\"" + tokens[id].secret + "\"></td></tr>";
|
|
markup += "<tbody id=\"adv\"><tr><td>Period:</td><td><input name=\"period\" type=\"text\" value=\"" + tokens[id].period + "\"></td></tr>";
|
|
markup += "<tr><td>Digits:</td><td>";
|
|
markup += selectMarkup("digits", ["6","8","10"], tokens[id].digits);
|
|
markup += "</td></tr><tr><td>Hash:</td><td>";
|
|
markup += selectMarkup("algorithm", ["SHA1","SHA256","SHA512"], tokens[id].algorithm);
|
|
markup += "</td></tr></tbody><tr><td id=\"advbtn\" colspan=\"2\">";
|
|
markup += "<button type=\"button\" onclick=\"document.getElementById('edittoken').classList.toggle('showadv')\">Advanced</button>";
|
|
markup += "</td></tr></table></form>";
|
|
markup += "<button type=\"button\" onclick=\"updateTokens()\">Cancel Edit</button>";
|
|
markup += "<button type=\"button\" onclick=\"saveEdit(" + id + ", false)\">Save Changes</button>";
|
|
if (tokens[id].isnew) {
|
|
markup += "<button type=\"button\" onclick=\"startScan()\">Scan QR Code</button>";
|
|
} else {
|
|
markup += "<button type=\"button\" onclick=\"showQrCode()\">Show QR Code</button>";
|
|
}
|
|
markup += "<button type=\"button\" onclick=\"saveEdit(" + id + ", true)\">Forget Token</button>";
|
|
document.getElementById("edit").innerHTML = markup;
|
|
document.body.className = "editing";
|
|
}
|
|
function addToken() {
|
|
tokens[tokens.length] = {"algorithm":"SHA1","digits":6,"period":30,"secret":"","label":"","isnew":true};
|
|
editToken(0,tokens.length - 1);
|
|
}
|
|
function moveToken(dir, id) {
|
|
tokens.splice(id + dir, 0, tokens.splice(id, 1)[0]);
|
|
updateTokens();
|
|
}
|
|
function updateTokens() {
|
|
const tokenButton = function(fn, dir, id, label) {
|
|
return "<button type=\"button\" onclick=\"" + fn + "(" + dir + "," + id + ")\">" + label + "</button>";
|
|
};
|
|
var markup = "<table><tr><th colspan=\"2\">Token</th><th colspan=\"2\">Order</th></tr>";
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
if (tokens[i].isnew) {
|
|
tokens.splice(i, 1);
|
|
}
|
|
}
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
markup += "<tr><td>";
|
|
markup += tokens[i].label;
|
|
markup += "</td><td>";
|
|
markup += tokenButton("editToken", 0, i, "Edit");
|
|
markup += "</td><td>";
|
|
if (i < (tokens.length - 1)) {
|
|
markup += tokenButton("moveToken", 1, i, "▼");
|
|
}
|
|
markup += "</td><td>";
|
|
if (i > 0) {
|
|
markup += tokenButton("moveToken", -1, i, "▲");
|
|
}
|
|
markup += "</td></tr>";
|
|
}
|
|
markup += "</table>";
|
|
markup += "<button type=\"button\" onclick=\"addToken()\">Add Token</button>";
|
|
markup += "<button type=\"button\" onclick=\"saveTokens()\">Save to watch</button>";
|
|
document.getElementById("tokens").innerHTML = markup;
|
|
document.body.className = "select";
|
|
}
|
|
/* Original QR-code reader: https://www.sitepoint.com/create-qr-code-reader-mobile-website/ */
|
|
qrcode.callback = res => {
|
|
if (res) {
|
|
console.log(res);
|
|
res = decodeURIComponent(res);
|
|
var paramsidx = res.indexOf('?');
|
|
var otplabel = res.substring(res.lastIndexOf('/', paramsidx)+1, paramsidx);
|
|
var params = res.substr(paramsidx+1).split('&');
|
|
var t = {
|
|
"algorithm":"SHA1",
|
|
"digits":"6",
|
|
"period":"30",
|
|
"secret":"",
|
|
"issuer":""
|
|
};
|
|
var otpok = true;
|
|
for (let pi in params) {
|
|
var param = params[pi].split('=');
|
|
if (param[0] in t) {
|
|
t[param[0]] = param[1];
|
|
} else {
|
|
otpok = false;
|
|
}
|
|
}
|
|
if (t["secret"] == "") {
|
|
otpok = false;
|
|
}
|
|
if (otpok) {
|
|
scanning = false;
|
|
editToken(0,parseInt(document.forms['edittoken'].elements['tokenid'].value));
|
|
t["issuer"] = (t["issuer"] == "") ? otplabel : t["issuer"] + " (" + otplabel + ")";
|
|
var fe = document.forms["edittoken"].elements;
|
|
fe["algorithm"].value = t["algorithm"];
|
|
fe["digits"].value = t["digits"];
|
|
fe["period"].value = t["period"];
|
|
fe["secret"].value = t["secret"];
|
|
fe["label"].value = t["issuer"];
|
|
}
|
|
}
|
|
};
|
|
function startScan() {
|
|
document.body.className = "scanning";
|
|
navigator.mediaDevices
|
|
.getUserMedia({video:{facingMode:"environment"}})
|
|
.then(function(stream){
|
|
scanning=true;
|
|
video.setAttribute("playsinline",true);
|
|
video.srcObject = stream;
|
|
video.play();
|
|
scanTick();
|
|
doScan();
|
|
});
|
|
}
|
|
function scanTick() {
|
|
canvasElement.height = video.videoHeight;
|
|
canvasElement.width = video.videoWidth;
|
|
canvas.drawImage(video,0,0,canvasElement.width,canvasElement.height);
|
|
if (scanning) {
|
|
requestAnimationFrame(scanTick);
|
|
} else {
|
|
video.srcObject.getTracks().forEach(track => {
|
|
track.stop();
|
|
});
|
|
}
|
|
}
|
|
function doScan() {
|
|
try {
|
|
qrcode.decode();
|
|
} catch (e) {
|
|
setTimeout(doScan,300);
|
|
}
|
|
}
|
|
function loadTokens() {
|
|
Util.showModal("Loading...");
|
|
Puck.eval(`require("Storage").read(${JSON.stringify("authentiwatch.json")})`,data=>{
|
|
Util.hideModal();
|
|
try {
|
|
tokens = JSON.parse(data);
|
|
updateTokens();
|
|
} catch {
|
|
tokens = [];
|
|
}
|
|
});
|
|
}
|
|
function saveTokens() {
|
|
Util.showModal("Saving...");
|
|
Puck.write(`\x10require("Storage").write(${JSON.stringify("authentiwatch.json")},${JSON.stringify(tokens)})\n`,()=>{
|
|
Util.hideModal();
|
|
});
|
|
}
|
|
function onInit() {
|
|
loadTokens();
|
|
updateTokens();
|
|
}
|
|
</script>
|
|
</head>
|
|
<body class="select">
|
|
<h1>Authentiwatch</h1>
|
|
<div id="tokens">
|
|
<p>No watch comms.</p>
|
|
</div>
|
|
<div id="scan">
|
|
<table>
|
|
<tr><td><canvas id="qr-canvas"></canvas></td></tr>
|
|
<tr><td><button type=\"button\" onclick="editToken(0,parseInt(document.forms['edittoken'].elements['tokenid'].value))">Cancel</button></td></tr>
|
|
</table>
|
|
</div>
|
|
<div id="edit">
|
|
</div>
|
|
<div id="tokenqr">
|
|
<table><tr><td id="qrcode"></td></tr><tr><td>
|
|
<button type=\"button\" onclick="document.body.className='editing'">Back</button>
|
|
</td></tr></table>
|
|
</div>
|
|
<script type="text/javascript">
|
|
const video=document.createElement("video");
|
|
const canvasElement=document.getElementById("qr-canvas");
|
|
const canvas=canvasElement.getContext("2d");
|
|
let scanning=false;
|
|
const tokenqr=new QRCode(document.getElementById("qrcode"), "");
|
|
</script>
|
|
<script src="../../core/lib/interface.js"></script>
|
|
</body>
|
|
</html>
|