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:
https://github.com/coder/coder/issues/17130

🤖 Generated with [Claude Code](https://claude.ai/code)
This commit is contained in:
Eric Paulsen
2025-03-27 13:41:01 +00:00
committed by GitHub
parent 0eec78d714
commit 5bd2a3f190
2 changed files with 75 additions and 2 deletions

View File

@ -1982,6 +1982,79 @@ func TestUserLogout(t *testing.T) {
// - JWT with issuer https://secondary.com
//
// Without this security check disabled, all three above would have to match.
// TestOIDCDomainErrorMessage ensures that when a user with an unauthorized domain
// attempts to login, the error message doesn't expose the list of authorized domains.
func TestOIDCDomainErrorMessage(t *testing.T) {
t.Parallel()
fake := oidctest.NewFakeIDP(t, oidctest.WithServing())
allowedDomains := []string{"allowed1.com", "allowed2.org", "company.internal"}
cfg := fake.OIDCConfig(t, nil, func(cfg *coderd.OIDCConfig) {
cfg.EmailDomain = allowedDomains
cfg.AllowSignups = true
})
server := coderdtest.New(t, &coderdtest.Options{
OIDCConfig: cfg,
})
// Test case 1: Email domain not in allowed list
t.Run("ErrorMessageOmitsDomains", func(t *testing.T) {
t.Parallel()
// Prepare claims with email from unauthorized domain
claims := jwt.MapClaims{
"email": "user@unauthorized.com",
"email_verified": true,
"sub": uuid.NewString(),
}
_, resp := fake.AttemptLogin(t, server, claims)
defer resp.Body.Close()
require.Equal(t, http.StatusForbidden, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Contains(t, string(data), "is not from an authorized domain")
require.Contains(t, string(data), "Please contact your administrator")
for _, domain := range allowedDomains {
require.NotContains(t, string(data), domain)
}
})
// Test case 2: Malformed email without @ symbol
t.Run("MalformedEmailErrorOmitsDomains", func(t *testing.T) {
t.Parallel()
// Prepare claims with an invalid email format (no @ symbol)
claims := jwt.MapClaims{
"email": "invalid-email-without-domain",
"email_verified": true,
"sub": uuid.NewString(),
}
_, resp := fake.AttemptLogin(t, server, claims)
defer resp.Body.Close()
require.Equal(t, http.StatusForbidden, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Contains(t, string(data), "is not from an authorized domain")
require.Contains(t, string(data), "Please contact your administrator")
for _, domain := range allowedDomains {
require.NotContains(t, string(data), domain)
}
})
}
func TestOIDCSkipIssuer(t *testing.T) {
t.Parallel()
const primaryURLString = "https://primary.com"