-
Notifications
You must be signed in to change notification settings - Fork 3
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
feat(auth): Add an auth middleware. #1
Conversation
I think this is a good initial sketch, I'd like to discuss this at a higher level as there are some issues we should get clear before reviewing the code too closely. I think the roles of Authentication and Authorization could be a bit more explicit, where, authentication would manage some kind of For the implementation, this is dealing pretty low-level with raw cookie values, a lot of this logic is useful for other potential middleware too, so I think we should be implementing this on top of a Session Security-wise, (afaik) you The shell script to test the implementation looks good, should be simple to get that running on travis via Lots to discuss! I am looking forward to see how this turns out, getting even a simple authentication/authorization setup going with a simple api would be wonderful! |
Cool! I'll work on splitting the code up in three middlewares then Authentication, Authorisation (well, do we have a consensus on wether we use American/International English? I can only think of utilize on the spot but there's an issue to replace that one anyways), and Session. I'll read up on bitflipping attacks and do something about that. Then I'll update to follow the new Cookie API, whenever you have something on that. I'm excited as well, it will feel like a much more complete framework once we get authentication and authorisation working, like all the basic pieces are there :) |
I've updated this a bit. Still a bit messy, I'll look into cleaning up the There's now a Then, there is the Lastly, there is the I used the encryption in the Session timeouts are dealt with in I'm not entirely sure what @Ryman means by ensuring the stored safety of sessions. As of now, they are retrieved from the |
Haven't read over the changes yet (hopefully will later or tomorrow), but to clarify what I said about safely storing the session I just mean that whatever Session type there is, it needs to guarantee it's data persists across pages and that the client cannot read or tamper with the data stored within the session. Most systems I'm aware of at least stores some kind of |
I understand. I haven't implemented it like that, rather I store all necessary data in the cookie. That makes the session handling stateless on the server side so that we don't have to keep track of sessions at all. I prefer this method, but there might be arguments for just sending a session id to the client and keeping track of the sessions on the server. |
Thinking a bit more about this, I wonder if we shouldn't make session handling generic, such that the user can store any serialisable type in the cookie, instead of just |
Sorry, I meant that even if they use something other than cookies as their store, some minor interaction with cookies is usually still required :) |
After having a closer look at this, it makes me realise that nickel probably still needs changes to allow a Specifically, I think the I'd imagine something like So, I think there's a bigger change required where we deal with plugins at an 'environment' level, that is, they have access to both the request and response, and writing to cookies would modify the response, reading would read from request (and changes made to the response). Perhaps there's a neater alternative?
Adding to your point, I don't think a session should care about authentication (other than verifying the session hasn't been tampered with). I feel the duplication present in a simple example highlights this where both sidenote: the formatting for much of this code is strange where opening and closing braces/brackets can be difficult to match, is your editor setup for a different language? |
impl<'a, D: AsRef<cookies::SecretKey>> CreateSession for Response<'a, D> { | ||
fn set_session_cookie(&mut self, plaintext: String) { | ||
// Doesn't compile without self as a parameter. Compiler bug? | ||
let token = CreateSession::create_session_token_str(self, &*plaintext); |
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.
Just fyi: This needs an annotation to compile, e.g. Response::<'a, D>::create_...
, or in more recent versions of rust you can do Self::create_...
.
Imo, something like create_session_token_str
should be a regular function rather than a static trait method.
Perhaps you're right, yes.
But, with a plugin on Further, I think we should make it so that
Is it possible to implement a plugin on
Yeah, let's throw that part out. It has a use case I suppose, where you want to serve different content in the same route depending on whether the session belongs to a valid user or not, but in those cases it's easy to just handle that in the Route. No need to make the most simple example look bad.
Nah, I use vim with the rust settings. I'm just terrible at formatting :) I will go through it more carefully once we settle on some design and read some style guide as well to improve a bit. |
If it's a plugin then it won't cache if
I think you're right that a simple hashmap wouldn't be ideal, I was thinking a bit too dynamic. What do you think about using something like the TypeMap?
I considered this, but I think it's really unintuitive for users. |
We could store the cached session in req though, could we not?
I agree!
I think the TypeMap might be ok. Ideally I'd like to be able to use any data that is serialisable. Do you think we can get the TypeMap serialised somehow though?
Yes, it seems a bit wild and crazy at first, but the more you think about it, the more sense it starts to make. We do need to make an operation on both req and res, we might as well be explicit about it. |
But what if the request's session never gets called (to make it cache), or do you mean through
Maybe not by default, but with the right bounds and probably introducing a newtype we can make it work 👍 |
But, that would mean no one read from the session, so why would we cache it? My idea is that a session is parsed and cached in the req upon it's first read, so that many middleware can operate on it without having to parse it each time. If no middleware reads the session, then it'd just be parsed and immediately moved to the res without any changes in the last middleware, I suppose. Perhaps I'm missing something! Although, this would still not be good as we still have the issue of remembering to move the session in the last middleware.
Sounds good! |
I thought we were talking about a plugin, but it seems like you're talking about a middleware for the session? This part can't work with a plugin as when a user accesses |
No, I'm talking about a plugin, which middleware will use to access the session. My point is that there does not need to be a |
That feels very similar to the 'bug waiting to happen' we agreed upon above? I think we're stuck in a bit of a deadlock here, maybe we should each code up something and compare? |
Yes, indeed it is! But perhaps we could attack that problem in Nickel somehow. Sounds good, I'll see if I can come up with something. |
I've pushed up an experimental branch showing the basics of what I was talking about (Response == Environment for now): https://github.com/Ryman/nickel.rs/tree/environment |
Cool! I have updated this repo to use that branch. Nice job! 👍 I threw out session handling and authentication from this repo, only the What I missed was a provided We can discuss your work further once you feel ready to send a pull request :) |
I don't fully understand this, can you give more detail? Why do we need to store Duration in ServerData? |
impl< T, D: 'static> Middleware<D> for Authorizer<T, D> | ||
where | ||
D: AsRef<cookies::SecretKey> + SessionStore<Store=T>, | ||
T: 'static + Any + Encodable + Decodable + Default + Debug |
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.
Instead of having ValidateUserFunc
, would an Authorize
trait be useful, which we can bound T
with?
e.g.
trait Authorize {
type Permissions = bool;
fn permission(&self) -> Self::Permissions;
}
Then the user could have more complicated permissions, e.g. if let Some(Write) = env.current_user().permissions() { .. }
. I guess in my head there's still also a missing abstraction of current_user
which is built upon session
(as the session can store a lot of unrelated stuff, not user details).
Thinking further, then the Authorizer
could be expressed something like server.get(Authorize::only(Permissions::Owner, middleware! { "Secrets" }))
?
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.
That's a great idea! It looks really nice, if you ask me.
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.
Regarding current_user
, I suppose that can be nice.
Could something like:
trait SessionUser{
fn current_user(&self) -> Option<String>;
}
work? The user would implement this for whatever session data they store and we can provide some default implementation for String
for example.
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 think the following may be along the lines we want:
trait CurrentUser : SessionStore {
type User;
fn current_user(&<Self as SessionStore>::Store) -> &Self::User;
}
And then base a trait on Environment/Response
on that. I'm not sure where the value of adding a default implementation lies though, this behavior is very specific to the application. I think this trait is really just to extract the current user from a session so that Permissions
can be written in terms of a user model rather than the entire Session
.
Yes, I'd like to be able to do I wonder though, if we can get ServerData to be generic. I suppose we can put a |
I've been through all your comments, @Ryman. Great advice, as always! 👍 |
I've updated the Finally, I'm still not sure how multiple permissions work with this trait. Right now we do an equality check in the middleware. If one route requires read permission, read and write permission is not going to match, for example. Do you have some other idea for how the check in the middleware should be implemented, @Ryman? |
Pushed a branch with 2 extra commits on top of this, one removes the boxing from |
Nice. I still don't understand how |
@simonpersson Hmm, actually I think I went a bit mad with that quick sketchup. What I was proposing was kind of abusing equality to be true for multiple things, but that sounds like a hell of a footgun if people don't expect it. I guess I was more thinking of an ordering rather than equality. Your suggestion of |
f0686d0
to
66d74da
Compare
@Ryman - Yes, I had the feeling that things would get a bit tricky once you wanted to use multiple permissions. I merged your changes in, rewrote history a bit to make the commit log reasonable again, and replaced the |
Definitely making progress 👍 I built on your rebase to add I fleshed out the example a bit to simulate more realistic requirements, it might be a bit overblown for a real example, but it should at least help us see how the design holds up. I think it's good that the user code has no nickel related code other than the trait name. |
Nice! Feel free to merge the current_user branch onto master once we get the changes in Nickel merged. |
I've updated this to latest nickel. If we're happy with how it looks, I'll start documenting so that we can eventually merge this. I feel that we might want to offer a more simple API on top of this that is not as generic, but perhaps that can come later. Ping: @cburgdorf @Ryman. |
I screwed up with git so I this PR closed... :( I guess I can manually merge and force push once we're happy. |
Sorry, I realize now when trying to test this branch that I had an updated version of the current_user branch that was basically in line with the extracted session/cookies which I hadn't pushed up. The diff for this pr seems to be broken so it's hard to see what the full code looks like, but if it's what's currently master on your fork then I still think it's worth pursuing the current_user style where permissions are not coupled so tightly to sessions. I just added some more fixes to the current_user branch and pushed it to here for comparison. related but offtopic: Doing the minor fixes did make me think naming the important trait in session as |
I see. Well, let's keep working from that branch then! :)
Good! It should make things a bit more readable. |
Related to nickel-org/nickel.rs#98.
Provides auth middleware with an example page. Depends on nickel-org/nickel.rs#234.
There's probably some stuff that needs cleaning up, but I figured we could get the discussion polish it up once we're happy with the design.
I have written some basic tests with bash and curl for the example page. I hope it's possible to get travis-ci to execute those.
As this is a matter of security, it'd be great to get as many eyes as possible on this. Ping @cburgdorf, @Ryman, @lambdaburrito, @rgs1, @ninjabear.