Skip to content

Commit

Permalink
Show multiple accounts
Browse files Browse the repository at this point in the history
Added WIP code/comments for implementing configuration
  • Loading branch information
gbraad committed Jun 10, 2012
1 parent eb5ae84 commit 9fbaf90
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 62 deletions.
10 changes: 9 additions & 1 deletion README
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
Based on the JavaScript implementation as provided by Tin Isles: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/
A simple Gnome-shell extension for Google Authenticator

Supports showing multiple accounts, but at the moment you need to configure it by editing the extension.js

TODO:
* implementing configuration/preferences widget
* show countdown
* store secrets in keyring

8 changes: 0 additions & 8 deletions gnome_shell_google-authenticator.json

This file was deleted.

65 changes: 65 additions & 0 deletions google-authenticator@gbraad.nl/convenience.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const Gettext = imports.gettext;
const Gio = imports.gi.Gio;

const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;

/**
* initTranslations:
* @domain: (optional): the gettext domain to use
*
* Initialize Gettext to load translations from extensionsdir/locale.
* If @domain is not provided, it will be taken from metadata['gettext-domain']
*/
function initTranslations(domain) {
let extension = ExtensionUtils.getCurrentExtension();

domain = domain || extension.metadata['gettext-domain'];

// check if this extension was built with "make zip-file", and thus
// has the locale files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell
let localeDir = extension.dir.get_child('locale');
if (localeDir.query_exists(null))
Gettext.bindtextdomain(domain, localeDir.get_path());
else
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
}

/**
* getSettings:
* @schema: (optional): the GSettings schema id
*
* Builds and return a GSettings schema for @schema, using schema files
* in extensionsdir/schemas. If @schema is not provided, it is taken from
* metadata['settings-schema'].
*/
function getSettings(schema) {
let extension = ExtensionUtils.getCurrentExtension();

schema = schema || extension.metadata['settings-schema'];

const GioSSS = Gio.SettingsSchemaSource;

// check if this extension was built with "make zip-file", and thus
// has the schema files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell (and therefore schemas are available
// in the standard folders)
let schemaDir = extension.dir.get_child('schemas');
let schemaSource;
if (schemaDir.query_exists(null))
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
GioSSS.get_default(),
false);
else
schemaSource = GioSSS.get_default();

let schemaObj = schemaSource.lookup(schema, true);
if (!schemaObj)
throw new Error('Schema ' + schema + ' could not be found for extension '
+ extension.metadata.uuid + '. Please check your installation.');

return new Gio.Settings({ settings_schema: schemaObj });
}
146 changes: 101 additions & 45 deletions google-authenticator@gbraad.nl/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,80 +18,136 @@ const Lang = imports.lang;
const Mainloop = imports.mainloop;

const St = imports.gi.St;
const Pango = imports.gi.Pango;
const GLib = imports.gi.GLib;

const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Tweener = imports.ui.tweener;

//const Tweener = imports.ui.tweener;
const Extension = imports.misc.extensionUtils.getCurrentExtension();
const Totp = Extension.imports.totp;

//const Gettext = imports.gettext.domain('gnome-shell-google-authenticator');
//const _ = Gettext.gettext;
//const ngettext = Gettext.ngettext;

let _accounts = [
{ "name": "alice@google.com", "secret" : "JBSWY3DPEHPK3PXP"}
];
const Convenience = Extension.imports.convenience;

let _settings;
//const SETTINGS_ACCOUNTS_KEY = 'accounts';
let _accounts;
let _defaultAccounts = [{
"name": "alice@google.com",
"secret": "JBSWY3DPEHPK3PXP"
}, {
"name": "alice+again@google.com",
"secret": "JBSWY3DPEHPK3PXP"
}];

const Indicator = new Lang.Class({
Name : 'GoogleAuthenticator',
Name: 'GoogleAuthenticator',
Extends: PanelMenu.SystemStatusButton,

_init: function() {
_init: function () {
this.parent("changes-prevent");

// Set default values of options, and then override from config file
this._parseConfig();

// This should occur every second
this._timeout = Mainloop.timeout_add_seconds(1, Lang.bind(this, function() {
this._updateTimer()
return true;
}));

this.connect('destroy', Lang.bind(this, this._onDestroy));
this._parseSettings();

// This should occur every second
this._timeout = Mainloop.timeout_add_seconds(1, Lang.bind(this, function () {
this._updateTimer()
return true;
}));
this._updateTimer(true)
},

// Update the keys based on timer tick
_updateTimer: function() {
// Should indicate the countdown time and only update when needed
// Update the keys based on timer tick or forced
_updateTimer: function (force) {
// Should indicate the countdown time and only update when needed
let epoch = Math.round(new Date().getTime() / 1000.0);
let countDown = 30 - (epoch % 30);
// If we need to update the OTP keys
if (epoch % 30 == 0) {
// Reset
this.menu.removeAll();

this.menu.addMenuItem(new PopupMenu.PopupMenuItem(this._keyAccount));
let key = Totp.updateOtp(this._keySecret);
this.menu.addMenuItem(new PopupMenu.PopupMenuItem(key));
}

// Weird way to show 2-digit number, but js doesn't have a native padding function
//if (countDown < 10)
// countDown = "0" + countDown.toString();
//else
// countDown = countDown.toString();
// "[" + countDown + "]";
// If we need to update the OTP keys
if (force || epoch % 30 == 0) {
// Reset
this.menu.removeAll();

for (let i = 0; i < _accounts.length; i++) {
let account = _accounts[i];
let index = i; // have to store local?
let key = Totp.updateOtp(account.secret);

let accountMenu = new PopupMenu.PopupMenuItem(account.name + "\n" + key);
/*
let delButton = new St.Bin({reactive: true,
can_focus: true,
x_fill: true,
y_fill: false,
track_hover: true });
let delIcon = new St.Icon({icon_name: 'edit-delete',
icon_type: St.IconType.SYMBOLIC,
icon_size: Main.panel.actor.height * 0.8});
delButton.set_child(delIcon);
delButton.connect('button-press-event', Lang.bind(this, function() { this._delAccount(index); }));
accountMenu.addActor(delButton, {align: St.Align.END});
*/
this.menu.addMenuItem(accountMenu);
}

//this._menuElements();
}
},

_parseConfig: function() {
_menuElements: function () {
// Need to find a convenient way to update only the elements itself
let addMenu = new PopupMenu.PopupMenuItem("Settings");
let addButton = new St.Bin({
reactive: true,
can_focus: true,
x_fill: true,
y_fill: false,
track_hover: true
});
let addIcon = new St.Icon({
icon_name: 'list-add',
icon_type: St.IconType.SYMBOLIC,
icon_size: Main.panel.actor.height * 0.8
});
addButton.set_child(addIcon);
addButton.connect('button-press-event', Lang.bind(this, function () {
this._addAccount();
}));
addMenu.addActor(addButton, {
align: St.Align.END
});
this.menu.addMenuItem(addMenu);
},

_delAccount: function (index) {
//_accounts.splice(index, 1);
//_settings.set_strv(SETTINGS_ACCOUNTS_KEY, _accounts);
this._updateTimer(true);
},

_onDestroy: function() {
_addAccount: function () {},

_parseSettings: function () {
// TODO: deal with gsettings and keyring
_acounts = null;
_settings = Convenience.getSettings();
try {
// TODO: parse account information
//_accounts = _settings.get_string(SETTINGS_ACCOUNTS_KEY);
} catch (e) {
global.logError("Google Authenticator: Error reading configuration = " + e);
} finally {
if (!_accounts) {
_accounts = _defaultAccounts;
//_settings.set_strv(SETTINGS_ACCOUNTS_KEY, _accounts);
}
}
}

});

let indicator;

function init(metadata) {
//Convenience.initTranslations();
}

function enable() {
Expand Down
6 changes: 5 additions & 1 deletion google-authenticator@gbraad.nl/metadata.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"shell-version": ["3.4"],
"settings-schema": "org.gnome.shell.extensions.google-authenticator",
"gettext-domain": "gnome-shell-extensions",
"extension-id": "google-authenticator",
"uuid": "google-authenticator@gbraad.nl",
"name": "Google Authenticator",
"description": "A simple Google Authenticator exension",
"url": "https://github.com/gbraad/gnome-shell-google-authenticator"
"url": "https://github.com/gbraad/gnome-shell-google-authenticator",
"original-authors": [ "me@gbraad.nl" ]
}
9 changes: 2 additions & 7 deletions google-authenticator@gbraad.nl/totp.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,7 @@ jsSHA.prototype = {
}
};

// Mock/stub code
//jsSHA = function (srcString, inputFormat) { /* ignore input */ }
//jsSHA.prototype = {
// getHMAC : function (key, inputFormat, variant, outputFormat) { return "JUSTATEST"; }
//}

// Based on the JavaScript implementation as provided by Tin Isles:
// http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/

function dec2hex(s) {
Expand Down Expand Up @@ -265,5 +260,5 @@ function updateOtp(secret) {
//var part3 = hmac.substr(offset * 2 + 8, hmac.length - offset);

let otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
return "" + (otp).substr(otp.length - 6, 6);
return (otp).substr(otp.length - 6, 6).toString();
}

0 comments on commit 9fbaf90

Please sign in to comment.