-
-
Notifications
You must be signed in to change notification settings - Fork 64
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
Add webhooks for instant package updates - Fix #10 #414
base: master
Are you sure you want to change the base?
Conversation
|
||
## `POST /api/packages/:packageName/update/github` | ||
|
||
Queues an update for the specified package. Compatible with GitHub webhooks and only triggers on `create` events. Must pass secret as query param and not in GitHub webhook settings. |
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.
Hmm, you still need to save the GitHub secret here to verify that it's the actual owner (though this is theoretically not needed).
Also, we need to rate-limit this.
And lastly it would be ideal to not use extra GitHub API requests for every event, but get as much of the info as possible.
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 wanted to implement github properly at first, but vibe.d with rest interface doesn't give me a way to get the full request body as string, which I need for the SHA1 HMAC verification with the GitHub secret field (at least I didn't find any way)
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.
Also I don't think any of the other dub API currently has rate limiting yet. This will only queue a recheck and is basically the same as spamming the "check for updates" button on your project page
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 wanted to implement github properly at first,
Don't reinvent the wheel -> https://github.com/dlang/dlang-bot
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.
basically the same as spamming the "check for updates" button on your project page
Fair point, but by providing hooks we make it a lot easier for people to spam / run us into the rate-limits of GitHub.
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 wanted to implement github properly at first,
Don't reinvent the wheel -> dlang/dlang-bot
I more accurately meant I wanted to support the "Secret" field on GitHub which sends a Signature of the body text as SHA1 HMAC with the Secret as password, but with vibe.d the rest interface implementation already ate the body so I couldn't access the raw string from code to hash. For hashing the stdlib provides everything needed
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.
Regarding the rate limiting, if we have web hook support, I would propose to stretch the polling over a much larger period than now (e.g. 6 hours vs. 30 minutes), so that it merely acts as a fallback in case the web hooks fail. This should reduce the risk of running into the rate limit considerable when compared to the current state.
Non-webhook repositories could be kept at the 30 min. polling interval, of course.
Regarding the signature issue, I believe this should work using an @before
parameter that reads the body, validates the signature, and then parses and returns the JSON.
`header`: string (optional) provide which header is used to check the secret (must start with X-) | ||
|
||
Body params: (application/x-www-form-urlencoded, multipart/form-data or application/json) | ||
`secret`: string (optional) provide the secret as body param |
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.
Hmm, what's the use case for this? Isn't this prone to be over-used/falsely used?
(I'm referring to the API method itself)
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 wanted to make sure it's easy to incoperate this into any service you might be using for webhooks, so I'm offering to make the API callable with a secret per query string, body param or per HTTP Header.
1243ce8
to
682feb9
Compare
682feb9
to
ff568d4
Compare
I missed this PR at the time, but I think it would be fine to add it like this. It wouldn't completely "fix" the original issue due to its manual nature, but it would be nice to have as a convenience feature for those willing to do some configuration work. Just getting a decent percentage of the packages being updated by push notifications would still require OAuth+PubSubHubub or a similar solution that works more or less seamlessly. |
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.
notes to self to fix
|
||
char[24] token; | ||
foreach (ref c; token) | ||
c = lowerHexDigits[uniform(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.
TODO: use secure random instead of uniform
@@ -187,6 +245,18 @@ private: | |||
} | |||
} | |||
|
|||
private string updateSecretReader(scope HTTPServerRequest req, scope HTTPServerResponse res) |
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 needs a better name
if (header.length) | ||
return req.headers.get(header); | ||
|
||
string ret = req.contentType == "application/json" ? req.json["secret"].get!string : req.form.get("secret", ""); |
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.
probably want to tryIndex the JSON
return "No secret sent"; | ||
|
||
string expected = m_registry.getPackageSecret(_name); | ||
if (!expected.length || secret != expected) |
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.
use constant-time comparison
foreach (ref c; token) | ||
c = lowerHexDigits[uniform(0, $)]; | ||
|
||
m_db.setPackageSecret(pack_name, token[].idup); |
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.
can just allocate in the first place instead of stack allocating & idup
enforceBadRequest(eventsObj.type == Json.Type.array, "Hook events must be of type array"); | ||
auto events = eventsObj.get!(Json[]); | ||
|
||
foreach (ev; events) |
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.
can simplify the eventsObj to events and just iterate over Json here
string expected = m_registry.getPackageSecret(_name); | ||
if (expected.length && secret.length && secret == expected) | ||
m_registry.addPackageError(_name, | ||
"GitHub hook configuration is invalid. Hook is missing 'create' event. (Tags or branches)"); |
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 test code will never be run if configuration is valid
return "valid"; | ||
|
||
string expected = m_registry.getPackageSecret(_name); | ||
if (expected.length && secret.length && secret == expected) |
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.
use constant-time compare
I put a http-update.md during development in the root folder just to collect some thoughts how I wanted to implement it, that file will need to be moved to some proper API documentation page (which I don't think we have yet)