@@ -677,24 +677,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
677677}
678678}
679679
680- if vals .OAuth2 .Github .ClientSecret != "" || vals .OAuth2 .Github .DeviceFlow .Value () {
681- options .GithubOAuth2Config ,err = configureGithubOAuth2 (
682- oauthInstrument ,
683- vals .AccessURL .Value (),
684- vals .OAuth2 .Github .ClientID .String (),
685- vals .OAuth2 .Github .ClientSecret .String (),
686- vals .OAuth2 .Github .DeviceFlow .Value (),
687- vals .OAuth2 .Github .AllowSignups .Value (),
688- vals .OAuth2 .Github .AllowEveryone .Value (),
689- vals .OAuth2 .Github .AllowedOrgs ,
690- vals .OAuth2 .Github .AllowedTeams ,
691- vals .OAuth2 .Github .EnterpriseBaseURL .String (),
692- )
693- if err != nil {
694- return xerrors .Errorf ("configure github oauth2: %w" ,err )
695- }
696- }
697-
698680// As OIDC clients can be confidential or public,
699681// we should only check for a client id being set.
700682// The underlying library handles the case of no
@@ -782,6 +764,20 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
782764return xerrors .Errorf ("set deployment id: %w" ,err )
783765}
784766
767+ githubOAuth2ConfigParams ,err := getGithubOAuth2ConfigParams (ctx ,options .Database ,vals )
768+ if err != nil {
769+ return xerrors .Errorf ("get github oauth2 config params: %w" ,err )
770+ }
771+ if githubOAuth2ConfigParams != nil {
772+ options .GithubOAuth2Config ,err = configureGithubOAuth2 (
773+ oauthInstrument ,
774+ githubOAuth2ConfigParams ,
775+ )
776+ if err != nil {
777+ return xerrors .Errorf ("configure github oauth2: %w" ,err )
778+ }
779+ }
780+
785781options .RuntimeConfig = runtimeconfig .NewManager ()
786782
787783// This should be output before the logs start streaming.
@@ -1832,25 +1828,95 @@ func configureCAPool(tlsClientCAFile string, tlsConfig *tls.Config) error {
18321828return nil
18331829}
18341830
1835- // TODO: convert the argument list to a struct, it's easy to mix up the order of the arguments
1836- //
1831+ type githubOAuth2ConfigParams struct {
1832+ accessURL * url.URL
1833+ clientID string
1834+ clientSecret string
1835+ deviceFlow bool
1836+ allowSignups bool
1837+ allowEveryone bool
1838+ allowOrgs []string
1839+ rawTeams []string
1840+ enterpriseBaseURL string
1841+ }
1842+
1843+ func getGithubOAuth2ConfigParams (ctx context.Context ,db database.Store ,vals * codersdk.DeploymentValues ) (* githubOAuth2ConfigParams ,error ) {
1844+ params := githubOAuth2ConfigParams {
1845+ accessURL :vals .AccessURL .Value (),
1846+ clientID :vals .OAuth2 .Github .ClientID .String (),
1847+ clientSecret :vals .OAuth2 .Github .ClientSecret .String (),
1848+ deviceFlow :vals .OAuth2 .Github .DeviceFlow .Value (),
1849+ allowSignups :vals .OAuth2 .Github .AllowSignups .Value (),
1850+ allowEveryone :vals .OAuth2 .Github .AllowEveryone .Value (),
1851+ allowOrgs :vals .OAuth2 .Github .AllowedOrgs .Value (),
1852+ rawTeams :vals .OAuth2 .Github .AllowedTeams .Value (),
1853+ enterpriseBaseURL :vals .OAuth2 .Github .EnterpriseBaseURL .String (),
1854+ }
1855+
1856+ // If the user manually configured the GitHub OAuth2 provider,
1857+ // we won't add the default configuration.
1858+ if params .clientID != "" || params .clientSecret != "" || params .enterpriseBaseURL != "" {
1859+ return & params ,nil
1860+ }
1861+
1862+ // Check if the user manually disabled the default GitHub OAuth2 provider.
1863+ if ! vals .OAuth2 .Github .DefaultProvider .Value () {
1864+ return nil ,nil //nolint:nilnil
1865+ }
1866+
1867+ // Check if the deployment is eligible for the default GitHub OAuth2 provider.
1868+ // We want to enable it only for new deployments, and avoid enabling it
1869+ // if a deployment was upgraded from an older version.
1870+ // nolint:gocritic // Requires system privileges
1871+ defaultEligible ,err := db .GetOAuth2GithubDefaultEligible (dbauthz .AsSystemRestricted (ctx ))
1872+ if err != nil && ! errors .Is (err ,sql .ErrNoRows ) {
1873+ return nil ,xerrors .Errorf ("get github default eligible: %w" ,err )
1874+ }
1875+ defaultEligibleNotSet := errors .Is (err ,sql .ErrNoRows )
1876+
1877+ if defaultEligibleNotSet {
1878+ // nolint:gocritic // User count requires system privileges
1879+ userCount ,err := db .GetUserCount (dbauthz .AsSystemRestricted (ctx ))
1880+ if err != nil {
1881+ return nil ,xerrors .Errorf ("get user count: %w" ,err )
1882+ }
1883+ // We check if a deployment is new by checking if it has any users.
1884+ defaultEligible = userCount == 0
1885+ // nolint:gocritic // Requires system privileges
1886+ if err := db .UpsertOAuth2GithubDefaultEligible (dbauthz .AsSystemRestricted (ctx ),defaultEligible );err != nil {
1887+ return nil ,xerrors .Errorf ("upsert github default eligible: %w" ,err )
1888+ }
1889+ }
1890+
1891+ if ! defaultEligible {
1892+ return nil ,nil //nolint:nilnil
1893+ }
1894+
1895+ // TODO: before merging, change this to use Coder's client ID instead of my dev one.
1896+ params .clientID = "Iv23liSBHklRMBNx5lk9"
1897+ params .allowEveryone = true
1898+ params .deviceFlow = true
1899+
1900+ return & params ,nil
1901+ }
1902+
18371903//nolint:revive // Ignore flag-parameter: parameter 'allowEveryone' seems to be a control flag, avoid control coupling (revive)
1838- func configureGithubOAuth2 (instrument * promoauth.Factory ,accessURL * url. URL , clientID , clientSecret string , deviceFlow , allowSignups , allowEveryone bool , allowOrgs [] string , rawTeams [] string , enterpriseBaseURL string ) (* coderd.GithubOAuth2Config ,error ) {
1839- redirectURL ,err := accessURL .Parse ("/api/v2/users/oauth2/github/callback" )
1904+ func configureGithubOAuth2 (instrument * promoauth.Factory ,params * githubOAuth2ConfigParams ) (* coderd.GithubOAuth2Config ,error ) {
1905+ redirectURL ,err := params . accessURL .Parse ("/api/v2/users/oauth2/github/callback" )
18401906if err != nil {
18411907return nil ,xerrors .Errorf ("parse github oauth callback url: %w" ,err )
18421908}
1843- if allowEveryone && len (allowOrgs )> 0 {
1909+ if params . allowEveryone && len (params . allowOrgs )> 0 {
18441910return nil ,xerrors .New ("allow everyone and allowed orgs cannot be used together" )
18451911}
1846- if allowEveryone && len (rawTeams )> 0 {
1912+ if params . allowEveryone && len (params . rawTeams )> 0 {
18471913return nil ,xerrors .New ("allow everyone and allowed teams cannot be used together" )
18481914}
1849- if ! allowEveryone && len (allowOrgs )== 0 {
1915+ if ! params . allowEveryone && len (params . allowOrgs )== 0 {
18501916return nil ,xerrors .New ("allowed orgs is empty: must specify at least one org or allow everyone" )
18511917}
1852- allowTeams := make ([]coderd.GithubOAuth2Team ,0 ,len (rawTeams ))
1853- for _ ,rawTeam := range rawTeams {
1918+ allowTeams := make ([]coderd.GithubOAuth2Team ,0 ,len (params . rawTeams ))
1919+ for _ ,rawTeam := range params . rawTeams {
18541920parts := strings .SplitN (rawTeam ,"/" ,2 )
18551921if len (parts )!= 2 {
18561922return nil ,xerrors .Errorf ("github team allowlist is formatted incorrectly. got %s; wanted <organization>/<team>" ,rawTeam )
@@ -1862,8 +1928,8 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
18621928}
18631929
18641930endpoint := xgithub .Endpoint
1865- if enterpriseBaseURL != "" {
1866- enterpriseURL ,err := url .Parse (enterpriseBaseURL )
1931+ if params . enterpriseBaseURL != "" {
1932+ enterpriseURL ,err := url .Parse (params . enterpriseBaseURL )
18671933if err != nil {
18681934return nil ,xerrors .Errorf ("parse enterprise base url: %w" ,err )
18691935}
@@ -1882,8 +1948,8 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
18821948}
18831949
18841950instrumentedOauth := instrument .NewGithub ("github-login" ,& oauth2.Config {
1885- ClientID :clientID ,
1886- ClientSecret :clientSecret ,
1951+ ClientID :params . clientID ,
1952+ ClientSecret :params . clientSecret ,
18871953Endpoint :endpoint ,
18881954RedirectURL :redirectURL .String (),
18891955Scopes : []string {
@@ -1895,17 +1961,17 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
18951961
18961962createClient := func (client * http.Client ,source promoauth.Oauth2Source ) (* github.Client ,error ) {
18971963client = instrumentedOauth .InstrumentHTTPClient (client ,source )
1898- if enterpriseBaseURL != "" {
1899- return github .NewEnterpriseClient (enterpriseBaseURL ,"" ,client )
1964+ if params . enterpriseBaseURL != "" {
1965+ return github .NewEnterpriseClient (params . enterpriseBaseURL ,"" ,client )
19001966}
19011967return github .NewClient (client ),nil
19021968}
19031969
19041970var deviceAuth * externalauth.DeviceAuth
1905- if deviceFlow {
1971+ if params . deviceFlow {
19061972deviceAuth = & externalauth.DeviceAuth {
19071973Config :instrumentedOauth ,
1908- ClientID :clientID ,
1974+ ClientID :params . clientID ,
19091975TokenURL :endpoint .TokenURL ,
19101976Scopes : []string {"read:user" ,"read:org" ,"user:email" },
19111977CodeURL :endpoint .DeviceAuthURL ,
@@ -1914,9 +1980,9 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19141980
19151981return & coderd.GithubOAuth2Config {
19161982OAuth2Config :instrumentedOauth ,
1917- AllowSignups :allowSignups ,
1918- AllowEveryone :allowEveryone ,
1919- AllowOrganizations :allowOrgs ,
1983+ AllowSignups :params . allowSignups ,
1984+ AllowEveryone :params . allowEveryone ,
1985+ AllowOrganizations :params . allowOrgs ,
19201986AllowTeams :allowTeams ,
19211987AuthenticatedUser :func (ctx context.Context ,client * http.Client ) (* github.User ,error ) {
19221988api ,err := createClient (client ,promoauth .SourceGitAPIAuthUser )
@@ -1955,15 +2021,15 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19552021team ,_ ,err := api .Teams .GetTeamMembershipBySlug (ctx ,org ,teamSlug ,username )
19562022return team ,err
19572023},
1958- DeviceFlowEnabled :deviceFlow ,
2024+ DeviceFlowEnabled :params . deviceFlow ,
19592025ExchangeDeviceCode :func (ctx context.Context ,deviceCode string ) (* oauth2.Token ,error ) {
1960- if ! deviceFlow {
2026+ if ! params . deviceFlow {
19612027return nil ,xerrors .New ("device flow is not enabled" )
19622028}
19632029return deviceAuth .ExchangeDeviceCode (ctx ,deviceCode )
19642030},
19652031AuthorizeDevice :func (ctx context.Context ) (* codersdk.ExternalAuthDevice ,error ) {
1966- if ! deviceFlow {
2032+ if ! params . deviceFlow {
19672033return nil ,xerrors .New ("device flow is not enabled" )
19682034}
19692035return deviceAuth .AuthorizeDevice (ctx )