Reject empty SAML NameIDs on login

Empty NameID values were stored as '' and occupied the
unique saml_subject index, causing duplicate-key failures
on later logins. Reject blank NameIDs during assertion
validation, return a clear error when a NameID is already
linked to another account, and stop returning internal
errors from the SAML consume endpoint.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
This commit is contained in:
Sacha Al Himdani
2026-07-24 09:47:09 +02:00
parent e12351e0f4
commit bcd05a2e55
5 changed files with 234 additions and 13 deletions

View File

@@ -52,6 +52,10 @@ type (
Identities []*Identity
)
var (
ErrSAMLSubjectAlreadyExists = errors.New("saml subject already exists")
)
func (i Identity) CursorKey(orderBy IdentityOrderField) page.CursorKey {
switch orderBy {
case IdentityOrderFieldCreatedAt:
@@ -247,8 +251,11 @@ VALUES (
_, err := conn.Exec(ctx, q, args)
if err != nil {
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
if pgErr.Code == "23505" && strings.Contains(pgErr.ConstraintName, "email_address") {
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok && pgErr.Code == "23505" {
switch pgErr.ConstraintName {
case "idx_users_saml_subject":
return ErrSAMLSubjectAlreadyExists
case "usrmgr_users_email_address_key":
return ErrResourceAlreadyExists
}
}
@@ -288,6 +295,15 @@ WHERE
result, err := conn.Exec(ctx, q, args)
if err != nil {
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok && pgErr.Code == "23505" {
switch pgErr.ConstraintName {
case "idx_users_saml_subject":
return ErrSAMLSubjectAlreadyExists
case "usrmgr_users_email_address_key":
return ErrResourceAlreadyExists
}
}
return fmt.Errorf("cannot update identity: %w", err)
}
@@ -304,6 +320,10 @@ func (i *Identity) LoadBySAMLSubject(
conn pg.Querier,
samlSubject string,
) error {
if strings.TrimSpace(samlSubject) == "" {
return ErrResourceNotFound
}
q := `
SELECT
id,