From 19a30ac9cac2cca6eea60fb669d9554da84106bc Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 27 Dec 2025 15:44:31 +0100 Subject: [PATCH] chore: switch to ReuseTokenSource (#26206) --- charger/easee/identity.go | 4 +- charger/smaevcharger/identity.go | 4 +- tariff/corrently/tokensource.go | 5 +-- tariff/edf-tempo.go | 4 +- tariff/octopusde/graphql/api.go | 4 +- tariff/octopusde/graphql/tokensource.go | 12 +++--- tariff/ostrom.go | 4 +- util/oauth/bootstraptokensource.go | 25 +++++++++++++ .../{tokensource.go => refreshtokensource.go} | 16 +++----- ...rce_test.go => refreshtokensource_test.go} | 20 ++++------ util/token.go | 1 + vehicle/bluelink/identity.go | 8 ++-- vehicle/bluelink_us/identity.go | 8 +--- vehicle/bmw/connected/identity.go | 8 ++-- vehicle/jlr/identity.go | 6 +-- vehicle/mercedes/identity.go | 10 ++--- vehicle/polestar/identity.go | 6 +-- vehicle/porsche/identity.go | 4 +- vehicle/psa/identity.go | 8 ++-- vehicle/saic/identity.go | 4 +- vehicle/smart/hello/identity.go | 4 +- vehicle/subaru/identity.go | 4 +- vehicle/tesla/identity.go | 6 +-- vehicle/toyota/identity.go | 4 +- vehicle/tronity.go | 23 +----------- vehicle/tronity/tokensource.go | 37 +++++++++++++++++++ vehicle/vag/loginapps/endpoint.go | 6 +-- 27 files changed, 137 insertions(+), 108 deletions(-) create mode 100644 util/oauth/bootstraptokensource.go rename util/oauth/{tokensource.go => refreshtokensource.go} (64%) rename util/oauth/{tokensource_test.go => refreshtokensource_test.go} (67%) create mode 100644 vehicle/tronity/tokensource.go diff --git a/charger/easee/identity.go b/charger/easee/identity.go index 35ab93711..164d64b40 100644 --- a/charger/easee/identity.go +++ b/charger/easee/identity.go @@ -48,7 +48,7 @@ func TokenSource(log *util.Logger, user, password string) (oauth2.TokenSource, e token, err := c.authenticate() - return oauth.RefreshTokenSource(token.AsOAuth2Token(), c), err + return oauth.RefreshTokenSource(token.AsOAuth2Token(), c.refreshToken), err } func (c *tokenSource) authenticate() (*Token, error) { @@ -72,7 +72,7 @@ func (c *tokenSource) authenticate() (*Token, error) { return &token, err } -func (c *tokenSource) RefreshToken(oauthToken *oauth2.Token) (*oauth2.Token, error) { +func (c *tokenSource) refreshToken(oauthToken *oauth2.Token) (*oauth2.Token, error) { data := struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` diff --git a/charger/smaevcharger/identity.go b/charger/smaevcharger/identity.go index 039c64c87..e51074deb 100644 --- a/charger/smaevcharger/identity.go +++ b/charger/smaevcharger/identity.go @@ -62,14 +62,14 @@ func TokenSource(log *util.Logger, uri, user, password string) (oauth2.TokenSour if err == nil { var token Token if err = c.DoJSON(req, &token); err == nil { - c.TokenSource = oauth.RefreshTokenSource(token.AsOAuth2Token(), c) + c.TokenSource = oauth.RefreshTokenSource(token.AsOAuth2Token(), c.refreshToken) } } return c, err } -func (c *tokenSource) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +func (c *tokenSource) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { data := url.Values{ "grant_type": {"refresh_token"}, "refresh_token": {token.RefreshToken}, diff --git a/tariff/corrently/tokensource.go b/tariff/corrently/tokensource.go index 13b6e6c85..837681d4e 100644 --- a/tariff/corrently/tokensource.go +++ b/tariff/corrently/tokensource.go @@ -5,7 +5,6 @@ import ( "time" "github.com/evcc-io/evcc/util" - "github.com/evcc-io/evcc/util/oauth" "github.com/evcc-io/evcc/util/request" "golang.org/x/oauth2" ) @@ -15,10 +14,10 @@ type tokenSource struct { } func TokenSource(log *util.Logger, token *oauth2.Token) oauth2.TokenSource { - return oauth.RefreshTokenSource(token, &tokenSource{log}) + return oauth2.ReuseTokenSource(token, &tokenSource{log}) } -func (ts *tokenSource) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { +func (ts *tokenSource) Token() (*oauth2.Token, error) { // "Content-Type: application/json" \ // --request POST \ // https://console.corrently.io/v2.0/auth/requestToken diff --git a/tariff/edf-tempo.go b/tariff/edf-tempo.go index 3f0749e99..01270e3f0 100644 --- a/tariff/edf-tempo.go +++ b/tariff/edf-tempo.go @@ -79,13 +79,13 @@ func NewEdfTempoFromConfig(other map[string]any) (api.Tariff, error) { t.Client.Transport = &oauth2.Transport{ Base: t.Client.Transport, - Source: oauth.RefreshTokenSource(new(oauth2.Token), t), + Source: oauth2.ReuseTokenSource(nil, oauth.BootstrapTokenSource(t.refreshToken)), } return runOrError(t) } -func (t *EdfTempo) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { +func (t *EdfTempo) refreshToken() (*oauth2.Token, error) { tokenURL := "https://digital.iservices.rte-france.com/token/oauth" req, _ := request.New(http.MethodPost, tokenURL, nil, map[string]string{ "Authorization": t.basic, diff --git a/tariff/octopusde/graphql/api.go b/tariff/octopusde/graphql/api.go index e27423410..97d080b95 100644 --- a/tariff/octopusde/graphql/api.go +++ b/tariff/octopusde/graphql/api.go @@ -9,10 +9,10 @@ import ( "time" "github.com/evcc-io/evcc/util" - "github.com/evcc-io/evcc/util/oauth" "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/transport" "github.com/hasura/go-graphql-client" + "golang.org/x/oauth2" ) // BaseURI is Octopus Energy Germany's Kraken API root. @@ -28,7 +28,7 @@ type OctopusDeGraphQLClient struct { // NewClient returns a new, authenticated instance of OctopusDeGraphQLClient. func NewClient(log *util.Logger, email, password, accountNumber string) (*OctopusDeGraphQLClient, error) { - ts := oauth.RefreshTokenSource(nil, &TokenSource{ + ts := oauth2.ReuseTokenSource(nil, &tokenSource{ log: log, email: email, password: password, diff --git a/tariff/octopusde/graphql/tokensource.go b/tariff/octopusde/graphql/tokensource.go index 6cc4fa158..47bc8ec28 100644 --- a/tariff/octopusde/graphql/tokensource.go +++ b/tariff/octopusde/graphql/tokensource.go @@ -12,25 +12,27 @@ import ( "golang.org/x/oauth2" ) -type TokenSource struct { +type tokenSource struct { log *util.Logger email, password string } +var _ oauth2.TokenSource = (*tokenSource)(nil) + // RefreshToken implements oauth.TokenRefresher to obtain a new JWT token. // It parses the JWT to extract the actual expiry time from the token claims. -func (c *TokenSource) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { +func (ts *tokenSource) Token() (*oauth2.Token, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() // Create a temporary client without authentication for the token request - cli := request.NewClient(c.log) + cli := request.NewClient(ts.log) tempClient := graphql.NewClient(BaseURI, cli) var q krakenTokenAuthentication if err := tempClient.Mutate(ctx, &q, map[string]any{ - "email": c.email, - "password": c.password, + "email": ts.email, + "password": ts.password, }); err != nil { return nil, fmt.Errorf("authentication failed: %w", err) } diff --git a/tariff/ostrom.go b/tariff/ostrom.go index d047eb3ae..256e11bd0 100644 --- a/tariff/ostrom.go +++ b/tariff/ostrom.go @@ -85,7 +85,7 @@ func NewOstromFromConfig(other map[string]any) (api.Tariff, error) { t.Client.Transport = &oauth2.Transport{ Base: t.Client.Transport, - Source: oauth.RefreshTokenSource(nil, t), + Source: oauth2.ReuseTokenSource(nil, oauth.BootstrapTokenSource(t.refreshToken)), } contracts, err := t.getContracts() @@ -158,7 +158,7 @@ func (t *Ostrom) getFixedPrice() (float64, error) { return 0, errors.New("tariff not found") } -func (t *Ostrom) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { +func (t *Ostrom) refreshToken() (*oauth2.Token, error) { uri := ostrom.URI_AUTH + "/oauth2/token" data := url.Values{"grant_type": {"client_credentials"}} req, _ := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), map[string]string{ diff --git a/util/oauth/bootstraptokensource.go b/util/oauth/bootstraptokensource.go new file mode 100644 index 000000000..82c291599 --- /dev/null +++ b/util/oauth/bootstraptokensource.go @@ -0,0 +1,25 @@ +package oauth + +import ( + "sync" + + "golang.org/x/oauth2" +) + +type bootstrapTokenSource struct { + mu sync.Mutex + refresher func() (*oauth2.Token, error) +} + +func BootstrapTokenSource(refresher func() (*oauth2.Token, error)) oauth2.TokenSource { + return &bootstrapTokenSource{ + refresher: refresher, + } +} + +func (ts *bootstrapTokenSource) Token() (*oauth2.Token, error) { + ts.mu.Lock() + defer ts.mu.Unlock() + + return ts.refresher() +} diff --git a/util/oauth/tokensource.go b/util/oauth/refreshtokensource.go similarity index 64% rename from util/oauth/tokensource.go rename to util/oauth/refreshtokensource.go index f3aaa8dbc..9a0a98c38 100644 --- a/util/oauth/tokensource.go +++ b/util/oauth/refreshtokensource.go @@ -7,23 +7,19 @@ import ( "golang.org/x/oauth2" ) -type TokenRefresher interface { - RefreshToken(token *oauth2.Token) (*oauth2.Token, error) -} - -type TokenSource struct { +type refreshTokenSource struct { mu sync.Mutex token *oauth2.Token - refresher TokenRefresher + refresher func(token *oauth2.Token) (*oauth2.Token, error) } -func RefreshTokenSource(token *oauth2.Token, refresher TokenRefresher) oauth2.TokenSource { +func RefreshTokenSource(token *oauth2.Token, refresher func(token *oauth2.Token) (*oauth2.Token, error)) oauth2.TokenSource { if token == nil { // allocate an (expired) token or mergeToken will fail token = new(oauth2.Token) } - ts := &TokenSource{ + ts := &refreshTokenSource{ token: token, refresher: refresher, } @@ -31,7 +27,7 @@ func RefreshTokenSource(token *oauth2.Token, refresher TokenRefresher) oauth2.To return ts } -func (ts *TokenSource) Token() (*oauth2.Token, error) { +func (ts *refreshTokenSource) Token() (*oauth2.Token, error) { ts.mu.Lock() defer ts.mu.Unlock() @@ -39,7 +35,7 @@ func (ts *TokenSource) Token() (*oauth2.Token, error) { return ts.token, nil } - token, err := ts.refresher.RefreshToken(ts.token) + token, err := ts.refresher(ts.token) if err != nil { return ts.token, err } diff --git a/util/oauth/tokensource_test.go b/util/oauth/refreshtokensource_test.go similarity index 67% rename from util/oauth/tokensource_test.go rename to util/oauth/refreshtokensource_test.go index af8d24454..ecb4e2721 100644 --- a/util/oauth/tokensource_test.go +++ b/util/oauth/refreshtokensource_test.go @@ -8,24 +8,20 @@ import ( "golang.org/x/oauth2" ) -type tkr struct{} - -func (tkr *tkr) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { - return (&oauth2.Token{ - AccessToken: "new", - }).WithExtra(map[string]any{ - "foo": "bar", - }), nil -} - func TestMerge(t *testing.T) { - ts := &TokenSource{ + ts := &refreshTokenSource{ token: &oauth2.Token{ AccessToken: "access", RefreshToken: "refresh", Expiry: time.Now(), }, - refresher: new(tkr), + refresher: func(_ *oauth2.Token) (*oauth2.Token, error) { + return (&oauth2.Token{ + AccessToken: "new", + }).WithExtra(map[string]any{ + "foo": "bar", + }), nil + }, } r, err := ts.Token() diff --git a/util/token.go b/util/token.go index 17a366af0..09c4737fa 100644 --- a/util/token.go +++ b/util/token.go @@ -6,6 +6,7 @@ import ( "golang.org/x/oauth2" ) +// TokenWithExpiry decorates an oauth2.Token with the expiry from the ExpiresIn property func TokenWithExpiry(token *oauth2.Token) *oauth2.Token { if token != nil && token.Expiry.IsZero() && token.ExpiresIn != 0 { token.Expiry = time.Now().Add(time.Second * time.Duration(token.ExpiresIn)) diff --git a/vehicle/bluelink/identity.go b/vehicle/bluelink/identity.go index 8a7ce661c..13f712872 100644 --- a/vehicle/bluelink/identity.go +++ b/vehicle/bluelink/identity.go @@ -102,8 +102,8 @@ func (v *Identity) getDeviceID() (string, error) { return res.ResMsg.DeviceID, err } -// RefreshToken implements oauth.TokenRefresher -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +// refreshToken renews BlueLink OAuth tokens +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { var res oauth2.Token uri := v.config.LoginFormHost + TokenURL @@ -144,11 +144,11 @@ func (v *Identity) Login(user, password, language, brand string) (err error) { return fmt.Errorf("unknown brand (%s)", brand) } - token, err := v.RefreshToken(&oauth2.Token{RefreshToken: password}) + token, err := v.refreshToken(&oauth2.Token{RefreshToken: password}) if err != nil { return fmt.Errorf("login failed: %w", err) } - v.TokenSource = oauth.RefreshTokenSource(token, v) + v.TokenSource = oauth.RefreshTokenSource(token, v.refreshToken) v.deviceID, err = v.getDeviceID() if err != nil { diff --git a/vehicle/bluelink_us/identity.go b/vehicle/bluelink_us/identity.go index ba1474b24..04d00d790 100644 --- a/vehicle/bluelink_us/identity.go +++ b/vehicle/bluelink_us/identity.go @@ -64,7 +64,7 @@ func (v *Identity) Login() error { return fmt.Errorf("login failed: %w", err) } - v.TokenSource = oauth.RefreshTokenSource(token, v) + v.TokenSource = oauth2.ReuseTokenSource(token, oauth.BootstrapTokenSource(v.login)) return nil } @@ -96,9 +96,3 @@ func (v *Identity) login() (*oauth2.Token, error) { Expiry: time.Now().Add(time.Duration(expiresIn) * time.Second), }, nil } - -// RefreshToken implements oauth.TokenRefresher -// US API doesn't have a refresh endpoint, so we re-login with credentials -func (v *Identity) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { - return v.login() -} diff --git a/vehicle/bmw/connected/identity.go b/vehicle/bmw/connected/identity.go index cceecb601..ddae743d9 100644 --- a/vehicle/bmw/connected/identity.go +++ b/vehicle/bmw/connected/identity.go @@ -49,9 +49,9 @@ func (v *Identity) Login(user, password, hcaptcha string) (oauth2.TokenSource, e var tok oauth2.Token if err := settings.Json(v.settingsKey(), &tok); err == nil { v.log.DEBUG.Println("identity.Login - database token found") - tok, err := v.RefreshToken(&tok) + tok, err := v.refreshToken(&tok) if err == nil { - ts := oauth2.ReuseTokenSourceWithExpiry(tok, oauth.RefreshTokenSource(tok, v), 15*time.Minute) + ts := oauth2.ReuseTokenSourceWithExpiry(tok, oauth.RefreshTokenSource(tok, v.refreshToken), 15*time.Minute) return ts, nil } v.log.DEBUG.Println("identity.Login - database token invalid. Proceeding to login via user, password and captcha.") @@ -150,7 +150,7 @@ func (v *Identity) Login(user, password, hcaptcha string) (oauth2.TokenSource, e return nil, err } - ts := oauth2.ReuseTokenSourceWithExpiry(token, oauth.RefreshTokenSource(token, v), 15*time.Minute) + ts := oauth2.ReuseTokenSourceWithExpiry(token, oauth.RefreshTokenSource(token, v.refreshToken), 15*time.Minute) return ts, nil } @@ -179,7 +179,7 @@ func (v *Identity) retrieveToken(data url.Values) (*oauth2.Token, error) { return tokex, err } -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { data := url.Values{ "redirect_uri": {RedirectURI}, "refresh_token": {token.RefreshToken}, diff --git a/vehicle/jlr/identity.go b/vehicle/jlr/identity.go index f3b37372b..7990f433b 100644 --- a/vehicle/jlr/identity.go +++ b/vehicle/jlr/identity.go @@ -71,14 +71,14 @@ func (v *Identity) Login() (Token, error) { token, err := v.login(data) if err == nil { - v.TokenSource = oauth.RefreshTokenSource(&token.Token, v) + v.TokenSource = oauth.RefreshTokenSource(&token.Token, v.refreshToken) } return token, err } -// RefreshToken implements oauth.TokenRefresher -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +// refreshToken renews the JLR token +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { data := map[string]string{ "grant_type": "refresh_token", "refresh_token": token.RefreshToken, diff --git a/vehicle/mercedes/identity.go b/vehicle/mercedes/identity.go index f6c782c48..1ec34dfda 100644 --- a/vehicle/mercedes/identity.go +++ b/vehicle/mercedes/identity.go @@ -75,14 +75,14 @@ func NewIdentity(log *util.Logger, token *oauth2.Token, account string, region s if !token.Valid() && token.RefreshToken != "" { v.log.DEBUG.Println("identity.NewIdentity - refreshToken started") - if tok, err := v.RefreshToken(token); err == nil { + if tok, err := v.refreshToken(token); err == nil { token = tok } } if !token.Valid() { v.log.DEBUG.Println("identity.NewIdentity - config refreshToken started") - if tok, err := v.RefreshToken(configToken); err == nil { + if tok, err := v.refreshToken(configToken); err == nil { token = tok } } @@ -91,7 +91,7 @@ func NewIdentity(log *util.Logger, token *oauth2.Token, account string, region s return nil, errors.New("token expired") } - v.TokenSource = oauth.RefreshTokenSource(token, v) + v.TokenSource = oauth.RefreshTokenSource(token, v.refreshToken) // add instance addInstance(account, v) @@ -103,7 +103,7 @@ func (v *Identity) settingsKey() string { return fmt.Sprintf("mercedes.%s-%s", v.account, v.region) } -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { v.mu.Lock() defer v.mu.Unlock() @@ -125,7 +125,7 @@ func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { } tok := util.TokenWithExpiry(&res) - v.TokenSource = oauth.RefreshTokenSource(tok, v) + v.TokenSource = oauth.RefreshTokenSource(tok, v.refreshToken) err := settings.SetJson(v.settingsKey(), tok) diff --git a/vehicle/polestar/identity.go b/vehicle/polestar/identity.go index 4c72a52af..00a0f2579 100644 --- a/vehicle/polestar/identity.go +++ b/vehicle/polestar/identity.go @@ -57,7 +57,7 @@ func NewIdentity(log *util.Logger, user, password string) (oauth2.TokenSource, e return nil, err } - v.TokenSource = oauth.RefreshTokenSource(token, v) + v.TokenSource = oauth.RefreshTokenSource(token, v.refreshToken) return v, nil } @@ -149,8 +149,8 @@ func (v *Identity) login() (*oauth2.Token, error) { return util.TokenWithExpiry(&token), nil } -// Token implements oauth.TokenSource -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +// refreshToken obtains a renewed oauth token +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { data := url.Values{ "grant_type": {"refresh_token"}, "refresh_token": {token.RefreshToken}, diff --git a/vehicle/porsche/identity.go b/vehicle/porsche/identity.go index 139906ffe..afac56bf1 100644 --- a/vehicle/porsche/identity.go +++ b/vehicle/porsche/identity.go @@ -67,7 +67,7 @@ func NewIdentity(log *util.Logger, user, password string) (oauth2.TokenSource, e return nil, fmt.Errorf("login failed: %w", err) } - return oauth.RefreshTokenSource(token, v), nil + return oauth.RefreshTokenSource(token, v.refreshToken), nil } func (v *Identity) login() (*oauth2.Token, error) { @@ -168,7 +168,7 @@ func (v *Identity) login() (*oauth2.Token, error) { return OAuth2Config.Exchange(ctx, code, oauth2.VerifierOption(cv)) } -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { ctx := context.WithValue(context.Background(), oauth2.HTTPClient, v.Client) ts := oauth2.ReuseTokenSource(token, OAuth2Config.TokenSource(ctx, token)) diff --git a/vehicle/psa/identity.go b/vehicle/psa/identity.go index 67d1e39bc..f954d6a82 100644 --- a/vehicle/psa/identity.go +++ b/vehicle/psa/identity.go @@ -45,7 +45,7 @@ func NewIdentity(log *util.Logger, brand, user string, oc *oauth2.Config, token } if !token.Valid() { - if tok, err := v.RefreshToken(token); err == nil { + if tok, err := v.refreshToken(token); err == nil { token = tok } } @@ -54,7 +54,7 @@ func NewIdentity(log *util.Logger, brand, user string, oc *oauth2.Config, token return nil, errors.New("token expired") } - v.TokenSource = oauth.RefreshTokenSource(token, v) + v.TokenSource = oauth.RefreshTokenSource(token, v.refreshToken) // add instance addInstance(v.subject, v) @@ -62,7 +62,7 @@ func NewIdentity(log *util.Logger, brand, user string, oc *oauth2.Config, token return v, nil } -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { v.mu.Lock() defer v.mu.Unlock() @@ -77,7 +77,7 @@ func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { return nil, err } - v.TokenSource = oauth.RefreshTokenSource(tok, v) + v.TokenSource = oauth.RefreshTokenSource(tok, v.refreshToken) err = settings.SetJson(v.subject, tok) return tok, err diff --git a/vehicle/saic/identity.go b/vehicle/saic/identity.go index b3654feac..aba8fb97c 100644 --- a/vehicle/saic/identity.go +++ b/vehicle/saic/identity.go @@ -56,7 +56,7 @@ func (v *Identity) Login() error { return err } - v.TokenSource = oauth2.ReuseTokenSourceWithExpiry(token, oauth.RefreshTokenSource(token, v), 15*time.Minute) + v.TokenSource = oauth2.ReuseTokenSourceWithExpiry(token, oauth.RefreshTokenSource(token, v.refreshToken), 15*time.Minute) return nil } @@ -112,7 +112,7 @@ func (v *Identity) retrieveToken(data url.Values) (*oauth2.Token, error) { return nil, err } -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { data := url.Values{ "refresh_token": {token.RefreshToken}, "grant_type": {"refresh_token"}, diff --git a/vehicle/smart/hello/identity.go b/vehicle/smart/hello/identity.go index 1a90ebc07..33e7740a6 100644 --- a/vehicle/smart/hello/identity.go +++ b/vehicle/smart/hello/identity.go @@ -30,14 +30,14 @@ func NewIdentity(log *util.Logger, user, password string) (*Identity, error) { deviceID: lo.RandomString(16, lo.AlphanumericCharset), } - v.TokenSource = oauth.RefreshTokenSource(nil, v) + v.TokenSource = oauth2.ReuseTokenSource(nil, oauth.BootstrapTokenSource(v.refreshToken)) _, err := v.Token() return v, err } -func (v *Identity) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { +func (v *Identity) refreshToken() (*oauth2.Token, error) { token, err := v.login() if err != nil { return nil, err diff --git a/vehicle/subaru/identity.go b/vehicle/subaru/identity.go index b2eb2cbee..e772e0f28 100644 --- a/vehicle/subaru/identity.go +++ b/vehicle/subaru/identity.go @@ -149,11 +149,11 @@ func (v *Identity) fetchTokenCredentials(code string) error { } v.uuid = uuid - v.TokenSource = oauth.RefreshTokenSource(util.TokenWithExpiry(&res.Token), v) + v.TokenSource = oauth.RefreshTokenSource(util.TokenWithExpiry(&res.Token), v.refreshToken) return nil } -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { uri := fmt.Sprintf("%s/%s", BaseUrl, AccessTokenPath) data := url.Values{ "client_id": {ClientID}, diff --git a/vehicle/tesla/identity.go b/vehicle/tesla/identity.go index 8640f2ba0..b36977b97 100644 --- a/vehicle/tesla/identity.go +++ b/vehicle/tesla/identity.go @@ -74,7 +74,7 @@ func NewIdentity(log *util.Logger, oc *oauth2.Config, token *oauth2.Token) (oaut } if !token.Valid() && token.RefreshToken != "" { - if tok, err := v.RefreshToken(token); err == nil { + if tok, err := v.refreshToken(token); err == nil { token = tok } } @@ -83,7 +83,7 @@ func NewIdentity(log *util.Logger, oc *oauth2.Config, token *oauth2.Token) (oaut return nil, errors.New("token expired") } - v.TokenSource = oauth.RefreshTokenSource(token, v) + v.TokenSource = oauth.RefreshTokenSource(token, v.refreshToken) // add instance addInstance(claims.Subject, v) @@ -95,7 +95,7 @@ func (v *Identity) settingsKey() string { return fmt.Sprintf("tesla-command.%s", v.subject) } -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { v.mu.Lock() defer v.mu.Unlock() diff --git a/vehicle/toyota/identity.go b/vehicle/toyota/identity.go index 1dc7302f7..3583f6c2a 100644 --- a/vehicle/toyota/identity.go +++ b/vehicle/toyota/identity.go @@ -138,11 +138,11 @@ func (v *Identity) fetchTokenCredentials(code string) error { } v.uuid = uuid - v.TokenSource = oauth.RefreshTokenSource(util.TokenWithExpiry(&res.Token), v) + v.TokenSource = oauth.RefreshTokenSource(util.TokenWithExpiry(&res.Token), v.refreshToken) return nil } -func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +func (v *Identity) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { uri := fmt.Sprintf("%s/%s", BaseUrl, AccessTokenPath) data := url.Values{ "client_id": {ClientID}, diff --git a/vehicle/tronity.go b/vehicle/tronity.go index 02ff7948a..2193d81ce 100644 --- a/vehicle/tronity.go +++ b/vehicle/tronity.go @@ -27,7 +27,6 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" - "github.com/evcc-io/evcc/util/oauth" "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/sponsor" "github.com/evcc-io/evcc/vehicle/tronity" @@ -95,7 +94,7 @@ func NewTronityFromConfig(other map[string]any) (api.Vehicle, error) { // https://app.platform.tronity.io/docs#tag/Authentication if err != nil { // use app flow if we don't have tokens - ts = oauth.RefreshTokenSource(nil, v) + ts = tronity.TokenSource(log, oc) } else { // use provided tokens generated by code flow ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewClient(log)) @@ -139,26 +138,6 @@ func NewTronityFromConfig(other map[string]any) (api.Vehicle, error) { return decorateTronity(v, status, odometer, chargeEnable), nil } -// RefreshToken performs token refresh by logging in with app context -func (v *Tronity) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { - data := struct { - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - GrantType string `json:"grant_type"` - }{ - ClientID: v.oc.ClientID, - ClientSecret: v.oc.ClientSecret, - GrantType: "app", - } - - req, _ := request.New(http.MethodPost, v.oc.Endpoint.TokenURL, request.MarshalJSON(data), request.JSONEncoding) - - var token oauth2.Token - err := request.NewHelper(v.log).DoJSON(req, &token) - - return util.TokenWithExpiry(&token), err -} - // vehicles implements the vehicles api func (v *Tronity) vehicles() ([]tronity.Vehicle, error) { uri := fmt.Sprintf("%s/tronity/vehicles", tronity.URI) diff --git a/vehicle/tronity/tokensource.go b/vehicle/tronity/tokensource.go new file mode 100644 index 000000000..e5b0ed1b8 --- /dev/null +++ b/vehicle/tronity/tokensource.go @@ -0,0 +1,37 @@ +package tronity + +import ( + "net/http" + + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/request" + "golang.org/x/oauth2" +) + +type tokenSource struct { + log *util.Logger + oc *oauth2.Config +} + +func TokenSource(log *util.Logger, oc *oauth2.Config) oauth2.TokenSource { + return oauth2.ReuseTokenSource(nil, &tokenSource{log, oc}) +} + +func (ts *tokenSource) Token() (*oauth2.Token, error) { + data := struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + GrantType string `json:"grant_type"` + }{ + ClientID: ts.oc.ClientID, + ClientSecret: ts.oc.ClientSecret, + GrantType: "app", + } + + req, _ := request.New(http.MethodPost, ts.oc.Endpoint.TokenURL, request.MarshalJSON(data), request.JSONEncoding) + + var token oauth2.Token + err := request.NewHelper(ts.log).DoJSON(req, &token) + + return util.TokenWithExpiry(&token), err +} diff --git a/vehicle/vag/loginapps/endpoint.go b/vehicle/vag/loginapps/endpoint.go index 46f2edcdc..c172eff42 100644 --- a/vehicle/vag/loginapps/endpoint.go +++ b/vehicle/vag/loginapps/endpoint.go @@ -84,13 +84,13 @@ func (v *Service) Refresh(token *Token) (*Token, error) { return &t, err } -// RefreshToken implements oauth.TokenRefresher -func (v *Service) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { +// refreshToken renews the LoginApps token +func (v *Service) refreshToken(token *oauth2.Token) (*oauth2.Token, error) { res, err := v.Refresh((*Token)(token)) return (*oauth2.Token)(res), err } // TokenSource creates a refreshing oauth2 token source func (v *Service) TokenSource(token *Token) oauth2.TokenSource { - return oauth.RefreshTokenSource((*oauth2.Token)(token), v) + return oauth.RefreshTokenSource((*oauth2.Token)(token), v.refreshToken) }