From 10b9f318c74749d11ee4cc992bbb3a7c34a36e6f Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 11 Jan 2025 17:15:32 +0100 Subject: [PATCH] Tesla: require personal developer account (#17982) --- .github/workflows/nightly.yml | 3 -- .github/workflows/release.yml | 2 -- .goreleaser-nightly.yml | 6 ++-- .goreleaser.yml | 6 ++-- api/error.go | 5 ++- charger/template_test.go | 4 ++- meter/tapo/connection.go | 3 +- meter/template_test.go | 5 +-- meter/tibber-pulse.go | 3 +- tariff/amber.go | 2 +- tariff/template_test.go | 12 ++++--- tariff/tibber.go | 3 +- templates/definition/vehicle/tesla.yaml | 41 +++++++++++++++++------ vehicle/niu/types.go | 3 +- vehicle/template_test.go | 13 ++++---- vehicle/tesla.go | 36 ++++++++------------- vehicle/tesla/controller.go | 11 +------ vehicle/tesla/identity.go | 43 +++++++++++-------------- vehicle/types.go | 7 ++-- 19 files changed, 105 insertions(+), 103 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f1985d8f6f..e60cdeafd1 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -68,8 +68,6 @@ jobs: context: . platforms: linux/amd64,linux/arm64,linux/arm/v6 push: true - build-args: | - TESLA_CLIENT_ID=${{ secrets.TESLA_CLIENT_ID }} tags: | evcc/evcc:nightly @@ -106,7 +104,6 @@ jobs: args: --snapshot -f .goreleaser-nightly.yml --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TESLA_CLIENT_ID: ${{ secrets.TESLA_CLIENT_ID }} - uses: actions/setup-python@v5 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb7662f8d3..06318113e1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,6 @@ jobs: push: true build-args: | RELEASE=1 - TESLA_CLIENT_ID=${{ secrets.TESLA_CLIENT_ID }} tags: ${{ steps.meta.outputs.tags }} apt: @@ -100,7 +99,6 @@ jobs: env: # use GH_TOKEN for access to evcc-io/homebrew-tap GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - TESLA_CLIENT_ID: ${{ secrets.TESLA_CLIENT_ID }} - uses: actions/setup-python@v5 with: diff --git a/.goreleaser-nightly.yml b/.goreleaser-nightly.yml index bba2360ceb..7438c6fe4d 100644 --- a/.goreleaser-nightly.yml +++ b/.goreleaser-nightly.yml @@ -11,7 +11,7 @@ builds: - -trimpath - -tags=release ldflags: - - -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w + - -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -s -w env: - CGO_ENABLED=0 goos: @@ -36,11 +36,11 @@ builds: - goos: darwin goarch: arm64 ldflags: - - -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w -B gobuildid + - -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -s -w -B gobuildid - goos: darwin goarch: amd64 ldflags: - - -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w -B gobuildid + - -X github.com/evcc-io/evcc/server.Version={{ .Tag }} -X github.com/evcc-io/evcc/server.Commit={{ .ShortCommit }} -s -w -B gobuildid archives: - builds: diff --git a/.goreleaser.yml b/.goreleaser.yml index 344981cac9..a840887884 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -14,7 +14,7 @@ builds: - -trimpath - -tags=release ldflags: - - -X github.com/evcc-io/evcc/server.Version={{ .Version }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w + - -X github.com/evcc-io/evcc/server.Version={{ .Version }} -s -w env: - CGO_ENABLED=0 goos: @@ -41,11 +41,11 @@ builds: - goos: darwin goarch: arm64 ldflags: - - -X github.com/evcc-io/evcc/server.Version={{ .Version }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w -B gobuildid + - -X github.com/evcc-io/evcc/server.Version={{ .Version }} -s -w -B gobuildid - goos: darwin goarch: amd64 ldflags: - - -X github.com/evcc-io/evcc/server.Version={{ .Version }} -X github.com/evcc-io/evcc/vehicle/tesla.TESLA_CLIENT_ID={{ .Env.TESLA_CLIENT_ID }} -s -w -B gobuildid + - -X github.com/evcc-io/evcc/server.Version={{ .Version }} -s -w -B gobuildid env: - CGO_ENABLED=0 diff --git a/api/error.go b/api/error.go index ce4d78accd..72b07b9703 100644 --- a/api/error.go +++ b/api/error.go @@ -12,7 +12,10 @@ var ErrMustRetry = errors.New("must retry") var ErrSponsorRequired = errors.New("sponsorship required, see https://github.com/evcc-io/evcc#sponsorship") // ErrMissingCredentials indicates that user/password are missing -var ErrMissingCredentials = errors.New("missing credentials") +var ErrMissingCredentials = errors.New("missing user/password credentials") + +// ErrMissingToken indicates that access/refresh tokens are missing +var ErrMissingToken = errors.New("missing token credentials") // ErrOutdated indicates that result is outdated var ErrOutdated = errors.New("outdated") diff --git a/charger/template_test.go b/charger/template_test.go index 15dbf95085..9b0e6c38aa 100644 --- a/charger/template_test.go +++ b/charger/template_test.go @@ -4,11 +4,14 @@ import ( "context" "testing" + "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util/templates" "github.com/evcc-io/evcc/util/test" ) var acceptable = []string{ + api.ErrMissingCredentials.Error(), + api.ErrMissingToken.Error(), "invalid plugin source: ...", "missing mqtt broker configuration", "mqtt not configured", @@ -29,7 +32,6 @@ var acceptable = []string{ "sponsorship required, see https://github.com/evcc-io/evcc#sponsorship", "eebus not configured", "context deadline exceeded", - "missing credentials", "timeout", // ocpp "must have uri and password", // Wattpilot } diff --git a/meter/tapo/connection.go b/meter/tapo/connection.go index d932ae2a4a..2431aeb38c 100644 --- a/meter/tapo/connection.go +++ b/meter/tapo/connection.go @@ -6,6 +6,7 @@ import ( "net/url" "strings" + "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/insomniacslk/tapo" ) @@ -33,7 +34,7 @@ func NewConnection(uri, user, password string) (*Connection, error) { } if user == "" || password == "" { - return nil, fmt.Errorf("missing user or password") + return nil, api.ErrMissingCredentials } log := util.NewLogger("tapo").Redact(user, password) diff --git a/meter/template_test.go b/meter/template_test.go index 982572ab66..0cdae033f3 100644 --- a/meter/template_test.go +++ b/meter/template_test.go @@ -4,18 +4,19 @@ import ( "context" "testing" + "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util/templates" "github.com/evcc-io/evcc/util/test" ) var acceptable = []string{ + api.ErrMissingCredentials.Error(), + api.ErrMissingToken.Error(), "invalid plugin source: ...", "missing mqtt broker configuration", - "missing token", "mqtt not configured", "not a SunSpec device", "connect: connection refused", // sockets - "missing credentials", // sockets "power: timeout", // sockets "missing password", // Powerwall "connect: no route to host", diff --git a/meter/tibber-pulse.go b/meter/tibber-pulse.go index bf6abe02cb..6e1f36bce8 100644 --- a/meter/tibber-pulse.go +++ b/meter/tibber-pulse.go @@ -3,7 +3,6 @@ package meter import ( "context" "encoding/json" - "errors" "net/http" "strings" "time" @@ -38,7 +37,7 @@ func NewTibberFromConfig(ctx context.Context, other map[string]interface{}) (api } if cc.Token == "" { - return nil, errors.New("missing token") + return nil, api.ErrMissingToken } log := util.NewLogger("pulse").Redact(cc.Token, cc.HomeID) diff --git a/tariff/amber.go b/tariff/amber.go index 7fd6e1ef21..fd00d833b4 100644 --- a/tariff/amber.go +++ b/tariff/amber.go @@ -44,7 +44,7 @@ func NewAmberFromConfig(other map[string]interface{}) (api.Tariff, error) { } if cc.Token == "" { - return nil, errors.New("missing token") + return nil, api.ErrMissingToken } if cc.SiteID == "" { diff --git a/tariff/template_test.go b/tariff/template_test.go index 53f8684b9f..189fcc6df5 100644 --- a/tariff/template_test.go +++ b/tariff/template_test.go @@ -4,16 +4,18 @@ import ( "context" "testing" + "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util/templates" "github.com/evcc-io/evcc/util/test" ) var acceptable = []string{ - "missing token", // amber, tibber - "invalid zipcode", // grünstromindex - "invalid apikey format", // octopusenergy - "missing region", // octopusenergy - "missing securitytoken", // entsoe + api.ErrMissingCredentials.Error(), + api.ErrMissingToken.Error(), + "invalid zipcode", // grünstromindex + "invalid apikey format", // octopusenergy + "missing region", // octopusenergy + "missing securitytoken", // entsoe "cannot define region and postcode simultaneously", // ngeso } diff --git a/tariff/tibber.go b/tariff/tibber.go index 155d10829b..d79c8edf6f 100644 --- a/tariff/tibber.go +++ b/tariff/tibber.go @@ -2,7 +2,6 @@ package tariff import ( "context" - "errors" "slices" "sync" "time" @@ -41,7 +40,7 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Tariff, error) { } if cc.Token == "" { - return nil, errors.New("missing token") + return nil, api.ErrMissingToken } if err := cc.init(); err != nil { diff --git a/templates/definition/vehicle/tesla.yaml b/templates/definition/vehicle/tesla.yaml index 178053a879..7c2bc7c2e0 100644 --- a/templates/definition/vehicle/tesla.yaml +++ b/templates/definition/vehicle/tesla.yaml @@ -5,22 +5,39 @@ products: requirements: description: de: | - Benötigt `access` und `refresh` Tokens. Diese können über [tesla.evcc.io](https://tesla.evcc.io) erstellt werden. - Die Steuerung von Fahrzeugen im Zusammenspiel mit einem Tesla Wall Connector erfolgt über einen evcc Proxy-Server und benötigt ein Sponsor Token. Der virtuelle evcc Schlüssel muss auf dem Fahrzeug installiert sein. - Siehe [tesla.evcc.io](https://tesla.evcc.io). + Tesla bietet eine offizielle, aber kostenpflichtige Fahrzeug-API an. + Für private Nutzung kannst du dir einen [Tesla Developer Account](https://developer.tesla.com/) erstellen und erhältst ein monatliches API-Guthaben von $10. Das ist für die gängigen evcc-Anwendungsfälle in der Regel ausreichend. + + Die Anleitung von [myteslamate.com](https://www.myteslamate.com/tesla-api-application-registration/) erklärt den Prozess und generiert dir kostenfrei die für evcc benötigten Access- und Refresh-Token. + Mit diesem Tokenpaar und deiner im Tesla Developer Account erstellten Client ID kann evcc direkt mit der Tesla API kommunizieren. Dein verbrauchtes Guthaben kannst du bei Tesla einsehen. + + Für die Nutzung des "Tesla Wall Connectors" benötigst du einen öffentlichen Command-Proxy-Server. [myteslamate.com](https://app.myteslamate.com/) stellt diesen Dienst für 12€/Jahr bereit. Konfiguriere dafür bei myteslamate.com die Command-Berechtigungen und trage das Proxy-Token hier ein. Start-, Stopp- und Stromstärken-Kommandos werden über diesen Proxy an Tesla geschickt. en: | - Tesla `access` and `refresh` tokens are required. These can be generated through [tesla.evcc.io](https://tesla.evcc.io). - Controlling vehicles in conjunction with a Tesla Wall Connector is done via an evcc proxy server and requires a sponsor token. The evcc virtual key must be installed on the vehicle. - See [tesla.evcc.io](https://tesla.evcc.io). + Tesla offers an official, but paid vehicle API. + For private use, you can create a [Tesla Developer Account](https://developer.tesla.com/) and receive a monthly API credit of $10. This is usually sufficient for the common evcc use cases. + + The [myteslamate.com](https://www.myteslamate.com/tesla-api-application-registration/) guide explains the process and generates a free Access and Refresh Token. With this token pair and your Client ID created in the Tesla Developer Account, evcc can directly communicate with the Tesla API. You can see your used credit at Tesla. + + To use a Tesla Wall Connector, you need a public Command Proxy Server. [myteslamate.com](https://app.myteslamate.com/) provides this service for 12€/year. Configure the Command permissions at myteslamate.com and enter the Proxy Token here. Start, stopp and current commands are sent to Tesla via this proxy. evcc: ["sponsorship"] params: - preset: vehicle-common - name: clientId + help: + en: Client ID of your [Tesla Developer App](https://developer.tesla.com/dashboard). + de: Kunden ID deiner [Tesla Developer App](https://developer.tesla.com/dashboard). + required: true advanced: true - name: accessToken + help: + en: From [myteslamate.com](https://app.myteslamate.com/). + de: Von [myteslamate.com](https://app.myteslamate.com/). required: true mask: true - name: refreshToken + help: + en: From [myteslamate.com](https://app.myteslamate.com/). + de: Von [myteslamate.com](https://app.myteslamate.com/). required: true mask: true - name: vin @@ -28,11 +45,16 @@ params: - name: control deprecated: true - name: commandProxy - default: https://tesla.evcc.io/ + default: https://api.myteslamate.com + advanced: true + help: + en: "When using a TWC3 (or other 'dumb' charger not capable of control), evcc can manage the charge directly by communicating with the vehicle through a Command Proxy. By default the [myteslamate.com](https://app.myteslamate.com/) proxy is used. With this parameter, you set the base URL of a custom Command Proxy. See for example [TeslaBleHttpProxy](https://github.com/wimaha/TeslaBleHttpProxy) for a proxy sending commands via bluetooth." + de: "Bei Verwendung eines TWC3 (oder eines anderen 'dummen' Ladegeräts, das nicht steuerbar ist) kann evcc die Ladung direkt verwalten, indem es über einen Command Proxy mit dem Fahrzeug kommuniziert. Standardmäßig wird der [myteslamate.com](https://app.myteslamate.com/) Proxy verwendet. Mit diesem Parameter kannst du die Basis-URL ändern. Siehe zum Beispiel [TeslaBleHttpProxy](https://github.com/wimaha/TeslaBleHttpProxy) für einen Proxy, der Kommandos über Bluetooth sendet." + - name: proxyToken advanced: true help: - en: "When using a TWC3 (or other 'dumb' charger not capable of control), evcc can manage the charge directly by communicating with the vehicle through a Command Proxy. By default, the proxy provided by evcc is used. With this parameter, you set the base URL of a custom Command Proxy to use instead of the default evcc one. See for example https://github.com/wimaha/TeslaBleHttpProxy for a proxy sending commands via bluetooth." - de: "Bei Verwendung eines TWC3 (oder eines anderen 'dummen' Ladegeräts, das nicht steuerbar ist) kann evcc die Ladung direkt verwalten, indem es über einen Command Proxy mit dem Fahrzeug kommuniziert. Standardmäßig wird der von evcc bereitgestellte Proxy verwendet. Dieses parameter setzt die Basis-URL eines benutzerdefinierten Command Proxy, der anstelle des standardmäßigen evcc-Proxy verwendet werden soll. Siehe zum Beispiel https://github.com/wimaha/TeslaBleHttpProxy für einen Proxy, der Kommandos über Bluetooth sendet." + en: Token for the [myteslamate.com](https://app.myteslamate.com/) command proxy (12€/year fee). Ensure, that you've installed their Virtual Key and granted 'Charge Start', 'Charge Stop' and 'Set Charging Amps' permissions. + de: Token für den Command Proxy von [myteslamate.com](https://app.myteslamate.com/) (12€/Jahr). Stelle sicher, dass du den Virtual Key installiert hast und die Berechtigungen 'Ladung starten', 'Ladung stoppen' und 'Ladestrom setzen' erteilt hast. - name: cache default: 15m render: | @@ -44,6 +66,7 @@ render: | access: {{ .accessToken }} refresh: {{ .refreshToken }} commandProxy: {{ .commandProxy }} + proxyToken: {{ .proxyToken }} {{ include "vehicle-common" . }} features: ["coarsecurrent"] cache: {{ .cache }} diff --git a/vehicle/niu/types.go b/vehicle/niu/types.go index cf13147009..e4729d4c75 100644 --- a/vehicle/niu/types.go +++ b/vehicle/niu/types.go @@ -5,6 +5,7 @@ import ( "errors" "time" + "github.com/evcc-io/evcc/api" "golang.org/x/oauth2" ) @@ -35,7 +36,7 @@ func (t *Token) UnmarshalJSON(data []byte) error { if msg := res.Data.Desc; msg != "" { return errors.New(msg) } - return errors.New("missing token") + return api.ErrMissingToken } (*t) = (Token)(res.Data.Token.Token) diff --git a/vehicle/template_test.go b/vehicle/template_test.go index f542b0cc7c..07f6ac96de 100644 --- a/vehicle/template_test.go +++ b/vehicle/template_test.go @@ -4,11 +4,15 @@ import ( "context" "testing" + "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util/templates" "github.com/evcc-io/evcc/util/test" ) var acceptable = []string{ + api.ErrMissingCredentials.Error(), + api.ErrMissingToken.Error(), + "missing client id", "invalid plugin source: ...", "missing mqtt broker configuration", "received status code 404 (INVALID PARAMS)", // Nissan @@ -20,12 +24,9 @@ var acceptable = []string{ "network is unreachable", "error connecting: Network Error", "unexpected status: 401", - "missing credentials", // Tesla - "missing credentials id", // Tronity - "missing access and/or refresh token, use `evcc token` to create", // Tesla - "login failed: code not found", // Polestar - "empty instance type- check for missing usage", // Merces - "invalid vehicle type: tesla", // Tesla + "discussions/17501", // Tesla + "login failed: code not found", // Polestar + "empty instance type- check for missing usage", // Merces } func TestTemplates(t *testing.T) { diff --git a/vehicle/tesla.go b/vehicle/tesla.go index 4813ff51ab..1b652619e8 100644 --- a/vehicle/tesla.go +++ b/vehicle/tesla.go @@ -2,13 +2,12 @@ package vehicle import ( "context" - "os" + "errors" "time" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" - "github.com/evcc-io/evcc/util/sponsor" "github.com/evcc-io/evcc/util/transport" "github.com/evcc-io/evcc/vehicle/tesla" teslaclient "github.com/evcc-io/tesla-proxy-client" @@ -23,15 +22,7 @@ type Tesla struct { } func init() { - if id := os.Getenv("TESLA_CLIENT_ID"); id != "" { - tesla.OAuth2Config.ClientID = id - } - if secret := os.Getenv("TESLA_CLIENT_SECRET"); secret != "" { - tesla.OAuth2Config.ClientSecret = secret - } - if tesla.OAuth2Config.ClientID != "" { - registry.Add("tesla", NewTeslaFromConfig) - } + registry.Add("tesla", NewTeslaFromConfig) } // NewTeslaFromConfig creates a new vehicle @@ -42,6 +33,7 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) { Tokens Tokens VIN string CommandProxy string + ProxyToken string Cache time.Duration Timeout time.Duration }{ @@ -54,12 +46,8 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) { return nil, err } - if cc.Credentials.ID != "" { - tesla.OAuth2Config.ClientID = cc.Credentials.ID - } - - if cc.Credentials.Secret != "" { - tesla.OAuth2Config.ClientSecret = cc.Credentials.Secret + if cc.Credentials.ID == "" { + return nil, errors.New("missing client id, see https://github.com/evcc-io/evcc/discussions/17501") } token, err := cc.Tokens.Token() @@ -68,19 +56,21 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) { } log := util.NewLogger("tesla").Redact( - cc.Tokens.Access, cc.Tokens.Refresh, - tesla.OAuth2Config.ClientID, tesla.OAuth2Config.ClientSecret, + cc.Tokens.Access, cc.Tokens.Refresh, cc.ProxyToken, + cc.Credentials.ID, cc.Credentials.Secret, ) - identity, err := tesla.NewIdentity(log, token) + identity, err := tesla.NewIdentity(log, tesla.OAuth2Config(cc.Credentials.ID, cc.Credentials.Secret), token) if err != nil { return nil, err } hc := request.NewClient(log) + baseTransport := hc.Transport + hc.Transport = &oauth2.Transport{ Source: identity, - Base: hc.Transport, + Base: baseTransport, } tc, err := teslaclient.NewClient(context.Background(), teslaclient.WithClient(hc)) @@ -109,9 +99,9 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) { pc := request.NewClient(log) pc.Transport = &transport.Decorator{ Decorator: transport.DecorateHeaders(map[string]string{ - "X-Auth-Token": sponsor.Token, + "Authorization": "Bearer " + cc.ProxyToken, }), - Base: hc.Transport, + Base: baseTransport, } tcc, err := teslaclient.NewClient(context.Background(), teslaclient.WithClient(pc)) diff --git a/vehicle/tesla/controller.go b/vehicle/tesla/controller.go index e9d1dc5690..300f635c80 100644 --- a/vehicle/tesla/controller.go +++ b/vehicle/tesla/controller.go @@ -5,11 +5,10 @@ import ( "slices" "github.com/evcc-io/evcc/api" - "github.com/evcc-io/evcc/util/sponsor" "github.com/evcc-io/tesla-proxy-client" ) -const ProxyBaseUrl = "https://tesla.evcc.io" +const ProxyBaseUrl = "https://api.myteslamate.com" type Controller struct { vehicle *tesla.Vehicle @@ -28,10 +27,6 @@ var _ api.CurrentController = (*Controller)(nil) // MaxCurrent implements the api.CurrentController interface func (v *Controller) MaxCurrent(current int64) error { - if !sponsor.IsAuthorized() { - return api.ErrSponsorRequired - } - return apiError(v.vehicle.SetChargingAmps(int(current))) } @@ -39,10 +34,6 @@ var _ api.ChargeController = (*Controller)(nil) // ChargeEnable implements the api.ChargeController interface func (v *Controller) ChargeEnable(enable bool) error { - if !sponsor.IsAuthorized() { - return api.ErrSponsorRequired - } - var err error if enable { diff --git a/vehicle/tesla/identity.go b/vehicle/tesla/identity.go index bc3295b152..8f49b431de 100644 --- a/vehicle/tesla/identity.go +++ b/vehicle/tesla/identity.go @@ -17,35 +17,28 @@ import ( // https://auth.tesla.com/oauth2/v3/.well-known/openid-configuration // OAuth2Config is the OAuth2 configuration for authenticating with the Tesla API. -var OAuth2Config = &oauth2.Config{ - RedirectURL: "https://auth.tesla.com/void/callback", - Endpoint: oauth2.Endpoint{ - AuthURL: "https://auth.tesla.com/en_us/oauth2/v3/authorize", - TokenURL: "https://auth.tesla.com/oauth2/v3/token", - AuthStyle: oauth2.AuthStyleInParams, - }, - Scopes: []string{"openid", "email", "offline_access"}, -} - -var TESLA_CLIENT_ID, TESLA_CLIENT_SECRET string - -func init() { - if TESLA_CLIENT_ID != "" { - OAuth2Config.ClientID = TESLA_CLIENT_ID - } - if TESLA_CLIENT_SECRET != "" { - OAuth2Config.ClientSecret = TESLA_CLIENT_SECRET +func OAuth2Config(id, secret string) *oauth2.Config { + return &oauth2.Config{ + ClientID: id, + ClientSecret: secret, + RedirectURL: "https://auth.tesla.com/void/callback", + Endpoint: oauth2.Endpoint{ + AuthURL: "https://auth.tesla.com/en_us/oauth2/v3/authorize", + TokenURL: "https://auth.tesla.com/oauth2/v3/token", + AuthStyle: oauth2.AuthStyleInParams, + }, + Scopes: []string{"openid", "email", "offline_access"}, } } type Identity struct { oauth2.TokenSource mu sync.Mutex - log *util.Logger + rts oauth2.TokenSource subject string } -func NewIdentity(log *util.Logger, token *oauth2.Token) (oauth2.TokenSource, error) { +func NewIdentity(log *util.Logger, oc *oauth2.Config, token *oauth2.Token) (oauth2.TokenSource, error) { // serialise instance handling mu.Lock() defer mu.Unlock() @@ -66,7 +59,6 @@ func NewIdentity(log *util.Logger, token *oauth2.Token) (oauth2.TokenSource, err } v := &Identity{ - log: log, subject: claims.Subject, } @@ -90,6 +82,10 @@ func NewIdentity(log *util.Logger, token *oauth2.Token) (oauth2.TokenSource, err v.TokenSource = oauth.RefreshTokenSource(token, v) + // refresh token source + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewClient(log)) + v.rts = oc.TokenSource(ctx, token) + // add instance addInstance(claims.Subject, v) @@ -104,10 +100,7 @@ func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { v.mu.Lock() defer v.mu.Unlock() - ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewClient(v.log)) - ts := OAuth2Config.TokenSource(ctx, token) - - token, err := ts.Token() + token, err := v.rts.Token() if err != nil { return nil, err } diff --git a/vehicle/types.go b/vehicle/types.go index c30b58bf24..717996123b 100644 --- a/vehicle/types.go +++ b/vehicle/types.go @@ -4,6 +4,7 @@ import ( "errors" "time" + "github.com/evcc-io/evcc/api" "golang.org/x/oauth2" ) @@ -15,11 +16,11 @@ type ClientCredentials struct { // Error validates the credentials and returns an error if they are incomplete func (c *ClientCredentials) Error() error { if c.ID == "" { - return errors.New("missing credentials id") + return errors.New("missing client id") } if c.Secret == "" { - return errors.New("missing credentials secret") + return errors.New("missing client secret") } return nil @@ -33,7 +34,7 @@ type Tokens struct { // Token builds token from credentials and returns an error if they are incomplete func (t *Tokens) Token() (*oauth2.Token, error) { if t.Access == "" && t.Refresh == "" { - return nil, errors.New("missing access and/or refresh token, use `evcc token` to create") + return nil, api.ErrMissingToken } return &oauth2.Token{