Hyundai: revert api change (#22833)

This commit is contained in:
stefan 2025-08-08 10:18:57 +02:00 • committed by GitHub
parent 418a49441d
commit 87b5f33830
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 158 additions and 17 deletions

View file

@ -29,11 +29,14 @@ func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
BasicToken: "NmQ0NzdjMzgtM2NhNC00Y2YzLTk1NTctMmExOTI5YTk0NjU0OktVeTQ5WHhQekxwTHVvSzB4aEJDNzdXNlZYaG10UVI5aVFobUlGampvWTRJcHhzVg==",
CCSPServiceID: "6d477c38-3ca4-4cf3-9557-2a1929a94654",
CCSPApplicationID: bluelink.HyundaiAppID,
AuthClientID: "6d477c38-3ca4-4cf3-9557-2a1929a94654",
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",
AuthClientID: "64621b96-0f0d-11ec-82a8-0242ac130003",
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",
PushType: "GCM",
Cfb: "RFtoRq/vDXJmRndoZaZQyfOot7OrIqGVFj96iY2WL3yyH5Z/pUvlUhqmCxD2t+D65SQ=",
LoginFormHost: "https://idpconnect-eu.hyundai.com",
// for oauth2??
// LoginFormHost: "https://idpconnect-eu.hyundai.com",
// AuthClientID: "6d477c38-3ca4-4cf3-9557-2a1929a94654",
// 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",
}
return newBluelinkFromConfig("hyundai", other, settings)
@ -78,7 +81,7 @@ func newBluelinkFromConfig(brand string, other map[string]interface{}, settings
log := util.NewLogger(brand).Redact(cc.User, cc.Password, cc.VIN)
identity := bluelink.NewIdentity(log, settings)
if err := identity.Login(cc.User, cc.Password, cc.Language); err != nil {
if err := identity.Login(cc.User, cc.Password, cc.Language, brand); err != nil {
return nil, err
}

View file

@ -11,6 +11,7 @@ import (
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/oauth"
@ -142,7 +143,111 @@ func (v *Identity) setLanguage(cookieClient *request.Helper, language string) er
return err
}
func (v *Identity) brandLogin(cookieClient *request.Helper, user, password string) (string, error) {
func (v *Identity) brandLoginHyundaiEU(cookieClient *request.Helper, user, password string) (string, error) {
req, err := request.New(http.MethodGet, v.config.URI+IntegrationInfoURL, nil, request.JSONEncoding)
var info struct {
UserId string `json:"userId"`
ServiceId string `json:"serviceId"`
}
if err == nil {
err = cookieClient.DoJSON(req, &info)
}
var action string
var resp *http.Response
if err == nil {
uri := fmt.Sprintf(v.config.BrandAuthUrl, v.config.AuthClientID, v.config.URI, "en", info.ServiceId, info.UserId)
req, err = request.New(http.MethodGet, uri, nil)
if err == nil {
if resp, err = cookieClient.Do(req); err == nil {
defer resp.Body.Close()
var doc *goquery.Document
if doc, err = goquery.NewDocumentFromReader(resp.Body); err == nil {
err = errors.New("form not found")
if form := doc.Find("form"); form != nil && form.Length() == 1 {
var ok bool
if action, ok = form.Attr("action"); ok {
err = nil
}
}
}
}
}
}
if err == nil {
data := url.Values{
"username": {user},
"password": {password},
"credentialId": {""},
"rememberMe": {"on"},
}
req, err = request.New(http.MethodPost, action, strings.NewReader(data.Encode()), request.URLEncoding)
if err == nil {
cookieClient.CheckRedirect = request.DontFollow
if resp, err = cookieClient.Do(req); err == nil {
defer resp.Body.Close()
// need 302
if resp.StatusCode != http.StatusFound {
err = errors.New("missing redirect")
if doc, err2 := goquery.NewDocumentFromReader(resp.Body); err2 == nil {
if span := doc.Find("span[class=kc-feedback-text]"); span != nil && span.Length() == 1 {
err = errors.New(span.Text())
}
}
}
}
cookieClient.CheckRedirect = nil
}
}
if err == nil {
resp, err = cookieClient.Get(resp.Header.Get("Location"))
if err == nil {
defer resp.Body.Close()
}
}
var code string
if err == nil {
data := map[string]string{
"intUserId": "",
}
req, err = request.New(http.MethodPost, v.config.URI+SilentSigninURL, request.MarshalJSON(data), request.JSONEncoding)
if err == nil {
req.Header.Set("ccsp-service-id", v.config.CCSPServiceID)
cookieClient.CheckRedirect = request.DontFollow
var res struct {
RedirectUrl string `json:"redirectUrl"`
}
if err = cookieClient.DoJSON(req, &res); err == nil {
var uri *url.URL
if uri, err = url.Parse(res.RedirectUrl); err == nil {
if code = uri.Query().Get("code"); len(code) == 0 {
err = errors.New("code not found")
}
}
}
}
}
return code, err
}
func (v *Identity) brandLoginKiaEU(cookieClient *request.Helper, user, password string) (string, error) {
req, _ := request.New(http.MethodGet, v.config.URI+IntegrationInfoURL, nil, request.JSONEncoding)
var info struct {
@ -264,7 +369,28 @@ func (v *Identity) bluelinkLogin(cookieClient *request.Helper, user, password st
return accCode, err
}
func (v *Identity) exchangeCode(accCode string) (*oauth2.Token, error) {
func (v *Identity) exchangeCodeHyundaiEU(accCode string) (*oauth2.Token, error) {
headers := map[string]string{
"Authorization": "Basic " + v.config.BasicToken,
"Content-type": "application/x-www-form-urlencoded",
"User-Agent": "okhttp/3.10.0",
}
data := url.Values{
"grant_type": {"authorization_code"},
"redirect_uri": {v.config.URI + "/api/v1/user/oauth2/redirect"},
"code": {accCode},
}
var token oauth2.Token
req, _ := request.New(http.MethodPost, v.config.URI+TokenURL, strings.NewReader(data.Encode()), headers)
err := v.DoJSON(req, &token)
return util.TokenWithExpiry(&token), err
}
func (v *Identity) exchangeCodeKiaEU(accCode string) (*oauth2.Token, error) {
uri := v.config.LoginFormHost + "/auth/api/v2/user/oauth2/token"
headers := map[string]string{
"Content-type": "application/x-www-form-urlencoded",
@ -310,7 +436,7 @@ func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
return util.TokenWithExpiry(&res), err
}
func (v *Identity) Login(user, password, language string) (err error) {
func (v *Identity) Login(user, password, language, brand string) (err error) {
if user == "" || password == "" {
return api.ErrMissingCredentials
}
@ -327,16 +453,28 @@ func (v *Identity) Login(user, password, language string) (err error) {
var code string
if err == nil {
// try new login first, then fallback
if code, err = v.brandLogin(cookieClient, user, password); err != nil {
code, err = v.bluelinkLogin(cookieClient, user, password)
}
}
if err == nil {
var token *oauth2.Token
if token, err = v.exchangeCode(code); err == nil {
v.TokenSource = oauth.RefreshTokenSource(token, v)
switch brand {
case "kia":
code, err = v.brandLoginKiaEU(cookieClient, user, password)
if err == nil {
var token *oauth2.Token
if token, err = v.exchangeCodeKiaEU(code); err == nil {
v.TokenSource = oauth.RefreshTokenSource(token, v)
}
}
case "hyundai":
// try new login first, then fallback
if code, err = v.brandLoginHyundaiEU(cookieClient, user, password); err != nil {
code, err = v.bluelinkLogin(cookieClient, user, password)
}
if err == nil {
var token *oauth2.Token
if token, err = v.exchangeCodeHyundaiEU(code); err == nil {
v.TokenSource = oauth.RefreshTokenSource(token, v)
}
}
default:
err = fmt.Errorf("unknown brand (%s)", brand)
}
}