-
Notifications
You must be signed in to change notification settings - Fork 89
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): httpsig request validation #387
Conversation
807b8dd
to
739fa38
Compare
try { | ||
const sig = ctx.headers['signature'] | ||
const sigInput = ctx.headers['signature-input'] | ||
|
||
if ( | ||
!sig || | ||
!sigInput || | ||
typeof sig !== 'string' || | ||
typeof sigInput !== 'string' | ||
) { | ||
ctx.status = 400 | ||
ctx.body = { | ||
error: 'invalid_request' | ||
} | ||
return | ||
} | ||
|
||
const verified = await deps.clientService.verifySigFromBoundKey( | ||
sig, | ||
sigInput, | ||
body['access_token'], | ||
ctx | ||
) | ||
if (introspectionResult) { | ||
ctx.body = introspectionToBody(introspectionResult) | ||
if (!verified) { | ||
ctx.status = 401 | ||
ctx.body = { | ||
error: 'invalid_client' | ||
} | ||
} | ||
} catch (err) { | ||
if ((err as Error).name === 'InvalidSigInputError') { | ||
ctx.status = 400 | ||
ctx.body = { | ||
error: 'invalid_request' | ||
} | ||
return | ||
} else { | ||
return ctx.throw(404, 'token not found') | ||
ctx.status = 401 | ||
ctx.body = { | ||
error: 'invalid_client' | ||
} | ||
return | ||
} | ||
} |
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.
Should we consider extracting this logic into a helper function so we don't have to duplicate this everywhere?
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.
Indeed, this should be better as a middleware we could place in front of the relevant routes. Will look into changing that.
c5147fd
to
30c65a8
Compare
package.json
Outdated
"lint-staged": ">=10", | ||
"prettier": "2.2.1", | ||
"ts-jest": "^26.5.5", | ||
"ts-jest": "^28.0.5", |
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'm seeing this message when running tests:
ts-jest[versions] (WARN) Version 4.2.4 of typescript installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version (>=4.3.0 <5.0.0-0). Please do not report issues in ts-jest if you are using unsupported versions.
packages/auth/src/app.ts
Outdated
@@ -168,12 +169,15 @@ export class App { | |||
for (const path in openApi.paths) { | |||
for (const method in openApi.paths[path]) { | |||
if (isHttpMethod(method)) { | |||
let useHttpSigMiddleware = false | |||
let route: (ctx: AppContext) => Promise<void> | |||
if (path.includes('continue')) { |
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.
Why no useHttpSigMiddleware
for continuation requests?
When used for delegation in GNAP, these key binding mechanisms allow
the AS to ensure that the keys presented by the client instance in
the initial request are in control of the party calling any follow-up
or continuation requests. To facilitate this requirement, the
continuation response ([Section 3.1](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-3.1)) includes an access token bound to
the client instance's key ([Section 2.3](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-2.3)), and that key (or its most
recent rotation) MUST be proved in all continuation requests
[Section 5](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-5). Token management requests [Section 6](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-6) are similarly bound
to either the access token's own key or, in the case of bearer
tokens, the client instance's key.
https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-7.3
if (!body['access_token']) { | ||
ctx.status = 400 | ||
ctx.body = { | ||
error: 'invalid_request', |
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.
Are these error responses only necessary for the client (not the RO)?
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's only for the client. Though looking at the spec, it does seem like these are only necessary during the grant authorization flow and not necessarily for token management endpoints.
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.
Yeah, I was thinking that since the RO would be sending token introspection requests that these error responses don't have to be used.
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.
Isn't that covered by checking against the spec?
packages/auth/src/client/service.ts
Outdated
} else if (path.includes('/token') && method === 'DELETE') { | ||
keyName = 'managementId' | ||
value = ctx.params['managementId'] | ||
} else { |
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.
Should continuation requests be included here?
#387 (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.
Oh yeah, they should. Thanks for pointing that out.
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.
@@ -44,6 +44,6 @@ | |||
"jest-openapi": "^0.14.2", | |||
"nock": "^13.2.4", | |||
"openapi-types": "^12.0.0", | |||
"typescript": "^4.2.4" | |||
"typescript": "^4.3.0" |
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'm still getting the warning and so is GitHub Actions
#387 (comment)
https://github.com/interledger/rafiki/runs/7682195245?check_suite_focus=true
I wonder if typescript should be updated in the root package.json
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'll try it and see what happens
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.
Bumping typescript breaks a lot of stuff
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 doesn't make any sense because ^4.2.4
actually installs 4.7.2
currently as the caret means any non breaking changes.
ctx.status = 200 | ||
}) | ||
|
||
this.publicRouter.del('/auth/token/:id', accessTokenRoutes.revoke) |
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.
Why was this removed?
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 guess because we have this section further up that also includes checking against the openapi spec (L168 - L1206). In the end, all of those individual routes should be moved into the block that also checks the openapi spec.
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 redundant to this block of code now. I originally stubbed it out when scaffolding the auth server but it didn't get removed once OpenApi was added.
// afterAll(async (): Promise<void> => { | ||
// nock.restore() | ||
// }) |
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.
remove?
packages/auth/src/client/service.ts
Outdated
|
||
const { body } = ctx.request | ||
const { path, method } = ctx | ||
// TODO: replace with HttpMethod types instead of string literals |
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.
bump
const requestBody = { | ||
access_token: v4(), | ||
proof: 'httpsig', | ||
resource_server: 'test' | ||
} | ||
const { signature, sigInput, contentDigest } = await generateSigHeaders( | ||
url, | ||
method, | ||
requestBody | ||
) | ||
const ctx = createContext( |
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.
Maybe you could add a wrapper for createContext
that takes url
, method
, requestBody
, etc as parameters and does generateSigHeaders
+ createContext
instead of having to do this full setup in all these tests.
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 can actually just remove it since the test doesn't run the middleware.
0f8a049
to
16deb5b
Compare
if (!body['access_token']) { | ||
ctx.status = 400 | ||
ctx.body = { | ||
error: 'invalid_request', |
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.
Isn't that covered by checking against the spec?
@@ -2,7 +2,7 @@ exports.up = function (knex) { | |||
return knex.schema.createTable('accessTokens', function (table) { | |||
table.uuid('id').notNullable().primary() | |||
table.string('value').notNullable().unique() | |||
table.string('managementId').notNullable() | |||
table.string('managementId').notNullable().unique() |
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.
Why do we introduce that? The id
is already unique, so why adding another unique id?
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 figured it would be the right practice to expose an external id instead of an internal database id. I should update it to a .uuid()
instead of a .string()
though since in practice it's always a uuid/v4.
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 should update it to a .uuid() instead of a .string() though since in practice it's always a uuid/v4.
bump
ctx.status = 200 | ||
}) | ||
|
||
this.publicRouter.del('/auth/token/:id', accessTokenRoutes.revoke) |
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 guess because we have this section further up that also includes checking against the openapi spec (L168 - L1206). In the end, all of those individual routes should be moved into the block that also checks the openapi spec.
Changes proposed in this pull request
httpsig
validation to requests where the client must present a public key.Context
Closes #274.
Bumped
jest
to 28 to fix: jestjs/jest#10221 (comment), which was affecting one of the tests usingfake-timers
.https://github.com/facebook/jest/releases/tag/v28.0.0
[jest-config]
[BREAKING] Rename config option name to id (fix: rename config option name to id jestjs/jest#11981)Checklist
fixes #number