From 58200dc9360ead458c890b637249d1922a3b257b Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 23 Feb 2025 14:03:06 +0100 Subject: [PATCH] Seat: deprecate tokenrefreshservice (#19094) --- vehicle/seat-cupra.go | 3 +- vehicle/seat.go | 2 +- vehicle/seat/cupra/params.go | 20 +++---- vehicle/vag/vwidentity/oauth2.go | 89 ++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 vehicle/vag/vwidentity/oauth2.go diff --git a/vehicle/seat-cupra.go b/vehicle/seat-cupra.go index 342b2b345..b6e405b7b 100644 --- a/vehicle/seat-cupra.go +++ b/vehicle/seat-cupra.go @@ -9,7 +9,6 @@ import ( "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/vehicle/seat/cupra" - "github.com/evcc-io/evcc/vehicle/vag/service" "github.com/evcc-io/evcc/vehicle/vag/vwidentity" "golang.org/x/oauth2" ) @@ -50,7 +49,7 @@ func NewCupraFromConfig(other map[string]interface{}) (api.Vehicle, error) { log := util.NewLogger("cupra").Redact(cc.User, cc.Password, cc.VIN) - ts, err := service.TokenRefreshServiceTokenSource(log, cupra.TRSParams, cupra.AuthParams, cc.User, cc.Password) + ts, err := vwidentity.Oauth2Login(log, cupra.OAuth2Config, cc.User, cc.Password) if err != nil { return nil, err } diff --git a/vehicle/seat.go b/vehicle/seat.go index 0a857f700..0d6673ed8 100644 --- a/vehicle/seat.go +++ b/vehicle/seat.go @@ -58,7 +58,7 @@ func NewSeatFromConfig(other map[string]interface{}) (api.Vehicle, error) { log := util.NewLogger("seat").Redact(cc.User, cc.Password, cc.VIN) - trs, err := service.TokenRefreshServiceTokenSource(log, cupra.TRSParams, cupra.AuthParams, cc.User, cc.Password) + trs, err := vwidentity.Oauth2Login(log, cupra.OAuth2Config, cc.User, cc.Password) if err != nil { return nil, err } diff --git a/vehicle/seat/cupra/params.go b/vehicle/seat/cupra/params.go index ce38d946c..7bc04e717 100644 --- a/vehicle/seat/cupra/params.go +++ b/vehicle/seat/cupra/params.go @@ -1,16 +1,12 @@ package cupra -import "net/url" +import ( + "golang.org/x/oauth2" +) -// Authorization parameters -var AuthParams = url.Values{ - "response_type": {"code id_token"}, // token - "client_id": {"30e33736-c537-4c72-ab60-74a7b92cfe83@apps_vw-dilab_com"}, - "redirect_uri": {"cupraconnect://identity-kit/login"}, - "scope": {"openid profile mbb"}, // cars birthdate nickname address phone -} - -// TokenRefreshService parameters -var TRSParams = url.Values{ - "brand": {"cupra"}, +var OAuth2Config = &oauth2.Config{ + ClientID: "3c756d46-f1ba-4d78-9f9a-cff0d5292d51@apps_vw-dilab_com", + ClientSecret: "eb8814e641c81a2640ad62eeccec11c98effc9bccd4269ab7af338b50a94b3a2", + RedirectURL: "cupra://oauth-callback", + Scopes: []string{"openid", "profile", "mbb"}, } diff --git a/vehicle/vag/vwidentity/oauth2.go b/vehicle/vag/vwidentity/oauth2.go new file mode 100644 index 000000000..351cf17e6 --- /dev/null +++ b/vehicle/vag/vwidentity/oauth2.go @@ -0,0 +1,89 @@ +package vwidentity + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/request" + "github.com/evcc-io/evcc/util/urlvalues" + "github.com/evcc-io/evcc/vehicle/vag" + "golang.org/x/oauth2" +) + +// Login performs VW identity login with optional code challenge +func Oauth2Login(log *util.Logger, oc *oauth2.Config, user, password string) (vag.TokenSource, error) { + oc.Endpoint = Config.NewProvider(context.Background()).Endpoint() + + // add code challenge + cv := oauth2.GenerateVerifier() + q := url.Values{ + "response_type": {"code id_token"}, + "client_id": {oc.ClientID}, + "redirect_uri": {oc.RedirectURL}, + "code_challenge_method": {"S256"}, + "code_challenge": {oauth2.S256ChallengeFromVerifier(cv)}, + "scope": {strings.Join(oc.Scopes, " ")}, + } + + uri := fmt.Sprintf("%s?%s", oc.Endpoint.AuthURL, q.Encode()) + + vwi := New(log) + q, err := vwi.Login(uri, user, password) + if err != nil { + return nil, err + } + + if err := urlvalues.Require(q, "id_token", "code"); err != nil { + return nil, err + } + + os := &Oauth2Service{Helper: request.NewHelper(log), Config: oc} + + token, err := os.retrieveToken(url.Values{ + "client_id": {oc.ClientID}, + "client_secret": {oc.ClientSecret}, + "redirect_uri": {oc.RedirectURL}, + "grant_type": {"authorization_code"}, + "code": {q.Get("code")}, + "code_verifier": {cv}, + }) + if err != nil { + return nil, err + } + + return os.TokenSource(token), nil +} + +type Oauth2Service struct { + *oauth2.Config + *request.Helper +} + +func (v *Oauth2Service) retrieveToken(data url.Values) (*vag.Token, error) { + var res vag.Token + + req, err := request.New(http.MethodPost, v.Endpoint.TokenURL, strings.NewReader(data.Encode()), request.URLEncoding, request.AcceptJSON) + if err == nil { + err = v.DoJSON(req, &res) + } + + return &res, err +} + +func (v *Oauth2Service) refresh(token *vag.Token) (*vag.Token, error) { + return v.retrieveToken(url.Values{ + "client_id": {v.ClientID}, + "client_secret": {v.ClientSecret}, + "grant_type": {"refresh_token"}, + "refresh_token": {token.RefreshToken}, + }) +} + +// TokenSource creates token source. Token is refreshed automatically. +func (v *Oauth2Service) TokenSource(token *vag.Token) vag.TokenSource { + return vag.RefreshTokenSource(token, v.refresh) +}