@@ -688,24 +688,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
688688 }
689689 }
690690
691- if vals .OAuth2 .Github .ClientSecret != "" || vals .OAuth2 .Github .DeviceFlow .Value () {
692- options .GithubOAuth2Config , err = configureGithubOAuth2 (
693- oauthInstrument ,
694- vals .AccessURL .Value (),
695- vals .OAuth2 .Github .ClientID .String (),
696- vals .OAuth2 .Github .ClientSecret .String (),
697- vals .OAuth2 .Github .DeviceFlow .Value (),
698- vals .OAuth2 .Github .AllowSignups .Value (),
699- vals .OAuth2 .Github .AllowEveryone .Value (),
700- vals .OAuth2 .Github .AllowedOrgs ,
701- vals .OAuth2 .Github .AllowedTeams ,
702- vals .OAuth2 .Github .EnterpriseBaseURL .String (),
703- )
704- if err != nil {
705- return xerrors .Errorf ("configure github oauth2: %w" , err )
706- }
707- }
708-
709691 // As OIDC clients can be confidential or public,
710692 // we should only check for a client id being set.
711693 // The underlying library handles the case of no
@@ -793,6 +775,20 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
793775 return xerrors .Errorf ("set deployment id: %w" , err )
794776 }
795777
778+ githubOAuth2ConfigParams , err := getGithubOAuth2ConfigParams (ctx , options .Database , vals )
779+ if err != nil {
780+ return xerrors .Errorf ("get github oauth2 config params: %w" , err )
781+ }
782+ if githubOAuth2ConfigParams != nil {
783+ options .GithubOAuth2Config , err = configureGithubOAuth2 (
784+ oauthInstrument ,
785+ githubOAuth2ConfigParams ,
786+ )
787+ if err != nil {
788+ return xerrors .Errorf ("configure github oauth2: %w" , err )
789+ }
790+ }
791+
796792 options .RuntimeConfig = runtimeconfig .NewManager ()
797793
798794 // This should be output before the logs start streaming.
@@ -1843,25 +1839,101 @@ func configureCAPool(tlsClientCAFile string, tlsConfig *tls.Config) error {
18431839 return nil
18441840}
18451841
1846- // TODO: convert the argument list to a struct, it's easy to mix up the order of the arguments
1847- //
1842+ const (
1843+ // Client ID for https://github.com/apps/coder
1844+ GithubOAuth2DefaultProviderClientID = "Iv1.6a2b4b4aec4f4fe7"
1845+ GithubOAuth2DefaultProviderAllowEveryone = true
1846+ GithubOAuth2DefaultProviderDeviceFlow = true
1847+ )
1848+
1849+ type githubOAuth2ConfigParams struct {
1850+ accessURL * url.URL
1851+ clientID string
1852+ clientSecret string
1853+ deviceFlow bool
1854+ allowSignups bool
1855+ allowEveryone bool
1856+ allowOrgs []string
1857+ rawTeams []string
1858+ enterpriseBaseURL string
1859+ }
1860+
1861+ func getGithubOAuth2ConfigParams (ctx context.Context , db database.Store , vals * codersdk.DeploymentValues ) (* githubOAuth2ConfigParams , error ) {
1862+ params := githubOAuth2ConfigParams {
1863+ accessURL : vals .AccessURL .Value (),
1864+ clientID : vals .OAuth2 .Github .ClientID .String (),
1865+ clientSecret : vals .OAuth2 .Github .ClientSecret .String (),
1866+ deviceFlow : vals .OAuth2 .Github .DeviceFlow .Value (),
1867+ allowSignups : vals .OAuth2 .Github .AllowSignups .Value (),
1868+ allowEveryone : vals .OAuth2 .Github .AllowEveryone .Value (),
1869+ allowOrgs : vals .OAuth2 .Github .AllowedOrgs .Value (),
1870+ rawTeams : vals .OAuth2 .Github .AllowedTeams .Value (),
1871+ enterpriseBaseURL : vals .OAuth2 .Github .EnterpriseBaseURL .String (),
1872+ }
1873+
1874+ // If the user manually configured the GitHub OAuth2 provider,
1875+ // we won't add the default configuration.
1876+ if params .clientID != "" || params .clientSecret != "" || params .enterpriseBaseURL != "" {
1877+ return & params , nil
1878+ }
1879+
1880+ // Check if the user manually disabled the default GitHub OAuth2 provider.
1881+ if ! vals .OAuth2 .Github .DefaultProviderEnable .Value () {
1882+ return nil , nil //nolint:nilnil
1883+ }
1884+
1885+ // Check if the deployment is eligible for the default GitHub OAuth2 provider.
1886+ // We want to enable it only for new deployments, and avoid enabling it
1887+ // if a deployment was upgraded from an older version.
1888+ // nolint:gocritic // Requires system privileges
1889+ defaultEligible , err := db .GetOAuth2GithubDefaultEligible (dbauthz .AsSystemRestricted (ctx ))
1890+ if err != nil && ! errors .Is (err , sql .ErrNoRows ) {
1891+ return nil , xerrors .Errorf ("get github default eligible: %w" , err )
1892+ }
1893+ defaultEligibleNotSet := errors .Is (err , sql .ErrNoRows )
1894+
1895+ if defaultEligibleNotSet {
1896+ // nolint:gocritic // User count requires system privileges
1897+ userCount , err := db .GetUserCount (dbauthz .AsSystemRestricted (ctx ))
1898+ if err != nil {
1899+ return nil , xerrors .Errorf ("get user count: %w" , err )
1900+ }
1901+ // We check if a deployment is new by checking if it has any users.
1902+ defaultEligible = userCount == 0
1903+ // nolint:gocritic // Requires system privileges
1904+ if err := db .UpsertOAuth2GithubDefaultEligible (dbauthz .AsSystemRestricted (ctx ), defaultEligible ); err != nil {
1905+ return nil , xerrors .Errorf ("upsert github default eligible: %w" , err )
1906+ }
1907+ }
1908+
1909+ if ! defaultEligible {
1910+ return nil , nil //nolint:nilnil
1911+ }
1912+
1913+ params .clientID = GithubOAuth2DefaultProviderClientID
1914+ params .allowEveryone = GithubOAuth2DefaultProviderAllowEveryone
1915+ params .deviceFlow = GithubOAuth2DefaultProviderDeviceFlow
1916+
1917+ return & params , nil
1918+ }
1919+
18481920//nolint:revive // Ignore flag-parameter: parameter 'allowEveryone' seems to be a control flag, avoid control coupling (revive)
1849- func configureGithubOAuth2 (instrument * promoauth.Factory , accessURL * url. URL , clientID , clientSecret string , deviceFlow , allowSignups , allowEveryone bool , allowOrgs [] string , rawTeams [] string , enterpriseBaseURL string ) (* coderd.GithubOAuth2Config , error ) {
1850- redirectURL , err := accessURL .Parse ("/api/v2/users/oauth2/github/callback" )
1921+ func configureGithubOAuth2 (instrument * promoauth.Factory , params * githubOAuth2ConfigParams ) (* coderd.GithubOAuth2Config , error ) {
1922+ redirectURL , err := params . accessURL .Parse ("/api/v2/users/oauth2/github/callback" )
18511923 if err != nil {
18521924 return nil , xerrors .Errorf ("parse github oauth callback url: %w" , err )
18531925 }
1854- if allowEveryone && len (allowOrgs ) > 0 {
1926+ if params . allowEveryone && len (params . allowOrgs ) > 0 {
18551927 return nil , xerrors .New ("allow everyone and allowed orgs cannot be used together" )
18561928 }
1857- if allowEveryone && len (rawTeams ) > 0 {
1929+ if params . allowEveryone && len (params . rawTeams ) > 0 {
18581930 return nil , xerrors .New ("allow everyone and allowed teams cannot be used together" )
18591931 }
1860- if ! allowEveryone && len (allowOrgs ) == 0 {
1932+ if ! params . allowEveryone && len (params . allowOrgs ) == 0 {
18611933 return nil , xerrors .New ("allowed orgs is empty: must specify at least one org or allow everyone" )
18621934 }
1863- allowTeams := make ([]coderd.GithubOAuth2Team , 0 , len (rawTeams ))
1864- for _ , rawTeam := range rawTeams {
1935+ allowTeams := make ([]coderd.GithubOAuth2Team , 0 , len (params . rawTeams ))
1936+ for _ , rawTeam := range params . rawTeams {
18651937 parts := strings .SplitN (rawTeam , "/" , 2 )
18661938 if len (parts ) != 2 {
18671939 return nil , xerrors .Errorf ("github team allowlist is formatted incorrectly. got %s; wanted <organization>/<team>" , rawTeam )
@@ -1873,8 +1945,8 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
18731945 }
18741946
18751947 endpoint := xgithub .Endpoint
1876- if enterpriseBaseURL != "" {
1877- enterpriseURL , err := url .Parse (enterpriseBaseURL )
1948+ if params . enterpriseBaseURL != "" {
1949+ enterpriseURL , err := url .Parse (params . enterpriseBaseURL )
18781950 if err != nil {
18791951 return nil , xerrors .Errorf ("parse enterprise base url: %w" , err )
18801952 }
@@ -1893,8 +1965,8 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
18931965 }
18941966
18951967 instrumentedOauth := instrument .NewGithub ("github-login" , & oauth2.Config {
1896- ClientID : clientID ,
1897- ClientSecret : clientSecret ,
1968+ ClientID : params . clientID ,
1969+ ClientSecret : params . clientSecret ,
18981970 Endpoint : endpoint ,
18991971 RedirectURL : redirectURL .String (),
19001972 Scopes : []string {
@@ -1906,17 +1978,17 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19061978
19071979 createClient := func (client * http.Client , source promoauth.Oauth2Source ) (* github.Client , error ) {
19081980 client = instrumentedOauth .InstrumentHTTPClient (client , source )
1909- if enterpriseBaseURL != "" {
1910- return github .NewEnterpriseClient (enterpriseBaseURL , "" , client )
1981+ if params . enterpriseBaseURL != "" {
1982+ return github .NewEnterpriseClient (params . enterpriseBaseURL , "" , client )
19111983 }
19121984 return github .NewClient (client ), nil
19131985 }
19141986
19151987 var deviceAuth * externalauth.DeviceAuth
1916- if deviceFlow {
1988+ if params . deviceFlow {
19171989 deviceAuth = & externalauth.DeviceAuth {
19181990 Config : instrumentedOauth ,
1919- ClientID : clientID ,
1991+ ClientID : params . clientID ,
19201992 TokenURL : endpoint .TokenURL ,
19211993 Scopes : []string {"read:user" , "read:org" , "user:email" },
19221994 CodeURL : endpoint .DeviceAuthURL ,
@@ -1925,9 +1997,9 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19251997
19261998 return & coderd.GithubOAuth2Config {
19271999 OAuth2Config : instrumentedOauth ,
1928- AllowSignups : allowSignups ,
1929- AllowEveryone : allowEveryone ,
1930- AllowOrganizations : allowOrgs ,
2000+ AllowSignups : params . allowSignups ,
2001+ AllowEveryone : params . allowEveryone ,
2002+ AllowOrganizations : params . allowOrgs ,
19312003 AllowTeams : allowTeams ,
19322004 AuthenticatedUser : func (ctx context.Context , client * http.Client ) (* github.User , error ) {
19332005 api , err := createClient (client , promoauth .SourceGitAPIAuthUser )
@@ -1966,19 +2038,20 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19662038 team , _ , err := api .Teams .GetTeamMembershipBySlug (ctx , org , teamSlug , username )
19672039 return team , err
19682040 },
1969- DeviceFlowEnabled : deviceFlow ,
2041+ DeviceFlowEnabled : params . deviceFlow ,
19702042 ExchangeDeviceCode : func (ctx context.Context , deviceCode string ) (* oauth2.Token , error ) {
1971- if ! deviceFlow {
2043+ if ! params . deviceFlow {
19722044 return nil , xerrors .New ("device flow is not enabled" )
19732045 }
19742046 return deviceAuth .ExchangeDeviceCode (ctx , deviceCode )
19752047 },
19762048 AuthorizeDevice : func (ctx context.Context ) (* codersdk.ExternalAuthDevice , error ) {
1977- if ! deviceFlow {
2049+ if ! params . deviceFlow {
19782050 return nil , xerrors .New ("device flow is not enabled" )
19792051 }
19802052 return deviceAuth .AuthorizeDevice (ctx )
19812053 },
2054+ DefaultProviderConfigured : params .clientID == GithubOAuth2DefaultProviderClientID ,
19822055 }, nil
19832056}
19842057
0 commit comments