137 lines
3.3 KiB
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{}
|
||
|
)
|