Skip to content

Commit

Permalink
Add support for parallel HTTP auth requests, fix #322
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Vogt committed Apr 4, 2018
1 parent bc51cc2 commit b9729a8
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 68 deletions.
1 change: 1 addition & 0 deletions src/content/itemPicker.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<script src="../modules/preferences.js"></script>
<script src="../modules/pass.js"></script>
<script src="../modules/menu.js"></script>
<script src="../modules/auth.js"></script>
</head>
<body id="itemPicker">
<div class="itemPickerTarget">https://...</div>
Expand Down
130 changes: 76 additions & 54 deletions src/modules/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,82 +6,94 @@ PassFF.Auth = (function () {
* This controller comes into play when HTTP authentication is required.
*/

var currentHttpAuth = {
requestId: null,
popupId: null,
popupClose: null,
resolveItem: null,
resolveAttempts: 0,
};
var currentAuths = [];

function getAuthById(requestId) {
let auth = currentAuths.filter(a => a.requestId === requestId);
return (!auth.length) ? null : auth[0];
}

function cancelAuth() {
log.debug("Cancelling auth", currentHttpAuth.requestId);
if (typeof currentHttpAuth.resolve === 'function') {
currentHttpAuth.resolve({ cancel: false });
function cancelAuth(auth) {
if (!auth) return;
log.debug("Cancelling auth", auth.requestId);
if (typeof auth.resolve === 'function') {
auth.resolve({ cancel: false });
}
closePopup();
currentHttpAuth.requestId = null;
currentHttpAuth.popupId = null;
currentHttpAuth.popupClose = null;
currentHttpAuth.resolveItem = null;
currentHttpAuth.resolveAttempts = 0;
closePopup(auth);
// only cancelled auths are ever removed from currentAuths
currentAuths.splice(currentAuths.indexOf(auth), 1);
}

function closePopup() {
log.debug("Clean up auth popup", currentHttpAuth.requestId);
if (currentHttpAuth.popupId !== null) {
browser.windows.onRemoved.removeListener(currentHttpAuth.popupClose);
browser.windows.remove(currentHttpAuth.popupId);
function closePopup(auth) {
if (!auth) return;
log.debug("Clean up auth popup", auth.requestId);
if (auth.popupId !== null) {
browser.windows.onRemoved.removeListener(auth.popupClose);
browser.windows.remove(auth.popupId);
}
PassFF.Menu.state['itemPickerTarget'] = undefined;
}

function onAuthRequired(details) {
log.debug("onAuthRequired", details.requestId, details.url);
let auth = getAuthById(details.requestId);
if (auth === null) {
auth = {
requestId: null,
requestUrl: null,
popupId: null,
popupClose: null,
resolveItem: null,
resolveAttempts: 0,
contextItems: [],
};
currentAuths.push(auth);
}

// Allow for two resolve attempts.
// This is crucial when we have a HTTP->HTTPS redirect after the first
// attempt (which triggers a second auth request with same id).
if (details.requestId !== currentHttpAuth.requestId
|| currentHttpAuth.resolveAttempts >= 2) cancelAuth();

currentHttpAuth.requestId = details.requestId;
PassFF.Menu.state['itemPickerTarget'] = details.url;
auth.requestId = details.requestId;
auth.requestUrl = details.url;
auth.contextItems = PassFF.Pass.getUrlMatchingItems(auth.requestUrl);
return new Promise((resolve, reject) => {
currentHttpAuth.resolve = resolve;
auth.resolve = resolve;
PassFF.Page.goToAutoFillPending()
.then(function (pending) {
if (pending !== null) {
log.debug("Handle pending auto fill", pending.item.fullKey,
"as HTTP auth", currentHttpAuth.requestId);
currentHttpAuth.resolveItem = pending.item;
"as HTTP auth", auth.requestId);
auth.resolveItem = pending.item;
PassFF.Page.resolveGoToAutoFillPending(false);
}
if (PassFF.Preferences.autoFill && PassFF.Preferences.autoSubmit) {
let url = details.url;
let matchItems = PassFF.Pass.getUrlMatchingItems(url);
let bestFitItem = PassFF.Pass.findBestFitItem(matchItems, url);
currentHttpAuth.resolveItem = bestFitItem;
let bestFitItem = PassFF.Pass.findBestFitItem(auth.contextItems,
auth.requestUrl);
auth.resolveItem = bestFitItem;
}
if (currentHttpAuth.resolveItem !== null) {
return PassFF.Auth.resolve(currentHttpAuth.resolveItem);

// Allow for a second automatic resolve attempt.
// This is crucial when we have a HTTP->HTTPS redirect after the first
// attempt (which triggers a second auth request with same id).
if (auth.resolveItem !== null && auth.resolveAttempts < 2) {
log.debug("Automatically resolving auth", auth.requestId,
"using", auth.resolveItem.fullKey);
PassFF.Auth.resolve(auth.resolveItem, auth.requestId);
return;
}

log.debug("Open HTTP auth dialog");
return browser.windows.create({
'url': browser.extension.getURL('/content/itemPicker.html'),
'width': 450,
'height': 281,
'type': 'popup',
})
});
})
.then((win) => {
if (typeof win === "undefined") return;
setTimeout(() => browser.windows.update(win.id, { height: 280 }));
currentHttpAuth.popupId = win.id;
currentHttpAuth.popupClose = function (windowId) {
if (win.id === windowId) cancelAuth();
auth.popupId = win.id;
auth.popupClose = function (windowId) {
if (win.id === windowId) cancelAuth(auth);
};
browser.windows.onRemoved.addListener(currentHttpAuth.popupClose);
browser.windows.onRemoved.addListener(auth.popupClose);
});
});
}
Expand All @@ -107,26 +119,36 @@ PassFF.Auth = (function () {
}
},

resolve: function (item) {
log.debug("Get pass data for HTTP auth", currentHttpAuth.requestId);
getAuthForPopup: background_function("Auth.getAuthForPopup", function (popupId) {
let auth = currentAuths.filter(a => a.popupId === popupId);
return (!auth.length) ? null : auth[0];
}),

resolve: background_function("Auth.resolve", function (item, requestId) {
let auth = getAuthById(requestId);
if (auth === null) {
log.debug("Auth request not found", requestId);
return false;
}
log.debug("Get pass data for HTTP auth", auth.requestId);
return PassFF.Pass.getPasswordData(item)
.then((passwordData) => {
if (typeof passwordData === "undefined") {
/* User has probably cancelled the GPG decryption */
return false;
}
log.debug("Resolve HTTP auth", currentHttpAuth.requestId,
"using", item.fullKey, currentHttpAuth.resolveAttempts);
currentHttpAuth.resolveItem = item;
currentHttpAuth.resolveAttempts += 1;
currentHttpAuth.resolve({
log.debug("Resolve HTTP auth", auth.requestId,
"using", item.fullKey, auth.resolveAttempts);
auth.resolveItem = item;
auth.resolveAttempts += 1;
auth.resolve({
authCredentials: {
username: passwordData.login,
password: passwordData.password
}
});
closePopup();
closePopup(auth);
});
}
})
};
})();
50 changes: 36 additions & 14 deletions src/modules/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,30 @@ PassFF.Menu = (function () {
document.body.classList.remove("error");
}

if (PassFF.mode === "itemPicker") {
let data_box = document.querySelector(".itemPickerTarget");
data_box.textContent = menuState['itemPickerTarget'];
data_box.title = menuState['itemPickerTarget'];
}

if (PassFF.Preferences.showStatus) showStatus();

let searchInput = document.getElementById('passff-search-box');
searchInput.value = menuState['search_val'];
createMenuList();
searchInput.focus();
if (PassFF.mode === "itemPicker") {
browser.windows.getCurrent()
.then((win) => PassFF.Auth.getAuthForPopup(win.id))
.then((auth) => {
let data_box = document.querySelector(".itemPickerTarget");
data_box.textContent = auth.requestUrl;
data_box.title = auth.requestUrl;
menuState['auth'] = auth;
if (!auth.contextItems.length) {
menuState['items'] = PassFF.Pass.rootItems;
} else {
menuState['items'] = auth.contextItems;
}
createMenuList();
searchInput.focus();
});
} else {
searchInput.value = menuState['search_val'];
createMenuList();
searchInput.focus();
}
}

/* #############################################################################
Expand Down Expand Up @@ -239,7 +251,15 @@ PassFF.Menu = (function () {
let searchInput = document.getElementById('passff-search-box');
searchInput.value = "";
searchInput.focus();
createMenuList(PassFF.Pass.contextItems);
if (PassFF.mode === "itemPicker") {
if (!menuState['auth'].contextItems.length) {
createMenuList(PassFF.Pass.rootItems);
} else {
createMenuList(menuState['auth'].contextItems);
}
} else {
createMenuList(PassFF.Pass.contextItems);
}
}

function createMenuList(items, cleanMenu) {
Expand All @@ -248,7 +268,9 @@ PassFF.Menu = (function () {
}
if (typeof items !== "undefined") {
menuState['items'] = items;
PassFF.Menu.backupState(menuState);
if (PassFF.mode !== "itemPicker") {
PassFF.Menu.backupState(menuState);
}
}
log.debug("Create menu list", menuState['items']);
if (menuState['items'] instanceof Array) {
Expand Down Expand Up @@ -509,10 +531,10 @@ PassFF.Menu = (function () {
}
}),

onPickItem: background_function("Menu.onPickItem", function (itemId) {
onPickItem: function (itemId) {
let item = PassFF.Pass.getItemById(itemId);
PassFF.Auth.resolve(item);
}),
PassFF.Auth.resolve(item, menuState['auth'].requestId);
},

onAutoFillMenuClick: background_function("Menu.onAutoFillMenuClick",
function (itemId) {
Expand Down

0 comments on commit b9729a8

Please sign in to comment.