You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As many APIs require this installation token (unique per installation of the integration, expires after short time period), I can easily (and do) provide a separate http.Client with a custom http.RoundTripper adding the necessary authentication headers, but, this requires a separate http.Client for every Integration installation, which means the underlying tcp connection is not being reused between installations (although I believe I can workaround that).
Further there is likely some room here to provide helpers for retrieving the required JWT tokens from a file as this is currently non-trivial and error prone (although just an example would be sufficient for most people).
I haven't developed a GitHub OAuth application before, so I'm not certain if the Integrations flow is any different, but ideally, I would like to be able to use a single client for all installations, swapping out the token on a per request basis - but I don't see how this could be possible without breaking api or not being thread safe (although I'm not certain on the safety guarantees already in place - I did look and couldn't see).
I haven't thought this through completely, and a separate client for each Integration installation maybe optimal (create another client, cache it for the life of the token), but I wanted to ask first if there has been some thought about how go-github would support with the new GitHub Integrations.
I have working prototype of a GitHub Integration and can provide samples if any maintainers want a quick start if no-one has started yet.
My current thinking (half tested):
Single http.Transport - ensures all derived clients reuse http connection.
Wrap that transport for each installation, the installationTransport adds necessary authorisation headers and is responsible for renewing tokens if they expire.
Pass the installationTransport into a http.Client into a github.Client.
So to provide this via a http.RoundTripper, I've done:
// acceptHeader is the GitHub Integrations Preview Accept header.constacceptHeader="application/vnd.github.machine-man-preview+json"// installationTransport provides a http.RoundTripper by wrapping an existing// http.RoundTripper (that's shared between multiple installation transports to// reuse underlying http connections), but provides GitHub Integration// authentication as an installation.//// See https://developer.github.com/early-access/integrations/authentication/#as-an-installationtypeinstallationTransportstruct {
tr http.RoundTripper// tr is the underlying roundtripper being wrappedkeyFilestring// keyFile is the path to GitHub Intregration's PEM encoded private keyintegrationIDint// integrationID is the GitHub Integration's Installation IDinstallationIDint// installationID is the GitHub Integration's Installation IDtoken*accessToken// token is the installation's access token
}
// accessToken is an installation access tokentypeaccessTokenstruct {
Tokenstring`json:"token"`ExpiresAt time.Time`json:"expires_at"`
}
func (t*installationTransport) RoundTrip(req*http.Request) (*http.Response, error) {
ift.token==nil||t.token.ExpiresAt.Add(-time.Minute).Before(time.Now()) {
// Token is not set or expired/nearly expired, so refreshiferr:=t.refreshToken(); err!=nil {
returnnil, errors.Wrap(err, "could not refresh installation token")
}
}
req.Header.Set("Authorization", "token "+t.token.Token)
req.Header.Set("Accept", acceptHeader)
resp, err:=t.tr.RoundTrip(req)
returnresp, err
}
func (t*installationTransport) refreshToken() error {
// TODO these claims could probably be reused between installations before expiryclaims:=&jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(time.Minute).Unix(),
Issuer: strconv.Itoa(t.integrationID),
}
bearer:=jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
key, err:=ioutil.ReadFile(t.keyFile)
iferr!=nil {
returnerrors.Wrap(err, "could not read private key")
}
privateKey, err:=jwt.ParseRSAPrivateKeyFromPEM(key)
iferr!=nil {
returnerrors.Wrap(err, "could not parse private key")
}
ss, err:=bearer.SignedString(privateKey)
iferr!=nil {
returnerrors.Wrap(err, "could not sign jwt")
}
req, err:=http.NewRequest("POST", fmt.Sprintf("https://api.github.com/installations/%v/access_tokens", t.installationID), nil)
iferr!=nil {
returnerrors.Wrap(err, "could not create request")
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", ss))
req.Header.Set("Accept", acceptHeader)
client:=&http.Client{}
resp, err:=client.Do(req)
iferr!=nil {
returnerrors.Wrap(err, "could not get access_tokens")
}
deferresp.Body.Close()
ifresp.StatusCode<200||resp.StatusCode>=300 {
returnfmt.Errorf("received response status %q when fetching %v", resp.Status, req.URL)
}
iferr:=json.NewDecoder(resp.Body).Decode(&t.token); err!=nil {
returnerr
}
returnnil
}
Above is missing constructor as my implementation is slightly more complicated, can provide a simpler version if required.
To use this:
tr:=http.DefaultTransport// shared for every installation// Per installationitr:=NewInstallationTransport(IntegrationID, InstallationID, KeyFile, tr)
client:=&http.Client{Transport: itr}
gh:=github.NewClient(client)
// gh will now manage authentication as an installation as defined by Integration Authentication
This generally works in dev, but hasn't been tested completely (in particular, I believe the TCP connections should be reused, but I haven't actually tested this), and I haven't ran this long enough to know tokens are refreshed, there's also an opportunity to split the refresh tokens into two steps to provide the ability to authenticate as an integration too (although the only use I have for it so far it to fetch tokens), I'm not a developer and little experience with GitHub APIs and this package so there maybe better options, and it's not clear on how rate limits are being implemented by GitHub right now (per integration rate limit or per installation). Feedback appreciated.
I'd like to know how this project would want to provide authentication for Integrations, as I know it doesn't currently provide OAuth, however, perhaps providing a similar transport would help people (there are a lot of steps specific to GitHub). If the project doesn't want to provide this (I can understand, I'll be glad to provide it as an external package).
Activity
bradleyfalzon commentedon Oct 21, 2016
So to provide this via a http.RoundTripper, I've done:
Above is missing constructor as my implementation is slightly more complicated, can provide a simpler version if required.
To use this:
This generally works in dev, but hasn't been tested completely (in particular, I believe the TCP connections should be reused, but I haven't actually tested this), and I haven't ran this long enough to know tokens are refreshed, there's also an opportunity to split the refresh tokens into two steps to provide the ability to authenticate as an integration too (although the only use I have for it so far it to fetch tokens), I'm not a developer and little experience with GitHub APIs and this package so there maybe better options, and it's not clear on how rate limits are being implemented by GitHub right now (per integration rate limit or per installation). Feedback appreciated.
I'd like to know how this project would want to provide authentication for Integrations, as I know it doesn't currently provide OAuth, however, perhaps providing a similar transport would help people (there are a lot of steps specific to GitHub). If the project doesn't want to provide this (I can understand, I'll be glad to provide it as an external package).
Thanks
bradleyfalzon commentedon Oct 27, 2016
Hi,
I've provided this via a work-in-progress external package in the meantime: https://github.com/bradleyfalzon/ghinstallation
Feedback welcome, but for the moment, will close the issue.
Thanks
doc: Add link to GitHub Apps authentication package. (#649)
doc: Add link to GitHub Apps authentication package. (google#649)
doc: Add link to GitHub Apps authentication package. (google#649)