@@ -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.
793775return 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+
796792options .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 {
18431839return 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" )
18511923if err != nil {
18521924return 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 {
18551927return 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 {
18581930return 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 {
18611933return 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 {
18651937parts := strings .SplitN (rawTeam ,"/" ,2 )
18661938if len (parts )!= 2 {
18671939return 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
18751947endpoint := xgithub .Endpoint
1876- if enterpriseBaseURL != "" {
1877- enterpriseURL ,err := url .Parse (enterpriseBaseURL )
1948+ if params . enterpriseBaseURL != "" {
1949+ enterpriseURL ,err := url .Parse (params . enterpriseBaseURL )
18781950if err != nil {
18791951return 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
18951967instrumentedOauth := instrument .NewGithub ("github-login" ,& oauth2.Config {
1896- ClientID :clientID ,
1897- ClientSecret :clientSecret ,
1968+ ClientID :params . clientID ,
1969+ ClientSecret :params . clientSecret ,
18981970Endpoint :endpoint ,
18991971RedirectURL :redirectURL .String (),
19001972Scopes : []string {
@@ -1906,17 +1978,17 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19061978
19071979createClient := func (client * http.Client ,source promoauth.Oauth2Source ) (* github.Client ,error ) {
19081980client = 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}
19121984return github .NewClient (client ),nil
19131985}
19141986
19151987var deviceAuth * externalauth.DeviceAuth
1916- if deviceFlow {
1988+ if params . deviceFlow {
19171989deviceAuth = & externalauth.DeviceAuth {
19181990Config :instrumentedOauth ,
1919- ClientID :clientID ,
1991+ ClientID :params . clientID ,
19201992TokenURL :endpoint .TokenURL ,
19211993Scopes : []string {"read:user" ,"read:org" ,"user:email" },
19221994CodeURL :endpoint .DeviceAuthURL ,
@@ -1925,9 +1997,9 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19251997
19261998return & coderd.GithubOAuth2Config {
19271999OAuth2Config :instrumentedOauth ,
1928- AllowSignups :allowSignups ,
1929- AllowEveryone :allowEveryone ,
1930- AllowOrganizations :allowOrgs ,
2000+ AllowSignups :params . allowSignups ,
2001+ AllowEveryone :params . allowEveryone ,
2002+ AllowOrganizations :params . allowOrgs ,
19312003AllowTeams :allowTeams ,
19322004AuthenticatedUser :func (ctx context.Context ,client * http.Client ) (* github.User ,error ) {
19332005api ,err := createClient (client ,promoauth .SourceGitAPIAuthUser )
@@ -1966,19 +2038,20 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19662038team ,_ ,err := api .Teams .GetTeamMembershipBySlug (ctx ,org ,teamSlug ,username )
19672039return team ,err
19682040},
1969- DeviceFlowEnabled :deviceFlow ,
2041+ DeviceFlowEnabled :params . deviceFlow ,
19702042ExchangeDeviceCode :func (ctx context.Context ,deviceCode string ) (* oauth2.Token ,error ) {
1971- if ! deviceFlow {
2043+ if ! params . deviceFlow {
19722044return nil ,xerrors .New ("device flow is not enabled" )
19732045}
19742046return deviceAuth .ExchangeDeviceCode (ctx ,deviceCode )
19752047},
19762048AuthorizeDevice :func (ctx context.Context ) (* codersdk.ExternalAuthDevice ,error ) {
1977- if ! deviceFlow {
2049+ if ! params . deviceFlow {
19782050return nil ,xerrors .New ("device flow is not enabled" )
19792051}
19802052return deviceAuth .AuthorizeDevice (ctx )
19812053},
2054+ DefaultProviderConfigured :params .clientID == GithubOAuth2DefaultProviderClientID ,
19822055},nil
19832056}
19842057