-
Notifications
You must be signed in to change notification settings - Fork 87
Add feature to launch URLs & support basic auth #224
Conversation
Nice! Let me throw some ideas first.
|
|
Thanks! Hmm, you are right, it is not very clear how to select the domain part from the entry name... Although I wouldn't suggest building a super comprehensive domain matcher, because the workaround is very simple (adding |
@maximbaz I've implemented extracting a URL from the entry path. What do you think? |
@maximbaz HTTP basic auth now works too :-). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice work! I left some notes and questions.
chrome/background.js
Outdated
} | ||
}); | ||
// only supply credentials if this is the first time for this tab | ||
if (!authAttempted) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you walk me through this logic? So we register the listener immediately after a tab is created, remove the listener after the tab is loaded (is it guaranteed that info.status
will become complete
only after basic auth is filled in?), and at the same time you additionally prevent supplying credentials more than once (why is this needed? won't removing the listener suffice?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Create a new tab, and get the tab ID in the callback
- Add onAuthRequired listener
- When the auth callback fires, add a status listener to the tab
- Supply login credentials if we haven't already supplied them
- When the status listener fires and the status is complete (i.e. the page has loaded), remove both listeners.
The reason both the authCompleted
and the listeners are required:
- The
onAuthRequired
listener cannot remove itself, as doing so crashes the extension. - Because it cannot remove itself, it may fire multiple times (e.g. if the password is incorrect)
- So the boolean restricts it to one time only
- And removing it in the status listener once the page is complete ensures that the event is no longer live, and avoids a crash.
is it guaranteed that
info.status
will become complete only after basic auth is filled in?
I could not find any specific documentation guaranteeing this, and I looked pretty hard. However, the documentation does say that this occurs when the page load is complete, and blocking authentication requirements prevent the page from loading until they are satisfied. I've tested it extensively, and I'm comfortable that this does seem to be the case, but I cannot absolutely guarantee that it is so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you, makes sense!
chrome/background.js
Outdated
// only supply credentials if this is the first time for this tab | ||
if (!authAttempted) { | ||
authAttempted = true; | ||
// ask the user before sending credentials over an insecure connection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
chrome/background.js
Outdated
} | ||
}, | ||
{urls: ["*://*/*"], tabId: tab.id}, | ||
["blocking"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the guarantee that info.status
will become complete only after basic auth is filled in? Or what is this for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not possible to mess with the request asynchronously (including providing authentication details). The "blocking"
option ensures that the request waits for our callback to complete before proceeding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks!
@@ -7,7 +7,7 @@ | |||
"author": "Danny van Kooten", | |||
"homepage_url": "https://github.com/dannyvankooten/browserpass", | |||
"background": { | |||
"persistent": false, | |||
"persistent": true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you comment on why this is needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chrome doesn't allow non-persistent event pages to use the webRequest
API. If you have this set to false
, you'll get a massive warning when you try to load the extension telling you that this isn't possible, and then Chrome will refuse to load the extension.
function(response) { | ||
if (chrome.runtime.lastError) { | ||
error = chrome.runtime.lastError.message; | ||
m.redraw(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: could you please reduce the nesting?
It's a little difficult to see which "else" belongs to which "if" 🙂
if (error) {
...
return;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
chrome/script.browserify.js
Outdated
} else { | ||
// get url from login path if not available in the host app response | ||
if (!response.hasOwnProperty("url") || response.url.length == 0) { | ||
var parts = entry.split(/\//).reverse(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see pros and cons in reversing the parts, and so far I found only one case among my passwords where this is not ideal. We will probably keep this as it is, but I want to ask anyway, what is your reasoning for and against reversing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems more likely that someone will have a workplace that is a valid domain (e.g. "I work for example.com"), than a username that is a valid domain. It also seems more likely that if domains are nested, the deepest one is the most likely to be what the user wants. So reversing it prioritises domains that are found closer to the end of the entry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed
chrome/script.browserify.js
Outdated
if (response.hasOwnProperty("url") && response.url.length > 0) { | ||
var url = response.url.match(/^([a-z]+:)?\/\//i) ? response.url : "http://" + response.url; | ||
chrome.runtime.sendMessage({action: "launch", url: url, username: response.u, password: response.p}); | ||
window.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here's another potentially good place to do return
and reduce nesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
chrome/script.browserify.js
Outdated
} else { | ||
// no url available | ||
if (!response.hasOwnProperty("url")) { | ||
resetWithError("Your host application is too old - must be at least 2.0.14 for URL launch."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First brick in building #164, I like this 👍
However, we need to adjust the message, because with added parsing of domain it's not consistent anymore. With the old host application I can open url for password entries like "personal/google.com" just fine, until one day I try to open url for the entry "personal/notes" and suddenly get the error "Your host application is too old".
I think we should say something like this:
Unable to detect the URL to open for this entry. If you defined it in the password file, update your host application to the latest version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point - done.
chrome/script.browserify.js
Outdated
if (!response.hasOwnProperty("url")) { | ||
resetWithError("Your host application is too old - must be at least 2.0.14 for URL launch."); | ||
} else { | ||
resetWithError("No URL is available for this login."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my mind something like this will sound better, maybe there is a URL but it's just us who cannot detect it:
Unable to detect the URL to open for this entry.
Attaching the compiled extension for the current code for others to test: |
- Host app will now return a "url" field - Browser plugin has a "launch URL" button for each entry - Browser plugin has a new hotkey (g) to launch the URL
chrome/background.js
Outdated
chrome.webRequest.onAuthRequired.addListener( | ||
function authListener(requestDetails) { | ||
// only supply credentials if this is the first time for this tab | ||
if (!authAttempted) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you invert this too, i.e. if (authAttempted) { return {}; }
? Also judging by line 123 returning {}
is important in this case too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem - done :-).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks once again, this is awesome! I wanted to get some additional feedback from people in #103, but we can as well make a release and wait for people to submit issues if they experience anything weird 😉
Just as a final confirmation, do you want to do anything else here or can I merge? |
Most welcome :-).
If you could give me two minutes, I want to tweak the 'insecure connection' error message slightly. Stand by. |
@maximbaz Thanks - I just wanted to clarify that message a bit; I figured the original wording wasn't the clearest. I'm happy with it now - all good to merge :-). Thanks very much for your help with this, and for your extremely speedy code reviews! |
authAttempted = true; | ||
// remove event listeners once tab loading is complete | ||
chrome.tabs.onUpdated.addListener(function statusListener(tabId, info) { | ||
if (info.status === "complete") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@erayd do you think this code should also compare tabId
with the tab.id
before removing the listeners?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't need to, because the listeners are specific to the tab anyway.
There is a security problem, but that's not it. I'm filing an issue now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure that this doesn't register a listener that fires for all tabs? Maybe it's just a weird API, but the tab object is not used when registering the listener, and the tabId
is provided as a first argument for a reason, both of these lead me to think that this will fire for all tabs...
I know that this is not about the security problem, it's just what I accidentally stumbled upon 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, good point. The onAuthRequested
handler is specific to the tab, but the status listener is not. My bad.
I'm going to roll the security fix PR right now - I feel quite bad I didn't notice that in the first place; I'm normally more careful than that. I will fix this at the same time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
Aside from this feature addressing the basic auth issue #103, what is the purpose of this? It seems strange to have this on all pass entries. Personally I don't have a single basic auth entry, so for me the button is a worse way to navigate to url? Is there another common use case for this that I'm missing? |
See #214 for more details, people seem to like this way of opening websites in Lastpass and other password managers, and they carry their habits to browserpass. In addition it now allows you to use |
Oh now that is useful. |
Note that basic auth will only work in Firefox >= 54. Older versions don't have the required API to do this.
Closes #214.
Closes #103.