package github import ( "context" "encoding/json" "io" "net/http" "golang.org/x/oauth2" "golang.org/x/oauth2/github" "base/internal/pkg/oauth/types" ) type client struct { oauthConfig *oauth2.Config } func New(config oauth2.Config) types.Oauth { oauthConfig := &oauth2.Config{ ClientID: config.ClientID, ClientSecret: config.ClientSecret, Endpoint: github.Endpoint, RedirectURL: config.RedirectURL, Scopes: config.Scopes, } return &client{oauthConfig: oauthConfig} } func (g client) GetConsentAuthUrl(ctx context.Context, state string) string { return g.oauthConfig.AuthCodeURL(state, oauth2.AccessTypeOffline) } func (g client) ExchangeCodeWithToken(ctx context.Context, code string) (*types.Token, error) { exchange, err := g.oauthConfig.Exchange(ctx, code, oauth2.AccessTypeOffline) if err != nil { return nil, err } token, err := g.oauthConfig.TokenSource(ctx, exchange).Token() if err != nil { return nil, err } return &types.Token{ AccessToken: token.AccessToken, TokenType: token.TokenType, RefreshToken: token.RefreshToken, ExpiresIn: token.ExpiresIn, }, nil } func (g client) GetUserInfo(ctx context.Context, token, _ string) (types.UserInfo, error) { oauthClient := g.oauthConfig.Client(ctx, &oauth2.Token{AccessToken: token}) resp, err := oauthClient.Get("https://api.github.com/user") if err != nil { return nil, err } defer resp.Body.Close() data, readErr := io.ReadAll(resp.Body) if readErr != nil { return nil, readErr } var user UserInfo if err = json.Unmarshal(data, &user); err != nil { return nil, err } // GitHub /user often returns null for email; fetch from /user/emails (requires user:email scope) if user.GEmail == "" { user.GEmail = g.fetchPrimaryEmail(ctx, oauthClient) } return &user, nil } // fetchPrimaryEmail gets the primary email from GitHub /user/emails (requires user:email scope). func (g client) fetchPrimaryEmail(_ context.Context, oauthClient *http.Client) string { resp, err := oauthClient.Get("https://api.github.com/user/emails") if err != nil { return "" } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return "" } var emails []struct { Email string `json:"email"` Primary bool `json:"primary"` Verified bool `json:"verified"` } if err := json.Unmarshal(data, &emails); err != nil { return "" } for _, e := range emails { if e.Primary && e.Verified { return e.Email } } if len(emails) > 0 { return emails[0].Email } return "" }