evcc-io/vehicle/mb/identity.go
2023-12-17 13:16:10 +01:00

132 lines
3 KiB
Go

package mb
import (
"context"
"fmt"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/samber/lo"
"golang.org/x/net/publicsuffix"
"golang.org/x/oauth2"
)
// https://github.com/TA2k/ioBroker.smart-eq
// https://id.mercedes-benz.com/.well-known/openid-configuration
const OAuthURI = "https://id.mercedes-benz.com"
type Identity struct {
*request.Helper
oc *oauth2.Config
oauth2.TokenSource
}
// NewIdentity creates Mercedes Benz identity
func NewIdentity(log *util.Logger, oc *oauth2.Config) *Identity {
return &Identity{
Helper: request.NewHelper(log),
oc: oc,
}
}
func (v *Identity) Login(user, password string) error {
if v.Client.Jar == nil {
v.Client.Jar, _ = cookiejar.New(&cookiejar.Options{
PublicSuffixList: publicsuffix.List,
})
}
var param request.InterceptResult
v.Client.CheckRedirect, param = request.InterceptRedirect("resume", false)
cv := oauth2.GenerateVerifier()
state := lo.RandomString(16, lo.AlphanumericCharset)
uri := v.oc.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(cv))
if _, err := v.Get(uri); err != nil {
return err
}
resume, err := param()
if err != nil {
return err
}
v.Client.CheckRedirect = nil
data := struct {
Username string `json:"username"`
Password string `json:"password,omitempty"`
RememberMe bool `json:"rememberMe,omitempty"`
}{
Username: user,
}
var res struct {
Result, Token string
Errors []struct{ Key string }
}
uri = fmt.Sprintf("%s/ciam/auth/login/user", OAuthURI)
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
if err == nil {
if err = v.DoJSON(req, &res); err != nil && len(res.Errors) > 0 {
err = fmt.Errorf("%s: %w", res.Errors[0].Key, err)
}
}
if err == nil {
data.Password = password
data.RememberMe = true
uri = fmt.Sprintf("%s/ciam/auth/login/pass", OAuthURI)
req, err = request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
if err == nil {
if err = v.DoJSON(req, &res); err != nil && len(res.Errors) > 0 {
err = fmt.Errorf("%s: %w", res.Errors[0].Key, err)
}
}
}
if err == nil && res.Token == "" && res.Result != "" {
err = fmt.Errorf("missing token: %s", res.Result)
}
var code string
if err == nil {
params := url.Values{
"token": {res.Token},
}
var param request.InterceptResult
v.Client.CheckRedirect, param = request.InterceptRedirect("code", true)
uri := OAuthURI + resume
if _, err = v.Post(uri, request.FormContent, strings.NewReader(params.Encode())); err == nil {
code, err = param()
}
v.Client.CheckRedirect = nil
}
var token *oauth2.Token
if err == nil {
ctx, cancel := context.WithTimeout(
context.WithValue(context.Background(), oauth2.HTTPClient, v.Client),
request.Timeout)
defer cancel()
token, err = v.oc.Exchange(ctx, code, oauth2.VerifierOption(cv))
}
if err == nil {
v.TokenSource = v.oc.TokenSource(context.Background(), token)
}
return err
}