Skip to content

Commit 5bd2a3f

Browse files
authored
fix: conceal sensitive domain information in auth error messages (#17132)
## Summary - Removes exposure of allowed domain list in OIDC authentication error messages - Replaces detailed error messages with a generic message that doesn't expose internal domains - Adds "Please contact your administrator" to guide users seeking assistance - Addresses security concern where third-party contractors could see internal domain information ## Test plan - Test accessing Coder with an email that doesn't match allowed domains - Verify error message no longer displays the list of authorized domains - Verify message now includes guidance to contact administrator Fixes issue related to domain information exposure during authentication. Linked issue: #17130 🤖 Generated with [Claude Code](https://claude.ai/code)
1 parent 0eec78d commit 5bd2a3f

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

coderd/userauth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,7 +1358,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
13581358
emailSp := strings.Split(email, "@")
13591359
if len(emailSp) == 1 {
13601360
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
1361-
Message: fmt.Sprintf("Your email %q is not in domains %q!", email, api.OIDCConfig.EmailDomain),
1361+
Message: fmt.Sprintf("Your email %q is not from an authorized domain! Please contact your administrator.", email),
13621362
})
13631363
return
13641364
}
@@ -1373,7 +1373,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
13731373
}
13741374
if !ok {
13751375
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
1376-
Message: fmt.Sprintf("Your email %q is not in domains %q!", email, api.OIDCConfig.EmailDomain),
1376+
Message: fmt.Sprintf("Your email %q is not from an authorized domain! Please contact your administrator.", email),
13771377
})
13781378
return
13791379
}

coderd/userauth_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,79 @@ func TestUserLogout(t *testing.T) {
19821982
// - JWT with issuer https://secondary.com
19831983
//
19841984
// Without this security check disabled, all three above would have to match.
1985+
1986+
// TestOIDCDomainErrorMessage ensures that when a user with an unauthorized domain
1987+
// attempts to login, the error message doesn't expose the list of authorized domains.
1988+
func TestOIDCDomainErrorMessage(t *testing.T) {
1989+
t.Parallel()
1990+
1991+
fake := oidctest.NewFakeIDP(t, oidctest.WithServing())
1992+
1993+
allowedDomains := []string{"allowed1.com", "allowed2.org", "company.internal"}
1994+
cfg := fake.OIDCConfig(t, nil, func(cfg *coderd.OIDCConfig) {
1995+
cfg.EmailDomain = allowedDomains
1996+
cfg.AllowSignups = true
1997+
})
1998+
1999+
server := coderdtest.New(t, &coderdtest.Options{
2000+
OIDCConfig: cfg,
2001+
})
2002+
2003+
// Test case 1: Email domain not in allowed list
2004+
t.Run("ErrorMessageOmitsDomains", func(t *testing.T) {
2005+
t.Parallel()
2006+
2007+
// Prepare claims with email from unauthorized domain
2008+
claims := jwt.MapClaims{
2009+
"email": "user@unauthorized.com",
2010+
"email_verified": true,
2011+
"sub": uuid.NewString(),
2012+
}
2013+
2014+
_, resp := fake.AttemptLogin(t, server, claims)
2015+
defer resp.Body.Close()
2016+
2017+
require.Equal(t, http.StatusForbidden, resp.StatusCode)
2018+
2019+
data, err := io.ReadAll(resp.Body)
2020+
require.NoError(t, err)
2021+
2022+
require.Contains(t, string(data), "is not from an authorized domain")
2023+
require.Contains(t, string(data), "Please contact your administrator")
2024+
2025+
for _, domain := range allowedDomains {
2026+
require.NotContains(t, string(data), domain)
2027+
}
2028+
})
2029+
2030+
// Test case 2: Malformed email without @ symbol
2031+
t.Run("MalformedEmailErrorOmitsDomains", func(t *testing.T) {
2032+
t.Parallel()
2033+
2034+
// Prepare claims with an invalid email format (no @ symbol)
2035+
claims := jwt.MapClaims{
2036+
"email": "invalid-email-without-domain",
2037+
"email_verified": true,
2038+
"sub": uuid.NewString(),
2039+
}
2040+
2041+
_, resp := fake.AttemptLogin(t, server, claims)
2042+
defer resp.Body.Close()
2043+
2044+
require.Equal(t, http.StatusForbidden, resp.StatusCode)
2045+
2046+
data, err := io.ReadAll(resp.Body)
2047+
require.NoError(t, err)
2048+
2049+
require.Contains(t, string(data), "is not from an authorized domain")
2050+
require.Contains(t, string(data), "Please contact your administrator")
2051+
2052+
for _, domain := range allowedDomains {
2053+
require.NotContains(t, string(data), domain)
2054+
}
2055+
})
2056+
}
2057+
19852058
func TestOIDCSkipIssuer(t *testing.T) {
19862059
t.Parallel()
19872060
const primaryURLString = "https://primary.com"

0 commit comments

Comments
 (0)