chore: reuse user token source (#26144)

This commit is contained in:
Michael Heß 2026-01-25 14:15:54 +01:00 • committed by GitHub
parent 2b13eb2668
commit 2691bbda1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 85 additions and 52 deletions

View file

@ -6,6 +6,7 @@ import (
"time"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/cache"
"github.com/evcc-io/evcc/util/oauth"
"github.com/evcc-io/evcc/util/request"
"golang.org/x/oauth2"
@ -38,17 +39,25 @@ type tokenSource struct {
user, password string
}
// TokenSource creates an Easee token source
// tokenSourceCache stores per-user token sources
var tokenSourceCache = cache.New[oauth2.TokenSource]()
// TokenSource returns a shared oauth2.TokenSource for the given user.
func TokenSource(log *util.Logger, user, password string) (oauth2.TokenSource, error) {
c := &tokenSource{
Helper: request.NewHelper(log),
user: user,
password: password,
}
return tokenSourceCache.GetOrCreate(user, func() (oauth2.TokenSource, error) {
c := &tokenSource{
Helper: request.NewHelper(log),
user: user,
password: password,
}
token, err := c.authenticate()
token, err := c.authenticate()
if err != nil {
return nil, err
}
return oauth.RefreshTokenSource(token.AsOAuth2Token(), c.refreshToken), err
return oauth.RefreshTokenSource(token.AsOAuth2Token(), c.refreshToken), nil
})
}
func (c *tokenSource) authenticate() (*Token, error) {

View file

@ -121,7 +121,7 @@ func NewZaptec(ctx context.Context, user, password, id string, priority bool, pa
})
// Get shared token source for this user (per-user uniqueness)
ts, err := zaptec.GetTokenSource(tsCtx, user, password)
ts, err := zaptec.TokenSource(tsCtx, user, password)
if err != nil {
return nil, err
}

View file

@ -6,6 +6,7 @@ import (
"sync"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/evcc-io/evcc/util/cache"
"golang.org/x/oauth2"
)
@ -23,10 +24,9 @@ func (p *passwordTokenSource) Token() (*oauth2.Token, error) {
return p.config.PasswordCredentialsToken(p.ctx, p.user, p.pass)
}
// tokenSourceCache stores per-user token sources
var (
tokenSourceMu sync.Mutex
tokenSourceCache = make(map[string]oauth2.TokenSource)
// TokenSourceCache stores per-user token sources
tokenSourceCache = cache.New[oauth2.TokenSource]()
oidcProvider *oidc.Provider
oidcProviderOnce sync.Once
@ -41,48 +41,35 @@ func getOIDCProvider(ctx context.Context) (*oidc.Provider, error) {
return oidcProvider, oidcProviderErr
}
// GetTokenSource returns a shared oauth2.TokenSource for the given user credentials.
// Multiple chargers using the same user credentials will share the same TokenSource,
// ensuring tokens are reused and authentication is deduplicated.
func GetTokenSource(ctx context.Context, user, pass string) (oauth2.TokenSource, error) {
tokenSourceMu.Lock()
defer tokenSourceMu.Unlock()
// TokenSource returns a shared oauth2.TokenSource for the given user.
func TokenSource(ctx context.Context, user, pass string) (oauth2.TokenSource, error) {
return tokenSourceCache.GetOrCreate(user, func() (oauth2.TokenSource, error) {
provider, err := getOIDCProvider(ctx)
if err != nil {
return nil, fmt.Errorf("failed to initialize OIDC provider: %w", err)
}
// Use username as the cache key (assuming username is unique)
if ts, exists := tokenSourceCache[user]; exists {
return ts, nil
}
oc := &oauth2.Config{
Endpoint: provider.Endpoint(),
Scopes: []string{
oidc.ScopeOpenID,
},
}
// Get the cached OIDC provider (initialized once)
provider, err := getOIDCProvider(ctx)
if err != nil {
return nil, fmt.Errorf("failed to initialize OIDC provider: %w", err)
}
// Create the password token source
pts := &passwordTokenSource{
ctx: ctx,
config: oc,
user: user,
pass: pass,
}
oc := &oauth2.Config{
Endpoint: provider.Endpoint(),
Scopes: []string{
oidc.ScopeOpenID,
},
}
// Get initial token
token, err := pts.Token()
if err != nil {
return nil, err
}
// Create the password token source
pts := &passwordTokenSource{
ctx: ctx,
config: oc,
user: user,
pass: pass,
}
// Get initial token
token, err := pts.Token()
if err != nil {
return nil, err
}
// Wrap with ReuseTokenSource to cache tokens
ts := oauth2.ReuseTokenSource(token, pts)
tokenSourceCache[user] = ts
return ts, nil
return oauth2.ReuseTokenSource(token, pts), nil
})
}

37
util/cache/cache.go vendored Normal file
View file

@ -0,0 +1,37 @@
package cache
import (
"sync"
)
// Cache provides thread-safe caching keyed by username
type Cache[T any] struct {
mu sync.Mutex
cache map[string]T
}
// New creates a new Cache instance
func New[T any]() *Cache[T] {
return &Cache[T]{
cache: make(map[string]T),
}
}
// GetOrCreate atomically gets or creates a cached object
func (c *Cache[T]) GetOrCreate(key string, createFn func() (T, error)) (T, error) {
c.mu.Lock()
defer c.mu.Unlock()
if res, ok := c.cache[key]; ok {
return res, nil
}
res, err := createFn()
if err != nil {
var zero T
return zero, err
}
c.cache[key] = res
return res, nil
}