diff --git a/.travis.yml b/.travis.yml index 9fcae2414..052564ae8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,9 @@ notifications: env: global: - - PATH=$HOME/gopath/bin:$PATH + - PATH=$HOME/gopath/bin:$PATH + # AUDI_HASH_SECRET + - secure: FLxALYSoDbHOy70ppjP9rjMA7YMrTTiUQz8cBkOM6CnXOOIFk1canfylmb/nKJWz77nLBvLgbt38jZtUWyiG0dRvAz9IGrRy4KIJ4wrLMS8ub6up4JiSiHkwIvWJl3Z3DUWF7WYpwg3agqp8M2eSbNzZQsjsgwBLDlOEpCKvEpqxN7kjMi2hmH93Fb6SbMxrZKpSRhjPE/GLQWgbrTWHhMLDZMtlcoazOeALJp+QPcncOu6wLeJXEmNhd2NDe5nNMkSe3P9J7dbaVVmCctPyBdD5WWrj9aa11ghtzAiuPOkg578VaSIORr1ZOFZKpTPch0ryCvFjBtCWXJIaTW5Vd6Xz1yv3N/lyz4LsPUcUvHstLP+sWkn8A80+Td6oNw5kR1ji14Mfk0ZYCvYkvj8U20QcmS46gehy6BNCSLQe/PXI2IxJGdjc5s5Bz84fk0+nERvXmTEIf/8V76JeT+1sTahEOp9eUkYvfObhr3Tu22BB2kTkpn42jnvdH25Egr10VmQYQcCa3cHSOQXVnw7fo7En/VvbObUVeW3UwcIIqWQO2eW2tSbWGHa5wN1CbPzpbjVylW++TI7Xzk+kjP3BHOZ2ZGSSoSM0s7F4cj6D3vK4+3grW0TqrvC1+mikwlMA5EeMzlPRaEjWBTwqoTZan3R29VadGYui+LQzbaDk+lw= before_install: # Install linters and misspell diff --git a/Makefile b/Makefile index b82735dfc..de88a2122 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ IMAGE := andig/evcc ALPINE := 3.12 TARGETS := arm.v6,arm.v8,amd64 +AUDI_HASH_SECRET := ${AUDI_HASH_SECRET} + default: clean install assets lint test build clean: @@ -31,7 +33,7 @@ assets: build: @echo Version: $(VERSION) $(BUILD_DATE) - go build -v -tags=release -ldflags '-X "github.com/andig/evcc/server.Version=${VERSION}" -X "github.com/andig/evcc/server.Commit=${SHA}"' + go build -v -tags=release -ldflags '-X "github.com/andig/evcc/server.Version=${VERSION}" -X "github.com/andig/evcc/server.Commit=${SHA}" -X "github.com/andig/evcc/vehicle.AudiHashSecret=${AUDI_HASH_SECRET}"' release-test: goreleaser --snapshot --skip-publish --rm-dist diff --git a/vehicle/audi.go b/vehicle/audi.go index 1b5f0387e..16586a450 100644 --- a/vehicle/audi.go +++ b/vehicle/audi.go @@ -1,10 +1,15 @@ package vehicle import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" "errors" + "fmt" "net/http" "net/http/cookiejar" "net/url" + "strconv" "strings" "time" @@ -31,7 +36,10 @@ func init() { registry.Add("audi", NewAudiFromConfig) } -const audiClientID = "77869e21-e30a-4a92-b016-48ab7d3db1d8" +// AudiHashSecret is used for obtaining the X-QMauth header hash value from the current timestamp +var AudiHashSecret = "not contained in repo due to legal concerns" + +const audiOAuthClientID = "77869e21-e30a-4a92-b016-48ab7d3db1d8" // NewAudiFromConfig creates a new vehicle func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) { @@ -90,6 +98,7 @@ func (v *Audi) authFlow() error { var tokens vw.Tokens const clientID = "09b6cbec-cd19-4589-82fd-363dfa8c24da@apps_vw-dilab_com" + const clientIDAlias = "934928ef" // "09b6cbec-cd19-4589-82fd-363dfa8c24da@apps_vw-dilab_com" const redirectURI = "myaudi:///" query := url.Values(map[string][]string{ @@ -123,8 +132,26 @@ func (v *Audi) authFlow() error { "response_type": {"token id_token"}, }) - uri = "https://app-api.my.audi.com/myaudiappidk/v1/token" - req, err = request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), request.URLEncoding) + var hash string + var secret []byte + if secret, err = hex.DecodeString(AudiHashSecret); err == nil { + // timestamp rounded to 100s precision + ts := strconv.FormatInt(time.Now().Unix()/100, 10) + + mac := hmac.New(sha256.New, secret) + _, err = mac.Write([]byte(ts)) + + hash = fmt.Sprintf("v1:%s:%0x", clientIDAlias, mac.Sum(nil)) + } + + if err == nil { + uri = "https://app-api.my.audi.com/myaudiappidk/v1/emea/token" + req, err = request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), map[string]string{ + "Content-Type": "application/x-www-form-urlencoded", + "X-QMAuth": hash, + }) + } + if err == nil { if err = v.DoJSON(req, &tokens); err == nil && tokens.IDToken == "" { err = errors.New("missing id token (1)") @@ -141,7 +168,7 @@ func (v *Audi) authFlow() error { req, err = request.New(http.MethodPost, vw.OauthTokenURI, strings.NewReader(data.Encode()), map[string]string{ "Content-Type": "application/x-www-form-urlencoded", - "X-Client-Id": audiClientID, + "X-Client-Id": audiOAuthClientID, }) if err == nil { if err = v.DoJSON(req, &v.tokens); err == nil && tokens.IDToken == "" { @@ -158,6 +185,6 @@ func (v *Audi) refreshHeaders() map[string]string { "Content-Type": "application/x-www-form-urlencoded", "X-App-Version": "3.14.0", "X-App-Name": "myAudi", - "X-Client-Id": audiClientID, + "X-Client-Id": audiOAuthClientID, } }