@@ -11,7 +11,6 @@ import (
11
11
"strings"
12
12
"time"
13
13
14
- "github.com/PuerkitoBio/goquery"
15
14
"github.com/evcc-io/evcc/api"
16
15
"github.com/evcc-io/evcc/util"
17
16
"github.com/evcc-io/evcc/util/oauth"
@@ -41,6 +40,7 @@ type Config struct {
41
40
CCSPApplicationID string
42
41
PushType string
43
42
Cfb string
43
+ LoginFormHost string
44
44
}
45
45
46
46
// Identity implements the Kia/Hyundai bluelink identity.
@@ -111,6 +111,7 @@ func (v *Identity) getCookies() (cookieClient *request.Helper, err error) {
111
111
PublicSuffixList : publicsuffix .List ,
112
112
})
113
113
114
+ // TODO: check whether &lang= is necessary
114
115
uri := fmt .Sprintf (
115
116
"%s/api/v1/user/oauth2/authorize?response_type=code&state=test&client_id=%s&redirect_uri=%s/api/v1/user/oauth2/redirect" ,
116
117
v .config .URI ,
@@ -154,24 +155,24 @@ func (v *Identity) brandLogin(cookieClient *request.Helper, user, password strin
154
155
err = cookieClient .DoJSON (req , & info )
155
156
}
156
157
157
- var action string
158
158
var resp * http.Response
159
159
160
+ // get the connector_session_key
161
+ var connectorSessionKey string
160
162
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" )
163
164
req , err = request .New (http .MethodGet , uri , nil )
164
165
if err == nil {
165
166
if resp , err = cookieClient .Do (req ); err == nil {
166
167
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 != "" {
175
176
err = nil
176
177
}
177
178
}
@@ -180,63 +181,44 @@ func (v *Identity) brandLogin(cookieClient *request.Helper, user, password strin
180
181
}
181
182
}
182
183
184
+ // if we have the connectorSessionKey, go on and find the login code
185
+ var code string
183
186
if err == nil {
187
+ // build new request uri
188
+ uri := fmt .Sprintf ("%s%s" , v .config .LoginFormHost , "/auth/account/signin" )
184
189
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" : {"" },
210
200
}
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
- }
219
201
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
+ },
224
208
}
225
209
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 ()) )
227
211
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
240
222
}
241
223
}
242
224
}
@@ -276,21 +258,22 @@ func (v *Identity) bluelinkLogin(cookieClient *request.Helper, user, password st
276
258
}
277
259
278
260
func (v * Identity ) exchangeCode (accCode string ) (* oauth2.Token , error ) {
261
+ uri := v .config .LoginFormHost + "/auth/api/v2/user/oauth2/token"
279
262
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" ,
283
265
}
284
-
285
266
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" },
289
272
}
290
273
291
274
var token oauth2.Token
292
275
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 )
294
277
err := v .DoJSON (req , & token )
295
278
296
279
return util .TokenWithExpiry (& token ), err
@@ -324,7 +307,6 @@ func (v *Identity) Login(user, password, language string) (err error) {
324
307
if user == "" || password == "" {
325
308
return api .ErrMissingCredentials
326
309
}
327
-
328
310
v .deviceID , err = v .getDeviceID ()
329
311
330
312
var cookieClient * request.Helper
0 commit comments