// A simple authentication application written in HTML
// Copyright (C) 2012 Gerard Braad
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
var StorageService = {}
StorageService.setObject = function(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
StorageService.getObject = function(key) {
var value = localStorage.getItem(key);
return value && JSON.parse(value);
}
StorageService.isSupported = function() {
return typeof (Storage) !== "undefined";
}
// Originally based on the JavaScript implementation as provided by Russell Sayers on his Tin Isles blog:
// http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/
var KeyUtilities = {}
KeyUtilities.dec2hex = function(s) {
return (s < 15.5 ? '0' : '') + Math.round(s).toString(16);
}
KeyUtilities.hex2dec = function(s) {
return parseInt(s, 16);
}
KeyUtilities.base32tohex = function(base32) {
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = "";
var hex = "";
for (var i = 0; i < base32.length; i++) {
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
bits += KeyUtilities.leftpad(val.toString(2), 5, '0');
}
for (var i = 0; i + 4 <= bits.length; i += 4) {
var chunk = bits.substr(i, 4);
hex = hex + parseInt(chunk, 2).toString(16);
}
return hex;
}
KeyUtilities.leftpad = function(str, len, pad) {
if (len + 1 >= str.length) {
str = Array(len + 1 - str.length).join(pad) + str;
}
return str;
}
KeyUtilities.generate = function(secret) {
var key = KeyUtilities.base32tohex(secret);
var epoch = Math.round(new Date().getTime() / 1000.0);
var time = KeyUtilities.leftpad(KeyUtilities.dec2hex(Math.floor(epoch / 30)), 16, '0');
var hmacObj = new jsSHA(time, "HEX");
var hmac = hmacObj.getHMAC(key, "HEX", "SHA-1", "HEX");
if (hmac != 'KEY MUST BE IN BYTE INCREMENTS') {
var offset = KeyUtilities.hex2dec(hmac.substring(hmac.length - 1));
// Debug
//var part1 = hmac.substr(0, offset * 2);
//var part2 = hmac.substr(offset * 2, 8);
//var part3 = hmac.substr(offset * 2 + 8, hmac.length - offset);
}
var otp = (KeyUtilities.hex2dec(hmac.substr(offset * 2, 8)) & KeyUtilities.hex2dec('7fffffff')) + '';
return (otp).substr(otp.length - 6, 6).toString();
}
var KeysController = {}
KeysController.init = function() {
// Check if local storage is supported
if (StorageService.isSupported()) {
if (!StorageService.getObject('accounts')) {
KeysController.addAccount('alice@google.com', 'JBSWY3DPEHPK3PXP');
}
KeysController.updateKeys();
setInterval(KeysController.timerTick, 1000);
} else {
// No support for localStorage
$('#updatingIn').text("x");
$('#accountsHeader').text("No Storage support");
}
// Bind to keypress event for the input
$('#add').click(function () {
var name = $('#keyAccount').val();
var secret = $('#keySecret').val();
// remove spaces from secret
secret = secret.replace(/ /g, '');
if(secret != '') {
KeysController.addAccount(name, secret);
}
});
}
KeysController.timerTick = function() {
var epoch = Math.round(new Date().getTime() / 1000.0);
var countDown = 30 - (epoch % 30);
if (epoch % 30 == 0) {
KeysController.updateKeys();
}
$('#updatingIn').text(countDown);
}
KeysController.updateKeys = function() {
var accountList = $('#accounts');
// Remove all except the first line
accountList.find("li:gt(0)").remove();
$.each(StorageService.getObject('accounts'), function (index, account) {
var key = KeyUtilities.generate(account.secret);
// Construct HTML
var delLink = $('');
delLink.click(function () {
KeysController.deleteAccount(index)
});
var detLink = $('' + key + '
' + account.name + '
');
var accElem = $('
').append(detLink).append(delLink);
// Add HTML element
accountList.append(accElem);
});
accountList.listview('refresh');
}
KeysController.deleteAccount = function(index) {
// Retrieve current objects
var accounts = StorageService.getObject('accounts');
accounts.splice(index, 1);
// Persist in localstorage
StorageService.setObject('accounts', accounts);
KeysController.updateKeys();
}
KeysController.addAccount = function(name, secret) {
if(secret == '') {
// Bailout
return false;
}
// Construct JSON object
var account = {
'name': name,
'secret': secret
};
// Get existing list of objects
var accounts = StorageService.getObject('accounts');
if (!accounts) {
accounts = [];
}
// Add to list
accounts.push(account);
// Empty fields
$('#keyAccount').val('');
$('#keySecret').val('');
// Persist in localstorage
StorageService.setObject('accounts', accounts);
KeysController.updateKeys();
return true;
}
// Main function
$(document).bind('pagecreate', function() {
// Background styling for dialogs
$('div[data-role="dialog"]').live('pagebeforeshow', function(e, ui) {
ui.prevPage.addClass("ui-dialog-background");
});
$('div[data-role="dialog"]').live('pagehide', function(e, ui) {
$(".ui-dialog-background ").removeClass("ui-dialog-background");
});
KeysController.init();
});