feat(auth): enforce OpenID Connect issuer uniqueness across providers

Detect when two configured OIDC providers resolve to the same issuer URL
at startup and halt with a fatal error, preventing team sync data
corruption caused by ambiguous (external_id, issuer) matching.

Also adds duplicate issuer detection to the doctor service diagnostics
and comprehensive tests with mock OIDC discovery servers.
This commit is contained in:
Claude
2026-03-24 15:22:56 +00:00
committed by kolaente
parent 59abe1bd84
commit fc9c21915d
4 changed files with 214 additions and 7 deletions

View File

@@ -17,6 +17,7 @@
package openid
import (
"errors"
"fmt"
"strconv"
@@ -28,6 +29,44 @@ import (
"golang.org/x/oauth2"
)
// ErrDuplicateOIDCIssuer is returned when two configured providers resolve to the same issuer URL.
type ErrDuplicateOIDCIssuer struct {
Issuer string
Provider1 string
Provider2 string
}
func (e *ErrDuplicateOIDCIssuer) Error() string {
return fmt.Sprintf(
"duplicate OpenID Connect issuer %q: providers %q and %q resolve to the same issuer, which will cause team sync conflicts",
e.Issuer, e.Provider1, e.Provider2,
)
}
// IsErrDuplicateOIDCIssuer checks if an error is a duplicate issuer error.
func IsErrDuplicateOIDCIssuer(err error) bool {
var target *ErrDuplicateOIDCIssuer
return errors.As(err, &target)
}
// FindDuplicateIssuers checks a map of provider key → issuer URL for duplicates.
// It returns a list of all duplicate pairs found.
func FindDuplicateIssuers(providerIssuers map[string]string) []ErrDuplicateOIDCIssuer {
issuerToKey := make(map[string]string)
var duplicates []ErrDuplicateOIDCIssuer
for key, issuer := range providerIssuers {
if existingKey, exists := issuerToKey[issuer]; exists {
duplicates = append(duplicates, ErrDuplicateOIDCIssuer{
Issuer: issuer,
Provider1: existingKey,
Provider2: key,
})
}
issuerToKey[issuer] = key
}
return duplicates
}
// GetAllProviders returns all configured providers
func GetAllProviders() (providers []*Provider, err error) {
if !config.AuthOpenIDEnabled.GetBool() {
@@ -97,6 +136,21 @@ func GetAllProviders() (providers []*Provider, err error) {
return nil, err
}
}
// Check for duplicate issuers across providers
providerIssuers := make(map[string]string)
for _, p := range providers {
issuer, issuerErr := p.Issuer()
if issuerErr != nil {
log.Errorf("Error getting issuer for openid provider %s: %s", p.Key, issuerErr)
continue
}
providerIssuers[p.Key] = issuer
}
if duplicates := FindDuplicateIssuers(providerIssuers); len(duplicates) > 0 {
return nil, &duplicates[0]
}
err = keyvalue.Put("openid_providers", providers)
}