-
Notifications
You must be signed in to change notification settings - Fork 27
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: add webhook verify utility function to node #457
Conversation
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 agree with this in principal for sure! Few comments inline. Can you add some docs for this? Wanna take a stab at adding for other languages, or do you think it's okay to merge in for JS initially?
email: String; | ||
} | ||
|
||
export function verify(body: WebhookBody, signature: string, secret: string): WebhookBody { |
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 would you feel about calling this verifyWebhook()
instead? verify on it's own is a little generic.
This is in core and doesnt need to come from npm
This test was throwing an error, but not the Expired Signature error that we were expecting. It was throwing because we were passing in `new Date()` into the test instead of `Date.now()`. I've fixed all of the tests here, and ensured that we're casting to int when constructing the new Date() in verify()
}); | ||
} | ||
|
||
const randomApiKey = 'rdme_abcdefghijklmnopqrstuvwxyz'; |
Check failure
Code scanning / CodeQL
Hard-coded credentials
app.post('/webhook', express.json({ type: 'application/json' }), (req, res) => { | ||
// Verify the request is legitimate and came from ReadMe | ||
const signature = req.headers['readme-signature']; | ||
// Your ReadMe secret | ||
const secret = process.env.README_API_KEY; | ||
try { | ||
readme.verify(req.body, signature, secret); | ||
} catch (e) { | ||
// Handle invalid requests | ||
return res.sendStatus(401); | ||
} | ||
// Fetch the user from the db | ||
db.find({ email: req.body.email }).then(user => { | ||
return res.json({ | ||
// OAS Security variables | ||
petstore_auth: 'default-key', | ||
basic_auth: { user: 'user', pass: 'pass' }, | ||
}); | ||
}); | ||
}); |
Check failure
Code scanning / CodeQL
Missing rate limiting
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 pretty happy about this apart from two things:
- Stripe uses
v1=
as their live scheme to hold the data - do we wanna replicate this? https://stripe.com/docs/webhooks/signatures#verify-manually - Naming of the function - should it be
verify()
orwebhookVerify()
orverifyWebhook()
? I could go either way.
@domharrington Maybe we should just go with verifyWebhook? I kinda like the explicitness I think? Other than my one small comment I'm happy with this! |
describe('verifyWebhook()', () => { | ||
it('should return the body if the signature is valid', () => { | ||
const body = { email: '[email protected]' }; | ||
const secret = 'docs4dayz'; |
Check failure
Code scanning / CodeQL
Hard-coded credentials
|
||
it('should throw an error if signature is invalid', () => { | ||
const body = { email: '[email protected]' }; | ||
const secret = 'docs4dayz'; |
Check failure
Code scanning / CodeQL
Hard-coded credentials
const secret = 'docs4dayz'; | ||
const time = Date.now(); | ||
const unsigned = `${time}.${JSON.stringify(body)}`; | ||
const hmac = crypto.createHmac('sha256', 'invalidsecret'); |
Check failure
Code scanning / CodeQL
Hard-coded credentials
|
||
it('should throw an error if timestamp is too old', () => { | ||
const body = { email: '[email protected]' }; | ||
const secret = 'docs4dayz'; |
Check failure
Code scanning / CodeQL
Hard-coded credentials
|
||
it('should throw an error if signature is missing', () => { | ||
const body = { email: '[email protected]' }; | ||
const secret = 'docs4dayz'; |
Check failure
Code scanning / CodeQL
Hard-coded credentials
Added a utility function to make verifying that a webhook request is legitimately from ReadMe easier!
Currently we recommend users have the following code in their endpoint:
This moves this logic to a
readme.verify
function so we don't have to explain the security of how this weeks (though we should keep the steps for doing this manually in the docs like stripe does).With this new function the code will look like this instead:
Even though the number of lines of code is similar, it's a lot less complex. I also added code in
readme.verify
that checks the timestamp is recent to mitigate against replay attacks, which we should probably also be doing in the manual version.We should also do this in the rest of the metrics sdks as well!