-
Notifications
You must be signed in to change notification settings - Fork 305
Fix 401 error handling, add tests #507
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
Conversation
e1d967d to
63d737b
Compare
63d737b to
42095fc
Compare
|
cc @dan-f for review |
test/integration/errors-oidc.js
Outdated
|
|
||
| describe('Unauthenticated requests to protected resources', () => { | ||
| describe('accepting json only', () => { | ||
| it('should return 401 Unauthorized with www-auth header', 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.
FWIW if this is mocha, you can just return promises from the test rather than explicitly call 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.
True - this version of supertest doesn't support promises I think, hence the explicit done(). I was planning on upgrading to the latest soon anyway, though.
test/integration/errors-oidc.js
Outdated
| .set('Accept', 'text/html') | ||
| .expect('Content-Type', 'text/html; charset=utf-8') | ||
| .expect(res => { | ||
| expect(res.text).to.match(/<meta http-equiv="refresh"/) |
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.
Where are we expected to be redirected to? I think that belongs with the test.
| }) | ||
|
|
||
| describe('with an expired bearer token', () => { | ||
| const expiredToken = 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImxOWk9CLURQRTFrIn0.eyJpc3MiOiJodHRwczovL2xvY2FsaG9zdDozNDU3Iiwic3ViIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6MzQ1Ny9wcm9maWxlL2NhcmQjbWUiLCJhdWQiOiJodHRwczovL2xvY2FsaG9zdDozNDU3IiwiZXhwIjoxNDk2MjM5ODY1LCJpYXQiOjE0OTYyMzk4NjUsImp0aSI6IjliN2MwNGQyNDY3MjQ1ZWEiLCJub25jZSI6IklXaUpMVFNZUmktVklSSlhjejVGdU9CQTFZR1lZNjFnRGRlX2JnTEVPMDAiLCJhdF9oYXNoIjoiRFpES3I0RU1xTGE1Q0x1elV1WW9pdyJ9.uBTLy_wG5rr4kxM0hjXwIC-NwGYrGiiiY9IdOk5hEjLj2ECc767RU7iZ5vZa0pSrGy0V2Y3BiZ7lnYIA7N4YUAuS077g_4zavoFWyu9xeq6h70R8yfgFUNPo91PGpODC9hgiNbEv2dPBzTYYHqf7D6_-3HGnnDwiX7TjWLTkPLRvPLTcsCUl7G7y-EedjcVRk3Jyv8TNSoBMeTwOR3ewuzNostmCjUuLsr73YpVid6HE55BBqgSCDCNtS-I7nYmO_lRqIWJCydjdStSMJgxzSpASvoeCJ_lwZF6FXmZOQNNhmstw69fU85J1_QsS78cRa76-SnJJp6JCWHFBUAolPQ' |
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 this is too opaque. The code should explicitly generate an expired token for the purposes of a test like this.
lib/handlers/error-pages.js
Outdated
| debug('401 error - redirect to Select Provider') | ||
| res.status(err.status) | ||
| redirectToLogin(req, res, next) | ||
| if (ldp.auth === 'oidc') { |
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 there a way of including this in the OIDC code rather than in the global error handler?
WWW-Authenticate is meant to be used for all 401s, so it should be here for WebID-TLS as well.
lib/handlers/error-pages.js
Outdated
| res.status(err.status) | ||
| redirectToLogin(req, res, next) | ||
| if (ldp.auth === 'oidc') { | ||
| let statusCode = err.status || err.statusCode || 500 |
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've talked about this before, but I can't think of an instance in which, in application code, a 500 error code should be explicitly set.
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 remember. The reason I added it here is that this is the last place to catch it (if some other module forgot to specify a status code for their error), otherwise it gets a '0 is an invalid status code' error. I can take it out tho.
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 I guess 500 is better than 0, hah.
lib/handlers/error-pages.js
Outdated
| .map(key => `${key}="${errorParams[key]}"`) | ||
| .join(', ') | ||
|
|
||
| res.set('WWW-Authenticate', 'Bearer ' + challengeParams) |
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 header is meant to be used with 401s, but it's being used here regardless of the error code.
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. (forgot to move it.)
lib/handlers/error-pages.js
Outdated
|
|
||
| res.set('WWW-Authenticate', 'Bearer ' + challengeParams) | ||
|
|
||
| if (statusCode === 401 && req.accepts('text/html')) { |
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.
And if status code is anything else? what does the return at the end of this function actually do?
61c7b97 to
2cb4c58
Compare
|
@dan-f I rewrote the error-pages handler to reflect feedback, and added tests. |
dan-f
left a comment
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.
Looking better. I still have a few points of review
| return | ||
| } | ||
| let locals = req.app.locals | ||
| let authMethod = locals.authMethod |
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.
Where does locals.authMethod come from? Does the client specify the auth method, or is it in the server configuration?
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.
Server configuration.
| }) | ||
|
|
||
| describe('Authenticated responses to protected resources', () => { | ||
| describe('with an empty bearer token', () => { |
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 there anything in the error telling the client that they messed up the Authorization header? I'd expect a 400 in this case, since I think it could count as malformed syntax.
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 Bearer Token spec dictates treating an empty bearer token as a plain 401 Unauthorized (with no error). And a malformed token as a 401 with a invalid_token error (which is tested below).
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.
Huh. According to section 3.1 of the spec, a missing bearer token seems to satisfy the invalid_request/400 scenario:
invalid_request
The request is missing a required parameter, includes an
unsupported parameter or parameter value, repeats the same
parameter, uses more than one method for including an access
token, or is otherwise malformed. The resource server SHOULD
respond with the HTTP 400 (Bad Request) status code.
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.
Not quite - the bearer token is not a required parameter.
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 bearer token in bearer token authentication is not a required parameter?
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.
@dan-f Changed to return an invalid_token / Empty access token error instead.
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.
Still needs to be 400, not 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.
I completely disagree with your interpretation, but since this seems to be an important issue for you, I will change it to a 400.
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.
invalid_request
The request is missing a required parameter, includes an
unsupported parameter or parameter value, repeats the same
parameter, uses more than one method for including an access
token, or is otherwise malformed. The resource server SHOULD
respond with the HTTP 400 (Bad Request) status code.
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.
@dan-f now returning a 400 on an empty bearer token header.
| res.header('Content-Type', 'text/html') | ||
| res.send(text) | ||
| res.status(statusCode) | ||
| res.header('Content-Type', 'text/html') |
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 really obey the Accept header.
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 would be nice, agreed. I've opened issue #508 to implement support for more error formats (other than text/html) based on the Accept header.
|
@dan-f any other issues on these 401 tests, or can I merge this? |
* Add WWW-Authenticate to exposed CORS headers * Fix 401 error handling, add tests * Add a test for expired token error * Update to supertest 3.0.0 * Convert 401 tests to Promise interface * Rewrite error-pages handler, add tests * Change 401 error logic on empty bearer token * Return a 400 error on empty bearer token header
No description provided.