Skip to content
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

Merged
merged 4 commits into from
Oct 1, 2017

Conversation

ProgramCpp
Copy link
Contributor

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);

@deitch
Copy link
Owner

deitch commented Sep 24, 2017

This looks pretty good, a few comments on the code itself.

@@ -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;
Copy link
Owner

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.

Copy link
Owner

@deitch deitch Sep 24, 2017

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}) => {

Copy link
Contributor Author

@ProgramCpp ProgramCpp Sep 24, 2017

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?

Copy link
Owner

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

Copy link
Owner

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?

Copy link
Contributor Author

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 :)

if (unauthenticatedResponse.location != null) {
res.header("location", unauthenticatedResponse.location);
}
sender(res,unauthenticatedResponse.code,errors.unauthenticated(),unauthenticatedResponse);
Copy link
Owner

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?

@@ -101,6 +109,11 @@ const checkLoggedIn = (req, res, next) => {
}
},
indirect: {
unauthenticated: (unauthenticatedResponse) => {
Copy link
Owner

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?

Copy link
Contributor Author

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?

Copy link
Owner

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.

Copy link
Contributor Author

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.

Copy link
Owner

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:

  1. Receive a given path app.get("/some/path",
  2. Pass it through first middleware filter, if it passes...
  3. Pass it through second middleware filter, if it passes...
  4. 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:

  1. 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 like f :-) )
  2. Why do your test and the README call restrictToLoggedIn after calling unauthenticated or whatever we rename it)? unauthenticated already calls restrictToLoggedIn.

Copy link
Contributor Author

@ProgramCpp ProgramCpp Sep 28, 2017

Choose a reason for hiding this comment

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

  1. restrictToLoggedInWithErrorCode sounds good.
  2. I am seeing unauthenticated as a gate before reaching one of restrictTo* gates. So, I was thinking unauthenticated 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 using restrictToLoggedIn. Now the user will have inconsistent experience. i.e, user will not use restrictToLoggedIn for the default response code and will use restrictToLoggedInWithErrorCode 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

Copy link
Owner

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 actual restrictToLoggedIn filtering
  • the code in the PR had unauthenticated actually calling restrictToLoggedIn

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?

Copy link
Contributor Author

@ProgramCpp ProgramCpp Sep 28, 2017

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?

Copy link
Owner

@deitch deitch Sep 28, 2017

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.


const checkLoggedIn = (req, res, next) => {
const checkLoggedIn = (req, res, next, unauthenticatedResponse = {code:unauthenticatedcode}) => {
Copy link
Owner

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());
  // ...
};

Copy link
Owner

@deitch deitch left a 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:

  1. HttpStatus instead of our own custom
  2. Simplify checkLoggedIn() (and update its caller)
  3. I wonder about cansec.unauthenticated(). It is identical to restrictToLoggedIn(), can we name it closer?
  4. Bump minor version

@ProgramCpp
Copy link
Contributor Author

Will submit all the changes based on your feedback on my comments.
I wonder what can cansec.unauthenticated() be named as! Please suggest :)

@deitch
Copy link
Owner

deitch commented Sep 24, 2017

Will submit all the changes based on your feedback on my comments

Thanks

  1. please bump minor version number.
  2. You have a great sample in the test, can you put that in the README? Pity for you to do all this work and no one knows it is there to use.

@ProgramCpp
Copy link
Contributor Author

ProgramCpp commented Sep 25, 2017

  1. Updated README
  2. Bumped minor version
  3. Used HTTP code from package http-status-codes
  4. Cleaned up checkLoggedIn()

@ProgramCpp
Copy link
Contributor Author

ProgramCpp commented Sep 28, 2017

Pending changes:

  1. Rename unauthenticated to setUnauthenticatedCode
  2. setUnauthenticatedCode should only set the custom code in the request and move on!

@ProgramCpp
Copy link
Contributor Author

Pending Changes:

  1. Include custom code in the declarative definitions.
    [verb,path,default,[test params,] test condition]
  2. Have an option to set the global default.
  3. Use global default for failures in session management?

@deitch deitch merged commit b3ed0f3 into deitch:master Oct 1, 2017
@deitch
Copy link
Owner

deitch commented Oct 1, 2017

This is great, thank you. And npm publish-ed as 3.2.0

For the other pending changes, we should do a separate PR.

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.

2 participants