diff --git a/auth.go b/auth.go index f34b72a..bcc109a 100644 --- a/auth.go +++ b/auth.go @@ -83,6 +83,7 @@ func (a *auth) Do(ctx context.Context, username, password string) (code string, if err != nil { return "", fmt.Errorf("login: %w", err) } + defer res.Body.Close() switch res.StatusCode { @@ -109,7 +110,8 @@ func (a *auth) Do(ctx context.Context, username, password string) (code string, return "", fmt.Errorf("select device: %w", err) } - if err := a.verify(ctx, transactionID, d, passcode); err != nil { + csrf := v.Get("_csrf") + if err := a.verify(ctx, csrf, transactionID, d, passcode); err != nil { return "", fmt.Errorf("verify: %w", err) } return a.commit(ctx, transactionID) @@ -174,13 +176,29 @@ func (a *auth) login(ctx context.Context, username, password string) (*http.Resp v["captcha"] = []string{solution} } + v.Set("_phase", "authenticate") + v.Set("_process", "1") + req, err = http.NewRequestWithContext(ctx, http.MethodPost, a.AuthURL, strings.NewReader(v.Encode())) if err != nil { return nil, nil, fmt.Errorf("new request: %w", err) } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + for _, cookie := range res.Cookies() { + cookie := &http.Cookie{ + Name: cookie.Name, + Value: cookie.Value, + } + req.AddCookie(cookie) + } + res, err = a.Client.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("do request: %w", err) + } + return res, v, err } @@ -214,12 +232,18 @@ func (a *auth) listDevices(ctx context.Context, transactionID string) ([]Device, return out.Data, nil } -func (a *auth) verify(ctx context.Context, transactionID string, d Device, passcode string) error { +func (a *auth) verify(ctx context.Context, csrf string, transactionID string, d Device, passcode string) error { + + if csrf == "" { + return errors.New("csrf token is missing for verifing MFA") + } + var buf bytes.Buffer if err := json.NewEncoder(&buf).Encode(map[string]string{ "transaction_id": transactionID, "factor_id": d.ID, "passcode": passcode, + "_csrf": csrf, }); err != nil { return fmt.Errorf("json encode: %w", err) } @@ -236,12 +260,13 @@ func (a *auth) verify(ctx context.Context, transactionID string, d Device, passc } defer res.Body.Close() - var out struct { - Data struct { - Approved bool `json:"approved"` - } `json:"data"` + b, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("read body: %w", err) } - if err := json.NewDecoder(res.Body).Decode(&out); err != nil { + + var out MFAVerify + if err := json.Unmarshal(b, &out); err != nil { return fmt.Errorf("json decode: %w", err) } @@ -264,6 +289,7 @@ func (a *auth) commit(ctx context.Context, transactionID string) (code string, e if err != nil { return "", fmt.Errorf("do: %w", err) } + defer res.Body.Close() if res.StatusCode != http.StatusFound { diff --git a/auth_models.go b/auth_models.go new file mode 100644 index 0000000..3b2d19b --- /dev/null +++ b/auth_models.go @@ -0,0 +1,17 @@ +package tesla + +type MFAVerify struct { + Data MFAData `json:"data"` +} + +type MFAData struct { + Id string `json:"id"` + ChallengeId string `json:"challengeId"` + FactorId string `json:"factorId"` + PassCode string `json:"passCode"` + Approved bool `json:"approved"` + Flagged bool `json:"flagged"` + Valid bool `json:"valid"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} diff --git a/client.go b/client.go index 02a9b8d..176acd7 100644 --- a/client.go +++ b/client.go @@ -20,7 +20,7 @@ var OAuth2Config = &oauth2.Config{ ClientID: "ownerapi", RedirectURL: "https://auth.tesla.com/void/callback", Endpoint: oauth2.Endpoint{ - AuthURL: "https://auth.tesla.com/oauth2/v3/authorize", + AuthURL: "https://auth.tesla.com/en_us/oauth2/v3/authorize", TokenURL: "https://auth.tesla.com/oauth2/v3/token", AuthStyle: oauth2.AuthStyleInParams, },