package github import ( "strconv" "time" "golang.org/x/oauth2" "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/idp" "github.com/zitadel/zitadel/internal/idp/providers/oauth" ) const ( authURL = "https://github.com/login/oauth/authorize" tokenURL = "https://github.com/login/oauth/access_token" profileURL = "https://api.github.com/user" name = "GitHub" ) var _ idp.Provider = (*Provider)(nil) // New creates a GitHub.com provider using the [oauth.Provider] (OAuth 2.0 generic provider) func New(clientID, secret, callbackURL string, scopes []string, options ...oauth.ProviderOpts) (*Provider, error) { return NewCustomURL(name, clientID, secret, callbackURL, authURL, tokenURL, profileURL, scopes, options...) } // NewCustomURL creates a GitHub provider using the [oauth.Provider] (OAuth 2.0 generic provider) // with custom endpoints, e.g. GitHub Enterprise server func NewCustomURL(name, clientID, secret, callbackURL, authURL, tokenURL, profileURL string, scopes []string, options ...oauth.ProviderOpts) (*Provider, error) { rp, err := oauth.New( newConfig(clientID, secret, callbackURL, authURL, tokenURL, scopes), name, profileURL, func() idp.User { return new(User) }, options..., ) if err != nil { return nil, err } return &Provider{ Provider: rp, }, nil } // Provider is the [idp.Provider] implementation for GitHub type Provider struct { *oauth.Provider } func newConfig(clientID, secret, callbackURL, authURL, tokenURL string, scopes []string) *oauth2.Config { c := &oauth2.Config{ ClientID: clientID, ClientSecret: secret, RedirectURL: callbackURL, Endpoint: oauth2.Endpoint{ AuthURL: authURL, TokenURL: tokenURL, }, Scopes: scopes, } return c } // User is a representation of the authenticated GitHub user and implements the [idp.User] interface // https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user type User struct { Login string `json:"login"` ID int `json:"id"` NodeId string `json:"node_id"` AvatarUrl string `json:"avatar_url"` GravatarId string `json:"gravatar_id"` Url string `json:"url"` HtmlUrl string `json:"html_url"` FollowersUrl string `json:"followers_url"` FollowingUrl string `json:"following_url"` GistsUrl string `json:"gists_url"` StarredUrl string `json:"starred_url"` SubscriptionsUrl string `json:"subscriptions_url"` OrganizationsUrl string `json:"organizations_url"` ReposUrl string `json:"repos_url"` EventsUrl string `json:"events_url"` ReceivedEventsUrl string `json:"received_events_url"` Type string `json:"type"` SiteAdmin bool `json:"site_admin"` Name string `json:"name"` Company string `json:"company"` Blog string `json:"blog"` Location string `json:"location"` Email domain.EmailAddress `json:"email"` Hireable bool `json:"hireable"` Bio string `json:"bio"` TwitterUsername string `json:"twitter_username"` PublicRepos int `json:"public_repos"` PublicGists int `json:"public_gists"` Followers int `json:"followers"` Following int `json:"following"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` PrivateGists int `json:"private_gists"` TotalPrivateRepos int `json:"total_private_repos"` OwnedPrivateRepos int `json:"owned_private_repos"` DiskUsage int `json:"disk_usage"` Collaborators int `json:"collaborators"` TwoFactorAuthentication bool `json:"two_factor_authentication"` Plan struct { Name string `json:"name"` Space int `json:"space"` PrivateRepos int `json:"private_repos"` Collaborators int `json:"collaborators"` } `json:"plan"` } // GetID is an implementation of the [idp.User] interface. func (u *User) GetID() string { return strconv.Itoa(u.ID) } // GetFirstName is an implementation of the [idp.User] interface. // It returns an empty string because GitHub does not provide the user's firstname. func (u *User) GetFirstName() string { return "" } // GetLastName is an implementation of the [idp.User] interface. // It returns an empty string because GitHub does not provide the user's lastname. func (u *User) GetLastName() string { // GitHub does not provide the user's lastname return "" } // GetDisplayName is an implementation of the [idp.User] interface. func (u *User) GetDisplayName() string { return u.Name } // GetNickname is an implementation of the [idp.User] interface // returning the login name of the GitHub user. func (u *User) GetNickname() string { return u.Login } // GetPreferredUsername is an implementation of the [idp.User] interface // returning the login name of the GitHub user. func (u *User) GetPreferredUsername() string { return u.Login } // GetEmail is an implementation of the [idp.User] interface. func (u *User) GetEmail() domain.EmailAddress { return u.Email } // IsEmailVerified is an implementation of the [idp.User] interface. // It returns true because GitHub validates emails themselves. func (u *User) IsEmailVerified() bool { return true } // GetPhone is an implementation of the [idp.User] interface. // It returns an empty string because GitHub does not provide the user's phone. func (u *User) GetPhone() domain.PhoneNumber { return "" } // IsPhoneVerified is an implementation of the [idp.User] interface // it returns false because GitHub does not provide the user's phone func (u *User) IsPhoneVerified() bool { return false } // GetPreferredLanguage is an implementation of the [idp.User] interface. // It returns [language.Und] because GitHub does not provide the user's language. func (u *User) GetPreferredLanguage() language.Tag { return language.Und } // GetProfile is an implementation of the [idp.User] interface. func (u *User) GetProfile() string { return u.HtmlUrl } // GetAvatarURL is an implementation of the [idp.User] interface. func (u *User) GetAvatarURL() string { return u.AvatarUrl }