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

Add webhooks for instant package updates - Fix #10 #414

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

WebFreak001
Copy link
Member

@WebFreak001 WebFreak001 commented Feb 19, 2019

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)


## `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.
Copy link
Member

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.

Copy link
Member Author

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)

Copy link
Member Author

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

Copy link
Member

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

Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member

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
Copy link
Member

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)

Copy link
Member Author

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.

@wilzbach wilzbach temporarily deployed to dub-registry-staging-pr-414 February 19, 2019 21:29 Inactive
@WebFreak001 WebFreak001 changed the title Add webhooks for instant package updates - Fix #412 Add webhooks for instant package updates - Fix #10 Sep 21, 2019
@s-ludwig
Copy link
Member

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.

Copy link
Member Author

@WebFreak001 WebFreak001 left a 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, $)];
Copy link
Member Author

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)
Copy link
Member Author

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", "");
Copy link
Member Author

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)
Copy link
Member Author

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);
Copy link
Member Author

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)
Copy link
Member Author

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)");
Copy link
Member Author

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)
Copy link
Member Author

Choose a reason for hiding this comment

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

use constant-time compare

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants