wg-portal/internal/app/auth/auth_oidc.go
h44z 8b820a5adf
V2 alpha - initial version (#172)
Initial alpha codebase for version 2 of WireGuard Portal.
This version is considered unstable and incomplete (for example, no public REST API)! 
Use with care!


Fixes/Implements the following issues:
 - OAuth support #154, #1 
 - New Web UI with internationalisation support #98, #107, #89, #62
 - Postgres Support #49 
 - Improved Email handling #47, #119 
 - DNS Search Domain support #46 
 - Bugfixes #94, #48 

---------

Co-authored-by: Fabian Wechselberger <wechselbergerf@hotmail.com>
2023-08-04 13:34:18 +02:00

108 lines
3.4 KiB
Go

package auth
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/h44z/wg-portal/internal"
"github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain"
"golang.org/x/oauth2"
)
type OidcAuthenticator struct {
name string
provider *oidc.Provider
verifier *oidc.IDTokenVerifier
cfg *oauth2.Config
userInfoMapping config.OauthFields
registrationEnabled bool
}
func newOidcAuthenticator(ctx context.Context, callbackUrl string, cfg *config.OpenIDConnectProvider) (*OidcAuthenticator, error) {
var err error
var provider = &OidcAuthenticator{}
provider.name = cfg.ProviderName
provider.provider, err = oidc.NewProvider(context.Background(), cfg.BaseUrl) // use new context here, see https://github.com/coreos/go-oidc/issues/339
if err != nil {
return nil, fmt.Errorf("failed to create new oidc provider: %w", err)
}
provider.verifier = provider.provider.Verifier(&oidc.Config{
ClientID: cfg.ClientID,
})
scopes := []string{oidc.ScopeOpenID}
scopes = append(scopes, cfg.ExtraScopes...)
provider.cfg = &oauth2.Config{
ClientID: cfg.ClientID,
ClientSecret: cfg.ClientSecret,
Endpoint: provider.provider.Endpoint(),
RedirectURL: callbackUrl,
Scopes: scopes,
}
provider.userInfoMapping = getOauthFieldMapping(cfg.FieldMap)
provider.registrationEnabled = cfg.RegistrationEnabled
return provider, nil
}
func (o OidcAuthenticator) GetName() string {
return o.name
}
func (o OidcAuthenticator) RegistrationEnabled() bool {
return o.registrationEnabled
}
func (o OidcAuthenticator) GetType() domain.AuthenticatorType {
return domain.AuthenticatorTypeOidc
}
func (o OidcAuthenticator) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
return o.cfg.AuthCodeURL(state, opts...)
}
func (o OidcAuthenticator) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
return o.cfg.Exchange(ctx, code, opts...)
}
func (o OidcAuthenticator) GetUserInfo(ctx context.Context, token *oauth2.Token, nonce string) (map[string]interface{}, error) {
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, errors.New("token does not contain id_token")
}
idToken, err := o.verifier.Verify(ctx, rawIDToken)
if err != nil {
return nil, fmt.Errorf("failed to validate id_token: %w", err)
}
if idToken.Nonce != nonce {
return nil, errors.New("nonce mismatch")
}
var tokenFields map[string]interface{}
if err = idToken.Claims(&tokenFields); err != nil {
return nil, fmt.Errorf("failed to parse extra claims: %w", err)
}
return tokenFields, nil
}
func (o OidcAuthenticator) ParseUserInfo(raw map[string]interface{}) (*domain.AuthenticatorUserInfo, error) {
isAdmin, _ := strconv.ParseBool(internal.MapDefaultString(raw, o.userInfoMapping.IsAdmin, ""))
userInfo := &domain.AuthenticatorUserInfo{
Identifier: domain.UserIdentifier(internal.MapDefaultString(raw, o.userInfoMapping.UserIdentifier, "")),
Email: internal.MapDefaultString(raw, o.userInfoMapping.Email, ""),
Firstname: internal.MapDefaultString(raw, o.userInfoMapping.Firstname, ""),
Lastname: internal.MapDefaultString(raw, o.userInfoMapping.Lastname, ""),
Phone: internal.MapDefaultString(raw, o.userInfoMapping.Phone, ""),
Department: internal.MapDefaultString(raw, o.userInfoMapping.Department, ""),
IsAdmin: isAdmin,
}
return userInfo, nil
}