-
Notifications
You must be signed in to change notification settings - Fork 2
baton-github: refersh appjwttoken once it expires #117
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,6 +30,9 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const githubDotCom = "https://github.com" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // JWT token expires in 10 minutes, so we set it to 9 minutes to leave some buffer. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const jwtExpiryTime = 9 * time.Minute | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ValidAssetDomains = []string{"avatars.githubusercontent.com"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxPageSize int = 100 // maximum page size github supported. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -219,9 +222,9 @@ func (gh *GitHub) validateAppCredentials(ctx context.Context) (annotations.Annot | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("github-connector: only one org is allowed when using github app") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _, resp, err := findInstallation(ctx, gh.appClient, orgLogins[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _, err := findInstallation(ctx, gh.appClient, orgLogins[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, wrapGitHubError(err, resp, "github-connector: failed to retrieve org installation") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+225
to
228
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against empty org list before indexing. ✅ Proposed fix func (gh *GitHub) validateAppCredentials(ctx context.Context) (annotations.Annotations, error) {
orgLogins := gh.orgs
if len(orgLogins) > 1 {
return nil, fmt.Errorf("github-connector: only one org is allowed when using github app")
}
+ if len(orgLogins) == 0 {
+ return nil, fmt.Errorf("github-connector: org is required when using github app")
+ }
_, err := findInstallation(ctx, gh.appClient, orgLogins[0])
if err != nil {
return nil, err
}
return nil, nil
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -273,9 +276,9 @@ func New(ctx context.Context, ghc *cfg.Github, appKey string) (*GitHub, error) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installation, resp, err := findInstallation(ctx, appClient, ghc.Orgs[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installation, err := findInstallation(ctx, appClient, ghc.Orgs[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, wrapGitHubError(err, resp, "github-connector: failed to find app installation") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token, err := getInstallationToken(ctx, appClient, installation.GetID()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -292,7 +295,16 @@ func New(ctx context.Context, ghc *cfg.Github, appKey string) (*GitHub, error) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx: ctx, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instanceURL: ghc.InstanceUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installationID: installation.GetID(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jwttoken: jwttoken, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jwtTokenSource: oauth2.ReuseTokenSource( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &oauth2.Token{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AccessToken: jwttoken, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Expiry: time.Now().Add(jwtExpiryTime), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so we set an expiration time on the token that is different than on the jwt, because it will expire sooner and lead to a refresh before the token expires. That aligns with the description of recommended library in #115
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The JWT is valid for 10 minutes. It’s better to refresh it a bit earlier to avoid a 401 error interrupting the workflow. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &appJWTTokenRefresher{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appID: ghc.AppId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privateKey: appKey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -377,28 +389,36 @@ func getClientToken(ghc *cfg.Github, privateKey string) (string, string, error) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "", ghc.Token, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key, err := loadPrivateKeyFromString(privateKey) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token, err := getJWTToken(ghc.AppId, privateKey) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "", "", err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return token, "", nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func getJWTToken(appID string, privateKey string) (string, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key, err := loadPrivateKeyFromString(privateKey) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "", err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| now := time.Now() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token, err := jwtv5.NewWithClaims(jwtv5.SigningMethodRS256, jwtv5.MapClaims{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "iat": now.Unix() - 60, // issued at | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "exp": now.Add(time.Minute * 10).Unix(), // expires | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "iss": ghc.AppId, // GitHub App ID | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "iss": appID, // GitHub App ID | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).SignedString(key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "", "", err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "", err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return token, "", nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return token, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+399
to
414
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate An empty Suggested fix func getJWTToken(appID string, privateKey string) (string, error) {
+ if strings.TrimSpace(appID) == "" {
+ return "", errors.New("github-connector: app ID is required")
+ }
key, err := loadPrivateKeyFromString(privateKey)
if err != nil {
return "", err
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func findInstallation(ctx context.Context, c *github.Client, orgName string) (*github.Installation, *github.Response, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func findInstallation(ctx context.Context, c *github.Client, orgName string) (*github.Installation, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installation, resp, err := c.Apps.FindOrganizationInstallation(ctx, orgName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, wrapGitHubError(err, resp, "github-connector: failed to find installation") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is an unrelated change just to wrap the GitHub error in one place yes?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return installation, resp, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return installation, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func getInstallationToken(ctx context.Context, c *github.Client, id int64) (*github.InstallationToken, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -415,19 +435,35 @@ func getInstallationToken(ctx context.Context, c *github.Client, id int64) (*git | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return token, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // appJWTTokenRefresher is used to refresh the app jwt token when it expires. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type appJWTTokenRefresher struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appID string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privateKey string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (r *appJWTTokenRefresher) Token() (*oauth2.Token, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token, err := getJWTToken(r.appID, r.privateKey) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return &oauth2.Token{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AccessToken: token, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Expiry: time.Now().Add(jwtExpiryTime), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type appTokenRefresher struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx context.Context | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jwttoken string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jwtTokenSource oauth2.TokenSource | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instanceURL string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installationID int64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (r *appTokenRefresher) Token() (*oauth2.Token, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appClient, err := newGitHubClient(r.ctx, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r.instanceURL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| oauth2.StaticTokenSource( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &oauth2.Token{AccessToken: r.jwttoken}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r.jwtTokenSource, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this 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.
@laurenleach
sleep timedoesn't solve the problem, but this line fixes the problem.The issue is that the user is a member of a team that has been granted access to this repository. Even if we remove the user’s direct access entitlement, they will still be able to access the repo through the team.
I really don't like grant the user with
adminentitlement, but we can't do anything if we don't have admin pat token. ( updating these tests requires the resource id )