router/pkg/authentication/authentication.go (74 lines of code) (raw):

package authentication import ( "context" "errors" "net/http" "strings" ) type Claims map[string]any // Provider is an interface that represents entities that might provide // authentication information. If no authentication information is available, // the AuthenticationHeaders method should return nil. type Provider interface { AuthenticationHeaders() http.Header } // Authenticator represents types that given a Provider, can authenticate it. // If no authentication information is available, the Authenticate method // should return nil without any errors. type Authenticator interface { Name() string Authenticate(ctx context.Context, p Provider) (Claims, error) } type Authentication interface { // Authenticator returns the name of the Authenticator that authenticated // the request. Authenticator() string // Claims returns the claims of the authenticated request, as returned by // the Authenticator. Claims() Claims // SetScopes sets the scopes of the authenticated request. It will replace the scopes already parsed from the claims. // If users desire to append the scopes, they can first run `Scopes` to get the current scopes, and then append the new scopes SetScopes(scopes []string) // Scopes returns the scopes of the authenticated request, as returned by // the Authenticator. Scopes() []string } type authentication struct { authenticator string claims Claims } func (a *authentication) Authenticator() string { return a.authenticator } func (a *authentication) Claims() Claims { if a == nil { return nil } return a.claims } func (a *authentication) SetScopes(scopes []string) { if a == nil { return } if a.claims == nil { a.claims = make(Claims) } // per https://datatracker.ietf.org/doc/html/rfc8693#section-2.1-4.8, scopes should be space separated a.claims["scope"] = strings.Join(scopes, " ") } func (a *authentication) Scopes() []string { if a == nil { return nil } scopes, ok := a.claims["scope"].(string) if !ok { return nil } return strings.Split(scopes, " ") } // Authenticate tries to authenticate the given Provider using the given authenticators. If any of // the authenticators succeeds, the Authentication result is returned with no error. If the Provider // has no authentication information, the Authentication result is nil with no error. If the authentication // information is present but some or all of the authenticators fail to validate it, then a non-nil error // will be produced. func Authenticate(ctx context.Context, authenticators []Authenticator, p Provider) (Authentication, error) { var joinedErrors error for _, auth := range authenticators { claims, err := auth.Authenticate(ctx, p) if err != nil { // If authentication fails for one provider, we try the // rest before returning an error. joinedErrors = errors.Join(joinedErrors, err) continue } // Claims is nil when no authentication information matched the authenticator. // In that case, we continue to the next authenticator. if claims == nil { continue } // If authentication succeeds, we return the authentication for the first provider. return &authentication{ authenticator: auth.Name(), claims: claims, }, nil } // If no authentication failed error will be nil here, // even if to claims were found. return nil, joinedErrors } func NewEmptyAuthentication() Authentication { return &authentication{} }