fix(coderd): explain default GitHub app org visibility on login rejection (#27374)

## Problem

On a fresh deployment with no custom GitHub OAuth app, Coder falls back
to the default Coder-managed GitHub app. That app can only see
organization memberships in organizations where it has been installed.
If `CODER_OAUTH2_GITHUB_ALLOWED_ORGS` is set but the app isn't installed
in the allowed organizations, the membership list comes back empty and
every login, including the first admin login, is rejected with a bare
"You aren't a member of the authorized Github organizations!" with no
hint about the actual cause. This leaves fresh deployments in an
apparently broken state.

## Fix

* Append a remediation hint to the login rejection when the default
provider is configured, pointing at the [app installation
page](<https://github.com/apps/coder/installations/select_target>) and
at configuring a custom GitHub OAuth app.
* Log a startup warning when the default provider is combined with
`CODER_OAUTH2_GITHUB_ALLOWED_ORGS`, listing the allowed orgs and the
install URL.
* Document the installation requirement next to the
`CODER_OAUTH2_GITHUB_ALLOWED_ORGS` step in the GitHub auth docs.

Access-control behavior is unchanged; the org check still rejects logins
as before, it just explains why and how to fix it.

## Testing

* New `TestUserOAuth2Github/NotInAllowedOrganizationDefaultProvider`
asserts the hint appears when `DefaultProviderConfigured` is set; the
existing `NotInAllowedOrganization` subtest asserts it does not leak
into the custom-app path.

Fixes coder/coder#17752
This commit is contained in:
Atif Ali
2026-07-21 20:43:10 +05:00
committed by GitHub
parent a9a1dcc65d
commit 48e9bb3391
4 changed files with 101 additions and 2 deletions
+27
View File
@@ -772,6 +772,20 @@ type GithubOAuth2Config struct {
DefaultProviderConfigured bool
}
const (
// GithubOAuth2DefaultProviderInstallURL is where admins install the
// default Coder GitHub app so it can see organization and team
// memberships.
GithubOAuth2DefaultProviderInstallURL = "https://github.com/apps/coder/installations/select_target"
// githubOAuth2DefaultProviderRemediation explains why the default GitHub
// app can fail org and team membership checks, and how to fix it. It is
// appended to login rejection messages and mirrored by the server startup
// warning and the GitHub auth docs, so keep those in sync.
githubOAuth2DefaultProviderRemediation = "The default GitHub OAuth provider can only see organizations and teams that have installed the Coder GitHub app. " +
"Install it from " + GithubOAuth2DefaultProviderInstallURL + " for each authorized organization, or configure a custom GitHub OAuth app."
)
func (*GithubOAuth2Config) PKCESupported() []promoauth.Oauth2PKCEChallengeMethod {
return []promoauth.Oauth2PKCEChallengeMethod{promoauth.PKCEChallengeMethodSha256}
}
@@ -931,6 +945,13 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
if len(selectedMemberships) == 0 {
status := http.StatusUnauthorized
msg := "You aren't a member of the authorized Github organizations!"
if api.GithubOAuth2Config.DefaultProviderConfigured {
// The default provider is a GitHub App, so it can only report
// memberships in organizations that have installed it. Without
// this hint, users in an allowed organization see a confusing
// rejection with no way to discover the missing installation.
msg += " " + githubOAuth2DefaultProviderRemediation
}
if api.GithubOAuth2Config.DeviceFlowEnabled {
// In the device flow, the error is rendered client-side.
httpapi.Write(ctx, rw, status, codersdk.Response{
@@ -977,6 +998,12 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
}
if allowedTeam == nil {
msg := fmt.Sprintf("You aren't a member of an authorized team in the %v Github organization(s)!", organizationNames)
if api.GithubOAuth2Config.DefaultProviderConfigured {
// Team visibility has the same limitation as org visibility:
// the default GitHub App cannot see teams in organizations
// where it isn't installed.
msg += " " + githubOAuth2DefaultProviderRemediation
}
status := http.StatusUnauthorized
if api.GithubOAuth2Config.DeviceFlowEnabled {
// In the device flow, the error is rendered client-side.