diff --git a/vehicle/toyota/identity.go b/vehicle/toyota/identity.go index 63d35ba6c0..da939f65ad 100644 --- a/vehicle/toyota/identity.go +++ b/vehicle/toyota/identity.go @@ -5,8 +5,10 @@ import ( "net/http" "net/url" "strings" + "time" "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/oauth" "github.com/evcc-io/evcc/util/request" "github.com/golang-jwt/jwt/v5" "golang.org/x/oauth2" @@ -106,7 +108,6 @@ func (v *Identity) fetchTokenCredentials(code string) error { } headers := request.URLEncoding - headers["Authorization"] = "Basic b25lYXBwOm9uZWFwcA==" req, err := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), headers) if err != nil { return err @@ -120,6 +121,8 @@ func (v *Identity) fetchTokenCredentials(code string) error { return fmt.Errorf("failed to fetch token credentials: %w", err) } + resp.Expiry = time.Now().Add(time.Duration(resp.ExpiresIn) * time.Second) + // Parse ID token without verification to extract UUID parser := jwt.NewParser(jwt.WithoutClaimsValidation()) token, _, err := parser.ParseUnverified(resp.IDToken, jwt.MapClaims{}) @@ -138,10 +141,27 @@ func (v *Identity) fetchTokenCredentials(code string) error { } v.uuid = uuid - v.TokenSource = oauth2.StaticTokenSource(&resp.Token) + v.TokenSource = oauth.RefreshTokenSource(&resp.Token, v) return nil } +func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { + uri := fmt.Sprintf("%s/%s", BaseUrl, AccessTokenPath) + data := url.Values{ + "client_id": {ClientID}, + "redirect_uri": {RedirectURI}, + "grant_type": {"refresh_token"}, + "code_verifier": {"plain"}, + "refresh_token": {token.RefreshToken}, + } + var res oauth2.Token + req, _ := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), request.URLEncoding) + if err := v.DoJSON(req, &res); err != nil { + return nil, err + } + return oauth.RefreshTokenSource(&res, v).Token() +} + func (v *Identity) Login(user, password string) error { uri := fmt.Sprintf("%s/%s", BaseUrl, AuthenticationPath) req, err := request.New(http.MethodPost, uri, nil, map[string]string{ diff --git a/vehicle/toyota/identity_test.go b/vehicle/toyota/identity_test.go index 8082a870da..8a432b0b98 100644 --- a/vehicle/toyota/identity_test.go +++ b/vehicle/toyota/identity_test.go @@ -19,6 +19,7 @@ func TestIdentityLogin(t *testing.T) { t.Fatal("TOYOTA_USER or TOYOTA_PASSWORD not set") } + util.LogLevel("trace", nil) // Enable trace logging log := util.NewLogger("test") identity := NewIdentity(log) @@ -31,4 +32,16 @@ func TestIdentityLogin(t *testing.T) { require.NotEmpty(t, token.AccessToken) require.NotEmpty(t, token.RefreshToken) require.True(t, token.Valid()) + + originalAccessToken := token.AccessToken + + // Test token refresh + newToken, err := identity.RefreshToken(token) + require.NoError(t, err) + require.NotEmpty(t, newToken.AccessToken) + require.NotEmpty(t, newToken.RefreshToken) + require.True(t, newToken.Valid()) + + // Verify we got a new access token + require.NotEqual(t, originalAccessToken, newToken.AccessToken, "Expected new access token after refresh") }