defmodule TeslaApi.Auth do use Tesla alias TeslaApi.Error @web_client_id "ownerapi" @redirect_uri "https://auth.tesla.com/void/callback" def web_client_id, do: @web_client_id def redirect_uri, do: @redirect_uri @default_headers [ {"user-agent", "TeslaMate/#{Mix.Project.config()[:version]}"}, {"Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, {"Accept-Language", "en-US,de-DE;q=0.5"} ] adapter Tesla.Adapter.Finch, name: TeslaMate.HTTP, receive_timeout: 60_000 plug TeslaApi.Middleware.FollowRedirects, except: [@redirect_uri] plug Tesla.Middleware.BaseUrl, System.get_env("TESLA_AUTH_HOST", "https://auth.tesla.com") plug Tesla.Middleware.Headers, @default_headers plug Tesla.Middleware.JSON plug Tesla.Middleware.Logger, debug: true, log_level: &log_level/1 defstruct [:token, :type, :expires_in, :refresh_token, :created_at] defdelegate refresh(auth), to: __MODULE__.Refresh def issuer_url(%__MODULE__{token: access_token}) do case derive_issuer_url_from_oat(access_token) do {:ok, issuer_url} -> issuer_url :error -> case decode_jwt_payload(access_token) do {:ok, %{"iss" => issuer_url}} -> issuer_url _ -> "https://auth.tesla.com/oauth2/v3" end end end def region(%__MODULE__{} = auth) do tld = auth |> issuer_url() |> URI.parse() |> Map.fetch!(:host) |> String.split(".") |> List.last() case tld do "cn" -> :chinese "com" -> :global _other -> :other end end defp derive_issuer_url_from_oat("qts-" <> _), do: {:ok, System.get_env("TESLA_AUTH_HOST", "https://auth.tesla.com") <> System.get_env("TESLA_AUTH_PATH", "/oauth2/v3")} defp derive_issuer_url_from_oat("eu-" <> _), do: {:ok, System.get_env("TESLA_AUTH_HOST", "https://auth.tesla.com") <> System.get_env("TESLA_AUTH_PATH", "/oauth2/v3")} defp derive_issuer_url_from_oat("cn-" <> _), do: {:ok, System.get_env("TESLA_AUTH_HOST", "https://auth.tesla.com") <> System.get_env("TESLA_AUTH_PATH", "/oauth2/v3")} defp derive_issuer_url_from_oat(_), do: :error 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, reason} -> Error.into({:error, reason}, :invalid_access_token) _error -> Error.into({:error, "Invalid access token"}, :invalid_access_token) end end defp log_level(%Tesla.Env{} = env) when env.status >= 400, do: :error defp log_level(%Tesla.Env{}), do: :info end