bouncer/internal/proxy/director/layer/authn/oidc/authenticator.go

137 lines
3.3 KiB
Go

package oidc
import (
"context"
"fmt"
"net/http"
"net/url"
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/authn"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gorilla/sessions"
"github.com/pkg/errors"
)
type Authenticator struct{}
func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request, layer *store.Layer, sess *sessions.Session) error {
ctx := r.Context()
originalURL, err := director.OriginalURL(ctx)
if err != nil {
return errors.WithStack(err)
}
options, err := fromStoreOptions(layer.Options)
if err != nil {
return errors.WithStack(err)
}
redirectURL := a.getRedirectURL(layer.Name, originalURL, options)
logoutURL := a.getLogoutURL(layer.Name, originalURL, options)
client, err := a.getClient(options, redirectURL.String())
if err != nil {
return errors.WithStack(err)
}
switch r.URL.Path {
case redirectURL.Path:
if err := client.HandleCallback(w, r, sess); err != nil {
return errors.WithStack(err)
}
case logoutURL.Path:
if err := client.HandleLogout(w, r, sess, options.OIDC.PostLogoutRedirectURL); err != nil {
return errors.WithStack(err)
}
}
return nil
}
// Authenticate implements authn.Authenticator.
func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, layer *store.Layer, sess *sessions.Session) (*authn.User, error) {
ctx := r.Context()
originalURL, err := director.OriginalURL(ctx)
if err != nil {
return nil, errors.WithStack(err)
}
options, err := fromStoreOptions(layer.Options)
if err != nil {
return nil, errors.WithStack(err)
}
redirectURL := a.getRedirectURL(layer.Name, originalURL, options)
client, err := a.getClient(options, redirectURL.String())
if err != nil {
return nil, errors.WithStack(err)
}
idToken, err := client.Authenticate(w, r, sess)
if err != nil {
if errors.Is(err, ErrLoginRequired) {
return nil, errors.WithStack(authn.ErrSkipRequest)
}
return nil, errors.WithStack(err)
}
var claims map[string]any
if err := idToken.Claims(&claims); err != nil {
return nil, errors.WithStack(err)
}
user := authn.NewUser(idToken.Subject, claims)
return user, nil
}
func (a *Authenticator) getRedirectURL(layerName store.LayerName, u *url.URL, options *LayerOptions) *url.URL {
return &url.URL{
Scheme: u.Scheme,
Host: u.Host,
Path: fmt.Sprintf(options.OIDC.LoginCallbackPath, layerName),
}
}
func (a *Authenticator) getLogoutURL(layerName store.LayerName, u *url.URL, options *LayerOptions) *url.URL {
return &url.URL{
Scheme: u.Scheme,
Host: u.Host,
Path: fmt.Sprintf(options.OIDC.LogoutPath, layerName),
}
}
func (a *Authenticator) getClient(options *LayerOptions, redirectURL string) (*Client, error) {
ctx := context.Background()
if options.OIDC.SkipIssuerVerification {
ctx = oidc.InsecureIssuerURLContext(ctx, options.OIDC.IssuerURL)
}
provider, err := oidc.NewProvider(ctx, options.OIDC.IssuerURL)
if err != nil {
return nil, errors.Wrap(err, "could not create oidc provider")
}
client := NewClient(
WithCredentials(options.OIDC.ClientID, options.OIDC.ClientSecret),
WithProvider(provider),
WithRedirectURL(redirectURL),
)
return client, nil
}
var (
_ authn.PreAuthentication = &Authenticator{}
_ authn.Authenticator = &Authenticator{}
)