From 1b1a48cf998af52edf02f5c6d0e28cdcdd9461de Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 18 May 2020 13:41:22 +0200 Subject: [PATCH] Implement token refresh (#146) --- util/http.go | 8 ++++++++ vehicle/renault.go | 47 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/util/http.go b/util/http.go index 7e4c1f52c..f63dcce88 100644 --- a/util/http.go +++ b/util/http.go @@ -13,6 +13,7 @@ import ( type HTTPHelper struct { Log *Logger Client *http.Client + last *http.Response // last response } // NewHTTPHelper creates http helper for simplified PUT GET logic @@ -24,8 +25,14 @@ func NewHTTPHelper(log *Logger) *HTTPHelper { return r } +// LastResponse returns last http.Response that was read without error +func (r *HTTPHelper) LastResponse() *http.Response { + return r.last +} + // Response codes other than HTTP 200 or 204 are raised as error func (r *HTTPHelper) readBody(resp *http.Response, err error) ([]byte, error) { + r.last = nil if err != nil { return []byte{}, err } @@ -40,6 +47,7 @@ func (r *HTTPHelper) readBody(resp *http.Response, err error) ([]byte, error) { r.Log.TRACE.Printf("%s\n%s", resp.Request.URL.String(), string(b)) } + r.last = resp if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { return b, fmt.Errorf("unexpected response %d: %s", resp.StatusCode, string(b)) } diff --git a/vehicle/renault.go b/vehicle/renault.go index 7ce1ef0dd..715ece556 100644 --- a/vehicle/renault.go +++ b/vehicle/renault.go @@ -169,12 +169,11 @@ func (v *Renault) authFlow() error { } } - if err == nil { - v.kamereonAccessToken, err = v.kamereonToken(v.accountID) - if v.kamereonAccessToken == "" { - return errors.New("missing kamereon access token") - } + token, err := v.kamereonToken(v.accountID) + if err != nil || token == "" { + return fmt.Errorf("refreshing kamereon access token failed: %v", err) } + v.kamereonAccessToken = token } } } @@ -323,18 +322,44 @@ func (v *Renault) kamereonToken(accountID string) (string, error) { return kr.AccessToken, err } +func (v *Renault) kamereonRequest(uri string) (*http.Request, error) { + if v.kamereonAccessToken == "" { + if err := v.authFlow(); err != nil { + return nil, err + } + } + + data := url.Values{"country": []string{"DE"}} + headers := v.kamereonHeaders(map[string]string{"x-kamereon-authorization": "Bearer " + v.kamereonAccessToken}) + + return v.request(uri, data, headers) +} + +// doJSONRequest executes request and handles token expiry +func (v *Renault) doKamereonRequest(uri string, kr interface{}) error { + req, err := v.kamereonRequest(uri) + if err == nil { + _, err = v.RequestJSON(req, &kr) + if err != nil { + if resp := v.LastResponse(); resp != nil && resp.StatusCode == http.StatusUnauthorized { + v.kamereonAccessToken = "" + } + } + } + return err +} + // chargeState implements the Vehicle.ChargeState interface func (v *Renault) chargeState() (float64, error) { var kr kamereonResponse uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v1/cars/%s/battery-status", v.kamereon.Target, v.accountID, v.vin) - data := url.Values{"country": []string{"DE"}} - headers := v.kamereonHeaders(map[string]string{"x-kamereon-authorization": "Bearer " + v.kamereonAccessToken}) - - req, err := v.request(uri, data, headers) - if err == nil { - _, err = v.RequestJSON(req, &kr) + // do request with retry + err := v.doKamereonRequest(uri, &kr) + if err != nil && v.kamereonAccessToken == "" { + err = v.doKamereonRequest(uri, &kr) } + return float64(kr.Data.Attributes.BatteryLevel), err }