Keep SAML auth failures generic for users
Specific SAML refusal reasons leak organization and account state. Still redirect to the shared auth error page, but always use authentication_failed while logging the real cause server-side. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
This commit is contained in:
committed by
Cursor Agent
parent
b8e5d279a0
commit
00ed11e30b
@@ -74,48 +74,6 @@ function useAuthErrorContent(code: string | null): AuthErrorContent {
|
||||
"This magic link is invalid. Please request a new one.",
|
||||
),
|
||||
};
|
||||
case "saml_disabled":
|
||||
return {
|
||||
title: __("SSO unavailable"),
|
||||
description: __(
|
||||
"Single sign-on is disabled for this organization. Please contact your administrator.",
|
||||
),
|
||||
};
|
||||
case "saml_configuration_not_found":
|
||||
return {
|
||||
title: __("SSO configuration not found"),
|
||||
description: __(
|
||||
"This single sign-on configuration could not be found. Please contact your administrator.",
|
||||
),
|
||||
};
|
||||
case "saml_email_domain_mismatch":
|
||||
return {
|
||||
title: __("Email domain not allowed"),
|
||||
description: __(
|
||||
"Your email domain is not allowed for this organization's single sign-on. Please use an account from the configured domain.",
|
||||
),
|
||||
};
|
||||
case "saml_auto_signup_disabled":
|
||||
return {
|
||||
title: __("Account not found"),
|
||||
description: __(
|
||||
"No account exists for this email, and automatic signup is disabled. Please contact your administrator.",
|
||||
),
|
||||
};
|
||||
case "saml_user_inactive":
|
||||
return {
|
||||
title: __("Account inactive"),
|
||||
description: __(
|
||||
"Your account is inactive. Please contact your administrator.",
|
||||
),
|
||||
};
|
||||
case "saml_subject_already_in_use":
|
||||
return {
|
||||
title: __("Account already linked"),
|
||||
description: __(
|
||||
"This single sign-on identity is already linked to another account. Please contact your administrator.",
|
||||
),
|
||||
};
|
||||
default:
|
||||
return {
|
||||
title: __("Authentication failed"),
|
||||
|
||||
@@ -36,12 +36,6 @@ const (
|
||||
authErrorMagicLinkExpired = "magic_link_expired"
|
||||
authErrorMagicLinkAlreadyUsed = "magic_link_already_used"
|
||||
authErrorMagicLinkInvalid = "magic_link_invalid"
|
||||
authErrorSAMLDisabled = "saml_disabled"
|
||||
authErrorSAMLConfigurationNotFound = "saml_configuration_not_found"
|
||||
authErrorSAMLEmailDomainMismatch = "saml_email_domain_mismatch"
|
||||
authErrorSAMLAutoSignupDisabled = "saml_auto_signup_disabled"
|
||||
authErrorSAMLUserInactive = "saml_user_inactive"
|
||||
authErrorSAMLSubjectAlreadyInUse = "saml_subject_already_in_use"
|
||||
)
|
||||
|
||||
func redirectAuthError(w http.ResponseWriter, r *http.Request, code string) {
|
||||
@@ -56,29 +50,32 @@ func redirectAuthError(w http.ResponseWriter, r *http.Request, code string) {
|
||||
http.Redirect(w, r, redirectURL.String(), http.StatusFound)
|
||||
}
|
||||
|
||||
// authErrorCodeFromSAML maps known SAML assertion failures to a browser error
|
||||
// code. Specific SAML reasons stay server-side only; users see a generic
|
||||
// authentication failure to avoid leaking org/account state.
|
||||
func authErrorCodeFromSAML(err error) (string, bool) {
|
||||
if _, ok := errors.AsType[*saml.ErrSAMLDisabled](err); ok {
|
||||
return authErrorSAMLDisabled, true
|
||||
return authErrorAuthenticationFailed, true
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*saml.ErrSAMLConfigurationNotFound](err); ok {
|
||||
return authErrorSAMLConfigurationNotFound, true
|
||||
return authErrorAuthenticationFailed, true
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*saml.ErrEmailDomainMismatch](err); ok {
|
||||
return authErrorSAMLEmailDomainMismatch, true
|
||||
return authErrorAuthenticationFailed, true
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*saml.ErrSAMLAutoSignupDisabled](err); ok {
|
||||
return authErrorSAMLAutoSignupDisabled, true
|
||||
return authErrorAuthenticationFailed, true
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*saml.ErrUserInactive](err); ok {
|
||||
return authErrorSAMLUserInactive, true
|
||||
return authErrorAuthenticationFailed, true
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*saml.ErrSAMLSubjectAlreadyInUse](err); ok {
|
||||
return authErrorSAMLSubjectAlreadyInUse, true
|
||||
return authErrorAuthenticationFailed, true
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*saml.ErrInvalidAssertion](err); ok {
|
||||
|
||||
@@ -63,49 +63,49 @@ func TestAuthErrorCodeFromSAML(t *testing.T) {
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "disabled",
|
||||
name: "disabled stays generic",
|
||||
err: saml.NewSAMLDisabledError(),
|
||||
code: authErrorSAMLDisabled,
|
||||
code: authErrorAuthenticationFailed,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "configuration not found",
|
||||
name: "configuration not found stays generic",
|
||||
err: saml.NewSAMLConfigurationNotFoundError(configID),
|
||||
code: authErrorSAMLConfigurationNotFound,
|
||||
code: authErrorAuthenticationFailed,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "email domain mismatch",
|
||||
name: "email domain mismatch stays generic",
|
||||
err: saml.NewEmailDomainMismatchError(email, "acme.com"),
|
||||
code: authErrorSAMLEmailDomainMismatch,
|
||||
code: authErrorAuthenticationFailed,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "auto signup disabled",
|
||||
name: "auto signup disabled stays generic",
|
||||
err: saml.NewSAMLAutoSignupDisabledError(configID),
|
||||
code: authErrorSAMLAutoSignupDisabled,
|
||||
code: authErrorAuthenticationFailed,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "user inactive",
|
||||
name: "user inactive stays generic",
|
||||
err: saml.NewUserInactiveError(configID),
|
||||
code: authErrorSAMLUserInactive,
|
||||
code: authErrorAuthenticationFailed,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "subject already in use",
|
||||
name: "subject already in use stays generic",
|
||||
err: saml.NewSAMLSubjectAlreadyInUseError("assertion-1"),
|
||||
code: authErrorSAMLSubjectAlreadyInUse,
|
||||
code: authErrorAuthenticationFailed,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "invalid assertion maps to generic failure",
|
||||
name: "invalid assertion stays generic",
|
||||
err: saml.NewInvalidAssertionError("assertion-1", errors.New("bad signature")),
|
||||
code: authErrorAuthenticationFailed,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "replay maps to generic failure",
|
||||
name: "replay stays generic",
|
||||
err: saml.NewReplayAttackDetectedError("assertion-1"),
|
||||
code: authErrorAuthenticationFailed,
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user