Fix Audi authentication and align with VW

This commit is contained in:
andig 2020-10-25 21:34:01 +01:00
parent d43f503474
commit cf4f62cccb
3 changed files with 40 additions and 117 deletions

View file

@ -1,15 +1,10 @@
package vehicle
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"net/http"
"net/http/cookiejar"
"net/url"
"strconv"
"strings"
"time"
@ -36,10 +31,7 @@ func init() {
registry.Add("audi", NewAudiFromConfig)
}
// 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"
const audiClientID = "77869e21-e30a-4a92-b016-48ab7d3db1d8"
// NewAudiFromConfig creates a new vehicle
func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
@ -91,88 +83,39 @@ func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
}
func (v *Audi) authFlow() error {
var err error
var uri string
var req *http.Request
var resp *http.Response
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{
"response_type": {"code"},
"response_type": {"id_token token"},
"client_id": {clientID},
"redirect_uri": {redirectURI},
"scope": {"address profile badge birthdate birthplace nationalIdentifier nationality profession email vin phone nickname name picture mbb gallery openid"},
"state": {"7f8260b5-682f-4db8-b171-50a5189a1c08"},
"nonce": {"7f8260b5-682f-4db8-b171-50a5189a1c08"},
"scope": {"openid profile mbb vin badge birthdate nickname email address phone name picture"},
"state": {vw.RandomString(43)},
"nonce": {vw.RandomString(43)},
"prompt": {"login"},
"ui_locales": {"de-DE"},
})
uri = "https://identity.vwgroup.io/oidc/v1/authorize?" + query.Encode()
if err == nil {
identity := &vw.Identity{Client: v.Client}
resp, err = identity.Login(uri, v.user, v.password)
}
if err == nil {
var code string
if location, err := url.Parse(resp.Header.Get("Location")); err == nil {
code = location.Query().Get("code")
}
data := url.Values(map[string][]string{
"client_id": {clientID},
"grant_type": {"authorization_code"},
"code": {code},
"redirect_uri": {redirectURI},
"response_type": {"token id_token"},
})
var hash string
secret, err := hex.DecodeString(AudiHashSecret)
if 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)")
}
}
}
identity := &vw.Identity{Client: v.Client}
idToken, err := identity.Login(query, v.user, v.password)
if err == nil {
data := url.Values(map[string][]string{
"grant_type": {"id_token"},
"scope": {"sc2:fal"},
"token": {tokens.IDToken},
"token": {idToken},
})
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": audiOAuthClientID,
"X-Client-Id": audiClientID,
})
if err == nil {
if err = v.DoJSON(req, &v.tokens); err == nil && tokens.IDToken == "" {
err = errors.New("missing id token (2)")
if err = v.DoJSON(req, &v.tokens); err == nil && v.tokens.AccessToken == "" {
err = errors.New("missing access token")
}
}
}
@ -185,6 +128,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": audiOAuthClientID,
"X-Client-Id": audiClientID,
}
}

View file

@ -82,60 +82,24 @@ func NewVWFromConfig(other map[string]interface{}) (api.Vehicle, error) {
}
func (v *VW) authFlow() error {
var err error
var uri string
var req *http.Request
var resp *http.Response
var idToken string
// execute login
challenge, verifier, err := vw.ChallengeVerifier()
const clientID = "9496332b-ea03-4091-a224-8c746b885068@apps_vw-dilab_com"
const redirectURI = "carnet://identity-kit/login"
query := url.Values(map[string][]string{
"prompt": {"login"},
"state": {vw.RandomString(43)},
"response_type": {"code id_token token"},
"code_challenge_method": {"s256"},
"response_type": {"id_token token"},
"client_id": {clientID},
"redirect_uri": {redirectURI},
"scope": {"openid profile mbb cars birthdate nickname address phone"},
"code_challenge": {challenge},
"redirect_uri": {"carnet://identity-kit/login"},
"client_id": {"9496332b-ea03-4091-a224-8c746b885068@apps_vw-dilab_com"},
"state": {vw.RandomString(43)},
"nonce": {vw.RandomString(43)},
"prompt": {"login"},
})
uri = "https://identity.vwgroup.io/oidc/v1/authorize?" + query.Encode()
if err == nil {
identity := &vw.Identity{Client: v.Client}
resp, err = identity.Login(uri, v.user, v.password)
}
if err == nil {
// var code string
loc := strings.ReplaceAll(resp.Header.Get("Location"), "#", "?") // convert to parsable url
if locationURL, err := url.Parse(loc); err == nil {
// code = locationURL.Query().Get("code")
idToken = locationURL.Query().Get("id_token")
}
_ = verifier
// if err == nil {
// data := url.Values(map[string][]string{
// "auth_code": {code},
// "code_verifier": {verifier},
// "id_token": {idToken},
// })
// uri := "https://tokenrefreshservice.apps.emea.vwapps.io/exchangeAuthCode"
// req, err = request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), request.URLEncoding)
// // if err == nil {
// // err = v.DoJSON(req, &tokens)
// // if err == nil && tokens.AccessToken == "" {
// // err = errors.New("missing access token")
// // }
// // }
// }
}
identity := &vw.Identity{Client: v.Client}
idToken, err := identity.Login(query, v.user, v.password)
// get client id
if err == nil {

View file

@ -1,6 +1,7 @@
package vw
import (
"errors"
"net/http"
"net/url"
"strings"
@ -51,11 +52,12 @@ func (v *Identity) redirect(resp *http.Response, err error) (*http.Response, err
}
// Login performs the identity.vwgroup.io login
func (v *Identity) Login(uri, user, password string) (*http.Response, error) {
func (v *Identity) Login(query url.Values, user, password string) (string, error) {
var vars FormVars
var req *http.Request
// GET identity.vwgroup.io/oidc/v1/authorize?ui_locales=de&scope=openid%20profile%20birthdate%20nickname%20address%20phone%20cars%20mbb&response_type=code&state=gmiJOaB4&redirect_uri=https%3A%2F%2Fwww.portal.volkswagen-we.com%2Fportal%2Fweb%2Fguest%2Fcomplete-login&nonce=38042ee3-b7a7-43cf-a9c1-63d2f3f2d9f3&prompt=login&client_id=b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com
uri := "https://identity.vwgroup.io/oidc/v1/authorize?" + query.Encode()
resp, err := v.Get(uri)
// GET identity.vwgroup.io/signin-service/v1/signin/b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com?relayState=15404cb51c8b4cc5efeee1d2c2a73e5b41562faa
@ -124,5 +126,19 @@ func (v *Identity) Login(uri, user, password string) (*http.Response, error) {
resp, err = v.redirect(resp, err)
}
return resp, err
var idToken string
if err == nil {
loc := strings.ReplaceAll(resp.Header.Get("Location"), "#", "?") // convert to parseable url
var location *url.URL
if location, err = url.Parse(loc); err == nil {
idToken = location.Query().Get("id_token")
if idToken == "" {
err = errors.New("missing id token")
}
}
}
return idToken, err
}