Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/cryptorand"
"github.com/coder/coder/v2/site"
)

type MergedClaimsSource string
Expand Down Expand Up @@ -1343,12 +1344,21 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
verified, ok := verifiedRaw.(bool)
if ok && !verified {
if !api.OIDCConfig.IgnoreEmailVerified {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Verify the %q email address on your OIDC provider to authenticate!", email),
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
Status: http.StatusForbidden,
HideStatus: true,
Title: "Email not verified",
Description: fmt.Sprintf(
"Verify the %q email address on your OIDC provider to authenticate!",
email,
),
Actions: []site.Action{
{URL: "/login", Text: "Back to login"},
},
})
return
}
Comment thread
angrycub marked this conversation as resolved.
logger.Warn(ctx, "allowing unverified oidc email %q")
logger.Warn(ctx, "allowing unverified oidc email", slog.F("email", email))
}
}

Expand All @@ -1370,8 +1380,17 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
ok = false
emailSp := strings.Split(email, "@")
if len(emailSp) == 1 {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Your email %q is not from an authorized domain! Please contact your administrator.", email),
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
Status: http.StatusForbidden,
HideStatus: true,
Title: "Unauthorized email",
Description: fmt.Sprintf(
"Your email %q is not from an authorized domain! Please contact your administrator.",
email,
),
Actions: []site.Action{
{URL: "/login", Text: "Back to login"},
},
})
return
}
Expand All @@ -1385,8 +1404,17 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
}
}
if !ok {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Your email %q is not from an authorized domain! Please contact your administrator.", email),
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
Status: http.StatusForbidden,
HideStatus: true,
Title: "Unauthorized email",
Description: fmt.Sprintf(
"Your email %q is not from an authorized domain! Please contact your administrator.",
email,
),
Actions: []site.Action{
{URL: "/login", Text: "Back to login"},
},
})
return
}
Expand All @@ -1406,7 +1434,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
if ok {
picture, _ = pictureRaw.(string)
}

ctx = slog.With(ctx, slog.F("email", email), slog.F("username", username), slog.F("name", name))

user, link, err := findLinkedUser(ctx, api.Database, oidcLinkedID(idToken), email)
Expand Down
52 changes: 49 additions & 3 deletions coderd/userauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1107,10 +1107,21 @@ func TestUserOIDC(t *testing.T) {
},
AllowSignups: true,
StatusCode: http.StatusForbidden,
AssertResponse: func(t testing.TB, resp *http.Response) {
data, err := io.ReadAll(resp.Body)
require.NoError(t, err)
body := string(data)
// Should be an HTML error page, not JSON.
require.Equal(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
require.Contains(t, body, "<!doctype html>")
require.Contains(t, body, "Email not verified")
require.Contains(t, body, "Verify the")
require.Contains(t, body, "Back to login")
require.NotContains(t, body, `"message"`)
},
},
{
Name: "EmailNotAString",
IDTokenClaims: jwt.MapClaims{
Name: "EmailNotAString", IDTokenClaims: jwt.MapClaims{
"email": 3.14159,
"email_verified": false,
"sub": uuid.NewString(),
Expand Down Expand Up @@ -1144,6 +1155,18 @@ func TestUserOIDC(t *testing.T) {
"coder.com",
},
StatusCode: http.StatusForbidden,
AssertResponse: func(t testing.TB, resp *http.Response) {
data, err := io.ReadAll(resp.Body)
require.NoError(t, err)
body := string(data)
// Should be an HTML error page, not JSON.
require.Equal(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
require.Contains(t, body, "<!doctype html>")
require.Contains(t, body, "Unauthorized email")
require.Contains(t, body, "is not from an authorized domain")
require.Contains(t, body, "Back to login")
require.NotContains(t, body, `"message"`)
},
},
{
Name: "EmailDomainWithLeadingAt",
Expand All @@ -1170,6 +1193,18 @@ func TestUserOIDC(t *testing.T) {
"@coder.com",
},
StatusCode: http.StatusForbidden,
AssertResponse: func(t testing.TB, resp *http.Response) {
data, err := io.ReadAll(resp.Body)
require.NoError(t, err)
body := string(data)
// Should be an HTML error page, not JSON.
require.Equal(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
require.Contains(t, body, "<!doctype html>")
require.Contains(t, body, "Unauthorized email")
require.Contains(t, body, "is not from an authorized domain")
require.Contains(t, body, "Back to login")
require.NotContains(t, body, `"message"`)
},
},
{
Name: "EmailDomainCaseInsensitive",
Expand Down Expand Up @@ -2062,6 +2097,12 @@ func TestOIDCDomainErrorMessage(t *testing.T) {

require.Contains(t, string(data), "is not from an authorized domain")
require.Contains(t, string(data), "Please contact your administrator")
// Verify the response is a rendered HTML error page, not raw JSON.
require.Equal(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
require.Contains(t, string(data), "<!doctype html>")
require.Contains(t, string(data), "Unauthorized email")
require.Contains(t, string(data), "Back to login")
require.NotContains(t, string(data), `"message"`)

for _, domain := range allowedDomains {
require.NotContains(t, string(data), domain)
Expand Down Expand Up @@ -2091,7 +2132,12 @@ func TestOIDCDomainErrorMessage(t *testing.T) {

require.Contains(t, string(data), "is not from an authorized domain")
require.Contains(t, string(data), "Please contact your administrator")

// Verify the response is a rendered HTML error page, not raw JSON.
require.Equal(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
require.Contains(t, string(data), "<!doctype html>")
require.Contains(t, string(data), "Unauthorized email")
require.Contains(t, string(data), "Back to login")
require.NotContains(t, string(data), `"message"`)
for _, domain := range allowedDomains {
require.NotContains(t, string(data), domain)
}
Expand Down
Loading