commit
e0018a495c
|
|
@ -4434,7 +4434,7 @@
|
||||||
"shortName": "AuthWatch",
|
"shortName": "AuthWatch",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "Google Authenticator compatible tool.",
|
"description": "Google Authenticator compatible tool.",
|
||||||
"tags": "tool",
|
"tags": "tool",
|
||||||
"interface": "interface.html",
|
"interface": "interface.html",
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
|
0.03: Add "Calculating" placeholder, update JSON save format
|
||||||
0.02: Fix JSON save format
|
0.02: Fix JSON save format
|
||||||
0.01: First release
|
0.01: First release
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
# Authentiwatch - 2FA Authenticator
|
# Authentiwatch - 2FA Authenticator
|
||||||
|
|
||||||
|
* GitHub: https://github.com/andrewgoz/Authentiwatch <-- Report bugs here
|
||||||
|
* Bleeding edge AppLoader: https://andrewgoz.github.io/Authentiwatch/
|
||||||
|
|
||||||
## Supports
|
## Supports
|
||||||
|
|
||||||
* Google Authenticator compatible 2-factor authentication
|
* Google Authenticator compatible 2-factor authentication
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mUywkBiIADCxoTFAAcQGBwY/DDQIKDBiMDDCgGCBI4YMGAIDFDCAFEBQwYLFgIYEGQgYMApoYJGAJjFMogYMSQgCDDBwDCY4oMEDBZgHHQQYQf4oYVBgwYQBogYPPYZpFDBKMEDAbdDCxT9IDYIFFABqSEAogySQYoWNFgrFDJZoQBJggYRBwhLGDBwyFDCZGEDCYAEDGrIMbwhnGDEpLGAwxlLFQgQDJiYoFDDAZDDCpMDMpQOCNxQYNBo4KKBpwYYBYJ8NeJgYkLBQY8UYQXVGQIwN"))
|
require("heatshrink").decompress(atob("mEwxH+AH4AD64ADFlgAFF04INFz4LUF0QwjEBwv/FzwwgF/4v/F6nMAAWi1AFD5nOeEHPEweoFooAB5/X5wvdFwotG5nN6/WAoQuaEoguHSYPQLwIIDF8uo5ouB6AJEFzuiFwup5/WFwI6GL0esXYKMBHYy9j1WqfBSOhBIYKJF8gAKF/4v6cZAvhGDAuWSDAvXMCwuYF+AwUFzX+0XGGAgxKFrYuBAAQxEeg4tcF4oABBQnGAAgv/F6b5KXsIvIGAqNnF/69fX8ZeSF7btNR8IuOF75ePL8ouOd74NKF8IANF94wEF1QAXA"))
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,13 @@ const algos = {
|
||||||
"SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 },
|
"SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 },
|
||||||
"SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 },
|
"SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 },
|
||||||
};
|
};
|
||||||
|
const calculating = "Calculating";
|
||||||
|
const notokens = "No tokens";
|
||||||
|
const notsupported = "Not supported";
|
||||||
|
|
||||||
var tokens = require("Storage").readJSON("authentiwatch.json", true) || [];
|
var settings = require("Storage").readJSON("authentiwatch.json", true) || {tokens:[],misc:{}};
|
||||||
tokens = tokens.data;
|
if (settings.data ) tokens = settings.data ; /* v0.02 settings */
|
||||||
|
if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */
|
||||||
|
|
||||||
// QR Code Text
|
// QR Code Text
|
||||||
//
|
//
|
||||||
|
|
@ -67,9 +71,8 @@ function do_hmac(key, message, algo) {
|
||||||
var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4);
|
var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4);
|
||||||
return v.getUint32(0) & 0x7FFFFFFF;
|
return v.getUint32(0) & 0x7FFFFFFF;
|
||||||
}
|
}
|
||||||
function hotp(token) {
|
function hotp(d, token, dohmac) {
|
||||||
var tick;
|
var tick;
|
||||||
var d = new Date();
|
|
||||||
if (token.period > 0) {
|
if (token.period > 0) {
|
||||||
// RFC6238 - timed
|
// RFC6238 - timed
|
||||||
var seconds = Math.floor(d.getTime() / 1000);
|
var seconds = Math.floor(d.getTime() / 1000);
|
||||||
|
|
@ -82,15 +85,17 @@ function hotp(token) {
|
||||||
var v = new DataView(msg.buffer);
|
var v = new DataView(msg.buffer);
|
||||||
v.setUint32(0, tick >> 16 >> 16);
|
v.setUint32(0, tick >> 16 >> 16);
|
||||||
v.setUint32(4, tick & 0xFFFFFFFF);
|
v.setUint32(4, tick & 0xFFFFFFFF);
|
||||||
var ret = "";
|
var ret = calculating;
|
||||||
try {
|
if (dohmac) {
|
||||||
var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase());
|
try {
|
||||||
ret = "" + hash % Math.pow(10, token.digits);
|
var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase());
|
||||||
while (ret.length < token.digits) {
|
ret = "" + hash % Math.pow(10, token.digits);
|
||||||
ret = "0" + ret;
|
while (ret.length < token.digits) {
|
||||||
|
ret = "0" + ret;
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
ret = notsupported;
|
||||||
}
|
}
|
||||||
} catch(err) {
|
|
||||||
ret = "Not supported";
|
|
||||||
}
|
}
|
||||||
return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)};
|
return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)};
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +115,7 @@ function drawToken(id, r) {
|
||||||
var y1 = r.y;
|
var y1 = r.y;
|
||||||
var x2 = r.x + r.w - 1;
|
var x2 = r.x + r.w - 1;
|
||||||
var y2 = r.y + r.h - 1;
|
var y2 = r.y + r.h - 1;
|
||||||
var adj;
|
var adj, sz;
|
||||||
g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ),
|
g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ),
|
||||||
Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2));
|
Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2));
|
||||||
if (id == state.curtoken) {
|
if (id == state.curtoken) {
|
||||||
|
|
@ -130,7 +135,7 @@ function drawToken(id, r) {
|
||||||
adj = (y1 + y2) / 2;
|
adj = (y1 + y2) / 2;
|
||||||
}
|
}
|
||||||
g.clearRect(x1, y1, x2, y2);
|
g.clearRect(x1, y1, x2, y2);
|
||||||
g.drawString(tokens[id].label, (x1 + x2) / 2, adj, false);
|
g.drawString(tokens[id].label.substr(0, 10), (x1 + x2) / 2, adj, false);
|
||||||
if (id == state.curtoken) {
|
if (id == state.curtoken) {
|
||||||
if (tokens[id].period > 0) {
|
if (tokens[id].period > 0) {
|
||||||
// timed - draw progress bar
|
// timed - draw progress bar
|
||||||
|
|
@ -144,7 +149,10 @@ function drawToken(id, r) {
|
||||||
adj = 5;
|
adj = 5;
|
||||||
}
|
}
|
||||||
// digits just below label
|
// digits just below label
|
||||||
g.setFont("Vector", (state.otp.length > 8) ? 26 : 30);
|
sz = 30;
|
||||||
|
do {
|
||||||
|
g.setFont("Vector", sz--);
|
||||||
|
} while (g.stringWidth(state.otp) > (r.w - adj));
|
||||||
g.drawString(state.otp, (x1 + x2) / 2 + adj, y1 + 16, false);
|
g.drawString(state.otp, (x1 + x2) / 2 + adj, y1 + 16, false);
|
||||||
}
|
}
|
||||||
// shaded lines top and bottom
|
// shaded lines top and bottom
|
||||||
|
|
@ -158,6 +166,9 @@ function draw() {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
if (state.curtoken != -1) {
|
if (state.curtoken != -1) {
|
||||||
var t = tokens[state.curtoken];
|
var t = tokens[state.curtoken];
|
||||||
|
if (state.otp == calculating) {
|
||||||
|
state.otp = hotp(d, t, true).hotp;
|
||||||
|
}
|
||||||
if (d.getTime() > state.nextTime) {
|
if (d.getTime() > state.nextTime) {
|
||||||
if (state.hide == 0) {
|
if (state.hide == 0) {
|
||||||
// auto-hide the current token
|
// auto-hide the current token
|
||||||
|
|
@ -168,7 +179,7 @@ function draw() {
|
||||||
state.nextTime = 0;
|
state.nextTime = 0;
|
||||||
} else {
|
} else {
|
||||||
// time to generate a new token
|
// time to generate a new token
|
||||||
var r = hotp(t);
|
var r = hotp(d, t, state.otp != "");
|
||||||
state.nextTime = r.next;
|
state.nextTime = r.next;
|
||||||
state.otp = r.hotp;
|
state.otp = r.hotp;
|
||||||
if (t.period <= 0) {
|
if (t.period <= 0) {
|
||||||
|
|
@ -196,7 +207,13 @@ function draw() {
|
||||||
if (state.drawtimer) {
|
if (state.drawtimer) {
|
||||||
clearTimeout(state.drawtimer);
|
clearTimeout(state.drawtimer);
|
||||||
}
|
}
|
||||||
state.drawtimer = setTimeout(draw, (tokens[state.curtoken].period > 0) ? 1000 : state.nexttime - d.getTime());
|
var dly;
|
||||||
|
if (tokens[state.curtoken].period > 0) {
|
||||||
|
dly = (state.otp == calculating) ? 1 : 1000;
|
||||||
|
} else {
|
||||||
|
dly = state.nexttime - d.getTime();
|
||||||
|
}
|
||||||
|
state.drawtimer = setTimeout(draw, dly);
|
||||||
if (tokens[state.curtoken].period <= 0) {
|
if (tokens[state.curtoken].period <= 0) {
|
||||||
state.hide = 0;
|
state.hide = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +228,7 @@ function draw() {
|
||||||
} else {
|
} else {
|
||||||
g.setFont("Vector", 30);
|
g.setFont("Vector", 30);
|
||||||
g.setFontAlign(0, 0, 0);
|
g.setFontAlign(0, 0, 0);
|
||||||
g.drawString("No tokens", Bangle.appRect.x + Bangle.appRect.w / 2,Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
g.drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,6 +249,7 @@ function onTouch(zone, e) {
|
||||||
if (y > Bangle.appRect.h) {
|
if (y > Bangle.appRect.h) {
|
||||||
state.listy += (y - Bangle.appRect.h);
|
state.listy += (y - Bangle.appRect.h);
|
||||||
}
|
}
|
||||||
|
state.otp = "";
|
||||||
}
|
}
|
||||||
state.nextTime = 0;
|
state.nextTime = 0;
|
||||||
state.curtoken = id;
|
state.curtoken = id;
|
||||||
|
|
@ -258,9 +276,10 @@ function onSwipe(e) {
|
||||||
}
|
}
|
||||||
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
|
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
|
||||||
tokens[state.curtoken].period--;
|
tokens[state.curtoken].period--;
|
||||||
let save={data:tokens,count:tokens.length};
|
let newsettings={tokens:tokens,misc:settings.misc};
|
||||||
require("Storage").writeJSON("authentiwatch.json", save);
|
require("Storage").writeJSON("authentiwatch.json", newsettings);
|
||||||
state.nextTime = 0;
|
state.nextTime = 0;
|
||||||
|
state.otp = "";
|
||||||
state.hide = 2;
|
state.hide = 2;
|
||||||
draw();
|
draw();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 964 B After Width: | Height: | Size: 1.6 KiB |
|
|
@ -35,8 +35,9 @@ const otpAuthUrl = 'otpauth://';
|
||||||
|
|
||||||
const tokentypes = ['TOTP (Timed)', 'HOTP (Counter)'];
|
const tokentypes = ['TOTP (Timed)', 'HOTP (Counter)'];
|
||||||
|
|
||||||
/* Array of TOTP tokens */
|
/* Settings */
|
||||||
var tokens=[];
|
var settings = {tokens:[], misc:{}};
|
||||||
|
var tokens = settings.tokens;
|
||||||
|
|
||||||
/* Remove any non-base-32 characters from the given string and collapses
|
/* Remove any non-base-32 characters from the given string and collapses
|
||||||
* whitespace to a single space. Optionally removes all whitespace from
|
* whitespace to a single space. Optionally removes all whitespace from
|
||||||
|
|
@ -261,6 +262,7 @@ qrcode.callback = res => {
|
||||||
scanning = false;
|
scanning = false;
|
||||||
editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value));
|
editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value));
|
||||||
t['label'] = (t['issuer'] == '') ? t['account'] : t['issuer'] + ' (' + t['account'] + ')';
|
t['label'] = (t['issuer'] == '') ? t['account'] : t['issuer'] + ' (' + t['account'] + ')';
|
||||||
|
t['label'] = t['label'].substr(0, 10);
|
||||||
var fe = document.forms['edittoken'].elements;
|
var fe = document.forms['edittoken'].elements;
|
||||||
if (res.startsWith(otpAuthUrl + 'hotp/')) {
|
if (res.startsWith(otpAuthUrl + 'hotp/')) {
|
||||||
t['period'] = '30';
|
t['period'] = '30';
|
||||||
|
|
@ -319,23 +321,21 @@ function doScan() {
|
||||||
*/
|
*/
|
||||||
function loadTokens() {
|
function loadTokens() {
|
||||||
Util.showModal('Loading...');
|
Util.showModal('Loading...');
|
||||||
Puck.eval(`require('Storage').read(${JSON.stringify('authentiwatch.json')})`,data=>{
|
Puck.eval(`require('Storage').readJSON(${JSON.stringify('authentiwatch.json')})`,data=>{
|
||||||
Util.hideModal();
|
Util.hideModal();
|
||||||
try {
|
if (data.data ) settings.tokens = data.data ; /* v0.02 settings */
|
||||||
let load = JSON.parse(data);
|
if (data.tokens) settings.tokens = data.tokens; /* v0.03+ settings */
|
||||||
tokens = load.data;
|
if (data.misc ) settings.misc = data.misc ; /* v0.03+ settings */
|
||||||
updateTokens();
|
tokens = settings.tokens;
|
||||||
} catch {
|
updateTokens();
|
||||||
tokens = [];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/* Save settings as a JSON file on the watch.
|
/* Save settings as a JSON file on the watch.
|
||||||
*/
|
*/
|
||||||
function saveTokens() {
|
function saveTokens() {
|
||||||
Util.showModal('Saving...');
|
Util.showModal('Saving...');
|
||||||
let save={data:tokens,count:tokens.length};
|
let newsettings={tokens:tokens,misc:settings.misc};
|
||||||
Puck.write(`\x10require('Storage').write(${JSON.stringify('authentiwatch.json')},${JSON.stringify(save)})\n`,()=>{
|
Puck.write(`\x10require('Storage').writeJSON(${JSON.stringify('authentiwatch.json')},${JSON.stringify(newsettings)})\n`,()=>{
|
||||||
Util.hideModal();
|
Util.hideModal();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue