Merge remote-tracking branch 'bitwarden/master'
This commit is contained in:
commit
54ba0cdce3
39
gulpfile.js
39
gulpfile.js
|
@ -9,7 +9,8 @@
|
|||
merge = require('merge-stream'),
|
||||
browserify = require('browserify'),
|
||||
source = require('vinyl-source-stream'),
|
||||
googleWebFonts = require('gulp-google-webfonts');
|
||||
googleWebFonts = require('gulp-google-webfonts'),
|
||||
webpack = require('webpack-stream');
|
||||
|
||||
var paths = {};
|
||||
paths.dist = './dist/';
|
||||
|
@ -29,7 +30,7 @@ gulp.task('lint', function () {
|
|||
gulp.task('build', function (cb) {
|
||||
return runSequence(
|
||||
'clean',
|
||||
['browserify', 'lib', 'less', 'lint', 'webfonts'],
|
||||
['browserify', 'webpack', 'lib', 'less', 'lint', 'webfonts'],
|
||||
cb);
|
||||
});
|
||||
|
||||
|
@ -91,10 +92,6 @@ gulp.task('lib', ['clean:lib'], function () {
|
|||
paths.npmDir + 'angular-toastr/dist/angular-toastr.css'],
|
||||
dest: paths.libDir + 'angular-toastr'
|
||||
},
|
||||
{
|
||||
src: [paths.npmDir + 'sjcl/core/cbc.js', paths.npmDir + 'sjcl/core/bitArray.js', paths.npmDir + 'sjcl/sjcl.js'],
|
||||
dest: paths.libDir + 'sjcl'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'ngclipboard/dist/ngclipboard.js',
|
||||
dest: paths.libDir + 'ngclipboard'
|
||||
|
@ -135,13 +132,41 @@ gulp.task('lib', ['clean:lib'], function () {
|
|||
return merge(tasks);
|
||||
});
|
||||
|
||||
gulp.task('browserify', function () {
|
||||
gulp.task('browserify', ['browserify:tldjs']);
|
||||
|
||||
gulp.task('browserify:tldjs', function () {
|
||||
return browserify(paths.npmDir + 'tldjs/index.js', { standalone: 'tldjs' })
|
||||
.bundle()
|
||||
.pipe(source('tld.js'))
|
||||
.pipe(gulp.dest(paths.libDir + 'tldjs'));
|
||||
});
|
||||
|
||||
gulp.task('webpack', ['webpack:forge']);
|
||||
|
||||
gulp.task('webpack:forge', function () {
|
||||
var forgeDir = paths.npmDir + '/node-forge/lib/';
|
||||
|
||||
return gulp.src([
|
||||
forgeDir + 'pbkdf2.js',
|
||||
forgeDir + 'aes.js',
|
||||
forgeDir + 'hmac.js',
|
||||
forgeDir + 'sha256.js',
|
||||
forgeDir + 'random.js',
|
||||
forgeDir + 'forge.js'
|
||||
]).pipe(webpack({
|
||||
output: {
|
||||
filename: 'forge.js',
|
||||
library: 'forge',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
node: {
|
||||
Buffer: false,
|
||||
process: false,
|
||||
crypto: false,
|
||||
setImmediate: false
|
||||
}
|
||||
})).pipe(gulp.dest(paths.libDir + 'forge'));
|
||||
});
|
||||
|
||||
gulp.task('less', function () {
|
||||
return gulp.src(paths.lessDir + 'popup.less')
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"name": "bitwarden",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"sjcl": "1.0.6",
|
||||
"jquery": "2.2.4",
|
||||
"bootstrap": "3.3.7",
|
||||
"font-awesome": "4.7.0",
|
||||
|
@ -33,6 +32,8 @@
|
|||
"browserify": "13.1.1",
|
||||
"vinyl-source-stream": "1.1.0",
|
||||
"gulp-google-webfonts": "0.0.14",
|
||||
"ng-infinite-scroll": "1.3.0"
|
||||
"ng-infinite-scroll": "1.3.0",
|
||||
"node-forge": "0.7.0",
|
||||
"webpack-stream": "3.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
"description": "Close"
|
||||
},
|
||||
"submit": {
|
||||
"message": "Lähetä",
|
||||
"message": "Jatka",
|
||||
"description": "Submit"
|
||||
},
|
||||
"emailAddress": {
|
||||
|
@ -600,7 +600,7 @@
|
|||
"description": "Are you sure you want to overwrite the current password?"
|
||||
},
|
||||
"lost2FAApp": {
|
||||
"message": "Ei pääsyä tunnistautumispalveluun?",
|
||||
"message": "Etkö pääse tunnistautumispalveluun?",
|
||||
"description": "Lost authenticator app?"
|
||||
},
|
||||
"credits": {
|
||||
|
|
|
@ -5,14 +5,15 @@ var constantsService = new ConstantsService();
|
|||
var utilsService = new UtilsService();
|
||||
var cryptoService = new CryptoService(constantsService);
|
||||
var tokenService = new TokenService();
|
||||
var apiService = new ApiService(tokenService);
|
||||
var appIdService = new AppIdService();
|
||||
var apiService = new ApiService(tokenService, appIdService, utilsService, logout);
|
||||
var userService = new UserService(tokenService, apiService, cryptoService);
|
||||
var loginService = new LoginService(cryptoService, userService, apiService);
|
||||
var settingsService = new SettingsService(userService);
|
||||
var loginService = new LoginService(cryptoService, userService, apiService, settingsService);
|
||||
var folderService = new FolderService(cryptoService, userService, apiService);
|
||||
var syncService = new SyncService(loginService, folderService, userService, apiService);
|
||||
var syncService = new SyncService(loginService, folderService, userService, apiService, settingsService);
|
||||
var autofillService = new AutofillService();
|
||||
var passwordGenerationService = new PasswordGenerationService();
|
||||
var appIdService = new AppIdService();
|
||||
|
||||
chrome.commands.onCommand.addListener(function (command) {
|
||||
if (command === 'generate_password') {
|
||||
|
@ -27,27 +28,20 @@ chrome.commands.onCommand.addListener(function (command) {
|
|||
}
|
||||
});
|
||||
|
||||
var loadMenuRan = false,
|
||||
loginToAutoFill = null,
|
||||
var loginToAutoFill = null,
|
||||
pageDetailsToAutoFill = [],
|
||||
autofillTimeout = null;
|
||||
autofillTimeout = null,
|
||||
menuOptionsLoaded = [];
|
||||
|
||||
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
|
||||
if (msg.command === 'loggedOut' || msg.command === 'loggedIn' || msg.command === 'unlocked' || msg.command === 'locked') {
|
||||
if (loadMenuRan) {
|
||||
return;
|
||||
}
|
||||
loadMenuRan = true;
|
||||
|
||||
if (msg.command === 'loggedIn' || msg.command === 'unlocked' || msg.command === 'locked') {
|
||||
setIcon();
|
||||
refreshBadgeAndMenu();
|
||||
}
|
||||
else if (msg.command === 'logout') {
|
||||
logout(msg.expired, function () { });
|
||||
}
|
||||
else if (msg.command === 'syncCompleted' && msg.successfully) {
|
||||
if (loadMenuRan) {
|
||||
return;
|
||||
}
|
||||
loadMenuRan = true;
|
||||
|
||||
setTimeout(refreshBadgeAndMenu, 2000);
|
||||
}
|
||||
else if (msg.command === 'bgOpenOverlayPopup') {
|
||||
|
@ -75,7 +69,6 @@ chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
|
|||
saveAddLogin(sender.tab);
|
||||
}
|
||||
else if (msg.command === 'collectPageDetailsResponse') {
|
||||
// messageCurrentTab('openNotificationBar', { type: 'add', typeData: null });
|
||||
if (msg.contentScript) {
|
||||
var forms = autofillService.getFormsWithPasswordFields(msg.details);
|
||||
messageTab(msg.tabId, 'pageDetails', { details: msg.details, forms: forms });
|
||||
|
@ -123,7 +116,13 @@ if (chrome.runtime.onInstalled) {
|
|||
});
|
||||
}
|
||||
|
||||
var buildingContextMenu = false;
|
||||
function buildContextMenu(callback) {
|
||||
if (buildingContextMenu) {
|
||||
return;
|
||||
}
|
||||
buildingContextMenu = true;
|
||||
|
||||
chrome.contextMenus.removeAll(function () {
|
||||
chrome.contextMenus.create({
|
||||
type: 'normal',
|
||||
|
@ -138,6 +137,15 @@ function buildContextMenu(callback) {
|
|||
contexts: ['all'],
|
||||
title: i18nService.autoFill
|
||||
}, function () {
|
||||
if (utilsService.isFirefox()) {
|
||||
// Firefox does not support writing to the clipboard from background
|
||||
buildingContextMenu = false;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.contextMenus.create({
|
||||
type: 'normal',
|
||||
id: 'copy-username',
|
||||
|
@ -164,6 +172,7 @@ function buildContextMenu(callback) {
|
|||
contexts: ['all'],
|
||||
title: i18nService.generatePasswordCopied
|
||||
}, function () {
|
||||
buildingContextMenu = false;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
@ -176,7 +185,6 @@ function buildContextMenu(callback) {
|
|||
}
|
||||
|
||||
chrome.tabs.onActivated.addListener(function (activeInfo) {
|
||||
checkLoginsToAdd();
|
||||
refreshBadgeAndMenu();
|
||||
});
|
||||
|
||||
|
@ -200,8 +208,16 @@ chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
|
|||
refreshBadgeAndMenu();
|
||||
});
|
||||
|
||||
chrome.windows.onFocusChanged.addListener(function (windowId) {
|
||||
if (windowId === null || windowId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
refreshBadgeAndMenu();
|
||||
});
|
||||
|
||||
function refreshBadgeAndMenu() {
|
||||
chrome.tabs.query({ active: true }, function (tabs) {
|
||||
chrome.tabs.query({ active: true, windowId: chrome.windows.WINDOW_ID_CURRENT }, function (tabs) {
|
||||
var tab = null;
|
||||
if (tabs.length > 0) {
|
||||
tab = tabs[0];
|
||||
|
@ -213,7 +229,7 @@ function refreshBadgeAndMenu() {
|
|||
|
||||
buildContextMenu(function () {
|
||||
loadMenuAndUpdateBadge(tab.url, tab.id, true);
|
||||
onUpdatedRan = onReplacedRan = loadMenuRan = false;
|
||||
onUpdatedRan = onReplacedRan = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -230,10 +246,12 @@ function loadMenuAndUpdateBadge(url, tabId, loadContextMenuOptions) {
|
|||
|
||||
chrome.browserAction.setBadgeBackgroundColor({ color: '#294e5f' });
|
||||
|
||||
menuOptionsLoaded = [];
|
||||
loginService.getAllDecryptedForDomain(tabDomain).then(function (logins) {
|
||||
sortLogins(logins);
|
||||
for (var i = 0; i < logins.length; i++) {
|
||||
if (loadContextMenuOptions) {
|
||||
|
||||
if (loadContextMenuOptions) {
|
||||
for (var i = 0; i < logins.length; i++) {
|
||||
loadLoginContextMenuOptions(logins[i]);
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +349,7 @@ function messageCurrentTab(command, data) {
|
|||
});
|
||||
}
|
||||
|
||||
function messageTab(tabId, command, data) {
|
||||
function messageTab(tabId, command, data, callback) {
|
||||
if (!tabId) {
|
||||
return;
|
||||
}
|
||||
|
@ -344,11 +362,16 @@ function messageTab(tabId, command, data) {
|
|||
obj['data'] = data;
|
||||
}
|
||||
|
||||
chrome.tabs.sendMessage(tabId, obj);
|
||||
chrome.tabs.sendMessage(tabId, obj, function () {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function collectPageDetailsForContentScript(tab) {
|
||||
chrome.tabs.sendMessage(tab.id, { command: 'collectPageDetails', tabId: tab.id, contentScript: true }, function () { });
|
||||
chrome.tabs.sendMessage(tab.id, { command: 'collectPageDetails', tabId: tab.id, contentScript: true }, function () {
|
||||
});
|
||||
}
|
||||
|
||||
function addLogin(login, tab) {
|
||||
|
@ -374,6 +397,7 @@ function addLogin(login, tab) {
|
|||
username: login.username,
|
||||
password: login.password,
|
||||
name: loginDomain,
|
||||
domain: loginDomain,
|
||||
uri: login.url,
|
||||
tabId: tab.id,
|
||||
expires: new Date((new Date()).getTime() + 30 * 60000) // 30 minutes
|
||||
|
@ -387,7 +411,7 @@ cleanupLoginsToAdd();
|
|||
setInterval(cleanupLoginsToAdd, 2 * 60 * 1000); // check every 2 minutes
|
||||
function cleanupLoginsToAdd() {
|
||||
var now = new Date();
|
||||
for (var i = loginsToAdd.length - 1; i >= 0 ; i--) {
|
||||
for (var i = loginsToAdd.length - 1; i >= 0; i--) {
|
||||
if (loginsToAdd[i].expires < now) {
|
||||
loginsToAdd.splice(i, 1);
|
||||
}
|
||||
|
@ -395,7 +419,7 @@ function cleanupLoginsToAdd() {
|
|||
}
|
||||
|
||||
function removeAddLogin(tab) {
|
||||
for (var i = loginsToAdd.length - 1; i >= 0 ; i--) {
|
||||
for (var i = loginsToAdd.length - 1; i >= 0; i--) {
|
||||
if (loginsToAdd[i].tabId === tab.id) {
|
||||
loginsToAdd.splice(i, 1);
|
||||
}
|
||||
|
@ -403,35 +427,42 @@ function removeAddLogin(tab) {
|
|||
}
|
||||
|
||||
function saveAddLogin(tab) {
|
||||
for (var i = loginsToAdd.length - 1; i >= 0 ; i--) {
|
||||
for (var i = loginsToAdd.length - 1; i >= 0; i--) {
|
||||
if (loginsToAdd[i].tabId === tab.id) {
|
||||
var loginToAdd = loginsToAdd[i];
|
||||
loginsToAdd.splice(i, 1);
|
||||
loginService.encrypt({
|
||||
id: null,
|
||||
folderId: null,
|
||||
favorite: false,
|
||||
name: loginToAdd.name,
|
||||
uri: loginToAdd.uri,
|
||||
username: loginToAdd.username,
|
||||
password: loginToAdd.password,
|
||||
notes: null
|
||||
}).then(function (loginModel) {
|
||||
var login = new Login(loginModel, true);
|
||||
loginService.saveWithServer(login).then(function (login) {
|
||||
ga('send', {
|
||||
hitType: 'event',
|
||||
eventAction: 'Added Login from Notification Bar'
|
||||
|
||||
var tabDomain = tldjs.getDomain(tab.url);
|
||||
if (tabDomain && tabDomain === loginToAdd.domain) {
|
||||
loginsToAdd.splice(i, 1);
|
||||
loginService.encrypt({
|
||||
id: null,
|
||||
folderId: null,
|
||||
favorite: false,
|
||||
name: loginToAdd.name,
|
||||
uri: loginToAdd.uri,
|
||||
username: loginToAdd.username,
|
||||
password: loginToAdd.password,
|
||||
notes: null
|
||||
}).then(function (loginModel) {
|
||||
var login = new Login(loginModel, true);
|
||||
loginService.saveWithServer(login).then(function (login) {
|
||||
ga('send', {
|
||||
hitType: 'event',
|
||||
eventAction: 'Added Login from Notification Bar'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
messageTab(tab.id, 'closeNotificationBar');
|
||||
messageTab(tab.id, 'closeNotificationBar');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkLoginsToAdd(tab) {
|
||||
function checkLoginsToAdd(tab, callback) {
|
||||
if (!loginsToAdd.length) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -449,18 +480,29 @@ function checkLoginsToAdd(tab) {
|
|||
|
||||
function check() {
|
||||
if (!tab) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var tabDomain = tldjs.getDomain(tab.url);
|
||||
if (!tabDomain) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < loginsToAdd.length; i++) {
|
||||
// loginsToAdd[x].name is the domain here
|
||||
if (loginsToAdd[i].tabId === tab.id && loginsToAdd[i].name === tabDomain) {
|
||||
messageTab(tab.id, 'openNotificationBar', { type: 'add' });
|
||||
if (loginsToAdd[i].tabId === tab.id && loginsToAdd[i].domain === tabDomain) {
|
||||
messageTab(tab.id, 'openNotificationBar', {
|
||||
type: 'add'
|
||||
}, function () {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -482,7 +524,8 @@ function startAutofillPage(login) {
|
|||
return;
|
||||
}
|
||||
|
||||
chrome.tabs.sendMessage(tabId, { command: 'collectPageDetails', tabId: tabId }, function () { });
|
||||
chrome.tabs.sendMessage(tabId, { command: 'collectPageDetails', tabId: tabId }, function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -552,6 +595,11 @@ function loadNoLoginsContextMenuOptions(noLoginsMessage) {
|
|||
}
|
||||
|
||||
function loadContextMenuOptions(title, idSuffix, login) {
|
||||
if (menuOptionsLoaded.indexOf(idSuffix) > -1) {
|
||||
return;
|
||||
}
|
||||
menuOptionsLoaded.push(idSuffix);
|
||||
|
||||
if (!login || (login.password && login.password !== '')) {
|
||||
chrome.contextMenus.create({
|
||||
type: 'normal',
|
||||
|
@ -562,6 +610,11 @@ function loadContextMenuOptions(title, idSuffix, login) {
|
|||
});
|
||||
}
|
||||
|
||||
if (utilsService.isFirefox()) {
|
||||
// Firefox does not support writing to the clipboard from background
|
||||
return;
|
||||
}
|
||||
|
||||
if (!login || (login.username && login.username !== '')) {
|
||||
chrome.contextMenus.create({
|
||||
type: 'normal',
|
||||
|
@ -583,6 +636,34 @@ function loadContextMenuOptions(title, idSuffix, login) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Fix callback hell by moving to promises
|
||||
function logout(expired, callback) {
|
||||
userService.getUserId(function (userId) {
|
||||
syncService.setLastSync(new Date(0), function () {
|
||||
settingsService.clear(function () {
|
||||
tokenService.clearToken(function () {
|
||||
cryptoService.clearKey(function () {
|
||||
cryptoService.clearKeyHash(function () {
|
||||
userService.clearUserIdAndEmail(function () {
|
||||
loginService.clear(userId, function () {
|
||||
folderService.clear(userId, function () {
|
||||
chrome.runtime.sendMessage({
|
||||
command: 'doneLoggingOut', expired: expired
|
||||
});
|
||||
setIcon();
|
||||
refreshBadgeAndMenu();
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
if (window.clipboardData && window.clipboardData.setData) {
|
||||
// IE specific code path to prevent textarea being shown while dialog is visible.
|
||||
|
@ -617,10 +698,12 @@ setInterval(fullSync, 5 * 60 * 1000); // check every 5 minutes
|
|||
var syncInternal = 6 * 60 * 60 * 1000; // 6 hours
|
||||
|
||||
function fullSync(override) {
|
||||
override = override || false;
|
||||
syncService.getLastSync(function (lastSync) {
|
||||
var now = new Date();
|
||||
if (override || !lastSync || (now - lastSync) >= syncInternal) {
|
||||
syncService.fullSync(function () { });
|
||||
syncService.fullSync(override || false, function () {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
!(function() {
|
||||
!(function () {
|
||||
/*
|
||||
1Password Extension
|
||||
|
||||
|
@ -28,55 +28,928 @@
|
|||
SOFTWARE.
|
||||
*/
|
||||
|
||||
// MODIFICATIONS: populate isFirefox. Remove isChrome and isSafari since they are not used.
|
||||
/*
|
||||
MODIFICATIONS FROM ORIGINAL
|
||||
|
||||
1. Populate isFirefox
|
||||
2. Remove isChrome and isSafari since they are not used.
|
||||
3. Unminify and format to meet Mozilla review requirements.
|
||||
*/
|
||||
|
||||
function collect(document, undefined) {
|
||||
var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1;
|
||||
|
||||
document.elementsByOPID={};document.addEventListener('input',function(b){!1!==b.a&&'input'===b.target.tagName.toLowerCase()&&(b.target.dataset['com.agilebits.onepassword.userEdited']='yes')},!0);
|
||||
function q(b,d){function f(a,e){var c=a[e];if('string'==typeof c)return c;c=a.getAttribute(e);return'string'==typeof c?c:null}function h(a,e){if(-1===['text','password'].indexOf(e.type.toLowerCase())||!(m.test(a.value)||m.test(a.htmlID)||m.test(a.htmlName)||m.test(a.placeholder)||m.test(a['label-tag'])||m.test(a['label-data'])||m.test(a['label-aria'])))return!1;if(!a.visible)return!0;if('password'==e.type.toLowerCase())return!1;var c=e.type;v(e,!0);return c!==e.type}function n(a){switch(p(a.type)){case 'checkbox':return a.checked?
|
||||
'✓':'';case 'hidden':a=a.value;if(!a||'number'!=typeof a.length)return'';254<a.length&&(a=a.substr(0,254)+'...SNIPPED');return a;default:return a.value}}function l(a){return a.options?(a=Array.prototype.slice.call(a.options).map(function(a){var c=a.text,c=c?p(c).replace(/\\s/mg,'').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\?]/mg,''):null;return[c?c:null,a.value]}),{options:a}):null}function r(a){var e;for(a=a.parentElement||a.parentNode;a&&'td'!=p(a.tagName);)a=a.parentElement||a.parentNode;if(!a||
|
||||
void 0===a)return null;e=a.parentElement||a.parentNode;if('tr'!=e.tagName.toLowerCase())return null;e=e.previousElementSibling;if(!e||'tr'!=(e.tagName+'').toLowerCase()||e.cells&&a.cellIndex>=e.cells.length)return null;a=e.cells[a.cellIndex];a=a.textContent||a.innerText;return a=x(a)}function s(a){var e,c=[];if(a.labels&&a.labels.length&&0<a.labels.length)c=Array.prototype.slice.call(a.labels);else{a.id&&(c=c.concat(Array.prototype.slice.call(w(b,'label[for='+JSON.stringify(a.id)+']'))));if(a.name){e=
|
||||
w(b,'label[for='+JSON.stringify(a.name)+']');for(var f=0;f<e.length;f++)-1===c.indexOf(e[f])&&c.push(e[f])}for(e=a;e&&e!=b;e=e.parentNode)'label'===p(e.tagName)&&-1===c.indexOf(e)&&c.push(e)}0===c.length&&(e=a.parentNode,'dd'===e.tagName.toLowerCase()&&null!==e.previousElementSibling&&'dt'===e.previousElementSibling.tagName.toLowerCase()&&c.push(e.previousElementSibling));return 0<c.length?c.map(function(a){return(a.textContent||a.innerText).replace(/^\\s+/,'').replace(/\\s+$/,'').replace('\\n','').replace(/\\s{2,}/,
|
||||
' ')}).join(''):null}function g(a,e,c,b){void 0!==b&&b===c||null===c||void 0===c||(a[e]=c)}function p(a){return'string'===typeof a?a.toLowerCase():(''+a).toLowerCase()}function w(a,b){var c=[];try{c=a.querySelectorAll(b)}catch(f){}return c}var t=b.defaultView?b.defaultView:window,m=RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|(\\\\b|_|-)passe(\\\\b|_|-)|contraseña|senha|密码|adgangskode|hasło|wachtwoord)','i'),u=Array.prototype.slice.call(w(b,'form')).map(function(a,b){var c={},d='__form__'+
|
||||
b;a.opid=d;c.opid=d;g(c,'htmlName',f(a,'name'));g(c,'htmlID',f(a,'id'));d=f(a,'action');d=new URL(d,window.location.href);g(c,'htmlAction',d?d.href:null);g(c,'htmlMethod',f(a,'method'));return c}),E=Array.prototype.slice.call(y(b)).map(function(a,e){var c={},d='__'+e,k=-1==a.maxLength?999:a.maxLength;if(!k||'number'===typeof k&&isNaN(k))k=999;b.elementsByOPID[d]=a;a.opid=d;c.opid=d;c.elementNumber=e;g(c,'maxLength',Math.min(k,999),999);c.visible=z(a);c.viewable=A(a);g(c,'htmlID',f(a,'id'));g(c,'htmlName',
|
||||
f(a,'name'));g(c,'htmlClass',f(a,'class'));g(c,'tabindex',f(a,'tabindex'));g(c,'title',f(a,'title'));g(c,'userEdited',!!a.dataset['com.agilebits.onepassword.userEdited']);if('hidden'!=p(a.type)){g(c,'label-tag',s(a));g(c,'label-data',f(a,'data-label'));g(c,'label-aria',f(a,'aria-label'));g(c,'label-top',r(a));d=[];for(k=a;k&&k.nextSibling;){k=k.nextSibling;if(B(k))break;C(d,k)}g(c,'label-right',d.join(''));d=[];D(a,d);d=d.reverse().join('');g(c,'label-left',d);g(c,'placeholder',f(a,'placeholder'))}g(c,
|
||||
'rel',f(a,'rel'));g(c,'type',p(f(a,'type')));g(c,'value',n(a));g(c,'checked',a.checked,!1);g(c,'autoCompleteType',a.getAttribute('x-autocompletetype')||a.getAttribute('autocompletetype')||a.getAttribute('autocomplete'),'off');g(c,'disabled',a.disabled);g(c,'readonly',a.b||a.readOnly);g(c,'selectInfo',l(a));g(c,'aria-hidden','true'==a.getAttribute('aria-hidden'),!1);g(c,'aria-disabled','true'==a.getAttribute('aria-disabled'),!1);g(c,'aria-haspopup','true'==a.getAttribute('aria-haspopup'),!1);g(c,'data-unmasked',
|
||||
a.dataset.unmasked);g(c,'data-stripe',f(a,'data-stripe'));g(c,'onepasswordFieldType',a.dataset.onepasswordFieldType||a.type);g(c,'onepasswordDesignation',a.dataset.onepasswordDesignation);g(c,'onepasswordSignInUrl',a.dataset.onepasswordSignInUrl);g(c,'onepasswordSectionTitle',a.dataset.onepasswordSectionTitle);g(c,'onepasswordSectionFieldKind',a.dataset.onepasswordSectionFieldKind);g(c,'onepasswordSectionFieldTitle',a.dataset.onepasswordSectionFieldTitle);g(c,'onepasswordSectionFieldValue',a.dataset.onepasswordSectionFieldValue);
|
||||
a.form&&(c.form=f(a.form,'opid'));g(c,'fakeTested',h(c,a),!1);return c});E.filter(function(a){return a.fakeTested}).forEach(function(a){var e=b.elementsByOPID[a.opid];e.getBoundingClientRect();var c=e.value;!e||e&&'function'!==typeof e.click||e.click();v(e,!1);e.dispatchEvent(F(e,'keydown'));e.dispatchEvent(F(e,'keypress'));e.dispatchEvent(F(e,'keyup'));e.value!==c&&(e.value=c);e.click&&e.click();a.postFakeTestVisible=z(e);a.postFakeTestViewable=A(e);a.postFakeTestType=e.type;a=e.value;var c=e.ownerDocument.createEvent('HTMLEvents'),
|
||||
d=e.ownerDocument.createEvent('HTMLEvents');e.dispatchEvent(F(e,'keydown'));e.dispatchEvent(F(e,'keypress'));e.dispatchEvent(F(e,'keyup'));d.initEvent('input',!0,!0);e.dispatchEvent(d);c.initEvent('change',!0,!0);e.dispatchEvent(c);e.blur();e.value!==a&&(e.value=a)});t={documentUUID:d,title:b.title,url:t.location.href,documentUrl:b.location.href,tabUrl:t.location.href,forms:function(a){var b={};a.forEach(function(a){b[a.opid]=a});return b}(u),fields:E,collectedTimestamp:(new Date).getTime()};(u=document.querySelector('[data-onepassword-title]'))&&
|
||||
u.dataset[DISPLAY_TITLE_ATTRIBUE]&&(t.displayTitle=u.dataset.onepasswordTitle);return t};document.elementForOPID=G;function F(b,d){var f;isFirefox?(f=document.createEvent('KeyboardEvent'),f.initKeyEvent(d,!0,!1,null,!1,!1,!1,!1,0,0)):(f=b.ownerDocument.createEvent('Events'),f.initEvent(d,!0,!1),f.charCode=0,f.keyCode=0,f.which=0,f.srcElement=b,f.target=b);return f}window.LOGIN_TITLES=[/^\\W*log\\W*[oi]n\\W*$/i,/log\\W*[oi]n (?:securely|now)/i,/^\\W*sign\\W*[oi]n\\W*$/i,'continue','submit','weiter','accès','вход','connexion','entrar','anmelden','accedi','valider','登录','लॉग इन करें','change password'];
|
||||
window.LOGIN_RED_HERRING_TITLES=['already have an account','sign in with'];window.REGISTER_TITLES='register;sign up;signup;join;create my account;регистрация;inscription;regístrate;cadastre-se;registrieren;registrazione;注册;साइन अप करें'.split(';');window.SEARCH_TITLES='search find поиск найти искать recherche suchen buscar suche ricerca procurar 検索'.split(' ');window.FORGOT_PASSWORD_TITLES='forgot geändert vergessen hilfe changeemail español'.split(' ');
|
||||
window.REMEMBER_ME_TITLES=['remember me','rememberme','keep me signed in'];window.BACK_TITLES=['back','назад'];function x(b){var d=null;b&&(d=b.replace(/^\\s+|\\s+$|\\r?\\n.*$/mg,''),d=0<d.length?d:null);return d}function C(b,d){var f;f='';3===d.nodeType?f=d.nodeValue:1===d.nodeType&&(f=d.textContent||d.innerText);(f=x(f))&&b.push(f)}
|
||||
function B(b){var d;b&&void 0!==b?(d='select option input form textarea button table iframe body head script'.split(' '),b?(b=b?(b.tagName||'').toLowerCase():'',d=d.constructor==Array?0<=d.indexOf(b):b===d):d=!1):d=!0;return d}
|
||||
function D(b,d,f){var h;for(f||(f=0);b&&b.previousSibling;){b=b.previousSibling;if(B(b))return;C(d,b)}if(b&&0===d.length){for(h=null;!h;){b=b.parentElement||b.parentNode;if(!b)return;for(h=b.previousSibling;h&&!B(h)&&h.lastChild;)h=h.lastChild}B(h)||(C(d,h),0===d.length&&D(h,d,f+1))}}
|
||||
function z(b){var d=b;b=(b=b.ownerDocument)?b.defaultView:{};for(var f;d&&d!==document;){f=b.getComputedStyle?b.getComputedStyle(d,null):d.style;if(!f)return!0;if('none'===f.display||'hidden'==f.visibility)return!1;d=d.parentNode}return d===document}
|
||||
function A(b){var d=b.ownerDocument.documentElement,f=b.getBoundingClientRect(),h=d.scrollWidth,n=d.scrollHeight,l=f.left-d.clientLeft,d=f.top-d.clientTop,r;if(!z(b)||!b.offsetParent||10>b.clientWidth||10>b.clientHeight)return!1;var s=b.getClientRects();if(0===s.length)return!1;for(var g=0;g<s.length;g++)if(r=s[g],r.left>h||0>r.right)return!1;if(0>l||l>h||0>d||d>n)return!1;for(f=b.ownerDocument.elementFromPoint(l+(f.right>window.innerWidth?(window.innerWidth-l)/2:f.width/2),d+(f.bottom>window.innerHeight?
|
||||
(window.innerHeight-d)/2:f.height/2));f&&f!==b&&f!==document;){if(f.tagName&&'string'===typeof f.tagName&&'label'===f.tagName.toLowerCase()&&b.labels&&0<b.labels.length)return 0<=Array.prototype.slice.call(b.labels).indexOf(f);f=f.parentNode}return f===b}
|
||||
function G(b){var d;if(void 0===b||null===b)return null;try{var f=Array.prototype.slice.call(y(document)),h=f.filter(function(d){return d.opid==b});if(0<h.length)d=h[0],1<h.length&&console.warn('More than one element found with opid '+b);else{var n=parseInt(b.split('__')[1],10);isNaN(n)||(d=f[n])}}catch(l){console.error('An unexpected error occurred: '+l)}finally{return d}};function y(b){var d=[];try{d=b.querySelectorAll('input, select, button')}catch(f){}return d}function v(b,d){if(d){var f=b.value;b.focus();b.value!==f&&(b.value=f)}else b.focus()};
|
||||
return JSON.stringify(q(document, 'oneshotUUID'));
|
||||
document.elementsByOPID = {};
|
||||
document.addEventListener('input', function (canuf) {
|
||||
false !== canuf.a && 'input' === canuf.target.tagName.toLowerCase() && (canuf.target.dataset['com.agilebits.onepassword.userEdited'] = 'yes');
|
||||
}, true);
|
||||
|
||||
function getPageDetails(theDoc, oneShotId) {
|
||||
// start helpers
|
||||
|
||||
// get the value of a dom element's attribute
|
||||
function getElementAttrValue(el, attrName) {
|
||||
var attrVal = el[attrName];
|
||||
if ('string' == typeof attrVal) {
|
||||
return attrVal;
|
||||
}
|
||||
attrVal = el.getAttribute(attrName);
|
||||
return 'string' == typeof attrVal ? attrVal : null;
|
||||
}
|
||||
|
||||
// has the element been fake tested?
|
||||
function checkIfFakeTested(field, el) {
|
||||
if (-1 === ['text', 'password'].indexOf(el.type.toLowerCase()) ||
|
||||
!(passwordRegEx.test(field.value) ||
|
||||
passwordRegEx.test(field.htmlID) || passwordRegEx.test(field.htmlName) ||
|
||||
passwordRegEx.test(field.placeholder) || passwordRegEx.test(field['label-tag']) ||
|
||||
passwordRegEx.test(field['label-data']) || passwordRegEx.test(field['label-aria']))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!field.visible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('password' == el.type.toLowerCase()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var elType = el.type;
|
||||
focusElement(el, true);
|
||||
return elType !== el.type;
|
||||
}
|
||||
|
||||
// get the value of a dom element
|
||||
function getElementValue(el) {
|
||||
switch (toLowerString(el.type)) {
|
||||
case 'checkbox':
|
||||
return el.checked ? '✓' : '';
|
||||
|
||||
case 'hidden':
|
||||
el = el.value;
|
||||
if (!el || 'number' != typeof el.length) {
|
||||
return '';
|
||||
}
|
||||
254 < el.length && (el = el.substr(0, 254) + '...SNIPPED');
|
||||
return el;
|
||||
|
||||
default:
|
||||
return el.value;
|
||||
}
|
||||
}
|
||||
|
||||
// get all the options for a "select" element
|
||||
function getSelectElementOptions(el) {
|
||||
if (!el.options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var options = Array.prototype.slice.call(el.options).map(function (option) {
|
||||
var optionText = option.text ?
|
||||
toLowerString(option.text).replace(/\\s/gm, '').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\?]/gm, '') :
|
||||
null;
|
||||
|
||||
return [optionText ? optionText : null, option.value];
|
||||
})
|
||||
|
||||
return {
|
||||
options: options
|
||||
};
|
||||
}
|
||||
|
||||
// get the top label
|
||||
function getLabelTop(el) {
|
||||
var parent;
|
||||
for (el = el.parentElement || el.parentNode; el && 'td' != toLowerString(el.tagName) ;) {
|
||||
el = el.parentElement || el.parentNode;
|
||||
}
|
||||
|
||||
if (!el || void 0 === el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
parent = el.parentElement || el.parentNode;
|
||||
if ('tr' != parent.tagName.toLowerCase()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
parent = parent.previousElementSibling;
|
||||
if (!parent || 'tr' != (parent.tagName + '').toLowerCase() ||
|
||||
parent.cells && el.cellIndex >= parent.cells.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
el = parent.cells[el.cellIndex];
|
||||
var elText = el.textContent || el.innerText;
|
||||
return elText = cleanText(elText);
|
||||
}
|
||||
|
||||
// get all the tags for a given label
|
||||
function getLabelTag(el) {
|
||||
var docLabel,
|
||||
theLabels = [];
|
||||
|
||||
if (el.labels && el.labels.length && 0 < el.labels.length) {
|
||||
theLabels = Array.prototype.slice.call(el.labels);
|
||||
} else {
|
||||
if (el.id) {
|
||||
theLabels = theLabels.concat(Array.prototype.slice.call(
|
||||
queryDoc(theDoc, 'label[for=' + JSON.stringify(el.id) + ']')));
|
||||
}
|
||||
|
||||
if (el.name) {
|
||||
docLabel = queryDoc(theDoc, 'label[for=' + JSON.stringify(el.name) + ']');
|
||||
|
||||
for (var labelIndex = 0; labelIndex < docLabel.length; labelIndex++) {
|
||||
if (-1 === theLabels.indexOf(docLabel[labelIndex])) {
|
||||
theLabels.push(docLabel[labelIndex])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var theEl = el; theEl && theEl != theDoc; theEl = theEl.parentNode) {
|
||||
if ('label' === toLowerString(theEl.tagName) && -1 === theLabels.indexOf(theEl)) {
|
||||
theLabels.push(theEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === theLabels.length) {
|
||||
theEl = el.parentNode;
|
||||
if ('dd' === theEl.tagName.toLowerCase() && null !== theEl.previousElementSibling
|
||||
&& 'dt' === theEl.previousElementSibling.tagName.toLowerCase()) {
|
||||
theLabels.push(theEl.previousElementSibling);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 > theLabels.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return theLabels.map(function (l) {
|
||||
return (l.textContent || l.innerText)
|
||||
.replace(/^\\s+/, '').replace(/\\s+$/, '').replace('\\n', '').replace(/\\s{2,}/, ' ');
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// add property and value to the object if there is a value
|
||||
function addProp(obj, prop, val, d) {
|
||||
if (0 !== d && d === val || null === val || void 0 === val) {
|
||||
return;
|
||||
}
|
||||
|
||||
obj[prop] = val;
|
||||
}
|
||||
|
||||
// lowercase helper
|
||||
function toLowerString(s) {
|
||||
return 'string' === typeof s ? s.toLowerCase() : ('' + s).toLowerCase();
|
||||
}
|
||||
|
||||
// query the document helper
|
||||
function queryDoc(doc, query) {
|
||||
var els = [];
|
||||
try {
|
||||
els = doc.querySelectorAll(query);
|
||||
} catch (e) { }
|
||||
return els;
|
||||
}
|
||||
|
||||
// end helpers
|
||||
|
||||
var theView = theDoc.defaultView ? theDoc.defaultView : window,
|
||||
passwordRegEx = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|(\\\\b|_|-)passe(\\\\b|_|-)|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i');
|
||||
|
||||
// get all the docs
|
||||
var theForms = Array.prototype.slice.call(queryDoc(theDoc, 'form')).map(function (formEl, elIndex) {
|
||||
var op = {},
|
||||
formOpId = '__form__' + elIndex;
|
||||
|
||||
formEl.opid = formOpId;
|
||||
op.opid = formOpId;
|
||||
addProp(op, 'htmlName', getElementAttrValue(formEl, 'name'));
|
||||
addProp(op, 'htmlID', getElementAttrValue(formEl, 'id'));
|
||||
formOpId = getElementAttrValue(formEl, 'action');
|
||||
formOpId = new URL(formOpId, window.location.href);
|
||||
addProp(op, 'htmlAction', formOpId ? formOpId.href : null);
|
||||
addProp(op, 'htmlMethod', getElementAttrValue(formEl, 'method'));
|
||||
|
||||
return op;
|
||||
});
|
||||
|
||||
// get all the form fields
|
||||
var theFields = Array.prototype.slice.call(getFormElements(theDoc)).map(function (el, elIndex) {
|
||||
var field = {},
|
||||
opId = '__' + elIndex,
|
||||
elMaxLen = -1 == el.maxLength ? 999 : el.maxLength;
|
||||
|
||||
if (!elMaxLen || 'number' === typeof elMaxLen && isNaN(elMaxLen)) {
|
||||
elMaxLen = 999;
|
||||
}
|
||||
|
||||
theDoc.elementsByOPID[opId] = el;
|
||||
el.opid = opId;
|
||||
field.opid = opId;
|
||||
field.elementNumber = elIndex;
|
||||
addProp(field, 'maxLength', Math.min(elMaxLen, 999), 999);
|
||||
field.visible = isElementVisible(el);
|
||||
field.viewable = isElementViewable(el);
|
||||
addProp(field, 'htmlID', getElementAttrValue(el, 'id'));
|
||||
addProp(field, 'htmlName', getElementAttrValue(el, 'name'));
|
||||
addProp(field, 'htmlClass', getElementAttrValue(el, 'class'));
|
||||
addProp(field, 'tabindex', getElementAttrValue(el, 'tabindex'));
|
||||
addProp(field, 'title', getElementAttrValue(el, 'title'));
|
||||
addProp(field, 'userEdited', !!el.dataset['com.agilebits.onepassword.userEdited']);
|
||||
|
||||
if ('hidden' != toLowerString(el.type)) {
|
||||
addProp(field, 'label-tag', getLabelTag(el));
|
||||
addProp(field, 'label-data', getElementAttrValue(el, 'data-label'));
|
||||
addProp(field, 'label-aria', getElementAttrValue(el, 'aria-label'));
|
||||
addProp(field, 'label-top', getLabelTop(el));
|
||||
var labelArr = [];
|
||||
for (var sib = el; sib && sib.nextSibling;) {
|
||||
sib = sib.nextSibling;
|
||||
if (isKnownTag(sib)) {
|
||||
break;
|
||||
}
|
||||
checkNodeType(labelArr, sib);
|
||||
}
|
||||
addProp(field, 'label-right', labelArr.join(''));
|
||||
labelArr = [];
|
||||
shiftForLeftLabel(el, labelArr);
|
||||
labelArr = labelArr.reverse().join('');
|
||||
addProp(field, 'label-left', labelArr);
|
||||
addProp(field, 'placeholder', getElementAttrValue(el, 'placeholder'));
|
||||
}
|
||||
|
||||
addProp(field, 'rel', getElementAttrValue(el, 'rel'));
|
||||
addProp(field, 'type', toLowerString(getElementAttrValue(el, 'type')));
|
||||
addProp(field, 'value', getElementValue(el));
|
||||
addProp(field, 'checked', el.checked, false);
|
||||
addProp(field, 'autoCompleteType', el.getAttribute('x-autocompletetype') || el.getAttribute('autocompletetype') || el.getAttribute('autocomplete'), 'off');
|
||||
addProp(field, 'disabled', el.disabled);
|
||||
addProp(field, 'readonly', el.b || el.readOnly);
|
||||
addProp(field, 'selectInfo', getSelectElementOptions(el));
|
||||
addProp(field, 'aria-hidden', 'true' == el.getAttribute('aria-hidden'), false);
|
||||
addProp(field, 'aria-disabled', 'true' == el.getAttribute('aria-disabled'), false);
|
||||
addProp(field, 'aria-haspopup', 'true' == el.getAttribute('aria-haspopup'), false);
|
||||
addProp(field, 'data-unmasked', el.dataset.unmasked);
|
||||
addProp(field, 'data-stripe', getElementAttrValue(el, 'data-stripe'));
|
||||
addProp(field, 'onepasswordFieldType', el.dataset.onepasswordFieldType || el.type);
|
||||
addProp(field, 'onepasswordDesignation', el.dataset.onepasswordDesignation);
|
||||
addProp(field, 'onepasswordSignInUrl', el.dataset.onepasswordSignInUrl);
|
||||
addProp(field, 'onepasswordSectionTitle', el.dataset.onepasswordSectionTitle);
|
||||
addProp(field, 'onepasswordSectionFieldKind', el.dataset.onepasswordSectionFieldKind);
|
||||
addProp(field, 'onepasswordSectionFieldTitle', el.dataset.onepasswordSectionFieldTitle);
|
||||
addProp(field, 'onepasswordSectionFieldValue', el.dataset.onepasswordSectionFieldValue);
|
||||
|
||||
if (el.form) {
|
||||
field.form = getElementAttrValue(el.form, 'opid');
|
||||
}
|
||||
|
||||
addProp(field, 'fakeTested', checkIfFakeTested(field, el), false);
|
||||
|
||||
return field;
|
||||
});
|
||||
|
||||
// test form fields
|
||||
theFields.filter(function (f) {
|
||||
return f.fakeTested;
|
||||
}).forEach(function (f) {
|
||||
var el = theDoc.elementsByOPID[f.opid];
|
||||
el.getBoundingClientRect();
|
||||
|
||||
var originalValue = el.value;
|
||||
// click it
|
||||
!el || el && 'function' !== typeof el.click || el.click();
|
||||
focusElement(el, false);
|
||||
|
||||
el.dispatchEvent(doEventOnElement(el, 'keydown'));
|
||||
el.dispatchEvent(doEventOnElement(el, 'keypress'));
|
||||
el.dispatchEvent(doEventOnElement(el, 'keyup'));
|
||||
|
||||
el.value !== originalValue && (el.value = originalValue);
|
||||
|
||||
el.click && el.click();
|
||||
f.postFakeTestVisible = isElementVisible(el);
|
||||
f.postFakeTestViewable = isElementViewable(el);
|
||||
f.postFakeTestType = el.type;
|
||||
|
||||
var elValue = el.value;
|
||||
|
||||
var event1 = el.ownerDocument.createEvent('HTMLEvents'),
|
||||
event2 = el.ownerDocument.createEvent('HTMLEvents');
|
||||
el.dispatchEvent(doEventOnElement(el, 'keydown'));
|
||||
el.dispatchEvent(doEventOnElement(el, 'keypress'));
|
||||
el.dispatchEvent(doEventOnElement(el, 'keyup'));
|
||||
event2.initEvent('input', true, true);
|
||||
el.dispatchEvent(event2);
|
||||
event1.initEvent('change', true, true);
|
||||
el.dispatchEvent(event1);
|
||||
|
||||
el.blur();
|
||||
el.value !== elValue && (el.value = elValue);
|
||||
});
|
||||
|
||||
// build out the page details object. this is the final result
|
||||
var pageDetails = {
|
||||
documentUUID: oneShotId,
|
||||
title: theDoc.title,
|
||||
url: theView.location.href,
|
||||
documentUrl: theDoc.location.href,
|
||||
tabUrl: theView.location.href,
|
||||
forms: function (forms) {
|
||||
var formObj = {};
|
||||
forms.forEach(function (f) {
|
||||
formObj[f.opid] = f;
|
||||
});
|
||||
return formObj;
|
||||
}(theForms),
|
||||
fields: theFields,
|
||||
collectedTimestamp: new Date().getTime()
|
||||
};
|
||||
|
||||
// get proper page title. maybe they are using the special meta tag?
|
||||
var theTitle = document.querySelector('[data-onepassword-title]')
|
||||
if (theTitle && theTitle.dataset[DISPLAY_TITLE_ATTRIBUE]) {
|
||||
pageDetails.displayTitle = theTitle.dataset.onepasswordTitle;
|
||||
}
|
||||
|
||||
return pageDetails;
|
||||
}
|
||||
|
||||
document.elementForOPID = getElementForOPID;
|
||||
|
||||
function doEventOnElement(kedol, fonor) {
|
||||
var quebo;
|
||||
isFirefox ? (quebo = document.createEvent('KeyboardEvent'), quebo.initKeyEvent(fonor, true, false, null, false, false, false, false, 0, 0)) : (quebo = kedol.ownerDocument.createEvent('Events'),
|
||||
quebo.initEvent(fonor, true, false), quebo.charCode = 0, quebo.keyCode = 0, quebo.which = 0,
|
||||
quebo.srcElement = kedol, quebo.target = kedol);
|
||||
return quebo;
|
||||
}
|
||||
|
||||
// some useful globals
|
||||
window.LOGIN_TITLES = [/^\\W*log\\W*[oi]n\\W*$/i, /log\\W*[oi]n (?:securely|now)/i, /^\\W*sign\\W*[oi]n\\W*$/i, 'continue', 'submit', 'weiter', 'accès', 'вход', 'connexion', 'entrar', 'anmelden', 'accedi', 'valider', '登录', 'लॉग इन करें', 'change password'];
|
||||
window.LOGIN_RED_HERRING_TITLES = ['already have an account', 'sign in with'];
|
||||
window.REGISTER_TITLES = 'register;sign up;signup;join;create my account;регистрация;inscription;regístrate;cadastre-se;registrieren;registrazione;注册;साइन अप करें'.split(';');
|
||||
window.SEARCH_TITLES = 'search find поиск найти искать recherche suchen buscar suche ricerca procurar 検索'.split(' ');
|
||||
window.FORGOT_PASSWORD_TITLES = 'forgot geändert vergessen hilfe changeemail español'.split(' ');
|
||||
window.REMEMBER_ME_TITLES = ['remember me', 'rememberme', 'keep me signed in'];
|
||||
window.BACK_TITLES = ['back', 'назад'];
|
||||
|
||||
// clean up the text
|
||||
function cleanText(s) {
|
||||
var sVal = null;
|
||||
s && (sVal = s.replace(/^\\s+|\\s+$|\\r?\\n.*$/gm, ''), sVal = 0 < sVal.length ? sVal : null);
|
||||
return sVal;
|
||||
}
|
||||
|
||||
// check the node type and adjust the array accordingly
|
||||
function checkNodeType(arr, el) {
|
||||
var theText = '';
|
||||
3 === el.nodeType ? theText = el.nodeValue : 1 === el.nodeType && (theText = el.textContent || el.innerText);
|
||||
(theText = cleanText(theText)) && arr.push(theText);
|
||||
}
|
||||
|
||||
function isKnownTag(el) {
|
||||
if (el && void 0 !== el) {
|
||||
var tags = 'select option input form textarea button table iframe body head script'.split(' ');
|
||||
|
||||
if (el) {
|
||||
var elTag = el ? (el.tagName || '').toLowerCase() : '';
|
||||
return tags.constructor == Array ? 0 <= tags.indexOf(elTag) : elTag === tags;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function shiftForLeftLabel(el, arr, steps) {
|
||||
var sib;
|
||||
for (steps || (steps = 0) ; el && el.previousSibling;) {
|
||||
el = el.previousSibling;
|
||||
if (isKnownTag(el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkNodeType(arr, el);
|
||||
}
|
||||
if (el && 0 === arr.length) {
|
||||
for (sib = null; !sib;) {
|
||||
el = el.parentElement || el.parentNode;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
for (sib = el.previousSibling; sib && !isKnownTag(sib) && sib.lastChild;) {
|
||||
sib = sib.lastChild;
|
||||
}
|
||||
}
|
||||
|
||||
// base case and recurse
|
||||
isKnownTag(sib) || (checkNodeType(arr, sib), 0 === arr.length && shiftForLeftLabel(sib, arr, steps + 1));
|
||||
}
|
||||
}
|
||||
|
||||
// is a dom element visible on screen?
|
||||
function isElementVisible(el) {
|
||||
var theEl = el;
|
||||
el = (el = el.ownerDocument) ? el.defaultView : {};
|
||||
|
||||
// walk the dom tree
|
||||
for (var elStyle; theEl && theEl !== document;) {
|
||||
elStyle = el.getComputedStyle ? el.getComputedStyle(theEl, null) : theEl.style;
|
||||
if (!elStyle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('none' === elStyle.display || 'hidden' == elStyle.visibility) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// walk up
|
||||
theEl = theEl.parentNode;
|
||||
}
|
||||
|
||||
return theEl === document;
|
||||
}
|
||||
|
||||
// is a dom element "viewable" on screen?
|
||||
function isElementViewable(el) {
|
||||
var theDoc = el.ownerDocument.documentElement,
|
||||
rect = el.getBoundingClientRect(),
|
||||
docScrollWidth = theDoc.scrollWidth,
|
||||
kosri = theDoc.scrollHeight,
|
||||
leftOffset = rect.left - theDoc.clientLeft,
|
||||
topOffset = rect.top - theDoc.clientTop,
|
||||
theRect;
|
||||
|
||||
if (!isElementVisible(el) || !el.offsetParent || 10 > el.clientWidth || 10 > el.clientHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var rects = el.getClientRects();
|
||||
if (0 === rects.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < rects.length; i++) {
|
||||
if (theRect = rects[i], theRect.left > docScrollWidth || 0 > theRect.right) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 > leftOffset || leftOffset > docScrollWidth || 0 > topOffset || topOffset > kosri) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// walk the tree
|
||||
for (var pointEl = el.ownerDocument.elementFromPoint(leftOffset + (rect.right > window.innerWidth ? (window.innerWidth - leftOffset) / 2 : rect.width / 2), topOffset + (rect.bottom > window.innerHeight ? (window.innerHeight - topOffset) / 2 : rect.height / 2)) ; pointEl && pointEl !== el && pointEl !== document;) {
|
||||
if (pointEl.tagName && 'string' === typeof pointEl.tagName && 'label' === pointEl.tagName.toLowerCase()
|
||||
&& el.labels && 0 < el.labels.length) {
|
||||
return 0 <= Array.prototype.slice.call(el.labels).indexOf(pointEl);
|
||||
}
|
||||
|
||||
// walk up
|
||||
pointEl = pointEl.parentNode;
|
||||
}
|
||||
|
||||
return pointEl === el;
|
||||
}
|
||||
|
||||
function getElementForOPID(opId) {
|
||||
var theEl;
|
||||
if (void 0 === opId || null === opId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
var formEls = Array.prototype.slice.call(getFormElements(document));
|
||||
var filteredFormEls = formEls.filter(function (el) {
|
||||
return el.opid == opId;
|
||||
});
|
||||
|
||||
if (0 < filteredFormEls.length) {
|
||||
theEl = filteredFormEls[0], 1 < filteredFormEls.length && console.warn('More than one element found with opid ' + opId);
|
||||
} else {
|
||||
var theIndex = parseInt(opId.split('__')[1], 10);
|
||||
isNaN(theIndex) || (theEl = formEls[theIndex]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('An unexpected error occurred: ' + e);
|
||||
} finally {
|
||||
return theEl;
|
||||
}
|
||||
}
|
||||
|
||||
// get all the form elements that we care about
|
||||
function getFormElements(theDoc) {
|
||||
var els = [];
|
||||
try {
|
||||
els = theDoc.querySelectorAll('input, select, button');
|
||||
} catch (e) { }
|
||||
return els;
|
||||
}
|
||||
|
||||
// focus the element and optionally restore its original value
|
||||
function focusElement(el, setVal) {
|
||||
if (setVal) {
|
||||
var initialValue = el.value;
|
||||
el.focus();
|
||||
|
||||
if (el.value !== initialValue) {
|
||||
el.value = initialValue;
|
||||
}
|
||||
} else {
|
||||
el.focus();
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(getPageDetails(document, 'oneshotUUID'));
|
||||
}
|
||||
|
||||
function fill(document, fillScript, undefined) {
|
||||
var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1;
|
||||
|
||||
var g=!0,k=!0;
|
||||
function n(a){var b=null;return a?0===a.indexOf('https://')&&'http:'===document.location.protocol&&(b=document.querySelectorAll('input[type=password]'),0<b.length&&(confirmResult=confirm('1Password warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page.\\n\\nDo you still wish to fill this login?'),0==confirmResult))?!0:!1:!1}
|
||||
function m(a){var b,c=[],d=a.properties,e=1,h,f=[];d&&d.delay_between_operations&&(e=d.delay_between_operations);if(!n(a.savedURL)){h=function(a,b){var d=a[0];if(void 0===d)b();else{if('delay'===d.operation||'delay'===d[0])e=d.parameters?d.parameters[0]:d[1];else{if(d=p(d))for(var l=0;l<d.length;l++)-1===f.indexOf(d[l])&&f.push(d[l]);c=c.concat(f.map(function(a){return a&&a.hasOwnProperty('opid')?a.opid:null}))}setTimeout(function(){h(a.slice(1),b)},e)}};if(b=a.options)b.hasOwnProperty('animate')&&
|
||||
(k=b.animate),b.hasOwnProperty('markFilling')&&(g=b.markFilling);a.itemType&&'fillPassword'===a.itemType&&(g=!1);a.hasOwnProperty('script')&&(b=a.script,h(b,function(){a.hasOwnProperty('autosubmit')&&'function'==typeof autosubmit&&(a.itemType&&'fillLogin'!==a.itemType||(0<f.length?setTimeout(function(){autosubmit(a.autosubmit,d.allow_clicky_autosubmit,f)},AUTOSUBMIT_DELAY):DEBUG_AUTOSUBMIT&&console.log('[AUTOSUBMIT] Not attempting to submit since no fields were filled: ',f)));'object'==typeof protectedGlobalPage&&
|
||||
protectedGlobalPage.b('fillItemResults',{documentUUID:documentUUID,fillContextIdentifier:a.fillContextIdentifier,usedOpids:c},function(){fillingItemType=null})}))}}var x={fill_by_opid:q,fill_by_query:r,click_on_opid:s,click_on_query:t,touch_all_fields:u,simple_set_value_by_query:v,focus_by_opid:w,delay:null};
|
||||
function p(a){var b;if(a.hasOwnProperty('operation')&&a.hasOwnProperty('parameters'))b=a.operation,a=a.parameters;else if('[object Array]'===Object.prototype.toString.call(a))b=a[0],a=a.splice(1);else return null;return x.hasOwnProperty(b)?x[b].apply(this,a):null}function q(a,b){var c;return(c=y(a))?(z(c,b),[c]):null}function r(a,b){var c;c=A(a);return Array.prototype.map.call(Array.prototype.slice.call(c),function(a){z(a,b);return a},this)}
|
||||
function v(a,b){var c,d=[];c=A(a);Array.prototype.forEach.call(Array.prototype.slice.call(c),function(a){a.disabled||a.a||a.readOnly||void 0===a.value||(a.value=b,d.push(a))});return d}function w(a){if(a=y(a))'function'===typeof a.click&&a.click(),'function'===typeof a.focus&&B(a,!0);return null}function s(a){return(a=y(a))?C(a)?[a]:null:null}
|
||||
function t(a){a=A(a);return Array.prototype.map.call(Array.prototype.slice.call(a),function(a){C(a);'function'===typeof a.click&&a.click();'function'===typeof a.focus&&B(a,!0);return[a]},this)}function u(){D()};var E={'true':!0,y:!0,1:!0,yes:!0,'✓':!0},F=200;function z(a,b){var c;if(a&&null!==b&&void 0!==b&&!(a.disabled||a.a||a.readOnly))switch(g&&a.form&&!a.form.opfilled&&(a.form.opfilled=!0),a.type?a.type.toLowerCase():null){case 'checkbox':c=b&&1<=b.length&&E.hasOwnProperty(b.toLowerCase())&&!0===E[b.toLowerCase()];a.checked===c||G(a,function(a){a.checked=c});break;case 'radio':!0===E[b.toLowerCase()]&&a.click();break;default:a.value==b||G(a,function(a){a.value=b})}}
|
||||
function G(a,b){H(a);b(a);I(a);J(a)&&(a.className+=' com-agilebits-onepassword-extension-animated-fill',setTimeout(function(){a&&a.className&&(a.className=a.className.replace(/(\\s)?com-agilebits-onepassword-extension-animated-fill/,''))},F))};document.elementForOPID=y;function K(a,b){var c;isFirefox?(c=document.createEvent('KeyboardEvent'),c.initKeyEvent(b,!0,!1,null,!1,!1,!1,!1,0,0)):(c=a.ownerDocument.createEvent('Events'),c.initEvent(b,!0,!1),c.charCode=0,c.keyCode=0,c.which=0,c.srcElement=a,c.target=a);return c}function H(a){var b=a.value;C(a);B(a,!1);a.dispatchEvent(K(a,'keydown'));a.dispatchEvent(K(a,'keypress'));a.dispatchEvent(K(a,'keyup'));a.value!==b&&(a.value=b)}
|
||||
function I(a){var b=a.value,c=a.ownerDocument.createEvent('HTMLEvents'),d=a.ownerDocument.createEvent('HTMLEvents');a.dispatchEvent(K(a,'keydown'));a.dispatchEvent(K(a,'keypress'));a.dispatchEvent(K(a,'keyup'));d.initEvent('input',!0,!0);a.dispatchEvent(d);c.initEvent('change',!0,!0);a.dispatchEvent(c);a.blur();a.value!==b&&(a.value=b)}function C(a){if(!a||a&&'function'!==typeof a.click)return!1;a.click();return!0}
|
||||
function L(){var a=RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|passe|contraseña|senha|密码|adgangskode|hasło|wachtwoord)','i');return Array.prototype.slice.call(A("input[type='text']")).filter(function(b){return b.value&&a.test(b.value)},this)}function D(){L().forEach(function(a){H(a);a.click&&a.click();I(a)})}
|
||||
window.LOGIN_TITLES=[/^\\W*log\\W*[oi]n\\W*$/i,/log\\W*[oi]n (?:securely|now)/i,/^\\W*sign\\W*[oi]n\\W*$/i,'continue','submit','weiter','accès','вход','connexion','entrar','anmelden','accedi','valider','登录','लॉग इन करें','change password'];window.LOGIN_RED_HERRING_TITLES=['already have an account','sign in with'];window.REGISTER_TITLES='register;sign up;signup;join;create my account;регистрация;inscription;regístrate;cadastre-se;registrieren;registrazione;注册;साइन अप करें'.split(';');
|
||||
window.SEARCH_TITLES='search find поиск найти искать recherche suchen buscar suche ricerca procurar 検索'.split(' ');window.FORGOT_PASSWORD_TITLES='forgot geändert vergessen hilfe changeemail español'.split(' ');window.REMEMBER_ME_TITLES=['remember me','rememberme','keep me signed in'];window.BACK_TITLES=['back','назад'];
|
||||
function J(a){var b;if(b=k)a:{b=a;for(var c=a.ownerDocument,c=c?c.defaultView:{},d;b&&b!==document;){d=c.getComputedStyle?c.getComputedStyle(b,null):b.style;if(!d){b=!0;break a}if('none'===d.display||'hidden'==d.visibility){b=!1;break a}b=b.parentNode}b=b===document}return b?-1!=='email text password number tel url'.split(' ').indexOf(a.type||''):!1}
|
||||
function y(a){var b;if(void 0===a||null===a)return null;try{var c=Array.prototype.slice.call(A('input, select, button')),d=c.filter(function(b){return b.opid==a});if(0<d.length)b=d[0],1<d.length&&console.warn('More than one element found with opid '+a);else{var e=parseInt(a.split('__')[1],10);isNaN(e)||(b=c[e])}}catch(h){console.error('An unexpected error occurred: '+h)}finally{return b}};function A(a){var b=document,c=[];try{c=b.querySelectorAll(a)}catch(d){}return c}function B(a,b){if(b){var c=a.value;a.focus();a.value!==c&&(a.value=c)}else a.focus()};
|
||||
m(fillScript);
|
||||
return JSON.stringify({'success': true});
|
||||
var markTheFilling = true,
|
||||
animateTheFilling = true;
|
||||
|
||||
// Check if URL is not secure when the original saved one was
|
||||
function urlNotSecure(savedURL) {
|
||||
var passwordInputs = null;
|
||||
if (!savedURL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0 === savedURL.indexOf('https://') && 'http:' === document.location.protocol && (passwordInputs = document.querySelectorAll('input[type=password]'),
|
||||
0 < passwordInputs.length && (confirmResult = confirm('Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page.\\n\\nDo you still wish to fill this login?'),
|
||||
0 == confirmResult)) ? true : false;
|
||||
}
|
||||
|
||||
function doFill(fillScript) {
|
||||
var fillScriptOps,
|
||||
theOpIds = [],
|
||||
fillScriptProperties = fillScript.properties,
|
||||
operationDelayMs = 1,
|
||||
doOperation,
|
||||
operationsToDo = [];
|
||||
|
||||
fillScriptProperties &&
|
||||
fillScriptProperties.delay_between_operations &&
|
||||
(operationDelayMs = fillScriptProperties.delay_between_operations);
|
||||
|
||||
if (urlNotSecure(fillScript.savedURL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
doOperation = function (ops, theOperation) {
|
||||
var op = ops[0];
|
||||
if (void 0 === op) {
|
||||
theOperation();
|
||||
} else {
|
||||
// should we delay?
|
||||
if ('delay' === op.operation || 'delay' === op[0]) {
|
||||
operationDelayMs = op.parameters ? op.parameters[0] : op[1];
|
||||
} else {
|
||||
if (op = normalizeOp(op)) {
|
||||
for (var opIndex = 0; opIndex < op.length; opIndex++) {
|
||||
-1 === operationsToDo.indexOf(op[opIndex]) && operationsToDo.push(op[opIndex]);
|
||||
}
|
||||
}
|
||||
theOpIds = theOpIds.concat(operationsToDo.map(function (operationToDo) {
|
||||
return operationToDo && operationToDo.hasOwnProperty('opid') ? operationToDo.opid : null;
|
||||
}));
|
||||
}
|
||||
setTimeout(function () {
|
||||
doOperation(ops.slice(1), theOperation);
|
||||
}, operationDelayMs);
|
||||
}
|
||||
};
|
||||
|
||||
if (fillScriptOps = fillScript.options) {
|
||||
fillScriptOps.hasOwnProperty('animate') && (animateTheFilling = fillScriptOps.animate),
|
||||
fillScriptOps.hasOwnProperty('markFilling') && (markTheFilling = fillScriptOps.markFilling);
|
||||
}
|
||||
|
||||
// don't mark a password filling
|
||||
fillScript.itemType && 'fillPassword' === fillScript.itemType && (markTheFilling = false);
|
||||
|
||||
if (!fillScript.hasOwnProperty('script')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// custom fill script
|
||||
|
||||
fillScriptOps = fillScript.script;
|
||||
doOperation(fillScriptOps, function () {
|
||||
// Done now
|
||||
// Do we have anything to autosubmit?
|
||||
if (fillScript.hasOwnProperty('autosubmit') && 'function' == typeof autosubmit) {
|
||||
fillScript.itemType && 'fillLogin' !== fillScript.itemType || (0 < operationsToDo.length ? setTimeout(function () {
|
||||
autosubmit(fillScript.autosubmit, fillScriptProperties.allow_clicky_autosubmit, operationsToDo);
|
||||
}, AUTOSUBMIT_DELAY) : DEBUG_AUTOSUBMIT && console.log('[AUTOSUBMIT] Not attempting to submit since no fields were filled: ', operationsToDo))
|
||||
}
|
||||
|
||||
// handle protectedGlobalPage
|
||||
if ('object' == typeof protectedGlobalPage) {
|
||||
protectedGlobalPage.b('fillItemResults', {
|
||||
documentUUID: documentUUID,
|
||||
fillContextIdentifier: fillScript.fillContextIdentifier,
|
||||
usedOpids: theOpIds
|
||||
}, function () {
|
||||
fillingItemType = null;
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// fill for reference
|
||||
var thisFill = {
|
||||
fill_by_opid: doFillByOpId,
|
||||
fill_by_query: doFillByQuery,
|
||||
click_on_opid: doClickByOpId,
|
||||
click_on_query: doClickByQuery,
|
||||
touch_all_fields: touchAllFields,
|
||||
simple_set_value_by_query: doSimpleSetByQuery,
|
||||
focus_by_opid: doFocusByOpId,
|
||||
delay: null
|
||||
};
|
||||
|
||||
// normalize the op versus the reference
|
||||
function normalizeOp(op) {
|
||||
var thisOperation;
|
||||
if (op.hasOwnProperty('operation') && op.hasOwnProperty('parameters')) {
|
||||
thisOperation = op.operation, op = op.parameters;
|
||||
} else {
|
||||
if ('[object Array]' === Object.prototype.toString.call(op)) {
|
||||
thisOperation = op[0],
|
||||
op = op.splice(1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return thisFill.hasOwnProperty(thisOperation) ? thisFill[thisOperation].apply(this, op) : null;
|
||||
}
|
||||
|
||||
// do a fill by opid operation
|
||||
function doFillByOpId(opId, op) {
|
||||
var el = getElementByOpId(opId);
|
||||
return el ? (fillTheElement(el, op), [el]) : null;
|
||||
}
|
||||
|
||||
// do a fill by query operation
|
||||
function doFillByQuery(query, op) {
|
||||
var elements = selectAllFromDoc(query);
|
||||
return Array.prototype.map.call(Array.prototype.slice.call(elements), function (el) {
|
||||
fillTheElement(el, op);
|
||||
return el;
|
||||
}, this);
|
||||
}
|
||||
|
||||
// do a simple set value by query
|
||||
function doSimpleSetByQuery(query, valueToSet) {
|
||||
var elements = selectAllFromDoc(query),
|
||||
arr = [];
|
||||
Array.prototype.forEach.call(Array.prototype.slice.call(elements), function (el) {
|
||||
el.disabled || el.a || el.readOnly || void 0 === el.value || (el.value = valueToSet, arr.push(el));
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
// focus by opid
|
||||
function doFocusByOpId(opId) {
|
||||
var el = getElementByOpId(opId)
|
||||
if (el) {
|
||||
'function' === typeof el.click && el.click(),
|
||||
'function' === typeof el.focus && doFocusElement(el, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// do a click by opid operation
|
||||
function doClickByOpId(opId) {
|
||||
var el = getElementByOpId(opId);
|
||||
return el ? clickElement(el) ? [el] : null : null;
|
||||
}
|
||||
|
||||
// do a click by query operation
|
||||
function doClickByQuery(query) {
|
||||
query = selectAllFromDoc(query);
|
||||
return Array.prototype.map.call(Array.prototype.slice.call(query), function (el) {
|
||||
clickElement(el);
|
||||
'function' === typeof el.click && el.click();
|
||||
'function' === typeof el.focus && doFocusElement(el, true);
|
||||
return [el];
|
||||
}, this);
|
||||
}
|
||||
|
||||
var checkRadioTrueOps = {
|
||||
'true': true,
|
||||
y: true,
|
||||
1: true,
|
||||
yes: true,
|
||||
'✓': true
|
||||
},
|
||||
styleTimeout = 200;
|
||||
|
||||
// fill an element
|
||||
function fillTheElement(el, op) {
|
||||
var shouldCheck;
|
||||
if (el && null !== op && void 0 !== op && !(el.disabled || el.a || el.readOnly)) {
|
||||
switch (markTheFilling && el.form && !el.form.opfilled && (el.form.opfilled = true),
|
||||
el.type ? el.type.toLowerCase() : null) {
|
||||
case 'checkbox':
|
||||
shouldCheck = op && 1 <= op.length && checkRadioTrueOps.hasOwnProperty(op.toLowerCase()) && true === checkRadioTrueOps[op.toLowerCase()];
|
||||
el.checked === shouldCheck || doAllFillOperations(el, function (theEl) {
|
||||
theEl.checked = shouldCheck;
|
||||
});
|
||||
break;
|
||||
case 'radio':
|
||||
true === checkRadioTrueOps[op.toLowerCase()] && el.click();
|
||||
break;
|
||||
default:
|
||||
el.value == op || doAllFillOperations(el, function (theEl) {
|
||||
theEl.value = op;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do all the full operations needed
|
||||
function doAllFillOperations(el, afterValSetFunc) {
|
||||
setValueForElement(el);
|
||||
afterValSetFunc(el);
|
||||
setValueForElementByEvent(el);
|
||||
canSeeElementToStyle(el) && (el.className += ' com-agilebits-onepassword-extension-animated-fill',
|
||||
setTimeout(function () {
|
||||
el && el.className && (el.className = el.className.replace(/(\\s)?com-agilebits-onepassword-extension-animated-fill/, ''));
|
||||
}, styleTimeout));
|
||||
}
|
||||
|
||||
document.elementForOPID = getElementByOpId;
|
||||
|
||||
// normalize the event since firefox handles events differently than others
|
||||
function normalizeEvent(el, eventName) {
|
||||
var ev;
|
||||
if (isFirefox) {
|
||||
ev = document.createEvent('KeyboardEvent');
|
||||
ev.initKeyEvent(eventName, true, false, null, false, false, false, false, 0, 0);
|
||||
}
|
||||
else {
|
||||
ev = el.ownerDocument.createEvent('Events');
|
||||
ev.initEvent(eventName, true, false);
|
||||
ev.charCode = 0;
|
||||
ev.keyCode = 0;
|
||||
ev.which = 0;
|
||||
ev.srcElement = el;
|
||||
ev.target = el;
|
||||
}
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
// set value of the given element
|
||||
function setValueForElement(el) {
|
||||
var valueToSet = el.value;
|
||||
clickElement(el);
|
||||
doFocusElement(el, false);
|
||||
el.dispatchEvent(normalizeEvent(el, 'keydown'));
|
||||
el.dispatchEvent(normalizeEvent(el, 'keypress'));
|
||||
el.dispatchEvent(normalizeEvent(el, 'keyup'));
|
||||
el.value !== valueToSet && (el.value = valueToSet);
|
||||
}
|
||||
|
||||
// set value of the given element by using events
|
||||
function setValueForElementByEvent(el) {
|
||||
var valueToSet = el.value,
|
||||
ev1 = el.ownerDocument.createEvent('HTMLEvents'),
|
||||
ev2 = el.ownerDocument.createEvent('HTMLEvents');
|
||||
|
||||
el.dispatchEvent(normalizeEvent(el, 'keydown'));
|
||||
el.dispatchEvent(normalizeEvent(el, 'keypress'));
|
||||
el.dispatchEvent(normalizeEvent(el, 'keyup'));
|
||||
ev2.initEvent('input', true, true);
|
||||
el.dispatchEvent(ev2);
|
||||
ev1.initEvent('change', true, true);
|
||||
el.dispatchEvent(ev1);
|
||||
el.blur();
|
||||
el.value !== valueToSet && (el.value = valueToSet);
|
||||
}
|
||||
|
||||
// click on an element
|
||||
function clickElement(el) {
|
||||
if (!el || el && 'function' !== typeof el.click) {
|
||||
return false;
|
||||
}
|
||||
el.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
// get all fields we care about
|
||||
function getAllFields() {
|
||||
var r = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|passe|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i');
|
||||
return Array.prototype.slice.call(selectAllFromDoc("input[type='text']")).filter(function (el) {
|
||||
return el.value && r.test(el.value);
|
||||
}, this);
|
||||
}
|
||||
|
||||
// touch all the fields
|
||||
function touchAllFields() {
|
||||
getAllFields().forEach(function (el) {
|
||||
setValueForElement(el);
|
||||
el.click && el.click();
|
||||
setValueForElementByEvent(el);
|
||||
});
|
||||
}
|
||||
|
||||
// some useful globals
|
||||
window.LOGIN_TITLES = [/^\\W*log\\W*[oi]n\\W*$/i, /log\\W*[oi]n (?:securely|now)/i, /^\\W*sign\\W*[oi]n\\W*$/i, 'continue', 'submit', 'weiter', 'accès', 'вход', 'connexion', 'entrar', 'anmelden', 'accedi', 'valider', '登录', 'लॉग इन करें', 'change password'];
|
||||
window.LOGIN_RED_HERRING_TITLES = ['already have an account', 'sign in with'];
|
||||
window.REGISTER_TITLES = 'register;sign up;signup;join;create my account;регистрация;inscription;regístrate;cadastre-se;registrieren;registrazione;注册;साइन अप करें'.split(';');
|
||||
window.SEARCH_TITLES = 'search find поиск найти искать recherche suchen buscar suche ricerca procurar 検索'.split(' ');
|
||||
window.FORGOT_PASSWORD_TITLES = 'forgot geändert vergessen hilfe changeemail español'.split(' ');
|
||||
window.REMEMBER_ME_TITLES = ['remember me', 'rememberme', 'keep me signed in'];
|
||||
window.BACK_TITLES = ['back', 'назад'];
|
||||
|
||||
// can we see the element to apply some styling?
|
||||
function canSeeElementToStyle(el) {
|
||||
var currentEl;
|
||||
if (currentEl = animateTheFilling) {
|
||||
a: {
|
||||
currentEl = el;
|
||||
for (var owner = el.ownerDocument, owner = owner ? owner.defaultView : {}, theStyle; currentEl && currentEl !== document;) {
|
||||
theStyle = owner.getComputedStyle ? owner.getComputedStyle(currentEl, null) : currentEl.style;
|
||||
if (!theStyle) {
|
||||
currentEl = true;
|
||||
break a;
|
||||
}
|
||||
if ('none' === theStyle.display || 'hidden' == theStyle.visibility) {
|
||||
currentEl = false;
|
||||
break a;
|
||||
}
|
||||
currentEl = currentEl.parentNode;
|
||||
}
|
||||
currentEl = currentEl === document;
|
||||
}
|
||||
}
|
||||
return currentEl ? -1 !== 'email text password number tel url'.split(' ').indexOf(el.type || '') : false;
|
||||
}
|
||||
|
||||
// find the element for this operation
|
||||
function getElementByOpId(theOpId) {
|
||||
var theElement;
|
||||
if (void 0 === theOpId || null === theOpId) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
var elements = Array.prototype.slice.call(selectAllFromDoc('input, select, button'));
|
||||
var filteredElements = elements.filter(function (o) {
|
||||
return o.opid == theOpId;
|
||||
});
|
||||
if (0 < filteredElements.length) {
|
||||
theElement = filteredElements[0],
|
||||
1 < filteredElements.length && console.warn('More than one element found with opid ' + theOpId);
|
||||
} else {
|
||||
var elIndex = parseInt(theOpId.split('__')[1], 10);
|
||||
isNaN(elIndex) || (theElement = elements[elIndex]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('An unexpected error occurred: ' + e);
|
||||
} finally {
|
||||
return theElement;
|
||||
}
|
||||
}
|
||||
|
||||
// helper for doc.querySelectorAll
|
||||
function selectAllFromDoc(theSelector) {
|
||||
var d = document, elements = [];
|
||||
try {
|
||||
elements = d.querySelectorAll(theSelector);
|
||||
} catch (e) { }
|
||||
return elements;
|
||||
}
|
||||
|
||||
// focus an element and optionally re-set its value after focusing
|
||||
function doFocusElement(el, setValue) {
|
||||
if (setValue) {
|
||||
var existingValue = el.value;
|
||||
el.focus();
|
||||
el.value !== existingValue && (el.value = existingValue);
|
||||
} else {
|
||||
el.focus();
|
||||
}
|
||||
}
|
||||
|
||||
doFill(fillScript);
|
||||
|
||||
return JSON.stringify({
|
||||
success: true
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
}
|
||||
icons = [];
|
||||
|
||||
var pageDetails = JSON.parse(collect(document));
|
||||
var pageDetails = null; TODO: collect //JSON.parse(collect(document));
|
||||
if (pageDetails) {
|
||||
var iconFields = [];
|
||||
|
||||
|
@ -207,83 +207,4 @@
|
|||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
1Password Extension
|
||||
|
||||
Lovingly handcrafted by Dave Teare, Michael Fey, Rad Azzouz, and Roustem Karimov.
|
||||
Copyright (c) 2014 AgileBits. All rights reserved.
|
||||
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2014 AgileBits Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
function collect(document, undefined) {
|
||||
document.elementsByOPID = {}; document.addEventListener('input', function (c) { !1 !== c.a && 'input' === c.target.tagName.toLowerCase() && (c.target.dataset['com.agilebits.onepassword.userEdited'] = 'yes') }, !0);
|
||||
function r(c, e) {
|
||||
function b(a, g) { var d = a[g]; if ('string' == typeof d) return d; d = a.getAttribute(g); return 'string' == typeof d ? d : null } function h(a, g) { if (-1 === ['text', 'password'].indexOf(g.type.toLowerCase()) || !(n.test(a.value) || n.test(a.htmlID) || n.test(a.htmlName) || n.test(a.placeholder) || n.test(a['label-tag']) || n.test(a['label-data']) || n.test(a['label-aria']))) return !1; if (!a.visible) return !0; if ('password' == g.type.toLowerCase()) return !1; var d = g.type; w(g, !0); return d !== g.type } function p(a) {
|
||||
switch (q(a.type)) {
|
||||
case 'checkbox': return a.checked ?
|
||||
'✓' : ''; case 'hidden': a = a.value; if (!a || 'number' != typeof a.length) return ''; 254 < a.length && (a = a.substr(0, 254) + '...SNIPPED'); return a; default: return a.value
|
||||
}
|
||||
} function m(a) { return a.options ? (a = Array.prototype.slice.call(a.options).map(function (a) { var d = a.text, d = d ? q(d).replace(/\\s/mg, '').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\?]/mg, '') : null; return [d ? d : null, a.value] }), { options: a }) : null } function s(a) {
|
||||
var c; for (a = a.parentElement || a.parentNode; a && 'td' != q(a.tagName) ;) a = a.parentElement || a.parentNode; if (!a ||
|
||||
void 0 === a) return null; c = a.parentElement || a.parentNode; if ('tr' != c.tagName.toLowerCase()) return null; c = c.previousElementSibling; if (!c || 'tr' != (c.tagName + '').toLowerCase() || c.cells && a.cellIndex >= c.cells.length) return null; a = c.cells[a.cellIndex]; a = a.textContent || a.innerText; return a = y(a)
|
||||
} function t(a) {
|
||||
var g, d = []; if (a.labels && a.labels.length && 0 < a.labels.length) d = Array.prototype.slice.call(a.labels); else {
|
||||
a.id && (d = d.concat(Array.prototype.slice.call(x(c, 'label[for=' + JSON.stringify(a.id) + ']')))); if (a.name) {
|
||||
g =
|
||||
x(c, 'label[for=' + JSON.stringify(a.name) + ']'); for (var b = 0; b < g.length; b++) -1 === d.indexOf(g[b]) && d.push(g[b])
|
||||
} for (g = a; g && g != c; g = g.parentNode) 'label' === q(g.tagName) && -1 === d.indexOf(g) && d.push(g)
|
||||
} 0 === d.length && (g = a.parentNode, 'dd' === g.tagName.toLowerCase() && 'dt' === g.previousElementSibling.tagName.toLowerCase() && d.push(g.previousElementSibling)); return 0 < d.length ? d.map(function (a) { return (a.textContent || a.innerText).replace(/^\\s+/, '').replace(/\\s+$/, '').replace('\\n', '').replace(/\\s{2,}/, ' ') }).join('') :
|
||||
null
|
||||
} function f(a, c, d, b) { void 0 !== b && b === d || null === d || void 0 === d || (a[c] = d) } function q(a) { return 'string' === typeof a ? a.toLowerCase() : ('' + a).toLowerCase() } function x(a, c) { var d = []; try { d = a.querySelectorAll(c) } catch (b) { } return d } var u = c.defaultView ? c.defaultView : window, n = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|(\\\\b|_|-)passe(\\\\b|_|-)|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i'), v = Array.prototype.slice.call(x(c, 'form')).map(function (a, c) {
|
||||
var d = {}, e = '__form__' + c; a.opid = e; d.opid =
|
||||
e; f(d, 'htmlName', b(a, 'name')); f(d, 'htmlID', b(a, 'id')); e = b(a, 'action'); e = new URL(e, window.location.href); f(d, 'htmlAction', e ? e.href : null); f(d, 'htmlMethod', b(a, 'method')); return d
|
||||
}), F = Array.prototype.slice.call(z(c)).map(function (a, e) {
|
||||
var d = {}, l = '__' + e, k = -1 == a.maxLength ? 999 : a.maxLength; if (!k || 'number' === typeof k && isNaN(k)) k = 999; c.elementsByOPID[l] = a; a.opid = l; d.opid = l; d.elementNumber = e; f(d, 'maxLength', Math.min(k, 999), 999); d.visible = A(a); d.viewable = B(a); f(d, 'htmlID', b(a, 'id')); f(d, 'htmlName', b(a, 'name'));
|
||||
f(d, 'htmlClass', b(a, 'class')); f(d, 'tabindex', b(a, 'tabindex')); f(d, 'title', b(a, 'title')); f(d, 'userEdited', !!a.dataset['com.agilebits.onepassword.userEdited']); if ('hidden' != q(a.type)) { f(d, 'label-tag', t(a)); f(d, 'label-data', b(a, 'data-label')); f(d, 'label-aria', b(a, 'aria-label')); f(d, 'label-top', s(a)); l = []; for (k = a; k && k.nextSibling;) { k = k.nextSibling; if (C(k)) break; D(l, k) } f(d, 'label-right', l.join('')); l = []; E(a, l); l = l.reverse().join(''); f(d, 'label-left', l); f(d, 'placeholder', b(a, 'placeholder')) } f(d, 'rel', b(a,
|
||||
'rel')); f(d, 'type', q(b(a, 'type'))); f(d, 'value', p(a)); f(d, 'checked', a.checked, !1); f(d, 'autoCompleteType', a.getAttribute('x-autocompletetype') || a.getAttribute('autocompletetype') || a.getAttribute('autocomplete'), 'off'); f(d, 'disabled', a.disabled); f(d, 'readonly', a.b || a.readOnly); f(d, 'selectInfo', m(a)); f(d, 'aria-hidden', 'true' == a.getAttribute('aria-hidden'), !1); f(d, 'aria-disabled', 'true' == a.getAttribute('aria-disabled'), !1); f(d, 'aria-haspopup', 'true' == a.getAttribute('aria-haspopup'), !1); f(d, 'data-unmasked',
|
||||
a.dataset.unmasked); f(d, 'data-stripe', b(a, 'data-stripe')); f(d, 'onepasswordFieldType', a.dataset.onepasswordFieldType || a.type); f(d, 'onepasswordDesignation', a.dataset.onepasswordDesignation); f(d, 'onepasswordSignInUrl', a.dataset.onepasswordSignInUrl); f(d, 'onepasswordSectionTitle', a.dataset.onepasswordSectionTitle); f(d, 'onepasswordSectionFieldKind', a.dataset.onepasswordSectionFieldKind); f(d, 'onepasswordSectionFieldTitle', a.dataset.onepasswordSectionFieldTitle); f(d, 'onepasswordSectionFieldValue', a.dataset.onepasswordSectionFieldValue);
|
||||
a.form && (d.form = b(a.form, 'opid')); f(d, 'fakeTested', h(d, a), !1); return d
|
||||
}); F.filter(function (a) { return a.fakeTested }).forEach(function (a) {
|
||||
var b = c.elementsByOPID[a.opid]; b.getBoundingClientRect(); var d = b.value; !b || b && 'function' !== typeof b.click || b.click(); w(b, !1); b.dispatchEvent(G(b, 'keydown')); b.dispatchEvent(G(b, 'keypress')); b.dispatchEvent(G(b, 'keyup')); b.value !== d && (b.value = d); b.click && b.click(); a.postFakeTestVisible = A(b); a.postFakeTestViewable = B(b); a.postFakeTestType = b.type; a = b.value; var d = b.ownerDocument.createEvent('HTMLEvents'),
|
||||
e = b.ownerDocument.createEvent('HTMLEvents'); b.dispatchEvent(G(b, 'keydown')); b.dispatchEvent(G(b, 'keypress')); b.dispatchEvent(G(b, 'keyup')); e.initEvent('input', !0, !0); b.dispatchEvent(e); d.initEvent('change', !0, !0); b.dispatchEvent(d); b.blur(); b.value !== a && (b.value = a)
|
||||
}); u = { documentUUID: e, title: c.title, url: u.location.href, documentUrl: c.location.href, tabUrl: u.location.href, forms: function (a) { var b = {}; a.forEach(function (a) { b[a.opid] = a }); return b }(v), fields: F, collectedTimestamp: (new Date).getTime() }; (v = document.querySelector('[data-onepassword-title]')) &&
|
||||
v.dataset[DISPLAY_TITLE_ATTRIBUE] && (u.displayTitle = v.dataset.onepasswordTitle); return u
|
||||
}; document.elementForOPID = H; function G(c, e) { var b; I ? (b = document.createEvent('KeyboardEvent'), b.initKeyEvent(e, !0, !1, null, !1, !1, !1, !1, 0, 0)) : (b = c.ownerDocument.createEvent('Events'), b.initEvent(e, !0, !1), b.charCode = 0, b.keyCode = 0, b.which = 0, b.srcElement = c, b.target = c); return b } window.LOGIN_TITLES = [/^\\W*log\\W*[oi]n\\W*$/i, /log\\W*[oi]n (?:securely|now)/i, /^\\W*sign\\W*[oi]n\\W*$/i, 'continue', 'submit', 'weiter', 'accès', 'вход', 'connexion', 'entrar', 'anmelden', 'accedi', 'valider', '登录', 'लॉग इन करें', 'change password'];
|
||||
window.LOGIN_RED_HERRING_TITLES = ['already have an account', 'sign in with']; window.REGISTER_TITLES = 'register;sign up;signup;join;регистрация;inscription;regístrate;cadastre-se;registrieren;registrazione;注册;साइन अप करें'.split(';'); window.SEARCH_TITLES = 'search find поиск найти искать recherche suchen buscar suche ricerca procurar 検索'.split(' '); window.FORGOT_PASSWORD_TITLES = 'forgot geändert vergessen hilfe changeemail español'.split(' '); window.REMEMBER_ME_TITLES = ['remember me', 'rememberme', 'keep me signed in'];
|
||||
window.BACK_TITLES = ['back', 'назад']; function y(c) { var e = null; c && (e = c.replace(/^\\s+|\\s+$|\\r?\\n.*$/mg, ''), e = 0 < e.length ? e : null); return e } function D(c, e) { var b; b = ''; 3 === e.nodeType ? b = e.nodeValue : 1 === e.nodeType && (b = e.textContent || e.innerText); (b = y(b)) && c.push(b) } function C(c) { var e; c && void 0 !== c ? (e = 'select option input form textarea button table iframe body head script'.split(' '), c ? (c = c ? (c.tagName || '').toLowerCase() : '', e = e.constructor == Array ? 0 <= e.indexOf(c) : c === e) : e = !1) : e = !0; return e }
|
||||
function E(c, e, b) { var h; for (b || (b = 0) ; c && c.previousSibling;) { c = c.previousSibling; if (C(c)) return; D(e, c) } if (c && 0 === e.length) { for (h = null; !h;) { c = c.parentElement || c.parentNode; if (!c) return; for (h = c.previousSibling; h && !C(h) && h.lastChild;) h = h.lastChild } C(h) || (D(e, h), 0 === e.length && E(h, e, b + 1)) } }
|
||||
function A(c) { var e = c; c = (c = c.ownerDocument) ? c.defaultView : {}; for (var b; e && e !== document;) { b = c.getComputedStyle ? c.getComputedStyle(e, null) : e.style; if (!b) return !0; if ('none' === b.display || 'hidden' == b.visibility) return !1; e = e.parentNode } return e === document }
|
||||
function B(c) {
|
||||
var e = c.ownerDocument.documentElement, b = c.getBoundingClientRect(), h = e.scrollWidth, p = e.scrollHeight, m = b.left - e.clientLeft, e = b.top - e.clientTop, s; if (!A(c) || !c.offsetParent || 10 > c.clientWidth || 10 > c.clientHeight) return !1; var t = c.getClientRects(); if (0 === t.length) return !1; for (var f = 0; f < t.length; f++) if (s = t[f], s.left > h || 0 > s.right) return !1; if (0 > m || m > h || 0 > e || e > p) return !1; for (b = c.ownerDocument.elementFromPoint(m + (b.right > window.innerWidth ? (window.innerWidth - m) / 2 : b.width / 2), e + (b.bottom > window.innerHeight ?
|
||||
(window.innerHeight - e) / 2 : b.height / 2)) ; b && b !== c && b !== document;) { if (b.tagName && 'string' === typeof b.tagName && 'label' === b.tagName.toLowerCase() && c.labels && 0 < c.labels.length) return 0 <= Array.prototype.slice.call(c.labels).indexOf(b); b = b.parentNode } return b === c
|
||||
}
|
||||
function H(c) { var e; if (void 0 === c || null === c) return null; try { var b = Array.prototype.slice.call(z(document)), h = b.filter(function (b) { return b.opid == c }); if (0 < h.length) e = h[0], 1 < h.length && console.warn('More than one element found with opid ' + c); else { var p = parseInt(c.split('__')[1], 10); isNaN(p) || (e = b[p]) } } catch (m) { console.error('An unexpected error occurred: ' + m) } finally { return e } }; var I = 'object' === typeof tabs || 'object' === typeof self && 'object' === typeof self.port; function z(c) { var e = []; try { e = c.querySelectorAll('input, select, button') } catch (b) { } return e } function w(c, e) { if (e) { var b = c.value; c.focus(); c.value !== b && (c.value = b) } else c.focus() };
|
||||
return JSON.stringify(r(document, 'oneshotUUID'));
|
||||
}
|
||||
|
||||
/*
|
||||
End 1Password Extension
|
||||
*/
|
||||
})();
|
||||
|
|
|
@ -3,18 +3,19 @@
|
|||
formData = [],
|
||||
barType = null;
|
||||
|
||||
chrome.storage.local.get('disableAddLoginNotification', function (obj) {
|
||||
if (!obj || !obj['disableAddLoginNotification']) {
|
||||
chrome.runtime.sendMessage({
|
||||
command: 'bgCollectPageDetails'
|
||||
});
|
||||
}
|
||||
});
|
||||
if (window.location.hostname.indexOf('bitwarden.com') === -1) {
|
||||
chrome.storage.local.get('disableAddLoginNotification', function (obj) {
|
||||
if (!obj || !obj['disableAddLoginNotification']) {
|
||||
chrome.runtime.sendMessage({
|
||||
command: 'bgCollectPageDetails'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
|
||||
if (msg.command === 'openNotificationBar') {
|
||||
closeBar(false);
|
||||
openBar(msg.data.type, msg.data.typeData);
|
||||
closeExistingAndOpenBar(msg.data.type, msg.data.typeData);
|
||||
sendResponse();
|
||||
return true;
|
||||
}
|
||||
|
@ -50,39 +51,63 @@
|
|||
}
|
||||
|
||||
if (form) {
|
||||
forms[i].formElement = form;
|
||||
formData.push(forms[i]);
|
||||
var formDataObj = {
|
||||
data: forms[i],
|
||||
formEl: form,
|
||||
usernameEl: null,
|
||||
passwordEl: null
|
||||
};
|
||||
locateFields(formDataObj);
|
||||
formData.push(formDataObj);
|
||||
form.addEventListener('submit', formSubmitted, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function locateFields(formDataObj) {
|
||||
var passwordId = formDataObj.data.password ? formDataObj.data.password.htmlID : null,
|
||||
usernameId = formDataObj.data.username ? formDataObj.data.username.htmlID : null,
|
||||
passwordName = formDataObj.data.password ? formDataObj.data.password.htmlName : null,
|
||||
usernameName = formDataObj.data.username ? formDataObj.data.username.htmlName : null,
|
||||
inputs = document.getElementsByTagName('input');
|
||||
|
||||
if (passwordId && passwordId !== '') {
|
||||
formDataObj.passwordEl = formDataObj.formEl.querySelector('#' + passwordId);
|
||||
}
|
||||
if (!formDataObj.passwordEl && passwordName !== '') {
|
||||
formDataObj.passwordEl = formDataObj.formEl.querySelector('input[name="' + passwordName + '"]');
|
||||
}
|
||||
if (!formDataObj.passwordEl && formDataObj.passwordEl) {
|
||||
formDataObj.passwordEl = inputs[formDataObj.data.password.elementNumber];
|
||||
if (formDataObj.passwordEl && formDataObj.passwordEl.type !== 'password') {
|
||||
formDataObj.passwordEl = null;
|
||||
}
|
||||
}
|
||||
if (!formDataObj.passwordEl) {
|
||||
formDataObj.passwordEl = formDataObj.formEl.querySelector('input[type="password"]');
|
||||
}
|
||||
|
||||
if (usernameId && usernameId !== '') {
|
||||
formDataObj.usernameEl = formDataObj.formEl.querySelector('#' + usernameId);
|
||||
}
|
||||
if (!formDataObj.usernameEl && usernameName !== '') {
|
||||
formDataObj.usernameEl = formDataObj.formEl.querySelector('input[name="' + usernameName + '"]');
|
||||
}
|
||||
if (!formDataObj.usernameEl && formDataObj.data.username) {
|
||||
formDataObj.usernameEl = inputs[formDataObj.data.username.elementNumber];
|
||||
}
|
||||
}
|
||||
|
||||
function formSubmitted(e) {
|
||||
for (var i = 0; i < formData.length; i++) {
|
||||
if (formData[i].formElement === e.target) {
|
||||
var password = null,
|
||||
username = null,
|
||||
passwordId = formData[i].password ? formData[i].password.htmlID : null,
|
||||
usernameId = formData[i].username ? formData[i].username.htmlID : null,
|
||||
inputs = document.getElementsByTagName('input');
|
||||
|
||||
if (passwordId && passwordId !== '') {
|
||||
password = document.getElementById(passwordId);
|
||||
}
|
||||
else if (formData[i].password) {
|
||||
password = inputs[formData[i].password.elementNumber];
|
||||
}
|
||||
|
||||
if (usernameId && usernameId !== '') {
|
||||
username = document.getElementById(usernameId);
|
||||
}
|
||||
else if (formData[i].username) {
|
||||
username = inputs[formData[i].username.elementNumber];
|
||||
if (formData[i].formEl === e.target) {
|
||||
if (!formData[i].usernameEl || !formData[i].passwordEl) {
|
||||
break;
|
||||
}
|
||||
|
||||
var login = {
|
||||
username: username.value,
|
||||
password: password.value,
|
||||
username: formData[i].usernameEl.value,
|
||||
password: formData[i].passwordEl.value,
|
||||
url: document.URL
|
||||
};
|
||||
|
||||
|
@ -97,9 +122,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
function openBar(type, typeData) {
|
||||
function closeExistingAndOpenBar(type, typeData) {
|
||||
var barPage = 'notification/bar.html';
|
||||
barType = type;
|
||||
switch (type) {
|
||||
case 'info':
|
||||
barPage = barPage + '?info=' + typeData.text;
|
||||
|
@ -120,6 +144,18 @@
|
|||
break;
|
||||
}
|
||||
|
||||
var frame = document.getElementById('bit-notification-bar-iframe');
|
||||
if (frame && frame.src.indexOf(barPage) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeBar(false);
|
||||
openBar(type, barPage);
|
||||
}
|
||||
|
||||
function openBar(type, barPage) {
|
||||
barType = type;
|
||||
|
||||
if (!document.body) {
|
||||
return;
|
||||
}
|
||||
|
@ -127,6 +163,7 @@
|
|||
var iframe = document.createElement('iframe');
|
||||
iframe.src = chrome.extension.getURL(barPage);
|
||||
iframe.style.cssText = 'height: 42px; width: 100%; border: 0;';
|
||||
iframe.id = 'bit-notification-bar-iframe';
|
||||
|
||||
var frameDiv = document.createElement('div');
|
||||
frameDiv.id = 'bit-notification-bar';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"manifest_version": 2,
|
||||
"name": "__MSG_extName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "1.7.0",
|
||||
"version": "1.9.2",
|
||||
"description": "__MSG_extDesc__",
|
||||
"default_locale": "en",
|
||||
"author": "8bit Solutions LLC",
|
||||
|
@ -31,11 +31,9 @@
|
|||
"background": {
|
||||
"scripts": [
|
||||
"lib/jquery/jquery.js",
|
||||
"lib/sjcl/sjcl.js",
|
||||
"lib/sjcl/cbc.js",
|
||||
"lib/sjcl/bitArray.js",
|
||||
"lib/q/q.js",
|
||||
"lib/tldjs/tld.js",
|
||||
"lib/forge/forge.js",
|
||||
"models/api/requestModels.js",
|
||||
"models/api/responseModels.js",
|
||||
"models/dataModels.js",
|
||||
|
@ -47,6 +45,7 @@
|
|||
"services/tokenService.js",
|
||||
"services/apiService.js",
|
||||
"services/userService.js",
|
||||
"services/settingsService.js",
|
||||
"services/folderService.js",
|
||||
"services/loginService.js",
|
||||
"services/syncService.js",
|
||||
|
@ -71,6 +70,7 @@
|
|||
"contextMenus",
|
||||
"storage",
|
||||
"unlimitedStorage",
|
||||
"clipboardWrite",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
|
|
|
@ -12,13 +12,39 @@ var FolderRequest = function (folder) {
|
|||
this.name = folder.name ? folder.name.encryptedString : null;
|
||||
};
|
||||
|
||||
var TokenRequest = function (email, masterPasswordHash, device) {
|
||||
var TokenRequest = function (email, masterPasswordHash, token, device) {
|
||||
this.email = email;
|
||||
this.masterPasswordHash = masterPasswordHash;
|
||||
if (device) {
|
||||
this.device = new DeviceRequest(device);
|
||||
}
|
||||
this.token = token;
|
||||
this.provider = 0; // 0 = Authenticator
|
||||
this.device = null;
|
||||
if (device) {
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
this.toIdentityToken = function () {
|
||||
var obj = {
|
||||
grant_type: 'password',
|
||||
username: this.email,
|
||||
password: this.masterPasswordHash,
|
||||
scope: 'api offline_access',
|
||||
client_id: 'browser'
|
||||
};
|
||||
|
||||
if (this.device) {
|
||||
obj.deviceType = this.device.type;
|
||||
obj.deviceIdentifier = this.device.identifier;
|
||||
obj.deviceName = this.device.name;
|
||||
obj.devicePushToken = this.device.pushToken;
|
||||
}
|
||||
|
||||
if (this.token && this.provider != null && (typeof this.provider !== 'undefined')) {
|
||||
obj.twoFactorToken = this.token;
|
||||
obj.twoFactorProvider = this.provider;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
};
|
||||
|
||||
var RegisterRequest = function (email, masterPasswordHash, masterPasswordHint) {
|
||||
|
@ -32,19 +58,13 @@ var PasswordHintRequest = function (email) {
|
|||
this.email = email;
|
||||
};
|
||||
|
||||
var TokenTwoFactorRequest = function (code) {
|
||||
this.code = code;
|
||||
this.provider = "Authenticator";
|
||||
this.device = null;
|
||||
};
|
||||
|
||||
var DeviceTokenRequest = function () {
|
||||
this.pushToken = null;
|
||||
};
|
||||
|
||||
var DeviceRequest = function () {
|
||||
this.type = null;
|
||||
this.name = null;
|
||||
this.identifier = null;
|
||||
var DeviceRequest = function (appId, utilsService) {
|
||||
this.type = utilsService.getDeviceType();
|
||||
this.name = utilsService.getBrowser();
|
||||
this.identifier = appId;
|
||||
this.pushToken = null;
|
||||
};
|
||||
|
|
|
@ -38,22 +38,31 @@ var ProfileResponse = function (response) {
|
|||
this.twoFactorEnabled = response.TwoFactorEnabled;
|
||||
};
|
||||
|
||||
var TokenResponse = function (response) {
|
||||
this.token = response.Token;
|
||||
var IdentityTokenResponse = function (response) {
|
||||
this.accessToken = response.access_token;
|
||||
this.expiresIn = response.expires_in;
|
||||
this.refreshToken = response.refresh_token;
|
||||
this.tokenType = response.token_type;
|
||||
|
||||
if (response.Profile) {
|
||||
this.profile = new ProfileResponse(response.Profile);
|
||||
}
|
||||
// TODO: extras
|
||||
};
|
||||
|
||||
var ListResponse = function (data) {
|
||||
this.data = data;
|
||||
};
|
||||
|
||||
var ErrorResponse = function (response) {
|
||||
if (response.responseJSON) {
|
||||
this.message = response.responseJSON.Message;
|
||||
this.validationErrors = response.responseJSON.ValidationErrors;
|
||||
var ErrorResponse = function (response, identityResponse) {
|
||||
var errorModel = null;
|
||||
if (identityResponse && identityResponse === true && response.responseJSON && response.responseJSON.ErrorModel) {
|
||||
errorModel = response.responseJSON.ErrorModel;
|
||||
}
|
||||
else if (response.responseJSON) {
|
||||
errorModel = response.responseJSON;
|
||||
}
|
||||
|
||||
if (errorModel) {
|
||||
this.message = errorModel.Message;
|
||||
this.validationErrors = errorModel.ValidationErrors;
|
||||
}
|
||||
this.statusCode = response.status;
|
||||
};
|
||||
|
@ -71,8 +80,27 @@ var CipherHistoryResponse = function (response) {
|
|||
|
||||
var revised = response.Revised;
|
||||
for (var i = 0; i < revised.length; i++) {
|
||||
revised.push(new CipherResponse(revised[i]));
|
||||
this.revised.push(new CipherResponse(revised[i]));
|
||||
}
|
||||
|
||||
this.deleted = response.Deleted;
|
||||
};
|
||||
|
||||
var DomainsResponse = function (response) {
|
||||
var GlobalDomainResponse = function (response) {
|
||||
this.type = response.Type;
|
||||
this.domains = response.Domains;
|
||||
this.excluded = response.Excluded;
|
||||
};
|
||||
|
||||
this.equivalentDomains = response.EquivalentDomains;
|
||||
this.globalEquivalentDomains = [];
|
||||
|
||||
var globalEquivalentDomains = response.GlobalEquivalentDomains;
|
||||
if (!globalEquivalentDomains) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < globalEquivalentDomains.length; i++) {
|
||||
this.globalEquivalentDomains.push(new GlobalDomainResponse(globalEquivalentDomains[i]));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -32,19 +32,24 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$scope.loginPromise = authService.logIn(model.email, model.masterPassword);
|
||||
$scope.loginPromise = authService.logIn(model.email, model.masterPassword, null);
|
||||
|
||||
$scope.loginPromise.then(function () {
|
||||
userService.isTwoFactorAuthenticated(function (isTwoFactorAuthenticated) {
|
||||
if (isTwoFactorAuthenticated) {
|
||||
$analytics.eventTrack('Logged In To Two-step');
|
||||
$state.go('twoFactor', { animation: 'in-slide-left' });
|
||||
}
|
||||
else {
|
||||
$analytics.eventTrack('Logged In');
|
||||
$state.go('tabs.vault', { animation: 'in-slide-left', syncOnLoad: true });
|
||||
}
|
||||
});
|
||||
$scope.loginPromise.then(function (twoFactor) {
|
||||
if (twoFactor) {
|
||||
$analytics.eventTrack('Logged In To Two-step');
|
||||
$state.go('twoFactor', {
|
||||
animation: 'in-slide-left',
|
||||
email: model.email,
|
||||
masterPassword: model.masterPassword
|
||||
});
|
||||
}
|
||||
else {
|
||||
$analytics.eventTrack('Logged In');
|
||||
$state.go('tabs.vault', {
|
||||
animation: 'in-slide-left',
|
||||
syncOnLoad: true
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsLoginTwoFactorController', function ($scope, $state, authService, toastr, utilsService,
|
||||
$analytics, i18nService) {
|
||||
$analytics, i18nService, $stateParams) {
|
||||
$scope.i18n = i18nService;
|
||||
$scope.model = {};
|
||||
utilsService.initListSectionItemListeners($(document), angular);
|
||||
$('#code').focus();
|
||||
|
||||
var email = $stateParams.email;
|
||||
var masterPassword = $stateParams.masterPassword;
|
||||
|
||||
$scope.loginPromise = null;
|
||||
$scope.login = function (model) {
|
||||
if (!model.code) {
|
||||
|
@ -15,7 +18,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$scope.loginPromise = authService.logInTwoFactor(model.code);
|
||||
$scope.loginPromise = authService.logIn(email, masterPassword, model.code);
|
||||
$scope.loginPromise.then(function () {
|
||||
$analytics.eventTrack('Logged In From Two-step');
|
||||
$state.go('tabs.vault', { animation: 'in-slide-left', syncOnLoad: true });
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
controller: 'accountsLoginTwoFactorController',
|
||||
templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html',
|
||||
data: { authorize: false },
|
||||
params: { animation: null }
|
||||
params: { animation: null, email: null, masterPassword: null }
|
||||
})
|
||||
.state('register', {
|
||||
url: '/register',
|
||||
|
@ -190,42 +190,38 @@
|
|||
params: { animation: null }
|
||||
});
|
||||
})
|
||||
.run(function ($rootScope, userService, authService, cryptoService, tokenService, $state, constantsService, stateService) {
|
||||
.run(function ($rootScope, userService, cryptoService, tokenService, $state, constantsService, stateService) {
|
||||
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
|
||||
if ($state.current.name.indexOf('tabs.') > -1 && toState.name.indexOf('tabs.') > -1) {
|
||||
stateService.purgeState();
|
||||
}
|
||||
|
||||
cryptoService.getKey(false, function (key) {
|
||||
tokenService.getToken(function (token) {
|
||||
userService.isAuthenticated(function (isAuthenticated) {
|
||||
if (isAuthenticated) {
|
||||
var obj = {};
|
||||
obj[constantsService.lastActiveKey] = (new Date()).getTime();
|
||||
chrome.storage.local.set(obj, function () { });
|
||||
}
|
||||
userService.isAuthenticated(function (isAuthenticated) {
|
||||
if (isAuthenticated) {
|
||||
var obj = {};
|
||||
obj[constantsService.lastActiveKey] = (new Date()).getTime();
|
||||
chrome.storage.local.set(obj, function () { });
|
||||
}
|
||||
|
||||
if (!toState.data || !toState.data.authorize) {
|
||||
if (isAuthenticated && !tokenService.isTokenExpired(token)) {
|
||||
event.preventDefault();
|
||||
if (!key) {
|
||||
$state.go('lock');
|
||||
}
|
||||
else {
|
||||
$state.go('tabs.current');
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthenticated || tokenService.isTokenExpired(token)) {
|
||||
if (!toState.data || !toState.data.authorize) {
|
||||
if (isAuthenticated && !tokenService.isTokenExpired()) {
|
||||
event.preventDefault();
|
||||
authService.logOut(function () {
|
||||
$state.go('home');
|
||||
});
|
||||
if (!key) {
|
||||
$state.go('lock');
|
||||
}
|
||||
else {
|
||||
$state.go('tabs.current');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthenticated || tokenService.isTokenExpired()) {
|
||||
event.preventDefault();
|
||||
chrome.runtime.sendMessage({ command: 'logout' });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ angular
|
|||
$scope.loaded = false;
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
$timeout(loadVault, 0);
|
||||
$timeout(loadVault, 100);
|
||||
});
|
||||
|
||||
function loadVault() {
|
||||
|
@ -41,17 +41,9 @@ angular
|
|||
canAutofill = true;
|
||||
});
|
||||
|
||||
var filteredLogins = [];
|
||||
var loginPromise = $q.when(loginService.getAllDecrypted());
|
||||
loginPromise.then(function (logins) {
|
||||
for (var i = 0; i < logins.length; i++) {
|
||||
if (logins[i].domain && logins[i].domain === domain) {
|
||||
filteredLogins.push(logins[i]);
|
||||
}
|
||||
}
|
||||
|
||||
$q.when(loginService.getAllDecryptedForDomain(domain)).then(function (logins) {
|
||||
$scope.loaded = true;
|
||||
$scope.logins = filteredLogins;
|
||||
$scope.logins = logins;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
angular
|
||||
.module('bit.global')
|
||||
|
||||
.controller('mainController', function ($scope, $state, authService, toastr, i18nService) {
|
||||
.controller('mainController', function ($scope, $state, authService, toastr, i18nService, $analytics) {
|
||||
var self = this;
|
||||
self.currentYear = new Date().getFullYear();
|
||||
self.animation = '';
|
||||
|
@ -23,9 +23,12 @@ angular
|
|||
else if (msg.command === 'syncStarted') {
|
||||
$scope.$broadcast('syncStarted');
|
||||
}
|
||||
else if (msg.command === 'logout') {
|
||||
else if (msg.command === 'doneLoggingOut') {
|
||||
authService.logOut(function () {
|
||||
toastr.warning(i18nService.loginExpired, i18nService.loggedOut);
|
||||
$analytics.eventTrack('Logged Out');
|
||||
if (msg.expired) {
|
||||
toastr.warning(i18nService.loginExpired, i18nService.loggedOut);
|
||||
}
|
||||
$state.go('home');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
angular
|
||||
.module('bit.lock')
|
||||
|
||||
.controller('lockController', function ($scope, $state, $analytics, i18nService, authService, cryptoService, toastr,
|
||||
.controller('lockController', function ($scope, $state, $analytics, i18nService, cryptoService, toastr,
|
||||
userService, SweetAlert) {
|
||||
$scope.i18n = i18nService;
|
||||
$('#master-password').focus();
|
||||
|
@ -15,10 +15,7 @@
|
|||
cancelButtonText: i18nService.cancel
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
authService.logOut(function () {
|
||||
$analytics.eventTrack('Logged Out');
|
||||
$state.go('home');
|
||||
});
|
||||
chrome.runtime.sendMessage({ command: 'logout' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -2,91 +2,50 @@
|
|||
.module('bit.services')
|
||||
|
||||
.factory('authService', function (cryptoService, apiService, userService, tokenService, $q, $rootScope, loginService,
|
||||
folderService) {
|
||||
folderService, settingsService, syncService, appIdService, utilsService) {
|
||||
var _service = {};
|
||||
|
||||
_service.logIn = function (email, masterPassword) {
|
||||
_service.logIn = function (email, masterPassword, twoFactorToken) {
|
||||
email = email.toLowerCase();
|
||||
var key = cryptoService.makeKey(masterPassword, email);
|
||||
var deferred = $q.defer();
|
||||
cryptoService.hashPassword(masterPassword, key, function (hashedPassword) {
|
||||
var request = new TokenRequest(email, hashedPassword);
|
||||
appIdService.getAppId(function (appId) {
|
||||
var deviceRequest = new DeviceRequest(appId, utilsService);
|
||||
var request = new TokenRequest(email, hashedPassword, twoFactorToken, deviceRequest);
|
||||
|
||||
apiService.postToken(request, function (response) {
|
||||
if (!response || !response.token) {
|
||||
return;
|
||||
}
|
||||
apiService.postIdentityToken(request, function (response) {
|
||||
// success
|
||||
if (!response || !response.accessToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
tokenService.setToken(response.token, function () {
|
||||
cryptoService.setKey(key, function () {
|
||||
cryptoService.setKeyHash(hashedPassword, function () {
|
||||
if (response.profile) {
|
||||
userService.setUserId(response.profile.id, function () {
|
||||
userService.setEmail(response.profile.email, function () {
|
||||
chrome.runtime.sendMessage({ command: 'loggedIn' });
|
||||
deferred.resolve(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
deferred.resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}, function (error) {
|
||||
deferred.reject(error);
|
||||
});
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
_service.logInTwoFactor = function (code) {
|
||||
var request = new TokenTwoFactorRequest(code.replace(' ', ''));
|
||||
|
||||
var deferred = $q.defer();
|
||||
apiService.postTokenTwoFactor(request, function (response) {
|
||||
if (!response || !response.token) {
|
||||
deferred.reject();
|
||||
return;
|
||||
}
|
||||
|
||||
tokenService.setToken(response.token, function () {
|
||||
userService.setUserId(response.profile.id, function () {
|
||||
userService.setEmail(response.profile.email, function () {
|
||||
chrome.runtime.sendMessage({ command: 'loggedIn' });
|
||||
deferred.resolve(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, function (error) {
|
||||
deferred.reject(error);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
_service.logOut = function (callback) {
|
||||
userService.getUserId(function (userId) {
|
||||
tokenService.clearToken(function () {
|
||||
cryptoService.clearKey(function () {
|
||||
cryptoService.clearKeyHash(function () {
|
||||
userService.clearUserId(function () {
|
||||
userService.clearEmail(function () {
|
||||
loginService.clear(userId, function () {
|
||||
folderService.clear(userId, function () {
|
||||
$rootScope.vaultLogins = null;
|
||||
$rootScope.vaultFolders = null;
|
||||
chrome.runtime.sendMessage({ command: 'loggedOut' });
|
||||
callback();
|
||||
});
|
||||
tokenService.setTokens(response.accessToken, response.refreshToken, function () {
|
||||
cryptoService.setKey(key, function () {
|
||||
cryptoService.setKeyHash(hashedPassword, function () {
|
||||
userService.setUserIdAndEmail(tokenService.getUserId(), tokenService.getEmail(), function () {
|
||||
chrome.runtime.sendMessage({ command: 'loggedIn' });
|
||||
deferred.resolve(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}, function () {
|
||||
// two factor required
|
||||
deferred.resolve(true);
|
||||
}, function (error) {
|
||||
// error
|
||||
deferred.reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
_service.logOut = function (callback) {
|
||||
$rootScope.vaultLogins = null;
|
||||
$rootScope.vaultFolders = null;
|
||||
callback();
|
||||
};
|
||||
|
||||
return _service;
|
||||
|
|
|
@ -42,4 +42,7 @@
|
|||
})
|
||||
.factory('constantsService', function () {
|
||||
return chrome.extension.getBackgroundPage().constantsService;
|
||||
})
|
||||
.factory('settingsService', function () {
|
||||
return chrome.extension.getBackgroundPage().settingsService;
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
angular
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsController', function ($scope, authService, $state, SweetAlert, utilsService, $analytics,
|
||||
.controller('settingsController', function ($scope, $state, SweetAlert, utilsService, $analytics,
|
||||
i18nService, constantsService, cryptoService) {
|
||||
utilsService.initListSectionItemListeners($(document), angular);
|
||||
$scope.lockOption = '';
|
||||
|
@ -38,10 +38,7 @@
|
|||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
cryptoService.toggleKey(function () { });
|
||||
authService.logOut(function () {
|
||||
$analytics.eventTrack('Logged Out');
|
||||
$state.go('home');
|
||||
});
|
||||
chrome.runtime.sendMessage({ command: 'logout' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -58,10 +55,7 @@
|
|||
cancelButtonText: i18nService.cancel
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
authService.logOut(function () {
|
||||
$analytics.eventTrack('Logged Out');
|
||||
$state.go('home');
|
||||
});
|
||||
chrome.runtime.sendMessage({ command: 'logout' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
$scope.sync = function () {
|
||||
$scope.loading = true;
|
||||
syncService.fullSync(function () {
|
||||
syncService.fullSync(true, function () {
|
||||
$analytics.eventTrack('Synced Full');
|
||||
$scope.loading = false;
|
||||
toastr.success(i18nService.syncingComplete);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
if (syncOnLoad) {
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
$timeout(function () {
|
||||
syncService.fullSync(function () { });
|
||||
syncService.fullSync(true, function () { });
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
<div class="list">
|
||||
<div class="list-grouped" ng-repeat="folder in vaultFolders | orderBy: folderSort" ng-show="vaultFolderLogins.length">
|
||||
<div class="list-grouped-header">
|
||||
<i class="fa fa-folder-open"></i> {{folder.name}}
|
||||
<small>{{vaultFolderLogins.length}}</small>
|
||||
<i class="fa fa-folder-open"></i> {{folder.name}}
|
||||
</div>
|
||||
<a href="javascript:void(0)" ng-click="viewLogin(login)"
|
||||
class="list-grouped-item condensed" title="{{i18n.edit}} {{login.name}}"
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
function ApiService(tokenService) {
|
||||
function ApiService(tokenService, appIdService, utilsService, logoutCallback) {
|
||||
//this.baseUrl = 'http://localhost:4000';
|
||||
this.baseUrl = 'https://api.bitwarden.com';
|
||||
this.tokenService = tokenService;
|
||||
this.logoutCallback = logoutCallback;
|
||||
this.appIdService = appIdService;
|
||||
this.utilsService = utilsService;
|
||||
|
||||
initApiService();
|
||||
};
|
||||
|
@ -8,58 +12,67 @@ function ApiService(tokenService) {
|
|||
function initApiService() {
|
||||
// Auth APIs
|
||||
|
||||
ApiService.prototype.postToken = function (tokenRequest, success, error) {
|
||||
ApiService.prototype.postIdentityToken = function (tokenRequest, success, successWithTwoFactor, error) {
|
||||
var self = this;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: self.baseUrl + '/auth/token',
|
||||
data: JSON.stringify(tokenRequest),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
url: self.baseUrl + '/connect/token',
|
||||
data: tokenRequest.toIdentityToken(),
|
||||
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
success(new TokenResponse(response));
|
||||
success(new IdentityTokenResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.postTokenTwoFactor = function (twoFactorTokenRequest, success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: self.baseUrl + '/auth/token/two-factor?access_token=' + token,
|
||||
data: JSON.stringify(twoFactorTokenRequest),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
success(new TokenResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
if (jqXHR.responseJSON && jqXHR.responseJSON.TwoFactorProviders &&
|
||||
jqXHR.responseJSON.TwoFactorProviders.length) {
|
||||
successWithTwoFactor();
|
||||
}
|
||||
});
|
||||
else {
|
||||
error(new ErrorResponse(jqXHR, true));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Account APIs
|
||||
|
||||
ApiService.prototype.getProfile = function (success, error) {
|
||||
ApiService.prototype.getAccountRevisionDate = function (success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: self.baseUrl + '/accounts/profile?access_token=' + token,
|
||||
url: self.baseUrl + '/accounts/revision-date?access_token2=' + token,
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
success(response);
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.getProfile = function (success, error) {
|
||||
var self = this;
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: self.baseUrl + '/accounts/profile?access_token2=' + token,
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
success(new ProfileResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -75,7 +88,7 @@ function initApiService() {
|
|||
success();
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -92,36 +105,59 @@ function initApiService() {
|
|||
success();
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Settings APIs
|
||||
|
||||
ApiService.prototype.getIncludedDomains = function (success, error) {
|
||||
var self = this;
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: self.baseUrl + '/settings/domains?excluded=false&access_token2=' + token,
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
success(new DomainsResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
// Login APIs
|
||||
|
||||
ApiService.prototype.getLogin = function (id, success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: self.baseUrl + '/sites/' + id + '?access_token=' + token,
|
||||
url: self.baseUrl + '/sites/' + id + '?access_token2=' + token,
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
success(new LoginResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.postLogin = function (loginRequest, success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: self.baseUrl + '/sites?access_token=' + token,
|
||||
url: self.baseUrl + '/sites?access_token2=' + token,
|
||||
data: JSON.stringify(loginRequest),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
|
@ -129,18 +165,20 @@ function initApiService() {
|
|||
success(new LoginResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.putLogin = function (id, loginRequest, success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: self.baseUrl + '/sites/' + id + '?access_token=' + token,
|
||||
url: self.baseUrl + '/sites/' + id + '?access_token2=' + token,
|
||||
data: JSON.stringify(loginRequest),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
|
@ -148,9 +186,11 @@ function initApiService() {
|
|||
success(new LoginResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -158,27 +198,29 @@ function initApiService() {
|
|||
|
||||
ApiService.prototype.getFolder = function (id, success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: self.baseUrl + '/folders/' + id + '?access_token=' + token,
|
||||
url: self.baseUrl + '/folders/' + id + '?access_token2=' + token,
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
success(new FolderResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.postFolder = function (folderRequest, success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: self.baseUrl + '/folders?access_token=' + token,
|
||||
url: self.baseUrl + '/folders?access_token2=' + token,
|
||||
data: JSON.stringify(folderRequest),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
|
@ -186,18 +228,20 @@ function initApiService() {
|
|||
success(new FolderResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.putFolder = function (id, folderRequest, success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: self.baseUrl + '/folders/' + id + '?access_token=' + token,
|
||||
url: self.baseUrl + '/folders/' + id + '?access_token2=' + token,
|
||||
data: JSON.stringify(folderRequest),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
|
@ -205,9 +249,11 @@ function initApiService() {
|
|||
success(new FolderResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -215,27 +261,29 @@ function initApiService() {
|
|||
|
||||
ApiService.prototype.getCipher = function (id, success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: self.baseUrl + '/ciphers/' + id + '?access_token=' + token,
|
||||
url: self.baseUrl + '/ciphers/' + id + '?access_token2=' + token,
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
success(new CipherResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.getCiphers = function (success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: self.baseUrl + '/ciphers?access_token=' + token,
|
||||
url: self.baseUrl + '/ciphers?access_token2=' + token,
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
var data = [];
|
||||
|
@ -246,37 +294,124 @@ function initApiService() {
|
|||
success(new ListResponse(data));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
ApiService.prototype.deleteCipher = function (id, success, error) {
|
||||
var self = this;
|
||||
this.tokenService.getToken(function (token) {
|
||||
handleTokenState(self).then(function (token) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: self.baseUrl + '/ciphers/' + id + '/delete?access_token=' + token,
|
||||
url: self.baseUrl + '/ciphers/' + id + '/delete?access_token2=' + token,
|
||||
dataType: 'text',
|
||||
success: function (response) {
|
||||
success();
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
handleError(error, jqXHR, textStatus, errorThrown);
|
||||
handleError(error, jqXHR, false, self);
|
||||
}
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
handleError(error, jqXHR, true, self);
|
||||
});
|
||||
};
|
||||
|
||||
// Helpers
|
||||
|
||||
function handleError(errorCallback, jqXHR, textStatus, errorThrown) {
|
||||
if (jqXHR.status === 401 || jqXHR.status === 403) {
|
||||
chrome.runtime.sendMessage({ command: 'logout' });
|
||||
function handleError(errorCallback, jqXHR, tokenError, self) {
|
||||
if (tokenError || jqXHR.status === 401 || jqXHR.status === 403) {
|
||||
if (self && self.logoutCallback) {
|
||||
self.logoutCallback(true, function () { })
|
||||
}
|
||||
else {
|
||||
chrome.runtime.sendMessage({ command: 'logout', expired: true });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
errorCallback(new ErrorResponse(jqXHR));
|
||||
}
|
||||
|
||||
function handleTokenState(self) {
|
||||
var deferred = Q.defer();
|
||||
self.tokenService.getAuthBearer(function (authBearer) {
|
||||
self.tokenService.getToken(function (accessToken) {
|
||||
// handle transferring from old auth bearer
|
||||
if (authBearer && !accessToken) {
|
||||
self.appIdService.getAppId(function (appId) {
|
||||
postConnectToken(self, {
|
||||
grant_type: 'password',
|
||||
oldAuthBearer: authBearer,
|
||||
username: 'abcdefgh', // has to be something
|
||||
password: 'abcdefgh', // has to be something
|
||||
scope: 'api offline_access',
|
||||
client_id: 'browser',
|
||||
deviceIdentifier: appId,
|
||||
deviceType: self.utilsService.getDeviceType(),
|
||||
deviceName: self.utilsService.getBrowser()
|
||||
}, function (token) {
|
||||
self.tokenService.clearAuthBearer(function () {
|
||||
tokenService.setTokens(token.accessToken, token.refreshToken, function () {
|
||||
deferred.resolve(token.accessToken);
|
||||
});
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
deferred.reject(jqXHR);
|
||||
});
|
||||
});
|
||||
} // handle token refresh
|
||||
else if (self.tokenService.tokenNeedsRefresh()) {
|
||||
self.tokenService.getRefreshToken(function (refreshToken) {
|
||||
if (!refreshToken || refreshToken === '') {
|
||||
deferred.reject();
|
||||
return;
|
||||
}
|
||||
|
||||
postConnectToken(self, {
|
||||
grant_type: 'refresh_token',
|
||||
client_id: 'browser',
|
||||
refresh_token: refreshToken
|
||||
}, function (token) {
|
||||
tokenService.setTokens(token.accessToken, token.refreshToken, function () {
|
||||
deferred.resolve(token.accessToken);
|
||||
});
|
||||
}, function (jqXHR) {
|
||||
deferred.reject(jqXHR);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (authBearer) {
|
||||
self.tokenService.clearAuthBearer(function () { });
|
||||
}
|
||||
|
||||
deferred.resolve(accessToken);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
function postConnectToken(self, data, success, error) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: self.baseUrl + '/connect/token',
|
||||
data: data,
|
||||
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
success(new IdentityTokenResponse(response));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
error(jqXHR);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -42,11 +42,11 @@ function initAutofill() {
|
|||
passwords.push(pf);
|
||||
|
||||
if (fillUsername) {
|
||||
username = findUsernameField(pageDetails, pf, false);
|
||||
username = findUsernameField(pageDetails, pf, false, false);
|
||||
|
||||
if (!username) {
|
||||
// not able to find any viewable username fields. maybe there are some "hidden" ones?
|
||||
username = findUsernameField(pageDetails, pf, true);
|
||||
username = findUsernameField(pageDetails, pf, true, false);
|
||||
}
|
||||
|
||||
if (username) {
|
||||
|
@ -64,11 +64,11 @@ function initAutofill() {
|
|||
passwords.push(pf);
|
||||
|
||||
if (fillUsername && pf.elementNumber > 0) {
|
||||
username = findUsernameFieldWithoutForm(pageDetails, pf, false);
|
||||
username = findUsernameField(pageDetails, pf, false, true);
|
||||
|
||||
if (!username) {
|
||||
// not able to find any viewable username fields. maybe there are some "hidden" ones?
|
||||
username = findUsernameFieldWithoutForm(pageDetails, pf, true);
|
||||
username = findUsernameField(pageDetails, pf, true, true);
|
||||
}
|
||||
|
||||
if (username) {
|
||||
|
@ -109,10 +109,10 @@ function initAutofill() {
|
|||
for (var i = 0; i < passwordFields.length; i++) {
|
||||
var pf = passwordFields[i];
|
||||
if (formKey === pf.form) {
|
||||
var uf = findUsernameField(pageDetails, pf, false);
|
||||
var uf = findUsernameField(pageDetails, pf, false, false);
|
||||
if (!uf) {
|
||||
// not able to find any viewable username fields. maybe there are some "hidden" ones?
|
||||
uf = findUsernameField(pageDetails, pf, true);
|
||||
uf = findUsernameField(pageDetails, pf, true, false);
|
||||
}
|
||||
|
||||
formData.push({
|
||||
|
@ -140,28 +140,16 @@ function initAutofill() {
|
|||
return arr;
|
||||
}
|
||||
|
||||
function findUsernameField(pageDetails, passwordField, canBeHidden) {
|
||||
for (var i = 0; i < pageDetails.fields.length; i++) {
|
||||
var f = pageDetails.fields[i];
|
||||
if (f.form === passwordField.form && (canBeHidden || f.viewable)
|
||||
&& (f.type === 'text' || f.type === 'email' || f.type === 'tel')
|
||||
&& f.elementNumber < passwordField.elementNumber) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findUsernameFieldWithoutForm(pageDetails, passwordField, canBeHidden) {
|
||||
function findUsernameField(pageDetails, passwordField, canBeHidden, withoutForm) {
|
||||
var usernameField = null;
|
||||
for (var i = 0; i < pageDetails.fields.length; i++) {
|
||||
var f = pageDetails.fields[i];
|
||||
if (f.elementNumber > passwordField.elementNumber) {
|
||||
if (f.elementNumber >= passwordField.elementNumber) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((canBeHidden || f.viewable) && (f.type === 'text' || f.type === 'email' || f.type === 'tel')) {
|
||||
if ((withoutForm || f.form === passwordField.form) && (canBeHidden || f.viewable) &&
|
||||
(f.type === 'text' || f.type === 'email' || f.type === 'tel')) {
|
||||
usernameField = f;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,8 @@ function initCryptoService() {
|
|||
_b64Key,
|
||||
_keyHash,
|
||||
_b64KeyHash,
|
||||
_aes,
|
||||
_aesWithMac;
|
||||
|
||||
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
|
||||
_encKey,
|
||||
_macKey;
|
||||
|
||||
CryptoService.prototype.setKey = function (key, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
|
@ -29,7 +27,7 @@ function initCryptoService() {
|
|||
}
|
||||
|
||||
chrome.storage.local.set({
|
||||
'key': sjcl.codec.base64.fromBits(key)
|
||||
'key': forge.util.encode64(key)
|
||||
}, function () {
|
||||
callback();
|
||||
});
|
||||
|
@ -41,7 +39,7 @@ function initCryptoService() {
|
|||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_keyHash = sjcl.codec.base64.toBits(keyHash);
|
||||
_keyHash = forge.util.encode64(keyHash);
|
||||
|
||||
chrome.storage.local.set({
|
||||
'keyHash': keyHash
|
||||
|
@ -64,7 +62,7 @@ function initCryptoService() {
|
|||
return;
|
||||
}
|
||||
else if (b64 && b64 === true && _key && !_b64Key) {
|
||||
_b64Key = sjcl.codec.base64.fromBits(_key);
|
||||
_b64Key = forge.util.encode64(_key);
|
||||
callback(_b64Key);
|
||||
return;
|
||||
}
|
||||
|
@ -79,7 +77,7 @@ function initCryptoService() {
|
|||
|
||||
chrome.storage.local.get('key', function (obj) {
|
||||
if (obj && obj.key) {
|
||||
_key = sjcl.codec.base64.toBits(obj.key);
|
||||
_key = forge.util.decode64(obj.key);
|
||||
|
||||
if (b64 && b64 === true) {
|
||||
_b64Key = obj.key;
|
||||
|
@ -98,8 +96,14 @@ function initCryptoService() {
|
|||
throw 'callback function required';
|
||||
}
|
||||
|
||||
if (_encKey) {
|
||||
callback(_encKey);
|
||||
}
|
||||
|
||||
this.getKey(false, function (key) {
|
||||
callback(key.slice(0, 4));
|
||||
var buffer = forge.util.createBuffer(key);
|
||||
_encKey = buffer.getBytes(16);
|
||||
callback(_encKey);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -108,8 +112,15 @@ function initCryptoService() {
|
|||
throw 'callback function required';
|
||||
}
|
||||
|
||||
if (_macKey) {
|
||||
callback(_macKey);
|
||||
}
|
||||
|
||||
this.getKey(false, function (key) {
|
||||
callback(key.slice(4));
|
||||
var buffer = forge.util.createBuffer(key);
|
||||
buffer.getBytes(16);
|
||||
_macKey = buffer.getBytes(16);
|
||||
callback(_macKey);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -126,14 +137,14 @@ function initCryptoService() {
|
|||
return;
|
||||
}
|
||||
else if (b64 && b64 === true && _keyHash && !_b64KeyHash) {
|
||||
_b64KeyHash = sjcl.codec.base64.fromBits(_keyHash);
|
||||
_b64KeyHash = forge.util.encode64(_keyHash);
|
||||
callback(_b64KeyHash);
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.storage.local.get('keyHash', function (obj) {
|
||||
if (obj && obj.keyHash) {
|
||||
_keyHash = sjcl.codec.base64.toBits(obj.keyHash);
|
||||
_keyHash = forge.util.decode64(obj.keyHash);
|
||||
|
||||
if (b64 && b64 === true) {
|
||||
_b64KeyHash = obj.keyHash;
|
||||
|
@ -151,7 +162,7 @@ function initCryptoService() {
|
|||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_key = _b64Key = _aes = _aesWithMac = null;
|
||||
_key = _b64Key = _macKey = _encKey = null;
|
||||
chrome.storage.local.remove('key', function () {
|
||||
callback();
|
||||
});
|
||||
|
@ -196,10 +207,10 @@ function initCryptoService() {
|
|||
};
|
||||
|
||||
CryptoService.prototype.makeKey = function (password, salt, b64) {
|
||||
var key = sjcl.misc.pbkdf2(password, salt, 5000, 256, null);
|
||||
var key = forge.pbkdf2(password, salt, 5000, 256 / 8, 'sha256');
|
||||
|
||||
if (b64 && b64 === true) {
|
||||
return sjcl.codec.base64.fromBits(key);
|
||||
return forge.util.encode64(key);
|
||||
}
|
||||
|
||||
return key;
|
||||
|
@ -215,36 +226,8 @@ function initCryptoService() {
|
|||
throw 'Invalid parameters.';
|
||||
}
|
||||
|
||||
var hashBits = sjcl.misc.pbkdf2(key, password, 1, 256, null);
|
||||
callback(sjcl.codec.base64.fromBits(hashBits));
|
||||
});
|
||||
};
|
||||
|
||||
CryptoService.prototype.getAes = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
this.getKey(false, function (key) {
|
||||
if (!_aes && key) {
|
||||
_aes = new sjcl.cipher.aes(key);
|
||||
}
|
||||
|
||||
callback(_aes);
|
||||
});
|
||||
};
|
||||
|
||||
CryptoService.prototype.getAesWithMac = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
this.getEncKey(function (encKey) {
|
||||
if (!_aesWithMac && encKey) {
|
||||
_aesWithMac = new sjcl.cipher.aes(encKey);
|
||||
}
|
||||
|
||||
callback(_aesWithMac);
|
||||
var hashBits = forge.pbkdf2(key, password, 1, 256 / 8, 'sha256');
|
||||
callback(forge.util.encode64(hashBits));
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -266,22 +249,21 @@ function initCryptoService() {
|
|||
// TODO: Turn on whenever ready to support encrypt-then-mac
|
||||
var encKey = false ? theEncKey : key;
|
||||
|
||||
var response = {};
|
||||
var params = {
|
||||
mode: 'cbc',
|
||||
iv: sjcl.random.randomWords(4, 10)
|
||||
};
|
||||
|
||||
var ctJson = sjcl.encrypt(encKey, plaintextValue, params, response);
|
||||
|
||||
var ct = ctJson.match(/"ct":"([^"]*)"/)[1];
|
||||
var iv = sjcl.codec.base64.fromBits(response.iv);
|
||||
var buffer = forge.util.createBuffer(plaintextValue, 'utf8');
|
||||
var ivBytes = forge.random.getBytesSync(16);
|
||||
var cipher = forge.cipher.createCipher('AES-CBC', encKey);
|
||||
cipher.start({ iv: ivBytes });
|
||||
cipher.update(buffer);
|
||||
cipher.finish();
|
||||
|
||||
var iv = forge.util.encode64(ivBytes);
|
||||
var ctBytes = cipher.output.getBytes();
|
||||
var ct = forge.util.encode64(ctBytes);
|
||||
var cipherString = iv + '|' + ct;
|
||||
|
||||
// TODO: Turn on whenever ready to support encrypt-then-mac
|
||||
if (false) {
|
||||
var mac = computeMac(ct, response.iv, macKey);
|
||||
var mac = computeMac(ctBytes, ivBytes, macKey);
|
||||
cipherString = cipherString + '|' + mac;
|
||||
}
|
||||
|
||||
|
@ -303,31 +285,32 @@ function initCryptoService() {
|
|||
throw 'cannot decrypt nothing';
|
||||
}
|
||||
|
||||
self.getMacKey(function (macKey) {
|
||||
if (!macKey) {
|
||||
throw 'MAC key unavailable.';
|
||||
}
|
||||
|
||||
self.getAes(function (aes) {
|
||||
self.getAesWithMac(function (aesWithMac) {
|
||||
if (!aes || !aesWithMac) {
|
||||
throw 'AES encryption unavailable.';
|
||||
self.getKey(false, function (key) {
|
||||
self.getEncKey(function (theEncKey) {
|
||||
self.getMacKey(function (macKey) {
|
||||
if (!macKey) {
|
||||
throw 'MAC key unavailable.';
|
||||
}
|
||||
|
||||
var ivBits = sjcl.codec.base64.toBits(cipherString.initializationVector);
|
||||
var ctBits = sjcl.codec.base64.toBits(cipherString.cipherText);
|
||||
var ivBytes = forge.util.decode64(cipherString.initializationVector);
|
||||
var ctBytes = forge.util.decode64(cipherString.cipherText);
|
||||
|
||||
var computedMac = null;
|
||||
if (cipherString.mac) {
|
||||
computedMac = computeMac(ctBits, ivBits, macKey);
|
||||
computedMac = computeMac(ctBytes, ivBytes, macKey);
|
||||
if (computedMac !== cipherString.mac) {
|
||||
console.error('MAC failed.');
|
||||
deferred.reject('MAC failed.');
|
||||
}
|
||||
}
|
||||
|
||||
var decBits = sjcl.mode.cbc.decrypt(computedMac ? aesWithMac : aes, ctBits, ivBits, null);
|
||||
var decValue = sjcl.codec.utf8String.fromBits(decBits);
|
||||
var ctBuffer = forge.util.createBuffer(ctBytes);
|
||||
var decipher = forge.cipher.createDecipher('AES-CBC', computedMac ? theEncKey : key);
|
||||
decipher.start({ iv: ivBytes });
|
||||
decipher.update(ctBuffer);
|
||||
decipher.finish();
|
||||
|
||||
var decValue = decipher.output.toString('utf8');
|
||||
deferred.resolve(decValue);
|
||||
});
|
||||
});
|
||||
|
@ -337,16 +320,10 @@ function initCryptoService() {
|
|||
};
|
||||
|
||||
function computeMac(ct, iv, macKey) {
|
||||
if (typeof ct === 'string') {
|
||||
ct = sjcl.codec.base64.toBits(ct);
|
||||
}
|
||||
if (typeof iv === 'string') {
|
||||
iv = sjcl.codec.base64.toBits(iv);
|
||||
}
|
||||
|
||||
var hmac = new sjcl.misc.hmac(macKey, sjcl.hash.sha256);
|
||||
var bits = iv.concat(ct);
|
||||
var mac = hmac.encrypt(bits);
|
||||
return sjcl.codec.base64.fromBits(mac);
|
||||
var hmac = forge.hmac.create();
|
||||
hmac.start('sha256', macKey);
|
||||
hmac.update(iv + ct);
|
||||
var mac = hmac.digest();
|
||||
return forge.util.encode64(mac.getBytes());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
function LoginService(cryptoService, userService, apiService) {
|
||||
function LoginService(cryptoService, userService, apiService, settingsService) {
|
||||
this.cryptoService = cryptoService;
|
||||
this.userService = userService;
|
||||
this.apiService = apiService;
|
||||
this.settingsService = settingsService;
|
||||
this.decryptedLoginCache = null;
|
||||
|
||||
initLoginService();
|
||||
|
@ -133,10 +134,31 @@ function initLoginService() {
|
|||
LoginService.prototype.getAllDecryptedForDomain = function (domain) {
|
||||
var self = this;
|
||||
|
||||
return self.getAllDecrypted().then(function (logins) {
|
||||
var eqDomainsPromise = self.settingsService.getEquivalentDomains().then(function (eqDomains) {
|
||||
var matchingDomains = [];
|
||||
for (var i = 0; i < eqDomains.length; i++) {
|
||||
if (eqDomains[i].length && eqDomains[i].indexOf(domain) >= 0) {
|
||||
matchingDomains = matchingDomains.concat(eqDomains[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingDomains.length) {
|
||||
matchingDomains.push(domain);
|
||||
}
|
||||
|
||||
return matchingDomains;
|
||||
});
|
||||
|
||||
var loginsPromise = self.getAllDecrypted().then(function (logins) {
|
||||
return logins;
|
||||
});
|
||||
|
||||
return Q.all([eqDomainsPromise, loginsPromise]).then(function (result) {
|
||||
var matchingDomains = result[0];
|
||||
var logins = result[1];
|
||||
var loginsToReturn = [];
|
||||
for (var i = 0; i < logins.length; i++) {
|
||||
if (logins[i].domain === domain) {
|
||||
if (logins[i].domain && matchingDomains.indexOf(logins[i].domain) >= 0) {
|
||||
loginsToReturn.push(logins[i]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
function SettingsService(userService) {
|
||||
this.userService = userService;
|
||||
this.settingsCache = null;
|
||||
|
||||
initSettingsService();
|
||||
};
|
||||
|
||||
function initSettingsService() {
|
||||
SettingsService.prototype.clearCache = function () {
|
||||
this.settingsCache = null
|
||||
};
|
||||
|
||||
SettingsService.prototype.getSettings = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
if (self.settingsCache) {
|
||||
callback(self.settingsCache);
|
||||
return;
|
||||
}
|
||||
|
||||
this.userService.getUserId(function (userId) {
|
||||
var key = 'settings_' + userId;
|
||||
chrome.storage.local.get(key, function (obj) {
|
||||
self.settingsCache = obj[key];
|
||||
callback(self.settingsCache);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
SettingsService.prototype.getEquivalentDomains = function (callback) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
getSettingsKey(this, 'equivalentDomains', function (domains) {
|
||||
deferred.resolve(domains);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
function getSettingsKey(self, key, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
self.getSettings(function (settings) {
|
||||
if (settings && settings[key]) {
|
||||
callback(settings[key]);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
SettingsService.prototype.setEquivalentDomains = function (equivalentDomains, callback) {
|
||||
setSettingsKey(this, 'equivalentDomains', equivalentDomains, callback);
|
||||
};
|
||||
|
||||
function setSettingsKey(self, key, value, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
self.userService.getUserId(function (userId) {
|
||||
var settingsKey = 'settings_' + userId;
|
||||
|
||||
self.getSettings(function (settings) {
|
||||
if (!settings) {
|
||||
settings = {};
|
||||
}
|
||||
settings[key] = value;
|
||||
|
||||
var obj = {};
|
||||
obj[settingsKey] = settings;
|
||||
|
||||
chrome.storage.local.set(obj, function () {
|
||||
self.settingsCache = settings;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SettingsService.prototype.clear = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
this.userService.getUserId(function (userId) {
|
||||
chrome.storage.local.remove('settings_' + userId, function () {
|
||||
self.settingsCache = null;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function handleError(error, deferred) {
|
||||
deferred.reject(error);
|
||||
}
|
||||
};
|
|
@ -1,15 +1,16 @@
|
|||
function SyncService(loginService, folderService, userService, apiService) {
|
||||
function SyncService(loginService, folderService, userService, apiService, settingsService) {
|
||||
this.loginService = loginService;
|
||||
this.folderService = folderService;
|
||||
this.userService = userService;
|
||||
this.apiService = apiService;
|
||||
this.settingsService = settingsService;
|
||||
this.syncInProgress = false;
|
||||
|
||||
initSyncService();
|
||||
};
|
||||
|
||||
function initSyncService() {
|
||||
SyncService.prototype.fullSync = function (callback) {
|
||||
SyncService.prototype.fullSync = function (forceSync, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
@ -26,40 +27,85 @@ function initSyncService() {
|
|||
|
||||
self.userService.getUserId(function (userId) {
|
||||
var now = new Date();
|
||||
var ciphers = self.apiService.getCiphers(function (response) {
|
||||
var logins = {};
|
||||
var folders = {};
|
||||
|
||||
for (var i = 0; i < response.data.length; i++) {
|
||||
var data = response.data[i];
|
||||
if (data.type === 1) {
|
||||
logins[data.id] = new LoginData(data, userId);
|
||||
}
|
||||
else if (data.type === 0) {
|
||||
folders[data.id] = new FolderData(data, userId);
|
||||
}
|
||||
needsSyncing(self, forceSync, function (needsSync) {
|
||||
if (!needsSync) {
|
||||
self.syncCompleted(false);
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
self.folderService.replace(folders, function () {
|
||||
self.loginService.replace(logins, function () {
|
||||
self.setLastSync(now, function () {
|
||||
self.syncCompleted(true);
|
||||
callback(true);
|
||||
});
|
||||
syncVault(userId).then(function () {
|
||||
return syncSettings(userId);
|
||||
}).then(function () {
|
||||
self.setLastSync(new Date(), function () {
|
||||
self.syncCompleted(true);
|
||||
callback(true);
|
||||
});
|
||||
}, function () {
|
||||
self.syncCompleted(false);
|
||||
callback(false);
|
||||
});
|
||||
}, handleError);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
SyncService.prototype.incrementalSync = function (callback) {
|
||||
function needsSyncing(self, forceSync, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
// TODO
|
||||
};
|
||||
if (forceSync) {
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
self.getLastSync(function (lastSync) {
|
||||
var now = new Date();
|
||||
|
||||
self.apiService.getAccountRevisionDate(function (response) {
|
||||
var accountRevisionDate = new Date(response);
|
||||
if (lastSync && accountRevisionDate <= lastSync) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function syncVault(userId) {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
self.apiService.getCiphers(function (response) {
|
||||
var logins = {};
|
||||
var folders = {};
|
||||
|
||||
for (var i = 0; i < response.data.length; i++) {
|
||||
var data = response.data[i];
|
||||
if (data.type === 1) {
|
||||
logins[data.id] = new LoginData(data, userId);
|
||||
}
|
||||
else if (data.type === 0) {
|
||||
folders[data.id] = new FolderData(data, userId);
|
||||
}
|
||||
}
|
||||
|
||||
self.folderService.replace(folders, function () {
|
||||
self.loginService.replace(logins, function () {
|
||||
deferred.resolve();
|
||||
return;
|
||||
});
|
||||
});
|
||||
}, function () {
|
||||
deferred.reject();
|
||||
return;
|
||||
});
|
||||
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
function syncFolders(serverFolders, callback) {
|
||||
var self = this;
|
||||
|
@ -129,6 +175,35 @@ function initSyncService() {
|
|||
});
|
||||
}
|
||||
|
||||
function syncSettings(userId) {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
var ciphers = self.apiService.getIncludedDomains(function (response) {
|
||||
var eqDomains = [];
|
||||
if (response && response.equivalentDomains) {
|
||||
eqDomains = eqDomains.concat(response.equivalentDomains);
|
||||
}
|
||||
if (response && response.globalEquivalentDomains) {
|
||||
for (var i = 0; i < response.globalEquivalentDomains.length; i++) {
|
||||
if (response.globalEquivalentDomains[i].domains.length) {
|
||||
eqDomains.push(response.globalEquivalentDomains[i].domains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.settingsService.setEquivalentDomains(eqDomains, function () {
|
||||
deferred.resolve();
|
||||
return;
|
||||
});
|
||||
}, function () {
|
||||
deferred.reject();
|
||||
return;
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
SyncService.prototype.getLastSync = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
|
@ -153,10 +228,6 @@ function initSyncService() {
|
|||
throw 'callback function required';
|
||||
}
|
||||
|
||||
if (!(date instanceof Date)) {
|
||||
throw 'date must be a Date object';
|
||||
}
|
||||
|
||||
this.userService.getUserId(function (userId) {
|
||||
var lastSyncKey = 'lastSync_' + userId;
|
||||
|
||||
|
@ -178,9 +249,4 @@ function initSyncService() {
|
|||
this.syncInProgress = false;
|
||||
chrome.runtime.sendMessage({ command: 'syncCompleted', successfully: successfully });
|
||||
};
|
||||
|
||||
function handleError() {
|
||||
syncCompleted(false);
|
||||
// TODO: check for unauth or forbidden and logout
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,7 +3,23 @@
|
|||
};
|
||||
|
||||
function initTokenService() {
|
||||
var _token;
|
||||
var _token,
|
||||
_authBearer,
|
||||
_decodedToken,
|
||||
_refreshToken;
|
||||
|
||||
TokenService.prototype.setTokens = function (accessToken, refreshToken, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
self.setToken(accessToken, function () {
|
||||
self.setRefreshToken(refreshToken, function () {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
TokenService.prototype.setToken = function (token, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
|
@ -11,8 +27,9 @@ function initTokenService() {
|
|||
}
|
||||
|
||||
_token = token;
|
||||
_decodedToken = null;
|
||||
chrome.storage.local.set({
|
||||
'authBearer': token
|
||||
'accessToken': token
|
||||
}, function () {
|
||||
callback();
|
||||
});
|
||||
|
@ -27,48 +44,120 @@ function initTokenService() {
|
|||
return callback(_token);
|
||||
}
|
||||
|
||||
chrome.storage.local.get('authBearer', function (obj) {
|
||||
if (obj && obj.authBearer) {
|
||||
_token = obj.authBearer;
|
||||
chrome.storage.local.get('accessToken', function (obj) {
|
||||
if (obj && obj.accessToken) {
|
||||
_token = obj.accessToken;
|
||||
}
|
||||
|
||||
return callback(_token);
|
||||
});
|
||||
};
|
||||
|
||||
TokenService.prototype.getAuthBearer = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
if (_authBearer) {
|
||||
return callback(_authBearer);
|
||||
}
|
||||
|
||||
chrome.storage.local.get('authBearer', function (obj) {
|
||||
if (obj && obj.authBearer) {
|
||||
_authBearer = obj.authBearer;
|
||||
}
|
||||
|
||||
return callback(_authBearer);
|
||||
});
|
||||
};
|
||||
|
||||
TokenService.prototype.setRefreshToken = function (refreshToken, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_refreshToken = refreshToken;
|
||||
chrome.storage.local.set({
|
||||
'refreshToken': refreshToken
|
||||
}, function () {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
TokenService.prototype.getRefreshToken = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
if (_refreshToken) {
|
||||
return callback(_refreshToken);
|
||||
}
|
||||
|
||||
chrome.storage.local.get('refreshToken', function (obj) {
|
||||
if (obj && obj.refreshToken) {
|
||||
_refreshToken = obj.refreshToken;
|
||||
}
|
||||
|
||||
return callback(_refreshToken);
|
||||
});
|
||||
};
|
||||
|
||||
TokenService.prototype.clearAuthBearer = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_authBearer = null;
|
||||
chrome.storage.local.remove('authBearer', function () {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
TokenService.prototype.clearToken = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_token = null;
|
||||
_token = _decodedToken = _refreshToken = _authBearer = null;
|
||||
chrome.storage.local.remove('authBearer', function () {
|
||||
callback();
|
||||
chrome.storage.local.remove('accessToken', function () {
|
||||
chrome.storage.local.remove('refreshToken', function () {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// jwthelper methods
|
||||
// ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js
|
||||
|
||||
TokenService.prototype.decodeToken = function (token) {
|
||||
var parts = token.split('.');
|
||||
TokenService.prototype.decodeToken = function () {
|
||||
if (_decodedToken) {
|
||||
return _decodedToken;
|
||||
}
|
||||
|
||||
if (!_token) {
|
||||
throw 'Token not found.';
|
||||
}
|
||||
|
||||
var parts = _token.split('.');
|
||||
if (parts.length !== 3) {
|
||||
throw new Error('JWT must have 3 parts');
|
||||
throw 'JWT must have 3 parts';
|
||||
}
|
||||
|
||||
var decoded = urlBase64Decode(parts[1]);
|
||||
if (!decoded) {
|
||||
throw new Error('Cannot decode the token');
|
||||
throw 'Cannot decode the token';
|
||||
}
|
||||
|
||||
return JSON.parse(decoded);
|
||||
_decodedToken = JSON.parse(decoded);
|
||||
return _decodedToken;
|
||||
};
|
||||
|
||||
TokenService.prototype.getTokenExpirationDate = function (token) {
|
||||
var decoded = this.decodeToken(token);
|
||||
TokenService.prototype.getTokenExpirationDate = function () {
|
||||
var decoded = this.decodeToken();
|
||||
|
||||
if (typeof decoded.exp === "undefined") {
|
||||
if (typeof decoded.exp === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -78,8 +167,8 @@ function initTokenService() {
|
|||
return d;
|
||||
};
|
||||
|
||||
TokenService.prototype.isTokenExpired = function (token, offsetSeconds) {
|
||||
var d = this.getTokenExpirationDate(token);
|
||||
TokenService.prototype.isTokenExpired = function (offsetSeconds) {
|
||||
var d = this.getTokenExpirationDate();
|
||||
offsetSeconds = offsetSeconds || 0;
|
||||
if (d === null) {
|
||||
return false;
|
||||
|
@ -89,6 +178,53 @@ function initTokenService() {
|
|||
return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
|
||||
};
|
||||
|
||||
TokenService.prototype.tokenSecondsRemaining = function (offsetSeconds) {
|
||||
var d = this.getTokenExpirationDate();
|
||||
offsetSeconds = offsetSeconds || 0;
|
||||
if (d === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var msRemaining = d.valueOf() - (new Date().valueOf() + (offsetSeconds * 1000));
|
||||
return Math.round(msRemaining / 1000);
|
||||
};
|
||||
|
||||
TokenService.prototype.tokenNeedsRefresh = function (minutes) {
|
||||
minutes = minutes || 5; // default 5 minutes
|
||||
var sRemaining = this.tokenSecondsRemaining();
|
||||
return sRemaining < (60 * minutes);
|
||||
};
|
||||
|
||||
TokenService.prototype.getUserId = function () {
|
||||
var decoded = this.decodeToken();
|
||||
|
||||
if (typeof decoded.sub === 'undefined') {
|
||||
throw 'No user id found';
|
||||
}
|
||||
|
||||
return decoded.sub;
|
||||
};
|
||||
|
||||
TokenService.prototype.getEmail = function () {
|
||||
var decoded = this.decodeToken();
|
||||
|
||||
if (typeof decoded.email === 'undefined') {
|
||||
throw 'No email found';
|
||||
}
|
||||
|
||||
return decoded.email;
|
||||
};
|
||||
|
||||
TokenService.prototype.getName = function () {
|
||||
var decoded = this.decodeToken();
|
||||
|
||||
if (typeof decoded.name === 'undefined') {
|
||||
throw 'No name found';
|
||||
}
|
||||
|
||||
return decoded.name;
|
||||
};
|
||||
|
||||
function urlBase64Decode(str) {
|
||||
var output = str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
switch (output.length % 4) {
|
||||
|
|
|
@ -13,16 +13,23 @@ function initUserService() {
|
|||
var _userId = null,
|
||||
_email = null;
|
||||
|
||||
UserService.prototype.setUserId = function (userId, callback) {
|
||||
UserService.prototype.setUserIdAndEmail = function (userId, email, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_email = email;
|
||||
var emailObj = {};
|
||||
emailObj[userEmailKey] = email;
|
||||
|
||||
_userId = userId;
|
||||
var obj = {};
|
||||
obj[userIdKey] = userId;
|
||||
chrome.storage.local.set(obj, function () {
|
||||
callback();
|
||||
var userIdObj = {};
|
||||
userIdObj[userIdKey] = userId;
|
||||
|
||||
chrome.storage.local.set(userIdObj, function () {
|
||||
chrome.storage.local.set(emailObj, function () {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -44,30 +51,6 @@ function initUserService() {
|
|||
});
|
||||
};
|
||||
|
||||
UserService.prototype.clearUserId = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_userId = null;
|
||||
chrome.storage.local.remove(userIdKey, function () {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
UserService.prototype.setEmail = function (email, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_email = email;
|
||||
var obj = {};
|
||||
obj[userEmailKey] = email;
|
||||
chrome.storage.local.set(obj, function () {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
UserService.prototype.getEmail = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
|
@ -86,14 +69,16 @@ function initUserService() {
|
|||
});
|
||||
};
|
||||
|
||||
UserService.prototype.clearEmail = function (callback) {
|
||||
UserService.prototype.clearUserIdAndEmail = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_email = null;
|
||||
chrome.storage.local.remove(userEmailKey, function () {
|
||||
callback();
|
||||
_userId = _email = null;
|
||||
chrome.storage.local.remove(userIdKey, function () {
|
||||
chrome.storage.local.remove(userEmailKey, function () {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -104,32 +89,16 @@ function initUserService() {
|
|||
|
||||
var self = this;
|
||||
self.tokenService.getToken(function (token) {
|
||||
if (!token) {
|
||||
callback(false);
|
||||
}
|
||||
else {
|
||||
self.getUserId(function (userId) {
|
||||
callback(userId !== null);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
UserService.prototype.isTwoFactorAuthenticated = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
self.tokenService.getToken(function (token) {
|
||||
if (!token) {
|
||||
callback(false);
|
||||
}
|
||||
else {
|
||||
self.getUserId(function (userId) {
|
||||
callback(userId === null);
|
||||
});
|
||||
}
|
||||
self.tokenService.getAuthBearer(function (authBearer) {
|
||||
if (!token && !authBearer) {
|
||||
callback(false);
|
||||
}
|
||||
else {
|
||||
self.getUserId(function (userId) {
|
||||
callback(userId !== null);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -64,6 +64,23 @@ function initUtilsService() {
|
|||
return this.analyticsIdCache;
|
||||
}
|
||||
|
||||
UtilsService.prototype.getDeviceType = function () {
|
||||
if (this.isChrome()) {
|
||||
return 2;
|
||||
}
|
||||
else if (this.isFirefox()) {
|
||||
return 3;
|
||||
}
|
||||
else if (this.isEdge()) {
|
||||
return 5;
|
||||
}
|
||||
else if (this.isOpera()) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
UtilsService.prototype.initListSectionItemListeners = function (doc, angular) {
|
||||
if (!doc) {
|
||||
throw 'doc parameter required';
|
||||
|
|
Loading…
Reference in New Issue