evcc-io / evcc

Solar Charging ☀️🚘
https://evcc.io
MIT License
3.65k stars 678 forks source link

Polestar: Enhance consent handling during authentication process #17252

Closed rostbeule closed 1 week ago

rostbeule commented 1 week ago

Change Summary

Enhanced Error Handling

User Consent for Cookies

membero commented 1 week ago

Hi Rostbeule, in my test with your patch I get "code not found" from line 96. So your additional confirmConsentAndGetCode is never called.

Even if I comment line 95-97 I get "Failed to extract user ID: code not found". Seems like the param() looks for the code and not the uid.

andig commented 1 week ago

Thank you for the PR! Afaiu, it does not seem to work yet?

rostbeule commented 1 week ago

Thanks for your feedback. Since I'm quite busy at the moment, I'll probably only get to it on Sunday. I'll take another look then.

scyomantion commented 1 week ago

following code works for me: line 86 - 110:

uri = fmt.Sprintf("%s/as/%s/resume/as/authorization.ping?client_id=%s", OAuthURI, resume, OAuth2Config.ClientID)

    var code string
    var uid string
    v.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
        code = req.URL.Query().Get("code")
        uid = req.URL.Query().Get("uid")
        return nil
    }
    defer func() { v.Client.CheckRedirect = nil }()

    if _, err = v.Post(uri, request.FormContent, strings.NewReader(params.Encode())); 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 == "" {
        code, err = v.confirmConsentAndGetCode(resume, uid)
        if err != nil {
            return nil, err
        }
    }

function confirmConsentAndGetCode:

func (v *Identity) confirmConsentAndGetCode(resume string, uid string) (string, error) {
    // Extract the user ID (UID) from the redirect parameters
    if uid == "" {
        return "", fmt.Errorf("Failed to extract user ID: %s", uid)
    }

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

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

    // 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)
    }

    // 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
}
loebse commented 1 week ago

I can confirm this works for me as well @scyomantion !!

loebse commented 1 week ago

This PR (https://github.com/evcc-io/evcc/pull/17252) is obsolete, we only need https://github.com/evcc-io/evcc/pull/17276