Conversation
appleboy
commented
Mar 3, 2026
- Refactor HTTP API auth tests to use time.Second and testify require and assert helpers for clearer assertions.
- Add OAuth user info implementations for GitHub, Gitea, and Microsoft as separate provider-specific files.
- Introduce a shared fetchJSON helper to centralize HTTP GET and JSON decoding logic.
- Move provider-specific OAuth logic out of the main provider file and remove duplicated request handling code.
- Extend OAuth provider support to include Microsoft accounts.
- Simplify OAuth provider display name capitalization logic.
- Refactor HTTP API auth tests to use time.Second and testify require and assert helpers for clearer assertions. - Add OAuth user info implementations for GitHub, Gitea, and Microsoft as separate provider-specific files. - Introduce a shared fetchJSON helper to centralize HTTP GET and JSON decoding logic. - Move provider-specific OAuth logic out of the main provider file and remove duplicated request handling code. - Extend OAuth provider support to include Microsoft accounts. - Simplify OAuth provider display name capitalization logic. Signed-off-by: Bo-Yi Wu <[email protected]>
- Reformat the construction of the Gitea user API URL to improve readability without changing behavior Signed-off-by: Bo-Yi Wu <[email protected]>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Refactors OAuth provider user-info fetching into provider-specific implementations, adds Microsoft Entra ID (Graph) user info support, and cleans up HTTP API auth tests for readability and correctness.
Changes:
- Introduces a shared
fetchJSONhelper to centralize GET + JSON decoding for provider user-info calls. - Moves GitHub, Gitea, and Microsoft user-info logic into separate provider files and adds Microsoft support.
- Updates
internal/auth/http_api_test.goto usetime.Secondandtestify’srequire/asserthelpers.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/auth/oauth_provider.go | Removes inlined provider user-info logic, simplifies display-name formatting, and adds shared fetchJSON helper. |
| internal/auth/oauth_microsoft.go | Adds Microsoft Graph /me user-info implementation using shared fetchJSON. |
| internal/auth/oauth_github.go | Adds GitHub user + primary-email fetching using shared fetchJSON. |
| internal/auth/oauth_gitea.go | Adds Gitea user-info fetching using shared fetchJSON. |
| internal/auth/http_api_test.go | Refactors tests to use time.Second and testify assertions for clearer test failures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if resp.StatusCode != http.StatusOK { | ||
| body, _ := io.ReadAll(resp.Body) | ||
| return nil, fmt.Errorf("GitHub API error: %s - %s", resp.Status, string(body)) | ||
| } | ||
|
|
||
| var user githubUser | ||
| if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { | ||
| return nil, fmt.Errorf("failed to decode user info: %w", err) | ||
| } | ||
|
|
||
| // If email is not public, fetch from emails endpoint | ||
| if user.Email == "" { | ||
| email, err := p.getGitHubPrimaryEmail(ctx, client) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to get user email: %w", err) | ||
| } | ||
| user.Email = email | ||
| } | ||
|
|
||
| // GitHub requires email for our integration | ||
| if user.Email == "" { | ||
| return nil, errors.New("GitHub account has no email address") | ||
| } | ||
|
|
||
| return &OAuthUserInfo{ | ||
| ProviderUserID: strconv.FormatInt(user.ID, 10), | ||
| Username: user.Login, | ||
| Email: user.Email, | ||
| FullName: user.Name, | ||
| AvatarURL: user.AvatarURL, | ||
| }, nil | ||
| } | ||
|
|
||
| // getGitHubPrimaryEmail fetches primary email from GitHub emails endpoint | ||
| func (p *OAuthProvider) getGitHubPrimaryEmail( | ||
| ctx context.Context, | ||
| client *http.Client, | ||
| ) (string, error) { | ||
| req, err := http.NewRequestWithContext( | ||
| ctx, http.MethodGet, "https://api.github.com/user/emails", nil, | ||
| ) | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to create request: %w", err) | ||
| return fmt.Errorf("unexpected status %s: %s", resp.Status, body) |
There was a problem hiding this comment.
fetchJSON reads the entire non-200 response body via io.ReadAll. Since this is an HTTP helper that may be used against external services, an unexpectedly large body can cause excessive memory use (and it’s also echoed into the error). Consider limiting/truncating the body (e.g., via io.LimitReader) before formatting it into the error, and possibly omitting the body entirely for very large responses.
| if err != nil { | ||
| return nil, fmt.Errorf("failed to get user info: %w", err) | ||
| return fmt.Errorf("failed to fetch user info: %w", err) | ||
| } | ||
| defer resp.Body.Close() | ||
|
|
||
| if resp.StatusCode != http.StatusOK { | ||
| body, _ := io.ReadAll(resp.Body) | ||
| return nil, fmt.Errorf("GitHub API error: %s - %s", resp.Status, string(body)) | ||
| } | ||
|
|
||
| var user githubUser | ||
| if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { | ||
| return nil, fmt.Errorf("failed to decode user info: %w", err) | ||
| } | ||
|
|
||
| // If email is not public, fetch from emails endpoint | ||
| if user.Email == "" { | ||
| email, err := p.getGitHubPrimaryEmail(ctx, client) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to get user email: %w", err) | ||
| } | ||
| user.Email = email | ||
| } | ||
|
|
||
| // GitHub requires email for our integration | ||
| if user.Email == "" { | ||
| return nil, errors.New("GitHub account has no email address") | ||
| } | ||
|
|
||
| return &OAuthUserInfo{ | ||
| ProviderUserID: strconv.FormatInt(user.ID, 10), | ||
| Username: user.Login, | ||
| Email: user.Email, | ||
| FullName: user.Name, | ||
| AvatarURL: user.AvatarURL, | ||
| }, nil | ||
| } | ||
|
|
||
| // getGitHubPrimaryEmail fetches primary email from GitHub emails endpoint | ||
| func (p *OAuthProvider) getGitHubPrimaryEmail( | ||
| ctx context.Context, | ||
| client *http.Client, | ||
| ) (string, error) { | ||
| req, err := http.NewRequestWithContext( | ||
| ctx, http.MethodGet, "https://api.github.com/user/emails", nil, | ||
| ) | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to create request: %w", err) | ||
| return fmt.Errorf("unexpected status %s: %s", resp.Status, body) |
There was a problem hiding this comment.
fetchJSON returns errors like "failed to fetch user info" / "unexpected status ..." without including the URL being fetched. Since this helper is shared by multiple providers/endpoints (user profile vs emails), the current wording is also a bit misleading. Consider including the request URL in the error and using a more generic message (e.g., "failed to fetch JSON") so failures are actionable when surfaced in logs.
| email = user.UserPrincipalName | ||
| } | ||
| if email == "" { | ||
| return nil, errors.New("microsoft account has no email address") |
There was a problem hiding this comment.
The returned error message uses a lowercase provider name ("microsoft account has no email address"). Since this is likely user-facing and other providers use capitalized names (e.g., "GitHub ..."), consider capitalizing it for consistency ("Microsoft account ...").
| return nil, errors.New("microsoft account has no email address") | |
| return nil, errors.New("Microsoft account has no email address") |
| } | ||
|
|
||
| if user.Email == "" { | ||
| return nil, errors.New("gitea account has no email address") |
There was a problem hiding this comment.
The returned error message uses a lowercase provider name ("gitea account has no email address"). For consistency with other providers and display names, consider capitalizing it ("Gitea account ...").
| return nil, errors.New("gitea account has no email address") | |
| return nil, errors.New("Gitea account has no email address") |