-
Notifications
You must be signed in to change notification settings - Fork 53
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
Added customizable authentication failure HTTP code and location header #23
Conversation
This looks pretty good, a few comments on the code itself. |
lib/authorization.js
Outdated
@@ -3,14 +3,22 @@ const errors = require('./errors'), rparams = require('./param'), sender = requi | |||
constants = require('./constants').get(), | |||
csauth = constants.header.AUTH, | |||
fields = {}, params = {}; | |||
// initialize [TODO: overridable] global default | |||
var unauthenticatedcode = constants.httpcodes.UNAUTHENTICATED; |
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.
This should be a const
instead of a var
.
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.
Actually wouldn't this just be easier to do:
const HttpStatus = require('http-status-codes');
// and later
const checkLoggedIn = (req, res, next, unauthenticatedResponse = {code:HttpStatus.UNAUTHORIZED}) => {
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 is var
instead of const
since it can be overridden in the init function when an option is provided to override the global default is added.
Should we provide that option in the init or should i still make the requested changes?
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 is var instead of const since it can be overridden in the init function
Ah, right. So the logic is something like: when returning unauthenticated error, do local override, else init()
override, else 401
?
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.
Yes
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.
Well, we have no init()
-driven override now, so why bother?
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.
Let me add that option then :)
lib/authorization.js
Outdated
if (unauthenticatedResponse.location != null) { | ||
res.header("location", unauthenticatedResponse.location); | ||
} | ||
sender(res,unauthenticatedResponse.code,errors.unauthenticated(),unauthenticatedResponse); |
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.
How does this work? sender()
only accepts 3 args: res, code, body
. Here you send 4?
lib/authorization.js
Outdated
@@ -101,6 +109,11 @@ const checkLoggedIn = (req, res, next) => { | |||
} | |||
}, | |||
indirect: { | |||
unauthenticated: (unauthenticatedResponse) => { |
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.
So this is middleware equivalent to restrictToLoggedIn
but with a custom per-invocation code and/or location?
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.
Yes! Anything else to add or change?
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.
The only thing that comes to mind is that we should make the naming consistent. If this is restrictToLoggedIn()
but with custom handler, then we probably should call it restrictToLoggedInCode()
or restrictToLoggedInErrorCode()
or something like that.
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 couldn't come up with a name for unauthenticated()
interface. Please do Suggest!
I think a generic name will be better since it can be used with any interface. Also restrictToLoggedIn()
is not explicitly called for other interfaces even though it is checked internally.
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 couldn't come up with a name for unauthenticated() interface
If you think about it from a user's perspective, it is middleware that we are calling:
- Receive a given path
app.get("/some/path",
- Pass it through first middleware filter, if it passes...
- Pass it through second middleware filter, if it passes...
- Pass it through third middleware filter, if it passes...
etc.
Each security middleware is a filter that acts like a gate. So restrictToLoggedIn
means, "only let someone through this middleware of they are logged in". We probably could have called it requireLogIn
or verifyLoggedIn
, but all is the same idea.
In that vein:
- Why not just call it
restrictToLoggedInWithErrorCode
or similar? It is long, but is expressive, and function names are cheap, these aren't mainframes, and it doesn't pass to the browser (even if it did, we would minify it which would replace it with something nice and clear likef
:-) ) - Why do your test and the
README
callrestrictToLoggedIn
after callingunauthenticated
or whatever we rename it)?unauthenticated
already callsrestrictToLoggedIn
.
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.
restrictToLoggedInWithErrorCode
sounds good.- I am seeing
unauthenticated
as a gate before reaching one ofrestrictTo*
gates. So, I was thinkingunauthenticated
can be used along with all the other filters. i.e, to expect custom code for all the restrictions. Earlier, user could pick one of the filters without explicitly usingrestrictToLoggedIn
. Now the user will have inconsistent experience. i.e, user will not userestrictToLoggedIn
for the default response code and will userestrictToLoggedInWithErrorCode
for the custom code.
OPTION 1:
for default response code,
app.get("/secure/loggedin",cansec.restrictToLoggedIn,send200);
app.get("/secure/user/:user",cansec.restrictToSelf,send200);
app.get("/secure/roles/admin",cansec.restrictToRoles("admin"),send200);
for custom response code,
app.get("/secure/loggedin", cansec.unauthenticated({code:302,location:"/login"}), cansec.restrictToLoggedIn,send200);
app.get("/secure/user/:user", cansec.unauthenticated({code:302,location:"/login"}), cansec.restrictToSelf,send200);
app.get("/secure/roles/admin", cansec.unauthenticated({code:302,location:"/login"}),cansec.restrictToRoles("admin"),send200);
user will first set authentication rules and then set authorization rules.
OPTION 2:
for default response code,
app.get("/secure/loggedin",cansec.restrictToLoggedIn,send200);
app.get("/secure/user/:user",cansec.restrictToSelf,send200);
app.get("/secure/roles/admin",cansec.restrictToRoles("admin"),send200);
User does not use restrictToLoggedIn
with restrictToSelf
or restrictToRoles
for custom response code,
app.get("/secure/loggedin", cansec.restrictToLoggedInWithErrorCode({code:302,location:"/login"}),send200); // I'm using a **different interface** now.
app.get("/secure/user/:user", cansec.restrictToLoggedInWithErrorCode({code:302,location:"/login"}), cansec.restrictToSelf,send200);
app.get("/secure/roles/admin", cansec.restrictToLoggedInWithErrorCode({code:302,location:"/login"}),cansec.restrictToRoles("admin"),send200);
User uses restrictToLoggedInWithErrorCode
with restrictToSelf
or restrictToRoles
If we have restrictToLoggedInWithErrorCode
, does the user expect restrictTo*WithErrorCode
?
Having said that, I do agree that restrictToLoggedIn
is unnecessary when unauthenticated
is used. A careful user can choose to use only unauthenticated
instead.
In case of OPTION 2 custom code, restrictToLoggedInWithErrorCode
and restrictToSelf
both check for checkLoggedIn
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.
OK, now I see where you are going. This is why I was confused.
- the comments implied that
unauthenticated
was just setting the error code, but not doing the actualrestrictToLoggedIn
filtering - the code in the PR had
unauthenticated
actually callingrestrictToLoggedIn
The former would look something like:
app.get("/secure/loggedin", cansec.unauthenticated({code:401, location: '/login'}),cansec.restrictToLogged,send200);
While the latter would look like this:
app.get("/secure/loggedin", cansec.restrictToLoggedWithErrorCode({code:401, location: '/login'}),send200);
Is that It? You were suggesting that unauthenticated
just set the unauthorized error code and location for future filters in this request chain?
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.
Sorry for the confusion.
Yes, unauthenticated
has to just set unauthorized code for future filters in this request chain.
For the implementation, Is it possible to do that? Do the interfaces share their states for each requests?
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.
Ah, now I get it. And I really like it. Yes, 100%, unauthenticated
makes more sense, and restrictToLoggedInErrorCode
does not..
But then unauthenticated
shouldn't call restrictToLoggedIn
. It should just set the code on the req
and move on, and restrictToLoggedIn
should take it from there.
And given that, yes, now a better name makes sense, I would call it setUnauthenticatedCode()
or similar. And we could (eventually) have one globally to set the default.
lib/authorization.js
Outdated
|
||
const checkLoggedIn = (req, res, next) => { | ||
const checkLoggedIn = (req, res, next, unauthenticatedResponse = {code:unauthenticatedcode}) => { |
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 we should clean this up a bit.
const checkLoggedIn = (req, res, next, unauthCode = HttpStatus.UNAUTHORIZED, unauthLocation = null) => {
// ...
// inside becomes simpler, lines 15-17 go away
if (unauthLocation !== null {
res.header("location", unauthenticatedResponse.location);
}
sender(res, unauthCode, errors.unauthenticated());
// ...
};
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.
Overall, I like this. Let's do the following:
HttpStatus
instead of our own custom- Simplify
checkLoggedIn()
(and update its caller) - I wonder about
cansec.unauthenticated()
. It is identical torestrictToLoggedIn()
, can we name it closer? - Bump minor version
Will submit all the changes based on your feedback on my comments. |
Thanks
|
|
Pending changes:
|
Pending Changes:
|
This is great, thank you. And For the other pending changes, we should do a separate PR. |
Fix for issue #20
Can customize authentication failure HTTP response code and location header for the routes as below
app.get("/secure/customloggedin",cansec.unauthenticated({code:302,location:"/login"}),cansec.restrictToLoggedIn,send200);