diff --git a/lib/tesla_api/auth/login.ex b/lib/tesla_api/auth/login.ex index 7e311ea7..dd916c46 100644 --- a/lib/tesla_api/auth/login.ex +++ b/lib/tesla_api/auth/login.ex @@ -2,7 +2,7 @@ defmodule TeslaApi.Auth.Login do import TeslaApi.Auth, only: [get: 2, post: 2, post: 3] alias TeslaApi.Error - alias TeslaApi.Auth.{MFA, OwnerApi, Util} + alias TeslaApi.Auth.{MFA, Util} require Logger @@ -56,9 +56,8 @@ defmodule TeslaApi.Auth.Login do with {:ok, %Tesla.Env{} = env} <- submit_form(form, ctx), {:ok, {redirect_uri, code}} <- Util.parse_location_header(env, ctx.state), - {:ok, tokens} <- - get_web_token(code, ctx.code_verifier, redirect_uri, ctx.state, base: ctx.base_url), - {:ok, auth} <- OwnerApi.get_api_tokens(tokens) do + {:ok, auth} <- + get_web_token(code, ctx.code_verifier, redirect_uri, ctx.state, base: ctx.base_url) do {:ok, auth} end rescue @@ -193,9 +192,8 @@ defmodule TeslaApi.Auth.Login do with {:ok, env} <- MFA.verify_passcode(device_id, mfa_passcode, transaction_id, headers), {:ok, {redirect_uri, code}} <- Util.parse_location_header(env, ctx.state), - {:ok, tokens} <- - get_web_token(code, ctx.code_verifier, redirect_uri, ctx.state), - {:ok, auth} <- OwnerApi.get_api_tokens(tokens) do + {:ok, auth} <- + get_web_token(code, ctx.code_verifier, redirect_uri, ctx.state) do {:ok, auth} end rescue @@ -234,16 +232,16 @@ defmodule TeslaApi.Auth.Login do } case post("#{opts[:base]}/oauth2/v3/token", data) do - {:ok, - %Tesla.Env{ - status: 200, - body: %{ - "access_token" => access_token, - "refresh_token" => refresh_token, - "state" => ^state - } - }} -> - {:ok, %{access_token: access_token, refresh_token: refresh_token}} + {:ok, %Tesla.Env{status: 200, body: %{"state" => ^state} = body}} -> + auth = %TeslaApi.Auth{ + token: body["access_token"], + type: body["token_type"], + expires_in: body["expires_in"], + refresh_token: body["refresh_token"], + created_at: body["created_at"] + } + + {:ok, auth} error -> Error.into(error, :web_token_error) diff --git a/lib/tesla_api/auth/owner_api.ex b/lib/tesla_api/auth/owner_api.ex deleted file mode 100644 index 41a34e39..00000000 --- a/lib/tesla_api/auth/owner_api.ex +++ /dev/null @@ -1,34 +0,0 @@ -defmodule TeslaApi.Auth.OwnerApi do - import TeslaApi.Auth, only: [post: 3] - - alias TeslaApi.{Auth, Error} - - @client_id "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384" - @client_secret "c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3" - - def get_api_tokens(%{access_token: access_token, refresh_token: refresh_token}) do - data = %{ - grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", - client_id: @client_id, - client_secret: @client_secret - } - - headers = [{"Authorization", "Bearer #{access_token}"}] - - case post("https://owner-api.teslamotors.com/oauth/token", data, headers: headers) do - {:ok, %Tesla.Env{status: 200, body: body}} -> - auth = %Auth{ - token: body["access_token"], - type: body["token_type"], - expires_in: body["expires_in"], - refresh_token: refresh_token, - created_at: body["created_at"] - } - - {:ok, auth} - - error -> - Error.into(error, :api_token_error) - end - end -end diff --git a/lib/tesla_api/auth/refresh.ex b/lib/tesla_api/auth/refresh.ex index caf50783..c727c91c 100644 --- a/lib/tesla_api/auth/refresh.ex +++ b/lib/tesla_api/auth/refresh.ex @@ -2,45 +2,62 @@ defmodule TeslaApi.Auth.Refresh do import TeslaApi.Auth, only: [post: 2] alias TeslaApi.{Auth, Error} - alias TeslaApi.Auth.OwnerApi @web_client_id TeslaApi.Auth.web_client_id() def refresh(%Auth{} = auth) do - with {:ok, %{access_token: _} = tokens} <- - refresh_oauth_access_token(auth.token, auth.refresh_token), - {:ok, auth} <- OwnerApi.get_api_tokens(tokens) do - {:ok, auth} - else + issuer_url = + case derive_issuer_url_from_oat(auth.token) do + {:ok, issuer_url} -> + issuer_url + + :error -> + case decode_jwt_payload(auth.token) do + {:ok, %{"iss" => iss}} -> URI.parse(iss) + _ -> "https://auth.tesla.com/oauth2/v3" + end + end + + data = %{ + grant_type: "refresh_token", + scope: "openid email offline_access", + client_id: @web_client_id, + refresh_token: auth.refresh_token + } + + case post("#{issuer_url}/token", data) do + {:ok, %Tesla.Env{status: 200, body: body}} -> + auth = %Auth{ + token: body["access_token"], + type: body["token_type"], + expires_in: body["expires_in"], + refresh_token: body["refresh_token"], + created_at: body["created_at"] + } + + {:ok, auth} + error -> Error.into(error, :token_refresh) end end - defp refresh_oauth_access_token(access_token, refresh_token) do - data = %{ - grant_type: "refresh_token", - scope: "openid email offline_access", - client_id: @web_client_id, - refresh_token: refresh_token - } + defp derive_issuer_url_from_oat("qts-" <> _), do: {:ok, "https://auth.tesla.com/oauth2/v3"} + defp derive_issuer_url_from_oat("eu-" <> _), do: {:ok, "https://auth.tesla.com/oauth2/v3"} + defp derive_issuer_url_from_oat("cn-" <> _), do: {:ok, "https://auth.tesla.cn/oauth2/v3"} + defp derive_issuer_url_from_oat(_), do: :error - base_url = - case access_token do - "cn-" <> _ -> "https://auth.tesla.cn" - _qts -> nil - end - - case post("#{base_url}/oauth2/v3/token", data) do - {:ok, - %Tesla.Env{ - status: 200, - body: %{"access_token" => access_token, "refresh_token" => refresh_token} - }} -> - {:ok, %{access_token: access_token, refresh_token: refresh_token}} + defp decode_jwt_payload(jwt) do + with [_algo, payload, _signature] <- String.split(jwt, "."), + {:ok, payload} <- Base.decode64(payload, padding: false), + {:ok, payload} <- Jason.decode(payload) do + {:ok, payload} + else + l when is_list(l) -> + Error.into({:error, :invalid_jwt}, :invalid_access_token) error -> - error + Error.into(error, :invalid_access_token) end end end diff --git a/lib/teslamate/api.ex b/lib/teslamate/api.ex index fa068cad..31263c1c 100644 --- a/lib/teslamate/api.ex +++ b/lib/teslamate/api.ex @@ -101,7 +101,7 @@ defmodule TeslaMate.Api do with %Tokens{access: at, refresh: rt} when is_binary(at) and is_binary(rt) <- call(deps.auth, :get_tokens) do - restored_tokens = %Auth{token: at, refresh_token: rt, expires_in: 1.12 * 60 * 60} + restored_tokens = %Auth{token: at, refresh_token: rt, expires_in: 20 * 60} case refresh_tokens(restored_tokens) do {:ok, refreshed_tokens} -> @@ -167,8 +167,8 @@ defmodule TeslaMate.Api do {:error, reason} -> Logger.warning("Token refresh failed: #{inspect(reason, pretty: true)}") - Logger.warning("Retrying in 1 hour...") - Process.send_after(self(), :refresh_auth, :timer.hours(1)) + Logger.warning("Retrying in 5 minutes...") + Process.send_after(self(), :refresh_auth, :timer.minutes(5)) end {:error, reason} -> @@ -202,7 +202,7 @@ defmodule TeslaMate.Api do defp schedule_refresh(%Auth{} = auth) do ms = auth.expires_in - |> Kernel.*(0.9) + |> Kernel.*(0.5) |> round() |> :timer.seconds()