Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polestar: Enhance consent handling during authentication process #17252

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions vehicle/polestar/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,19 @@ func (v *Identity) login() (*oauth2.Token, error) {
var code string
if _, err = v.Post(uri, request.FormContent, strings.NewReader(params.Encode())); err == nil {
code, err = param()
if err == nil && code == "" {
err = fmt.Errorf("authorization code is empty")
}
}

if err != nil {
return nil, err
// If the authorization code is empty, this indicates that user consent must be handled
// before the code can be obtained. The `confirmConsentAndGetCode` method is called as a
// workaround to guide the user through the consent process and retrieve the authorization code.
if code == "" {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will never reach this as in line 96 we will return due to the error of not having the code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn it – I missed the early return!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

code, err = v.confirmConsentAndGetCode(uri, resume, param)
if err != nil {
return nil, err
}
}

var res struct {
Expand Down Expand Up @@ -137,3 +146,47 @@ func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {

return v.login()
}

func (v *Identity) confirmConsentAndGetCode(uri, resume string, param request.InterceptResult) (string, error) {

fmt.Printf("Attempting to retrieve authorization code again after user consent confirmation")

// Attempt to extract the "uid" parameter
v.Client.CheckRedirect, param = request.InterceptRedirect("uid", true)
defer func() { v.Client.CheckRedirect = nil }()

// Extract the user ID (UID) from the redirect parameters
var uid string
if uid, err := param(); err != nil || uid == "" {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my tests, there is no UID in the params, still trying to figure out why.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are never reading a "uid" so it also cannot be extracted. I guess there is still something missing to make that work

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that actually work for you @rostbeule ?

For me it never retrieves the uid. Unfortunately, I am not really good with Go and do not fully understand how the this InterceptRedirect works.

There is a rather simple Python script that works perfectly fine for me:
https://github.com/CONSULitAS/Polestar_2_MQTT_Docker/blob/450503db2d1d5fce857326214b5f7709c3dea43e/Polestar_2_MQTT.py#L87

Here you can see that the uid is extracted from the response header after making the post request to https://polestarid.eu.polestar.com/as/{path_token}/resume/as/authorization.ping

I am still trying to figure out the difference to this working example but again struggle with Go 😉

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can reassure you there. These are actually my first experiences with Go. Honestly, I tested it in C# and tried to transfer it to Go. But I was upfront about that 😅

Will have a look into that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uid is NOT the one from the line above. Due to := you‘re „shadowing“ it. Use = instead!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I didn't expect that! Thanks for the tip. Learned something new :)
I'm curious to see what @loebse thinks about it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not enough to solve the problem of not reading the uid

The code proposal by @scyomantion works for me though. Are you making the changes accordingly @rostbeule?

return "", fmt.Errorf("Failed to extract user ID: %w", err)
}

// Confirm user consent by submitting the consent form, which rejects cookies
data := url.Values{
"pf.submit": []string{"false"},
"subject": []string{uid},
}

// Make a POST request to confirm the user consent
if _, err := v.Post(fmt.Sprintf("%s/as/%s/resume/as/authorization.ping", OAuthURI, resume), request.FormContent, strings.NewReader(data.Encode())); err != nil {
return "", fmt.Errorf("Error confirming user consent to reject cookies during the authentication process: %w", err)
}

// Retrieve the authorization code after consent has been confirmed
v.Client.CheckRedirect, param = request.InterceptRedirect("code", true)
defer func() { v.Client.CheckRedirect = nil }()

// Make a GET request to fetch the code
if _, err := v.Get(uri); err != nil {
return "", fmt.Errorf("Error retrieving the authorization code after consent confirmation: %w", err)
}

// Extract the authorization code from the response
code, err := param()
if err != nil || code == "" {
return "", fmt.Errorf("Failed to extract authorization code: %w", err)
}

// Return the retrieved code
return code, nil
}