@@ -677,24 +677,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
677
677
}
678
678
}
679
679
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
-
698
680
// As OIDC clients can be confidential or public,
699
681
// we should only check for a client id being set.
700
682
// The underlying library handles the case of no
@@ -782,6 +764,20 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
782
764
return xerrors .Errorf ("set deployment id: %w" ,err )
783
765
}
784
766
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
+
785
781
options .RuntimeConfig = runtimeconfig .NewManager ()
786
782
787
783
// This should be output before the logs start streaming.
@@ -1832,25 +1828,95 @@ func configureCAPool(tlsClientCAFile string, tlsConfig *tls.Config) error {
1832
1828
return nil
1833
1829
}
1834
1830
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
+
1837
1903
//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" )
1840
1906
if err != nil {
1841
1907
return nil ,xerrors .Errorf ("parse github oauth callback url: %w" ,err )
1842
1908
}
1843
- if allowEveryone && len (allowOrgs )> 0 {
1909
+ if params . allowEveryone && len (params . allowOrgs )> 0 {
1844
1910
return nil ,xerrors .New ("allow everyone and allowed orgs cannot be used together" )
1845
1911
}
1846
- if allowEveryone && len (rawTeams )> 0 {
1912
+ if params . allowEveryone && len (params . rawTeams )> 0 {
1847
1913
return nil ,xerrors .New ("allow everyone and allowed teams cannot be used together" )
1848
1914
}
1849
- if ! allowEveryone && len (allowOrgs )== 0 {
1915
+ if ! params . allowEveryone && len (params . allowOrgs )== 0 {
1850
1916
return nil ,xerrors .New ("allowed orgs is empty: must specify at least one org or allow everyone" )
1851
1917
}
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 {
1854
1920
parts := strings .SplitN (rawTeam ,"/" ,2 )
1855
1921
if len (parts )!= 2 {
1856
1922
return 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
1862
1928
}
1863
1929
1864
1930
endpoint := xgithub .Endpoint
1865
- if enterpriseBaseURL != "" {
1866
- enterpriseURL ,err := url .Parse (enterpriseBaseURL )
1931
+ if params . enterpriseBaseURL != "" {
1932
+ enterpriseURL ,err := url .Parse (params . enterpriseBaseURL )
1867
1933
if err != nil {
1868
1934
return nil ,xerrors .Errorf ("parse enterprise base url: %w" ,err )
1869
1935
}
@@ -1882,8 +1948,8 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
1882
1948
}
1883
1949
1884
1950
instrumentedOauth := instrument .NewGithub ("github-login" ,& oauth2.Config {
1885
- ClientID :clientID ,
1886
- ClientSecret :clientSecret ,
1951
+ ClientID :params . clientID ,
1952
+ ClientSecret :params . clientSecret ,
1887
1953
Endpoint :endpoint ,
1888
1954
RedirectURL :redirectURL .String (),
1889
1955
Scopes : []string {
@@ -1895,17 +1961,17 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
1895
1961
1896
1962
createClient := func (client * http.Client ,source promoauth.Oauth2Source ) (* github.Client ,error ) {
1897
1963
client = instrumentedOauth .InstrumentHTTPClient (client ,source )
1898
- if enterpriseBaseURL != "" {
1899
- return github .NewEnterpriseClient (enterpriseBaseURL ,"" ,client )
1964
+ if params . enterpriseBaseURL != "" {
1965
+ return github .NewEnterpriseClient (params . enterpriseBaseURL ,"" ,client )
1900
1966
}
1901
1967
return github .NewClient (client ),nil
1902
1968
}
1903
1969
1904
1970
var deviceAuth * externalauth.DeviceAuth
1905
- if deviceFlow {
1971
+ if params . deviceFlow {
1906
1972
deviceAuth = & externalauth.DeviceAuth {
1907
1973
Config :instrumentedOauth ,
1908
- ClientID :clientID ,
1974
+ ClientID :params . clientID ,
1909
1975
TokenURL :endpoint .TokenURL ,
1910
1976
Scopes : []string {"read:user" ,"read:org" ,"user:email" },
1911
1977
CodeURL :endpoint .DeviceAuthURL ,
@@ -1914,9 +1980,9 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
1914
1980
1915
1981
return & coderd.GithubOAuth2Config {
1916
1982
OAuth2Config :instrumentedOauth ,
1917
- AllowSignups :allowSignups ,
1918
- AllowEveryone :allowEveryone ,
1919
- AllowOrganizations :allowOrgs ,
1983
+ AllowSignups :params . allowSignups ,
1984
+ AllowEveryone :params . allowEveryone ,
1985
+ AllowOrganizations :params . allowOrgs ,
1920
1986
AllowTeams :allowTeams ,
1921
1987
AuthenticatedUser :func (ctx context.Context ,client * http.Client ) (* github.User ,error ) {
1922
1988
api ,err := createClient (client ,promoauth .SourceGitAPIAuthUser )
@@ -1955,15 +2021,15 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
1955
2021
team ,_ ,err := api .Teams .GetTeamMembershipBySlug (ctx ,org ,teamSlug ,username )
1956
2022
return team ,err
1957
2023
},
1958
- DeviceFlowEnabled :deviceFlow ,
2024
+ DeviceFlowEnabled :params . deviceFlow ,
1959
2025
ExchangeDeviceCode :func (ctx context.Context ,deviceCode string ) (* oauth2.Token ,error ) {
1960
- if ! deviceFlow {
2026
+ if ! params . deviceFlow {
1961
2027
return nil ,xerrors .New ("device flow is not enabled" )
1962
2028
}
1963
2029
return deviceAuth .ExchangeDeviceCode (ctx ,deviceCode )
1964
2030
},
1965
2031
AuthorizeDevice :func (ctx context.Context ) (* codersdk.ExternalAuthDevice ,error ) {
1966
- if ! deviceFlow {
2032
+ if ! params . deviceFlow {
1967
2033
return nil ,xerrors .New ("device flow is not enabled" )
1968
2034
}
1969
2035
return deviceAuth .AuthorizeDevice (ctx )