Skip to content

Commit c73c1d9

Browse files
authored
Kia/Hyundai: fix login api (#22709)
Signed-off-by: Stefan Rubner <[email protected]>
1 parent 7d3869a commit c73c1d9

File tree

2 files changed

+59
-75
lines changed

2 files changed

+59
-75
lines changed

vehicle/bluelink.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
2929
BasicToken: "NmQ0NzdjMzgtM2NhNC00Y2YzLTk1NTctMmExOTI5YTk0NjU0OktVeTQ5WHhQekxwTHVvSzB4aEJDNzdXNlZYaG10UVI5aVFobUlGampvWTRJcHhzVg==",
3030
CCSPServiceID: "6d477c38-3ca4-4cf3-9557-2a1929a94654",
3131
CCSPApplicationID: bluelink.HyundaiAppID,
32-
AuthClientID: "64621b96-0f0d-11ec-82a8-0242ac130003",
33-
BrandAuthUrl: "https://eu-account.hyundai.com/auth/realms/euhyundaiidm/protocol/openid-connect/auth?client_id=%s&scope=openid+profile+email+phone&response_type=code&hkid_session_reset=true&redirect_uri=%s/api/v1/user/integration/redirect/login&ui_locales=%s&state=%s:%s",
32+
AuthClientID: "6d477c38-3ca4-4cf3-9557-2a1929a94654",
33+
BrandAuthUrl: "%s/auth/api/v2/user/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=%s/api/v1/user/oauth2/redirect&lang=%s&state=ccsp",
3434
PushType: "GCM",
3535
Cfb: "RFtoRq/vDXJmRndoZaZQyfOot7OrIqGVFj96iY2WL3yyH5Z/pUvlUhqmCxD2t+D65SQ=",
36+
LoginFormHost: "https://idpconnect-eu.hyundai.com",
3637
}
3738

3839
return newBluelinkFromConfig("hyundai", other, settings)
@@ -45,10 +46,11 @@ func NewKiaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
4546
BasicToken: "ZmRjODVjMDAtMGEyZi00YzY0LWJjYjQtMmNmYjE1MDA3MzBhOnNlY3JldA==",
4647
CCSPServiceID: "fdc85c00-0a2f-4c64-bcb4-2cfb1500730a",
4748
CCSPApplicationID: bluelink.KiaAppID,
48-
AuthClientID: "572e0304-5f8d-4b4c-9dd5-41aa84eed160",
49-
BrandAuthUrl: "https://eu-account.kia.com/auth/realms/eukiaidm/protocol/openid-connect/auth?client_id=%s&scope=openid+profile+email+phone&response_type=code&hkid_session_reset=true&redirect_uri=%s/api/v1/user/integration/redirect/login&ui_locales=%s&state=%s:%s",
49+
AuthClientID: "fdc85c00-0a2f-4c64-bcb4-2cfb1500730a",
50+
BrandAuthUrl: "%s/auth/api/v2/user/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=%s/api/v1/user/oauth2/redirect&lang=%s&state=ccsp",
5051
PushType: "APNS",
5152
Cfb: "wLTVxwidmH8CfJYBWSnHD6E0huk0ozdiuygB4hLkM5XCgzAL1Dk5sE36d/bx5PFMbZs=",
53+
LoginFormHost: "https://idpconnect-eu.kia.com",
5254
}
5355

5456
return newBluelinkFromConfig("kia", other, settings)

vehicle/bluelink/identity.go

Lines changed: 53 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"strings"
1212
"time"
1313

14-
"github.com/PuerkitoBio/goquery"
1514
"github.com/evcc-io/evcc/api"
1615
"github.com/evcc-io/evcc/util"
1716
"github.com/evcc-io/evcc/util/oauth"
@@ -41,6 +40,7 @@ type Config struct {
4140
CCSPApplicationID string
4241
PushType string
4342
Cfb string
43+
LoginFormHost string
4444
}
4545

4646
// Identity implements the Kia/Hyundai bluelink identity.
@@ -111,6 +111,7 @@ func (v *Identity) getCookies() (cookieClient *request.Helper, err error) {
111111
PublicSuffixList: publicsuffix.List,
112112
})
113113

114+
// TODO: check whether &lang= is necessary
114115
uri := fmt.Sprintf(
115116
"%s/api/v1/user/oauth2/authorize?response_type=code&state=test&client_id=%s&redirect_uri=%s/api/v1/user/oauth2/redirect",
116117
v.config.URI,
@@ -154,24 +155,24 @@ func (v *Identity) brandLogin(cookieClient *request.Helper, user, password strin
154155
err = cookieClient.DoJSON(req, &info)
155156
}
156157

157-
var action string
158158
var resp *http.Response
159159

160+
// get the connector_session_key
161+
var connectorSessionKey string
160162
if err == nil {
161-
uri := fmt.Sprintf(v.config.BrandAuthUrl, v.config.AuthClientID, v.config.URI, "en", info.ServiceId, info.UserId)
162-
163+
uri := fmt.Sprintf(v.config.BrandAuthUrl, v.config.LoginFormHost, v.config.AuthClientID, v.config.URI, "en")
163164
req, err = request.New(http.MethodGet, uri, nil)
164165
if err == nil {
165166
if resp, err = cookieClient.Do(req); err == nil {
166167
defer resp.Body.Close()
167-
168-
var doc *goquery.Document
169-
if doc, err = goquery.NewDocumentFromReader(resp.Body); err == nil {
170-
err = errors.New("form not found")
171-
172-
if form := doc.Find("form"); form != nil && form.Length() == 1 {
173-
var ok bool
174-
if action, ok = form.Attr("action"); ok {
168+
// code adapted from hyundai_kia_connect_api
169+
// get redirect URL from request
170+
err = errors.New("connector session key not found")
171+
urlRedirect := resp.Request.URL.Query()
172+
// extract redirect URL
173+
if nextUri := urlRedirect.Get("next_uri"); nextUri != "" {
174+
if nextVal, ok := url.Parse(nextUri); ok == nil {
175+
if connectorSessionKey := nextVal.Query().Get("connector_session_key"); connectorSessionKey != "" {
175176
err = nil
176177
}
177178
}
@@ -180,63 +181,44 @@ func (v *Identity) brandLogin(cookieClient *request.Helper, user, password strin
180181
}
181182
}
182183

184+
// if we have the connectorSessionKey, go on and find the login code
185+
var code string
183186
if err == nil {
187+
// build new request uri
188+
uri := fmt.Sprintf("%s%s", v.config.LoginFormHost, "/auth/account/signin")
184189
data := url.Values{
185-
"username": {user},
186-
"password": {password},
187-
"credentialId": {""},
188-
"rememberMe": {"on"},
189-
}
190-
191-
req, err = request.New(http.MethodPost, action, strings.NewReader(data.Encode()), request.URLEncoding)
192-
if err == nil {
193-
cookieClient.CheckRedirect = request.DontFollow
194-
if resp, err = cookieClient.Do(req); err == nil {
195-
defer resp.Body.Close()
196-
197-
// need 302
198-
if resp.StatusCode != http.StatusFound {
199-
err = errors.New("missing redirect")
200-
201-
if doc, err2 := goquery.NewDocumentFromReader(resp.Body); err2 == nil {
202-
if span := doc.Find("span[class=kc-feedback-text]"); span != nil && span.Length() == 1 {
203-
err = errors.New(span.Text())
204-
}
205-
}
206-
}
207-
}
208-
209-
cookieClient.CheckRedirect = nil
190+
"client_id": {v.config.CCSPServiceID},
191+
"encryptedPassword": {"false"},
192+
"orgHmgSid": {""},
193+
"password": {password},
194+
"redirect_uri": {v.config.URI + "/api/v1/user/oauth2/redirect"},
195+
"state": {"ccsp"},
196+
"username": {user},
197+
"remember_me": {"false"},
198+
"connector_session_key": {connectorSessionKey},
199+
"_csrf": {""},
210200
}
211-
}
212-
213-
if err == nil {
214-
resp, err = cookieClient.Get(resp.Header.Get("Location"))
215-
if err == nil {
216-
defer resp.Body.Close()
217-
}
218-
}
219201

220-
var code string
221-
if err == nil {
222-
data := map[string]string{
223-
"intUserId": "",
202+
// create a client that doesn't honor redirects so we receive the original response
203+
// no idea how to do that with the internal request.New(...) function
204+
sc := http.Client{
205+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
206+
return http.ErrUseLastResponse
207+
},
224208
}
225209

226-
req, err = request.New(http.MethodPost, v.config.URI+SilentSigninURL, request.MarshalJSON(data), request.JSONEncoding)
210+
req, err = http.NewRequest(http.MethodPost, uri, strings.NewReader(data.Encode()))
227211
if err == nil {
228-
req.Header.Set("ccsp-service-id", v.config.CCSPServiceID)
229-
cookieClient.CheckRedirect = request.DontFollow
230-
231-
var res struct {
232-
RedirectUrl string `json:"redirectUrl"`
233-
}
234-
235-
if err = cookieClient.DoJSON(req, &res); err == nil {
236-
var uri *url.URL
237-
if uri, err = url.Parse(res.RedirectUrl); err == nil {
238-
if code = uri.Query().Get("code"); len(code) == 0 {
239-
err = errors.New("code not found")
212+
req.PostForm = data
213+
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
214+
req.Header.Add("Origin", v.config.LoginFormHost)
215+
216+
if resp, err = sc.Do(req); err == nil {
217+
location := resp.Header.Get("Location")
218+
err = errors.New("code location not found")
219+
if locationUrl, ok := url.Parse(location); ok == nil {
220+
if code = locationUrl.Query().Get("code"); code != "" {
221+
err = nil
240222
}
241223
}
242224
}
@@ -276,21 +258,22 @@ func (v *Identity) bluelinkLogin(cookieClient *request.Helper, user, password st
276258
}
277259

278260
func (v *Identity) exchangeCode(accCode string) (*oauth2.Token, error) {
261+
uri := v.config.LoginFormHost + "/auth/api/v2/user/oauth2/token"
279262
headers := map[string]string{
280-
"Authorization": "Basic " + v.config.BasicToken,
281-
"Content-type": "application/x-www-form-urlencoded",
282-
"User-Agent": "okhttp/3.10.0",
263+
"Content-type": "application/x-www-form-urlencoded",
264+
"User-Agent": "okhttp/3.10.0",
283265
}
284-
285266
data := url.Values{
286-
"grant_type": {"authorization_code"},
287-
"redirect_uri": {v.config.URI + "/api/v1/user/oauth2/redirect"},
288-
"code": {accCode},
267+
"grant_type": {"authorization_code"},
268+
"code": {accCode},
269+
"redirect_uri": {v.config.URI + "/api/v1/user/oauth2/redirect"},
270+
"client_id": {v.config.CCSPServiceID},
271+
"client_secret": {"secret"},
289272
}
290273

291274
var token oauth2.Token
292275

293-
req, _ := request.New(http.MethodPost, v.config.URI+TokenURL, strings.NewReader(data.Encode()), headers)
276+
req, _ := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), headers)
294277
err := v.DoJSON(req, &token)
295278

296279
return util.TokenWithExpiry(&token), err
@@ -324,7 +307,6 @@ func (v *Identity) Login(user, password, language string) (err error) {
324307
if user == "" || password == "" {
325308
return api.ErrMissingCredentials
326309
}
327-
328310
v.deviceID, err = v.getDeviceID()
329311

330312
var cookieClient *request.Helper

0 commit comments

Comments
 (0)