package auth import ( "context" "crypto/sha256" "net/url" "git.icedream.tech/icedream/oneandone-billing-mailer/pkg/appcontext" "github.com/go-logr/logr" "github.com/google/uuid" "golang.org/x/oauth2" "golang.org/x/oauth2/authhandler" ) type OAuth struct { config *oauth2.Config ctx context.Context pkceKey string } func NewOAuth(ctx context.Context, clientID string, clientSecret string, baseUri *url.URL) *OAuth { endpoint := oauth2.Endpoint{ AuthURL: baseUri.ResolveReference(&url.URL{Path: "auth/init"}).String(), TokenURL: baseUri.ResolveReference(&url.URL{Path: "token"}).String(), } config := &oauth2.Config{ ClientID: clientID, ClientSecret: clientSecret, Endpoint: endpoint, RedirectURL: redirect_url, Scopes: []string{"openid", "account_otk"}, } return &OAuth{ ctx: ctx, config: config, } } func NewOAuthFromEnvironment(ctx context.Context) (*OAuth, error) { appctx, err := appcontext.GetEnvironmentContext() if err != nil { return nil, err } baseURL, err := appctx.CentralLogin.BaseURL() if err != nil { return nil, err } clientID := client_id clientSecret := clientSecretForEnvironment() return NewOAuth(ctx, clientID, clientSecret, baseURL), nil } func (h *OAuth) randomKey() (string, error) { pkceKeyUUID, err := uuid.NewRandom() if err != nil { return "", err } pkceKey := pkceKeyUUID.String() return pkceKey, nil } func (h *OAuth) TokenSource(t *oauth2.Token, authHandler authhandler.AuthorizationHandler) (oauth2.TokenSource, error) { log := logr.FromContextOrDiscard(h.ctx).WithName("OAuth") log.V(2).Info("TokenSource was called") if t != nil && len(t.RefreshToken) > 0 { // Use a refresher based on given token's refresh token instead return h.config.TokenSource(h.ctx, t), nil } // Generate state. // // Note that the Control Center app does not even pass a state at all, but // the oauth server properly implements handling it. //state, err := h.randomKey() //if err != nil { // return nil, err //} state := "" // Generate pkce key (which becomes our code verifier) if len(h.pkceKey) == 0 { pkceKey, err := h.randomKey() if err != nil { return nil, err } h.pkceKey = pkceKey } // Generate pkce key digest (which becomes our code challenge) messageDigest := sha256.New() messageDigest.Write([]byte(h.pkceKey)) messageDigestResult := messageDigest.Sum(nil) messageDigestResultB64 := urlEncodingWithoutPadding.EncodeToString(messageDigestResult) pkce := &authhandler.PKCEParams{ Challenge: messageDigestResultB64, ChallengeMethod: "S256", Verifier: h.pkceKey, } tok := authhandler.TokenSourceWithPKCE( context.Background(), h.config, state, authHandler, pkce) return tok, nil }