Skip to content

Defer user authentication until requested by endpoint #2077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 16, 2020

Conversation

jtgeibel
Copy link
Member

Many endpoints do not require user authentication, and by moving the
logic out of the middleware layer these endpoints now avoid obtaining a
database connection from the pool and 1 or 2 queries.

Due to lifetime issues a small middlware component remains. Further
details are described in the middleware's documentation.

r? @carols10cents

Copy link
Member

@Turbo87 Turbo87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work!

req.mut_extensions().insert(api_token);
}
if let Some(id) = req.session().get("user_id").and_then(|s| s.parse().ok()) {
req.mut_extensions().insert(TrustedUserId(id));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of curiosity, why is it not possible to put this into the authenticate call too?

Copy link
Member

@carols10cents carols10cents Jan 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(in response to tobias) I think this req.session() is the call that needs the mutable reference to the request

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this req.session() is the call that needs the mutable reference to the request

Yes, that's exactly the problem I ran into. session() would probably be better called session_mut(), since it calls mut_extensions() and find_mut() internally to return a &mut HashMap<_, _>.

Copy link
Member

@carols10cents carols10cents left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice!!! I just have a few questions :)

//! Due to lifetimes it is not possible to call `mut_extensions()` while a reference obtained from
//! `extensions()` is still live. The call to `conduit_cookie::RequestSession::session` needs
//! mutable access to the request and its extensions, and there is no read-only alternative in
//! `conduit_cookie` to access the session cookie. This means that it is not possible to access
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would our code be better if there was a read-only alternative to get the session cookie? It looks like the write access is used for caching the results of decoding the cookie, if we don't care about that, would that be a better alternative? Or is this PR acceptable/better? Just making sure that enhancing conduit-cookie isn't what we actually want :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would our code be better if there was a read-only alternative to get the session cookie?

It would be nice to support that upstream in conduit-cookie. We only use the session cookie here and during the login oauth workflow, and in both cases we don't need a database connection at that particular moment so it hasn't been a problem, yet. Although I don't really see us adding more complex cookie logic anytime soon.

It looks like the write access is used for caching the results of decoding the cookie.

As I understand it, the middleware layer from conduit-cookie decodes the cookie before the request is processed by the endpoint, and then the middleware adds the (possibly updated) cookie back into every response. So the &mut HashMap<_, _> returned here is what provides the API for mutating the session cookie.

There are some changes (especially with respect to bumping old deps, and more especially wrt our very old dependency on semver) that I would like make to the conduit-* ecosystem. Last I looked we (crates.io) are the only public users of these crates, so it would be awesome if we could make some improvements and release new versions of these crates. Longer term, it would be nice if we could add async/await support in our middleware stack.

For now, I think its fine to document the workaround. This also provides an easy way for the test harness to simulate cookie authenticated requests, although it would certainly be nice to construct real cookies in the test harness instead.

req.mut_extensions().insert(api_token);
}
if let Some(id) = req.session().get("user_id").and_then(|s| s.parse().ok()) {
req.mut_extensions().insert(TrustedUserId(id));
Copy link
Member

@carols10cents carols10cents Jan 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(in response to tobias) I think this req.session() is the call that needs the mutable reference to the request

user_id: user.id,
crate_id,
})
Ok(Follow { user_id, crate_id })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this abstraction gets rid of a User lookup in cases like this spot when it's not needed 👍


let conn = app.diesel_database.get()?;
let ids = req.authenticate(&conn)?;
let user = ids.find_user(&conn)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOVE that this is out of parse_new_headers now!!!!

use crate::models::{Crate, CrateVersions, Version};
use crate::schema::versions;

fn version_and_crate(req: &mut dyn Request) -> AppResult<(Version, Crate)> {
fn version_and_crate(req: &dyn Request) -> AppResult<(DieselPooledConn<'_>, Version, Crate)> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is a little strange now that it returns a connection too, but 🤷‍♀ it's not exactly the cleanest abstraction to begin with anyway given that half the time we don't even use the returned crate. Always more to do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is a little strange now that it returns a connection too

I would be cleaner to obtain a connection and pass it to this function, but it just isn't wasn't possible because of the lifetimes here. As I write this, I now see that I eventually dropped the mutness of the &dyn Request, so it should now be possible to pass in a &PgConnection as well, at least I think.

Always more to do.

Yup!

@bors
Copy link
Contributor

bors commented Jan 4, 2020

☔ The latest upstream changes (presumably #2072) made this pull request unmergeable. Please resolve the merge conflicts.

@jtgeibel jtgeibel force-pushed the defer-user-auth branch 2 times, most recently from 6458a59 to d13d22a Compare January 4, 2020 03:14
@jtgeibel
Copy link
Member Author

jtgeibel commented Jan 4, 2020

I've rebased to resolve merge conflicts, and added a 2nd commit that addresses the review comments.

@bors
Copy link
Contributor

bors commented Jan 6, 2020

☔ The latest upstream changes (presumably #2032) made this pull request unmergeable. Please resolve the merge conflicts.

@bors
Copy link
Contributor

bors commented Jan 15, 2020

☔ The latest upstream changes (presumably #1955) made this pull request unmergeable. Please resolve the merge conflicts.

Many endpoints do not require user authentication, and by moving the
logic out of the middleware layer these endpoints now avoid obtaining a
database connection from the pool and 1 or 2 queries.

Due to lifetime issues a small middlware component remains.  Further
details are described in the middleware's documentation.
@jtgeibel
Copy link
Member Author

Rebased to resolve merge conflicts. I believe all review comments have been addressed. I'd like to deploy to staging tonight, and possibly production tomorrow.

@bors r=carols10cents

@bors
Copy link
Contributor

bors commented Jan 16, 2020

📌 Commit c06e7c2 has been approved by carols10cents

@bors
Copy link
Contributor

bors commented Jan 16, 2020

⌛ Testing commit c06e7c2 with merge 63d45b6...

bors added a commit that referenced this pull request Jan 16, 2020
Defer user authentication until requested by endpoint

Many endpoints do not require user authentication, and by moving the
logic out of the middleware layer these endpoints now avoid obtaining a
database connection from the pool and 1 or 2 queries.

Due to lifetime issues a small middlware component remains.  Further
details are described in the middleware's documentation.

r? @carols10cents
@bors
Copy link
Contributor

bors commented Jan 16, 2020

☀️ Test successful - checks-travis
Approved by: carols10cents
Pushing 63d45b6 to master...

@bors bors merged commit c06e7c2 into rust-lang:master Jan 16, 2020
@jtgeibel jtgeibel deleted the defer-user-auth branch January 24, 2020 02:09
carols10cents added a commit to integer32llc/crates.io that referenced this pull request Jan 27, 2020
Fixes rust-lang#2166.

In rust-lang#2077 (d0b9bcc), we changed the type
of the authentication data stored in the extensions from `User` to
`TrustedUserId`, but we missed this spot :( We're allowed to insert
anything we want into the extensions, but if we use a different type
than what we use when looking up data in the extensions, it won't find
what we inserted.
@carols10cents carols10cents mentioned this pull request Jan 27, 2020
bors added a commit that referenced this pull request Jan 27, 2020
Fix auth

Set the TrustedUserId in the extensions when logging in

Fixes #2166.

In #2077 (d0b9bcc), we changed the type of the authentication data stored in the extensions from `User` to `TrustedUserId`, but we missed this spot :( We're allowed to insert anything we want into the extensions, but if we use a different type than what we use when looking up data in the extensions, it won't find what we inserted.

I checked all the other locations of `mut_extensions` and `extensions` and I think they're good, but overall this system feels a bit loosey-goosey to me. Not sure how to improve it, though.

r? @jtgeibel
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants