Major changes. Completely revamped notifications system.

This commit is contained in:
Paul Sawaya 2012-09-04 22:31:32 -07:00
Родитель 3050511021
Коммит fa6eb1009b
21 изменённых файлов: 16398 добавлений и 119 удалений

Просмотреть файл

@ -0,0 +1,13 @@
window.addEventListener('login-success', function() {
alert('login-success');
});//
// window.__authSuccess = function(data) {
// alert('login-success');
// chrome.extension.sendMessage({
// type: 'login_success',
// message: {
// email: data.email
// }
// });
// };
//

5774
data/css/bootstrap.css поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,3 @@
#save-login-buttons {
display:none;
}

17
data/notification.html Normal file
Просмотреть файл

@ -0,0 +1,17 @@
<!doctype html>
<html>
<head>
<link href="css/bootstrap.css" rel="stylesheet">
<link href="css/notifications.css" rel="stylesheet">
<script src="notification.js"></script>
</head>
<body>
<div id="save-login-buttons">
<button id="btn-save" class="btn btn-primary">Save</button>
<button id="btn-not-now" class="btn">Not now</button>
<button id="btn-never-for-site" class="btn btn-warning">Never for this site</button>
</div>
</body>
</html>

84
data/notification.js Normal file
Просмотреть файл

@ -0,0 +1,84 @@
var notifID = null;
function init() {
// Notification ID is stored after the hash ('#') in the URL
notifID = parseInt(window.location.hash.substr(1));
// Get the notification object from the background script
var notifObj = chrome.extension.getBackgroundPage().getNotificationForID(notifID);
displayAlert(notifObj);
}
function getAlertBody(alertObj) {
var bodyEl = document.createElement('div');
if (alertObj.type == 'password_saved') {
bodyEl.appendChild(document.createTextNode("SkyCrane can save login " + alertObj.username + ":"));
var passwordImg = document.createElement('img');
passwordImg.src = getDataURLForHash(alertObj.hash,50,13);
bodyEl.appendChild(passwordImg);
bodyEl.appendChild(document.createTextNode(" for site " + alertObj.hostname + "!"));
// Show save buttons, and wire to events
document.getElementById('save-login-buttons').style.display = 'block';
function wire(func,id) {
return function() { func(id); };
}
var bPage = chrome.extension.getBackgroundPage();
document.getElementById('btn-save').addEventListener('click', wire(bPage.saveLogin,notifID));
document.getElementById('btn-not-now').addEventListener('click', wire(bPage.notNow,notifID));
document.getElementById('btn-never-for-site').addEventListener('click', wire(bPage.neverForSite,notifID));
}
return bodyEl;
}
function displayAlert(alertObj) {
// document.body.appendChild(createAlert(alertObj));
document.body.insertBefore(getAlertBody(alertObj),document.body.children[0]);
}
function getDataURLForHash(passwordHash,inputWidth,inputHeight,numColorBars) {
function randomizeHash(passwordHash) {
// Add a little bit of randomness to each byte
for (var byteIdx = 0; byteIdx < passwordHash.length/2; byteIdx++) {
var byte = parseInt(passwordHash.substr(byteIdx*2,2),16);
// +/- 3, within 0-255
byte = Math.min(Math.max(byte + parseInt(Math.random()*6)-3,0),255);
var hexStr = byte.toString(16).length == 2 ? byte.toString(16) : '0' + byte.toString(16);
passwordHash = passwordHash.substr(0,byteIdx*2) + hexStr + passwordHash.substr(byteIdx*2+2);
}
return passwordHash;
}
if (!(numColorBars = Number(numColorBars)))
numColorBars = 4;
// Make sure there's enough data for the number of desired colorBars
numColorBars = Math.min(numColorBars,passwordHash.length/6);
var canvas = document.createElement('canvas');
canvas.height = inputHeight;
canvas.width = inputWidth;
var context = canvas.getContext('2d');
passwordHash = randomizeHash(passwordHash);
for (var hashBandX = 0; hashBandX < numColorBars; hashBandX++) {
context.fillStyle='#' + passwordHash.substr(hashBandX*6,6);
context.fillRect(hashBandX/numColorBars*inputWidth,0,inputWidth/numColorBars,inputHeight);
context.fillStyle='#000000';
context.fillRect(((hashBandX+1)/numColorBars*inputWidth)-1,0,2,inputHeight);
}
context.strokeStyle='#000000';
return canvas.toDataURL();
}
window.onload = init;

Просмотреть файл

@ -1,59 +1,101 @@
const VALID_USERNAME_INPUT_TYPES = ['text','email','url','tel','number'];
var forms = document.getElementsByTagName('form');
for (var formIdx = 0; formIdx < forms.length; formIdx++) {
console.log(forms[formIdx]);
forms[formIdx].addEventListener('submit', function() {
var newLoginObj = {
hostname: window.location.host,
formSubmitURL: this.action,
// Always null for logins extracted from HTML forms
httpRealm: null
};
var inputs = this.getElementsByTagName('input');
var inputsList = Array.prototype.slice.call(inputs);
var pwInputs = inputsList.filter(function(x) {
return x.type == 'password';
});
var pwInput = null;
(function() {
var forms = document.getElementsByTagName('form');
for (var formIdx = 0; formIdx < forms.length; formIdx++) {
console.log(forms[formIdx]);
forms[formIdx].addEventListener('submit', function() {
var newLoginObj = {
hostname: window.location.host,
formSubmitURL: this.action,
// Always null for logins extracted from HTML forms
httpRealm: null
};
var inputs = this.getElementsByTagName('input');
var inputsList = Array.prototype.slice.call(inputs);
if (pwInputs.length == 1) {
// Find the username input - the input before the password field
// of a valid type.
var pwInputIdx = inputsList.indexOf(pwInputs[0]);
var usernameInput = null;
for (var inputIdx = pwInputIdx-1; inputIdx >= 0; inputIdx--) {
if (VALID_USERNAME_INPUT_TYPES.indexOf(inputs[inputIdx].type) != -1) {
usernameInput = inputs[inputIdx];
break;
var pwFields = [];
for (var inputIdx = 0; inputIdx < inputsList.length; inputIdx++) {
if (inputsList[inputIdx].type == 'password') {
pwFields.push({
idx: inputIdx,
val: inputsList[inputIdx].value
});
}
}
// Couldn't find a valid username input field, so bail.
if (!usernameInput) {
return;
var usernameInput = null;
var pwInput = null;
if (pwFields.length == 1) {
// Find the username input - the input before the password field
// of a valid type.
usernameInput = getUsernameFieldForPasswordField(inputsList,pwFields[0].idx);
pwInput = inputsList[pwFields[0].idx];
}
else if (pwFields.length == 2) {
console.log('two pwinput fields!');
// Assume an account creation form. As long as the two passwords
// are equal, we should be good.
if (pwFields[0].val != pwFields[1].val) {
// Password inputs aren't equal, so bail.
return;
}
usernameInput = getUsernameFieldForPasswordField(inputsList,pwFields[0].idx);
pwInput = inputsList[pwFields[0].idx];
}
else if (pwFields.length == 3) {
// Assume a password change form. Look for two passwords that are equal.
var pwInputIdx = null;
for (var pwFieldIdx in pwFields) {
if (pwFields[0].val == pwFields[1].val) {
pwInputIdx = pwFields[0].idx;
}
else if (pwFields[1].val == pwFields[2].val) {
pwInputIdx = pwFields[1].idx;
}
else if (pwFields[0].val == pwFields[2].val) {
pwInputIdx = pwFields[0].idx;
}
}
// All password fields differ. No idea what's going on, so bail.
if (!pwInputIdx) return;
pwInput = inputsList[pwInputIdx];
usernameInput = getUsernameFieldForPasswordField(inputsList,pwInputIdx);
}
newLoginObj['username'] = usernameInput.value;
newLoginObj['password'] = pwInput.value;
newLoginObj['usernameField'] = getFieldDescriptor(usernameInput);
newLoginObj['passwordField'] = getFieldDescriptor(pwInput);
storeLogin(newLoginObj);
return false;
});
}
// Returns the username field from inputsList that is
// closest before the field at pwFieldIdx. Or null, if
// no valid field exists.
function getUsernameFieldForPasswordField(inputsList,pwFieldIdx) {
const VALID_USERNAME_INPUT_TYPES = ['text','email','url','tel','number'];
for (var inputIdx = pwFieldIdx-1; inputIdx >= 0; inputIdx--) {
if (VALID_USERNAME_INPUT_TYPES.indexOf(inputsList[inputIdx].type) != -1) {
return inputsList[inputIdx];
}
pwInput = pwInputs[0];
}
newLoginObj['username'] = usernameInput.value;
newLoginObj['password'] = pwInput.value;
newLoginObj['usernameField'] = getFieldDescriptor(usernameInput);
newLoginObj['passwordField'] = getFieldDescriptor(pwInput);
storeLogin(newLoginObj);
return false;
});
}
// Couldn't find a valid username input field.
return null;
}
function getFieldDescriptor(elem) {
return {
'name': elem.name,
'id': elem.id
};
}
function getFieldDescriptor(elem) {
return {
'name': elem.name,
'id': elem.id
};
}
function storeLogin(loginObj) {
//username,password,site,usernameElement,passwordElement
chrome.extension.sendMessage({
type: 'add_login',
message: loginObj
});
}
function storeLogin(loginObj) {
//username,password,site,usernameElement,passwordElement
chrome.extension.sendMessage({
type: 'add_login',
message: loginObj
});
}
})();

38
data/pwmgr.css Normal file
Просмотреть файл

@ -0,0 +1,38 @@
.pwmgr-alert {
left:0px;
top:0px;
position:fixed;
width:100%;
min-height:50px;
background-color:#E4EDF7;
border:2px #e0e0ff solid;
text-align:center;
z-index:1000000100;
opacity:0.7;
font-family:helvetica,sans-serif;
}
.pwmgr-alert:hover {
opacity:1.0;
}
.pwmgr-alert .pwmgr-close {
color:red;
float:right;
text-decoration:none;
margin-right:30px;
}
.pwmgr-close:hover {
background-color:red;
color:white;
}
.pwmgr-alert .pwmgr-alert-text {
padding:20px;
}

89
data/pwmgr_inject.js Normal file
Просмотреть файл

@ -0,0 +1,89 @@
(function() {
function createAlert(alertObj) {
var alertDiv = document.createElement('div');
alertDiv.className = "pwmgr-alert";
var alertMessage = document.createElement('div');
alertMessage.className = "pwmgr-alert-text";
alertMessage.appendChild(getAlertBody(alertObj));
var closeAlert = document.createElement('a');
closeAlert.href = "#";
closeAlert.className = 'pwmgr-close';
closeAlert.innerHTML = 'X';
closeAlert.addEventListener('click', function() {
document.body.removeChild(alertDiv);
});
alertDiv.appendChild(closeAlert);
alertDiv.appendChild(alertMessage);
return alertDiv;
}
function getAlertBody(alertObj) {
var bodyEl = document.createElement('div');
if (alertObj.type == 'password_saved') {
bodyEl.appendChild(document.createTextNode("SkyCrane saved login " + alertObj.username + ":"));
var passwordImg = document.createElement('img');
passwordImg.src = getDataURLForHash(alertObj.hash,50,13);
bodyEl.appendChild(passwordImg);
bodyEl.appendChild(document.createTextNode(" for site " + alertObj.hostname + "!"));
}
return bodyEl;
}
function displayAlert(alertObj) {
// document.body.appendChild(createAlert(alertObj));
document.body.insertBefore(createAlert(alertObj),document.body.children[0]);
}
chrome.extension.onMessage.addListener(function(msg) {
console.log('message recvd! ' + JSON.stringify(msg));
if (msg.type == 'notify' && msg.notification !== undefined) {
displayAlert(msg.notification);
}
});
function getDataURLForHash(passwordHash,inputWidth,inputHeight,numColorBars) {
function randomizeHash(passwordHash) {
// Add a little bit of randomness to each byte
for (var byteIdx = 0; byteIdx < passwordHash.length/2; byteIdx++) {
var byte = parseInt(passwordHash.substr(byteIdx*2,2),16);
// +/- 3, within 0-255
byte = Math.min(Math.max(byte + parseInt(Math.random()*6)-3,0),255);
var hexStr = byte.toString(16).length == 2 ? byte.toString(16) : '0' + byte.toString(16);
passwordHash = passwordHash.substr(0,byteIdx*2) + hexStr + passwordHash.substr(byteIdx*2+2);
}
return passwordHash;
}
if (!(numColorBars = Number(numColorBars)))
numColorBars = 4;
// Make sure there's enough data for the number of desired colorBars
numColorBars = Math.min(numColorBars,passwordHash.length/6);
var canvas = document.createElement('canvas');
canvas.height = inputHeight;
canvas.width = inputWidth;
var context = canvas.getContext('2d');
passwordHash = randomizeHash(passwordHash);
for (var hashBandX = 0; hashBandX < numColorBars; hashBandX++) {
context.fillStyle='#' + passwordHash.substr(hashBandX*6,6);
context.fillRect(hashBandX/numColorBars*inputWidth,0,inputWidth/numColorBars,inputHeight);
context.fillStyle='#000000';
context.fillRect(((hashBandX+1)/numColorBars*inputWidth)-1,0,2,inputHeight);
}
context.strokeStyle='#000000';
return canvas.toDataURL();
}
})();

473
lib/jsuri.js Normal file
Просмотреть файл

@ -0,0 +1,473 @@
/*!
* jsUri v1.1.1
* https://github.com/derek-watson/jsUri
*
* Copyright 2011, Derek Watson
* Released under the MIT license.
* http://jquery.org/license
*
* Includes parseUri regular expressions
* http://blog.stevenlevithan.com/archives/parseuri
* Copyright 2007, Steven Levithan
* Released under the MIT license.
*
* Date: Mon Nov 14 20:06:34 2011 -0800
*/
var Query = function (queryString) {
// query string parsing, parameter manipulation and stringification
'use strict';
var // parseQuery(q) parses the uri query string and returns a multi-dimensional array of the components
parseQuery = function (q) {
var arr = [], i, ps, p, keyval;
if (typeof (q) === 'undefined' || q === null || q === '') {
return arr;
}
if (q.indexOf('?') === 0) {
q = q.substring(1);
}
ps = q.toString().split(/[&;]/);
for (i = 0; i < ps.length; i++) {
p = ps[i];
keyval = p.split('=');
arr.push([keyval[0], keyval[1]]);
}
return arr;
},
params = parseQuery(queryString),
// toString() returns a string representation of the internal state of the object
toString = function () {
var s = '', i, param;
for (i = 0; i < params.length; i++) {
param = params[i];
if (s.length > 0) {
s += '&';
}
s += param.join('=');
}
return s.length > 0 ? '?' + s : s;
},
decode = function (s) {
s = decodeURIComponent(s);
s = s.replace('+', ' ');
return s;
},
// getParamValues(key) returns the first query param value found for the key 'key'
getParamValue = function (key) {
var param, i;
for (i = 0; i < params.length; i++) {
param = params[i];
if (decode(key) === decode(param[0])) {
return param[1];
}
}
},
// getParamValues(key) returns an array of query param values for the key 'key'
getParamValues = function (key) {
var arr = [], i, param;
for (i = 0; i < params.length; i++) {
param = params[i];
if (decode(key) === decode(param[0])) {
arr.push(param[1]);
}
}
return arr;
},
// deleteParam(key) removes all instances of parameters named (key)
// deleteParam(key, val) removes all instances where the value matches (val)
deleteParam = function (key, val) {
var arr = [], i, param, keyMatchesFilter, valMatchesFilter;
for (i = 0; i < params.length; i++) {
param = params[i];
keyMatchesFilter = decode(param[0]) === decode(key);
valMatchesFilter = decode(param[1]) === decode(val);
if ((arguments.length === 1 && !keyMatchesFilter) || (arguments.length === 2 && !keyMatchesFilter && !valMatchesFilter)) {
arr.push(param);
}
}
params = arr;
return this;
},
// addParam(key, val) Adds an element to the end of the list of query parameters
// addParam(key, val, index) adds the param at the specified position (index)
addParam = function (key, val, index) {
if (arguments.length === 3 && index !== -1) {
index = Math.min(index, params.length);
params.splice(index, 0, [key, val]);
} else if (arguments.length > 0) {
params.push([key, val]);
}
return this;
},
// replaceParam(key, newVal) deletes all instances of params named (key) and replaces them with the new single value
// replaceParam(key, newVal, oldVal) deletes only instances of params named (key) with the value (val) and replaces them with the new single value
// this function attempts to preserve query param ordering
replaceParam = function (key, newVal, oldVal) {
var index = -1, i, param;
if (arguments.length === 3) {
for (i = 0; i < params.length; i++) {
param = params[i];
if (decode(param[0]) === decode(key) && decodeURIComponent(param[1]) === decode(oldVal)) {
index = i;
break;
}
}
deleteParam(key, oldVal).addParam(key, newVal, index);
} else {
for (i = 0; i < params.length; i++) {
param = params[i];
if (decode(param[0]) === decode(key)) {
index = i;
break;
}
}
deleteParam(key);
addParam(key, newVal, index);
}
return this;
};
// public api
return {
getParamValue: getParamValue,
getParamValues: getParamValues,
deleteParam: deleteParam,
addParam: addParam,
replaceParam: replaceParam,
toString: toString
};
};
var Uri = function (uriString) {
// uri string parsing, attribute manipulation and stringification
'use strict';
/*global Query: true */
/*jslint regexp: false, plusplus: false */
var strictMode = false,
// parseUri(str) parses the supplied uri and returns an object containing its components
parseUri = function (str) {
/*jslint unparam: true */
var parsers = {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
},
keys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"],
q = {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
m = parsers[strictMode ? "strict" : "loose"].exec(str),
uri = {},
i = 14;
while (i--) {
uri[keys[i]] = m[i] || "";
}
uri[q.name] = {};
uri[keys[12]].replace(q.parser, function ($0, $1, $2) {
if ($1) {
uri[q.name][$1] = $2;
}
});
return uri;
},
uriParts = parseUri(uriString || ''),
queryObj = new Query(uriParts.query),
/*
Basic get/set functions for all properties
*/
protocol = function (val) {
if (typeof val !== 'undefined') {
uriParts.protocol = val;
}
return uriParts.protocol;
},
hasAuthorityPrefixUserPref = null,
// hasAuthorityPrefix: if there is no protocol, the leading // can be enabled or disabled
hasAuthorityPrefix = function (val) {
if (typeof val !== 'undefined') {
hasAuthorityPrefixUserPref = val;
}
if (hasAuthorityPrefixUserPref === null) {
return (uriParts.source.indexOf('//') !== -1);
} else {
return hasAuthorityPrefixUserPref;
}
},
userInfo = function (val) {
if (typeof val !== 'undefined') {
uriParts.userInfo = val;
}
return uriParts.userInfo;
},
host = function (val) {
if (typeof val !== 'undefined') {
uriParts.host = val;
}
return uriParts.host;
},
port = function (val) {
if (typeof val !== 'undefined') {
uriParts.port = val;
}
return uriParts.port;
},
path = function (val) {
if (typeof val !== 'undefined') {
uriParts.path = val;
}
return uriParts.path;
},
query = function (val) {
if (typeof val !== 'undefined') {
queryObj = new Query(val);
}
return queryObj;
},
anchor = function (val) {
if (typeof val !== 'undefined') {
uriParts.anchor = val;
}
return uriParts.anchor;
},
/*
Fluent setters for Uri uri properties
*/
setProtocol = function (val) {
protocol(val);
return this;
},
setHasAuthorityPrefix = function (val) {
hasAuthorityPrefix(val);
return this;
},
setUserInfo = function (val) {
userInfo(val);
return this;
},
setHost = function (val) {
host(val);
return this;
},
setPort = function (val) {
port(val);
return this;
},
setPath = function (val) {
path(val);
return this;
},
setQuery = function (val) {
query(val);
return this;
},
setAnchor = function (val) {
anchor(val);
return this;
},
/*
Query method wrappers
*/
getQueryParamValue = function (key) {
return query().getParamValue(key);
},
getQueryParamValues = function (key) {
return query().getParamValues(key);
},
deleteQueryParam = function (key, val) {
if (arguments.length === 2) {
query().deleteParam(key, val);
} else {
query().deleteParam(key);
}
return this;
},
addQueryParam = function (key, val, index) {
if (arguments.length === 3) {
query().addParam(key, val, index);
} else {
query().addParam(key, val);
}
return this;
},
replaceQueryParam = function (key, newVal, oldVal) {
if (arguments.length === 3) {
query().replaceParam(key, newVal, oldVal);
} else {
query().replaceParam(key, newVal);
}
return this;
},
/*
Serialization
*/
// toString() stringifies the current state of the uri
toString = function () {
var s = '',
is = function (s) {
return (s !== null && s !== '');
};
if (is(protocol())) {
s += protocol();
if (protocol().indexOf(':') !== protocol().length - 1) {
s += ':';
}
s += '//';
} else {
if (hasAuthorityPrefix() && is(host())) {
s += '//';
}
}
if (is(userInfo()) && is(host())) {
s += userInfo();
if (userInfo().indexOf('@') !== userInfo().length - 1) {
s += '@';
}
}
if (is(host())) {
s += host();
if (is(port())) {
s += ':' + port();
}
}
if (is(path())) {
s += path();
} else {
if (is(host()) && (is(query().toString()) || is(anchor()))) {
s += '/';
}
}
if (is(query().toString())) {
if (query().toString().indexOf('?') !== 0) {
s += '?';
}
s += query().toString();
}
if (is(anchor())) {
if (anchor().indexOf('#') !== 0) {
s += '#';
}
s += anchor();
}
return s;
},
/*
Cloning
*/
// clone() returns a new, identical Uri instance
clone = function () {
return new Uri(toString());
};
// public api
return {
protocol: protocol,
hasAuthorityPrefix: hasAuthorityPrefix,
userInfo: userInfo,
host: host,
port: port,
path: path,
query: query,
anchor: anchor,
setProtocol: setProtocol,
setHasAuthorityPrefix: setHasAuthorityPrefix,
setUserInfo: setUserInfo,
setHost: setHost,
setPort: setPort,
setPath: setPath,
setQuery: setQuery,
setAnchor: setAnchor,
getQueryParamValue: getQueryParamValue,
getQueryParamValues: getQueryParamValues,
deleteQueryParam: deleteQueryParam,
addQueryParam: addQueryParam,
replaceQueryParam: replaceQueryParam,
toString: toString,
clone: clone
};
};
/* add compatibility for users of jsUri <= 1.1.1 */
var jsUri = Uri;

Просмотреть файл

@ -1,10 +0,0 @@
<html>
<head>
<script src="https://login.persona.org/include.js"></script>
<script src="lib/jquery.js"></script>
<script src="login.js"></script>
</head>
<body>
<a href="#" id="login-link">login</a>
</body>
</html>

Просмотреть файл

@ -1,5 +0,0 @@
$(document).ready(function() {
$('#login-link').click(function() {
navigator.id.request();
});
});

78
main.js
Просмотреть файл

@ -4,17 +4,89 @@ var socket = io.connect(SKYCRANE_SERVER);
console.log('In chrome script');
// Maps a notificationID to the data extracted from it by an observer.
var loginData = {};
var lastNotificationID = 0;
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.create({
url: /*SKYCRANE_SERVER + "*/"login.html"
url: SKYCRANE_SERVER + "/persona_auth"
}, function(tab) {
chrome.tabs.executeScript(tab.id, {file: "data/auth_content_script.js"});
});
});
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (request.type == 'add_login') {
socket.emit('add_login',request.message);
// Prompt the user to save the login
displayNotification({
notify: true,
tabId: sender.tab.id,
notification: {
type: 'password_saved',
hash: SHA1(request.message.password),
hostname: request.message.hostname,
username: request.message.username
}
});
// socket.emit('add_login',request.message);
// chrome.tabs.get(sender.tab.id,function(tab) {
// });
console.log("add_login for tab: " + sender.tab.id);
}
else if (request.type == 'login_success') {
console.log('Successfully logged in with ',JSON.stringify(request.message));
}
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
});
});
function displayNotification(notificationObj) {
loginData[lastNotificationID] = notificationObj;
var notif = webkitNotifications.createHTMLNotification('data/notification.html#' + lastNotificationID);
notif.show();
loginData[lastNotificationID].popupNotifs = [notif];
lastNotificationID++;
}
function getNotificationForID(notifID) {
return loginData[notifID].notification;
}
function saveToStorage(newLogin) {
// TODO: Encrypt data, and send over chrome sync?
chrome.storage.local.get('logins', function(storageLogins) {
var siteName = request.message.hostname;
if (storageLogins.sites === undefined) storageLogins.sites = {};
if (storageLogins.sites[siteName] === undefined) storageLogins.sites[siteName] = [];
storageLogins.sites[siteName].push(request.message);
chrome.storage.local.set({'logins': storageLogins});
});
}
function saveLogin(notifID) {
console.log('saveLogin for notifID: ', notifID);
closeNotif(notifID);
}
function notNow(notifID) {
console.log('notNow for notifID: ', notifID);
closeNotif(notifID);
}
function neverForSite(notifID) {
console.log('neverForSite for notifID: ', notifID);
closeNotif(notifID);
}
function closeNotif(notifID) {
loginData[notifID].popupNotifs[0].close();
delete loginData[notifID];
}

Просмотреть файл

@ -5,15 +5,14 @@
"description": "Cross-platform password sync and management.",
"permissions": [
"tabs",
"notifications"
"notifications",
"storage",
"http://*/",
"https://*/"
],
"background": {
"scripts": ["lib/jquery.js","lib/socket.io.js","main.js"]
"scripts": ["lib/jquery.js","lib/jsuri.js","lib/socket.io.js","util.js","main.js"]
},
"permissions": [
"http://*/",
"https://*/"
],
"browser_action": {
"default_icon": "icons/small.png",
"default_title": "SkyCrane"
@ -21,9 +20,12 @@
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": [],
"js": ["data/observer.js"]
"css": ["data/pwmgr.css"],
"js": ["data/pwmgr_inject.js","data/observer.js"]
}
],
"content_security_policy": "script-src 'self' https://login.persona.org https://persona.org; object-src 'self'"
"content_security_policy": "script-src 'self' https://login.persona.org https://persona.org; object-src 'self'; style-src 'self';",
"web_accessible_resources": [
"icons/small.png"
]
}

Просмотреть файл

@ -1,39 +0,0 @@
//javascript:(function(){if(window.myBookmarklet!==undefined){myBookmarklet();}else{document.body.appendChild(document.createElement('script')).src='http://localhost:8000/public/bookmarklet.js';}})();
function initSkyCrane() {
$.ajax({
url: "http://localhost:8000/get_login/" + window.location.host,
dataType: 'json',
success: function(data) {
var usernameFieldName = data.data.usernameField.name;
var passwordFieldName = data.data.passwordField.name;
$('input[name="' + usernameFieldName + '"]').val(data.data.username);
$('input[name="' + passwordFieldName + '"]').val(data.data.password);
}
});
}
// Thanks, http://coding.smashingmagazine.com/2010/05/23/make-your-own-bookmarklets-with-jquery/
(function(){
// the minimum version of jQuery we want
var v = "1.3.2";
// check prior inclusion and version
if (window.jQuery === undefined || window.jQuery.fn.jquery < v) {
var done = false;
var script = document.createElement("script");
script.src = "http://ajax.googleapis.com/ajax/libs/jquery/" + v + "/jquery.min.js";
script.onload = script.onreadystatechange = function(){
if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
done = true;
initSkyCrane();
}
};
document.getElementsByTagName("head")[0].appendChild(script);
} else {
initSkyCrane();
}
})();

Двоичные данные
server/public/.DS_Store поставляемый Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,73 @@
// Include jquery and Persona
// Thanks, http://coding.smashingmagazine.com/2010/05/23/make-your-own-bookmarklets-with-jquery/
(function(){
var libsLoaded = {
'jquery': false,
'persona': false
};
function initIfReady() {
for (var lib in libsLoaded) {
if (!libsLoaded[lib]) return;
}
initSkyCrane();
// navigator.id.request();
appendPersonaButton();
}
function loadScript(libName,scriptURL) {
var script = document.createElement("script");
script.src = scriptURL;
script.onload = script.onreadystatechange = function(){
if (!this.readyState || this.readyState == "loaded" || this.readyState == "complete") {
libsLoaded[libName] = true;
initIfReady();
}
};
document.getElementsByTagName("head")[0].appendChild(script);
}
// the minimum version of jQuery we want
var v = "1.3.2";
// check prior inclusion and version
if (window.jQuery === undefined || window.jQuery.fn.jquery < v) {
loadScript("jquery","http://ajax.googleapis.com/ajax/libs/jquery/" + v + "/jquery.min.js");
} else {
libsLoaded.jquery = true;
initIfReady();
}
if (navigator.id === undefined) {
loadScript("persona","https://login.persona.org/include.js");
} else {
libsLoaded.persona = true;
initIfReady();
}
//javascript:(function(){if(window.myBookmarklet!==undefined){myBookmarklet();}else{document.body.appendChild(document.createElement('script')).src='http://localhost:8000/public/bookmarklet.js';}})();
function initSkyCrane() {
$.ajax({
url: "http://localhost:8000/get_login/" + window.location.host,
dataType: 'json',
success: function(data) {
var usernameFieldName = data.data.usernameField.name;
var passwordFieldName = data.data.passwordField.name;
$('input[name="' + usernameFieldName + '"]').val(data.data.username);
$('input[name="' + passwordFieldName + '"]').val(data.data.password);
}
});
}
function appendPersonaButton() {
var personaButton = $('<input type="submit" value="Login with Persona"/>');
personaButton.click(function() {
navigator.id.request();
});
$(document.body).append(personaButton);
}
})();

9404
server/public/lib/jquery.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

11
server/public/login.js Normal file
Просмотреть файл

@ -0,0 +1,11 @@
// $(document).ready(function() {
// navigator.id.watch({
// onlogin: function(assertion) {
//
// }
// });
//
// $('#login-link').click(function() {
// navigator.id.request();
// });
// });

Просмотреть файл

@ -1,8 +1,13 @@
var redis = require("redis"),
express = require('express'),
http = require('http');
http = require('http'),
https = require('https'),
browserid = require('express-browserid');
app = express();
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
server = http.createServer(app).listen(8000);
io = require('socket.io').listen(server);
@ -17,12 +22,40 @@ app.configure(function () {
next();
}
// Need to allow crossdomain access for bookmarklet to work
app.use(allowCrossDomain);
// Serve static files
app.use('/public',express.static('public'));
app.use(express.bodyParser());
var MemoryStore = require('connect').session.MemoryStore;
// TODO: Substitute actual session secret
app.use(express.cookieParser("seeekrit string"));
app.use(express.session());
// Install express-browserid routes
browserid.plugAll(app);
});
app.listen();
// Authenticate with Persona.
app.get('/persona_auth', function(req, res) {
res.render('auth');
});
app.get('/test_auth', function(req, res) {
if (req.session.email) {
res.send("Hello, " + req.session.email + "!");
}
else {
res.send("I don't know who you are.");
}
});
//////////////////////////////////////////////
app.get('/get_login/:host', function(req, res, next) {
client.hget('me@paulsawaya.com', req.params.host, function(err, data) {
res.send(JSON.stringify({

31
server/views/auth.ejs Normal file
Просмотреть файл

@ -0,0 +1,31 @@
<html>
<head>
<script src="https://login.persona.org/include.js"></script>
<script src="/public/lib/jquery.js"></script>
<script src="/public/login.js"></script>
<script>
$(document).ready(function() {
// TODO: Get express-browserid to load on document ready
var newScript = document.createElement('script');
newScript.src = '/browserid/js/browserid-helper.js';
$(newScript).attr('data-auto','false');
document.head.appendChild(newScript);
$(window).on('login-success', window.__authSuccess);
alert(window.__authSuccess);
});
</script>
<style>
a {
font-size:48pt;
}
</style>
</head>
<body>
<h1>Welcome to SkyCrane</h1>
<a href="#" id="browserid-login">login</a>
</body>
</html>

174
util.js Normal file
Просмотреть файл

@ -0,0 +1,174 @@
/**
*
* Secure Hash Algorithm (SHA1)
* http://www.webtoolkit.info/
*
*
**/
function SHA1 (msg) {
function rotate_left(n,s) {
var t4 = ( n<<s ) | (n>>>(32-s));
return t4;
};
function lsb_hex(val) {
var str="";
var i;
var vh;
var vl;
for( i=0; i<=6; i+=2 ) {
vh = (val>>>(i*4+4))&0x0f;
vl = (val>>>(i*4))&0x0f;
str += vh.toString(16) + vl.toString(16);
}
return str;
};
function cvt_hex(val) {
var str="";
var i;
var v;
for( i=7; i>=0; i-- ) {
v = (val>>>(i*4))&0x0f;
str += v.toString(16);
}
return str;
};
function Utf8Encode(string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
};
var blockstart;
var i, j;
var W = new Array(80);
var H0 = 0x67452301;
var H1 = 0xEFCDAB89;
var H2 = 0x98BADCFE;
var H3 = 0x10325476;
var H4 = 0xC3D2E1F0;
var A, B, C, D, E;
var temp;
msg = Utf8Encode(msg);
var msg_len = msg.length;
var word_array = new Array();
for( i=0; i<msg_len-3; i+=4 ) {
j = msg.charCodeAt(i)<<24 | msg.charCodeAt(i+1)<<16 |
msg.charCodeAt(i+2)<<8 | msg.charCodeAt(i+3);
word_array.push( j );
}
switch( msg_len % 4 ) {
case 0:
i = 0x080000000;
break;
case 1:
i = msg.charCodeAt(msg_len-1)<<24 | 0x0800000;
break;
case 2:
i = msg.charCodeAt(msg_len-2)<<24 | msg.charCodeAt(msg_len-1)<<16 | 0x08000;
break;
case 3:
i = msg.charCodeAt(msg_len-3)<<24 | msg.charCodeAt(msg_len-2)<<16 | msg.charCodeAt(msg_len-1)<<8 | 0x80;
break;
}
word_array.push( i );
while( (word_array.length % 16) != 14 ) word_array.push( 0 );
word_array.push( msg_len>>>29 );
word_array.push( (msg_len<<3)&0x0ffffffff );
for ( blockstart=0; blockstart<word_array.length; blockstart+=16 ) {
for( i=0; i<16; i++ ) W[i] = word_array[blockstart+i];
for( i=16; i<=79; i++ ) W[i] = rotate_left(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1);
A = H0;
B = H1;
C = H2;
D = H3;
E = H4;
for( i= 0; i<=19; i++ ) {
temp = (rotate_left(A,5) + ((B&C) | (~B&D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B,30);
B = A;
A = temp;
}
for( i=20; i<=39; i++ ) {
temp = (rotate_left(A,5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B,30);
B = A;
A = temp;
}
for( i=40; i<=59; i++ ) {
temp = (rotate_left(A,5) + ((B&C) | (B&D) | (C&D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B,30);
B = A;
A = temp;
}
for( i=60; i<=79; i++ ) {
temp = (rotate_left(A,5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B,30);
B = A;
A = temp;
}
H0 = (H0 + A) & 0x0ffffffff;
H1 = (H1 + B) & 0x0ffffffff;
H2 = (H2 + C) & 0x0ffffffff;
H3 = (H3 + D) & 0x0ffffffff;
H4 = (H4 + E) & 0x0ffffffff;
}
var temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);
return temp.toLowerCase();
}