You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
3.4 KiB
Go
130 lines
3.4 KiB
Go
package authconfig
|
|
|
|
import (
|
|
"strings"
|
|
"context"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/coreos/go-oidc"
|
|
"github.com/gorilla/mux"
|
|
"github.com/pkg/errors"
|
|
"github.com/place1/wg-access-server/pkg/authnz/authruntime"
|
|
"github.com/place1/wg-access-server/pkg/authnz/authsession"
|
|
"github.com/place1/wg-access-server/pkg/authnz/authutil"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
type OIDCConfig struct {
|
|
Name string `yaml:"name"`
|
|
Issuer string `yaml:"issuer"`
|
|
ClientID string `yaml:"clientID"`
|
|
ClientSecret string `yaml:"clientSecret"`
|
|
Scopes []string `yaml:"scopes"`
|
|
RedirectURL string `yaml:"redirectURL"`
|
|
EmailDomains []string `yaml:"emailDomains"`
|
|
}
|
|
|
|
func (c *OIDCConfig) Provider() *authruntime.Provider {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
provider, err := oidc.NewProvider(ctx, c.Issuer)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
if c.Scopes == nil {
|
|
c.Scopes = []string{"openid"}
|
|
}
|
|
|
|
oauthConfig := &oauth2.Config{
|
|
RedirectURL: c.RedirectURL,
|
|
ClientID: c.ClientID,
|
|
ClientSecret: c.ClientSecret,
|
|
Scopes: c.Scopes,
|
|
Endpoint: provider.Endpoint(),
|
|
}
|
|
|
|
redirectURL, err := url.Parse(c.RedirectURL)
|
|
if err != nil {
|
|
panic(errors.Wrapf(err, "redirect url is not valid: %s", c.RedirectURL))
|
|
}
|
|
|
|
return &authruntime.Provider{
|
|
Type: "OIDC",
|
|
Invoke: func(w http.ResponseWriter, r *http.Request, runtime *authruntime.ProviderRuntime) {
|
|
c.loginHandler(runtime, oauthConfig)(w, r)
|
|
},
|
|
RegisterRoutes: func(router *mux.Router, runtime *authruntime.ProviderRuntime) error {
|
|
router.HandleFunc(redirectURL.Path, c.callbackHandler(runtime, oauthConfig, provider))
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func (c *OIDCConfig) loginHandler(runtime *authruntime.ProviderRuntime, oauthConfig *oauth2.Config) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
oauthStateString := authutil.RandomString(32)
|
|
runtime.SetSession(w, r, &authsession.AuthSession{
|
|
Nonce: &oauthStateString,
|
|
})
|
|
url := oauthConfig.AuthCodeURL(oauthStateString)
|
|
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
|
}
|
|
}
|
|
|
|
func (c *OIDCConfig) callbackHandler(runtime *authruntime.ProviderRuntime, oauthConfig *oauth2.Config, provider *oidc.Provider) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
s, err := runtime.GetSession(r)
|
|
if err != nil {
|
|
http.Error(w, "no session", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
state := r.FormValue("state")
|
|
if s.Nonce == nil || *s.Nonce != state {
|
|
http.Error(w, "bad nonce", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
code := r.FormValue("code")
|
|
token, _ := oauthConfig.Exchange(r.Context(), code)
|
|
info, err := provider.UserInfo(r.Context(), oauthConfig.TokenSource(r.Context(), token))
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if !verifyEmailDomain(c.EmailDomains, info.Email) {
|
|
http.Error(w, "email domain not authorized", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
runtime.SetSession(w, r, &authsession.AuthSession{
|
|
Identity: &authsession.Identity{
|
|
Subject: info.Subject,
|
|
},
|
|
})
|
|
|
|
runtime.Done(w, r)
|
|
}
|
|
}
|
|
|
|
func verifyEmailDomain(allowedDomains []string, email string) bool {
|
|
if len(allowedDomains) == 0 {
|
|
return true
|
|
}
|
|
|
|
parsed := strings.Split(email, "@")
|
|
|
|
for _, domain := range allowedDomains {
|
|
if domain == parsed[1] {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|