Fix OAuth token refresh in webhook handling for Bitbucket and GitHub (#6059)

## Summary

Fixes #5590
Fixes #5713

This PR fixes an issue where webhook handling fails with "failure to parse hook" error when the user's OAuth access token has expired. The root cause is that the Bitbucket and GitHub forge implementations make API calls during webhook processing without first refreshing the OAuth token.

## Problem

When a webhook arrives from Bitbucket or GitHub, the `Hook()` function (and its helper functions) make API calls to fetch additional data (changed files, repo info, etc.). These API calls use the stored OAuth access token, which may have expired.

**Before this fix:**
1. Webhook arrives
2. `Hook()` makes API calls with potentially expired token
3. API call fails with "OAuth2 access token expired"
4. Error bubbles up as HTTP 400 "failure to parse hook"
5. `forge.Refresh()` is called later in `PostHook()` - but it's too late

**Example error from logs:**

`failure to parse hook error="OAuth2 access token expired. Use your refresh token to obtain a new access token."`


## Solution

Add `forge.Refresh()` calls before making API calls in the webhook handling code paths. This follows the same pattern already used by:
- Bitbucket Data Center forge (`server/forge/bitbucketdatacenter/bitbucketdatacenter.go`)
- Other code paths like `pipeline.Create()`, `cron.go`, etc.

### Changes

**Bitbucket** (`server/forge/bitbucket/bitbucket.go`):
- Added `forge.Refresh()` in `Hook()` before API calls

**GitHub** (`server/forge/github/github.go`):
- Added `forge.Refresh()` in `loadChangedFilesFromPullRequest()`
- Added `forge.Refresh()` in `getTagCommitSHA()`
- Added `forge.Refresh()` in `loadChangedFilesFromCommits()`

## Testing

- All existing Bitbucket and GitHub forge tests pass
- Tested in production environment with Bitbucket (waited for token expiry, webhook succeeded after fix)
This commit is contained in:
Kevin Web
2026-02-03 07:34:01 -05:00
committed by GitHub
parent 57b2449bb1
commit 894ba77d94
2 changed files with 24 additions and 0 deletions

View File

@@ -32,6 +32,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v3/server/forge/common"
forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v3/server/model"
"go.woodpecker-ci.org/woodpecker/v3/server/store"
"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils"
)
@@ -423,6 +424,14 @@ func (c *config) Hook(ctx context.Context, req *http.Request) (*model.Repo, *mod
return nil, nil, err
}
// Refresh the OAuth token before making API calls.
// The token may be expired, and without this refresh the API calls below
// would fail with "OAuth2 access token expired" error.
_store, ok := store.TryFromContext(ctx)
if ok {
forge.Refresh(ctx, c, _store, u)
}
switch pl.Event {
case model.EventPush:
// List only the latest push changes

View File

@@ -692,6 +692,11 @@ func (c *client) loadChangedFilesFromPullRequest(ctx context.Context, pull *gith
return nil, err
}
// Refresh the OAuth token before making API calls.
// The token may be expired, and without this refresh the API calls below
// would fail with an authentication error.
forge.Refresh(ctx, c, _store, user)
gh := c.newClientToken(ctx, user.AccessToken)
fileList := make([]string, 0, 16)
@@ -730,6 +735,11 @@ func (c *client) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName
return "", err
}
// Refresh the OAuth token before making API calls.
// The token may be expired, and without this refresh the API calls below
// would fail with an authentication error.
forge.Refresh(ctx, c, _store, user)
gh := c.newClientToken(ctx, user.AccessToken)
page := 1
@@ -785,6 +795,11 @@ func (c *client) loadChangedFilesFromCommits(ctx context.Context, tmpRepo *model
return nil, err
}
// Refresh the OAuth token before making API calls.
// The token may be expired, and without this refresh the API calls below
// would fail with an authentication error.
forge.Refresh(ctx, c, _store, user)
gh := c.newClientToken(ctx, user.AccessToken)
fileList := make([]string, 0, 16)