mirror of
https://github.com/netfun2000/hipudding-teslamate.git
synced 2026-02-27 09:44:28 +08:00
Use SSO Access Tokens
This commit is contained in:
+14
-16
@@ -2,7 +2,7 @@ defmodule TeslaApi.Auth.Login do
|
|||||||
import TeslaApi.Auth, only: [get: 2, post: 2, post: 3]
|
import TeslaApi.Auth, only: [get: 2, post: 2, post: 3]
|
||||||
|
|
||||||
alias TeslaApi.Error
|
alias TeslaApi.Error
|
||||||
alias TeslaApi.Auth.{MFA, OwnerApi, Util}
|
alias TeslaApi.Auth.{MFA, Util}
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@@ -56,9 +56,8 @@ defmodule TeslaApi.Auth.Login do
|
|||||||
|
|
||||||
with {:ok, %Tesla.Env{} = env} <- submit_form(form, ctx),
|
with {:ok, %Tesla.Env{} = env} <- submit_form(form, ctx),
|
||||||
{:ok, {redirect_uri, code}} <- Util.parse_location_header(env, ctx.state),
|
{:ok, {redirect_uri, code}} <- Util.parse_location_header(env, ctx.state),
|
||||||
{:ok, tokens} <-
|
{:ok, auth} <-
|
||||||
get_web_token(code, ctx.code_verifier, redirect_uri, ctx.state, base: ctx.base_url),
|
get_web_token(code, ctx.code_verifier, redirect_uri, ctx.state, base: ctx.base_url) do
|
||||||
{:ok, auth} <- OwnerApi.get_api_tokens(tokens) do
|
|
||||||
{:ok, auth}
|
{:ok, auth}
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
@@ -193,9 +192,8 @@ defmodule TeslaApi.Auth.Login do
|
|||||||
with {:ok, env} <-
|
with {:ok, env} <-
|
||||||
MFA.verify_passcode(device_id, mfa_passcode, transaction_id, headers),
|
MFA.verify_passcode(device_id, mfa_passcode, transaction_id, headers),
|
||||||
{:ok, {redirect_uri, code}} <- Util.parse_location_header(env, ctx.state),
|
{:ok, {redirect_uri, code}} <- Util.parse_location_header(env, ctx.state),
|
||||||
{:ok, tokens} <-
|
{:ok, auth} <-
|
||||||
get_web_token(code, ctx.code_verifier, redirect_uri, ctx.state),
|
get_web_token(code, ctx.code_verifier, redirect_uri, ctx.state) do
|
||||||
{:ok, auth} <- OwnerApi.get_api_tokens(tokens) do
|
|
||||||
{:ok, auth}
|
{:ok, auth}
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
@@ -234,16 +232,16 @@ defmodule TeslaApi.Auth.Login do
|
|||||||
}
|
}
|
||||||
|
|
||||||
case post("#{opts[:base]}/oauth2/v3/token", data) do
|
case post("#{opts[:base]}/oauth2/v3/token", data) do
|
||||||
{:ok,
|
{:ok, %Tesla.Env{status: 200, body: %{"state" => ^state} = body}} ->
|
||||||
%Tesla.Env{
|
auth = %TeslaApi.Auth{
|
||||||
status: 200,
|
token: body["access_token"],
|
||||||
body: %{
|
type: body["token_type"],
|
||||||
"access_token" => access_token,
|
expires_in: body["expires_in"],
|
||||||
"refresh_token" => refresh_token,
|
refresh_token: body["refresh_token"],
|
||||||
"state" => ^state
|
created_at: body["created_at"]
|
||||||
}
|
}
|
||||||
}} ->
|
|
||||||
{:ok, %{access_token: access_token, refresh_token: refresh_token}}
|
{:ok, auth}
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
Error.into(error, :web_token_error)
|
Error.into(error, :web_token_error)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -2,45 +2,62 @@ defmodule TeslaApi.Auth.Refresh do
|
|||||||
import TeslaApi.Auth, only: [post: 2]
|
import TeslaApi.Auth, only: [post: 2]
|
||||||
|
|
||||||
alias TeslaApi.{Auth, Error}
|
alias TeslaApi.{Auth, Error}
|
||||||
alias TeslaApi.Auth.OwnerApi
|
|
||||||
|
|
||||||
@web_client_id TeslaApi.Auth.web_client_id()
|
@web_client_id TeslaApi.Auth.web_client_id()
|
||||||
|
|
||||||
def refresh(%Auth{} = auth) do
|
def refresh(%Auth{} = auth) do
|
||||||
with {:ok, %{access_token: _} = tokens} <-
|
issuer_url =
|
||||||
refresh_oauth_access_token(auth.token, auth.refresh_token),
|
case derive_issuer_url_from_oat(auth.token) do
|
||||||
{:ok, auth} <- OwnerApi.get_api_tokens(tokens) 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}
|
{:ok, auth}
|
||||||
else
|
|
||||||
error ->
|
error ->
|
||||||
Error.into(error, :token_refresh)
|
Error.into(error, :token_refresh)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp refresh_oauth_access_token(access_token, refresh_token) do
|
defp derive_issuer_url_from_oat("qts-" <> _), do: {:ok, "https://auth.tesla.com/oauth2/v3"}
|
||||||
data = %{
|
defp derive_issuer_url_from_oat("eu-" <> _), do: {:ok, "https://auth.tesla.com/oauth2/v3"}
|
||||||
grant_type: "refresh_token",
|
defp derive_issuer_url_from_oat("cn-" <> _), do: {:ok, "https://auth.tesla.cn/oauth2/v3"}
|
||||||
scope: "openid email offline_access",
|
defp derive_issuer_url_from_oat(_), do: :error
|
||||||
client_id: @web_client_id,
|
|
||||||
refresh_token: refresh_token
|
|
||||||
}
|
|
||||||
|
|
||||||
base_url =
|
defp decode_jwt_payload(jwt) do
|
||||||
case access_token do
|
with [_algo, payload, _signature] <- String.split(jwt, "."),
|
||||||
"cn-" <> _ -> "https://auth.tesla.cn"
|
{:ok, payload} <- Base.decode64(payload, padding: false),
|
||||||
_qts -> nil
|
{:ok, payload} <- Jason.decode(payload) do
|
||||||
end
|
{:ok, payload}
|
||||||
|
else
|
||||||
case post("#{base_url}/oauth2/v3/token", data) do
|
l when is_list(l) ->
|
||||||
{:ok,
|
Error.into({:error, :invalid_jwt}, :invalid_access_token)
|
||||||
%Tesla.Env{
|
|
||||||
status: 200,
|
|
||||||
body: %{"access_token" => access_token, "refresh_token" => refresh_token}
|
|
||||||
}} ->
|
|
||||||
{:ok, %{access_token: access_token, refresh_token: refresh_token}}
|
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
error
|
Error.into(error, :invalid_access_token)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ defmodule TeslaMate.Api do
|
|||||||
|
|
||||||
with %Tokens{access: at, refresh: rt} when is_binary(at) and is_binary(rt) <-
|
with %Tokens{access: at, refresh: rt} when is_binary(at) and is_binary(rt) <-
|
||||||
call(deps.auth, :get_tokens) do
|
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
|
case refresh_tokens(restored_tokens) do
|
||||||
{:ok, refreshed_tokens} ->
|
{:ok, refreshed_tokens} ->
|
||||||
@@ -167,8 +167,8 @@ defmodule TeslaMate.Api do
|
|||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
Logger.warning("Token refresh failed: #{inspect(reason, pretty: true)}")
|
Logger.warning("Token refresh failed: #{inspect(reason, pretty: true)}")
|
||||||
Logger.warning("Retrying in 1 hour...")
|
Logger.warning("Retrying in 5 minutes...")
|
||||||
Process.send_after(self(), :refresh_auth, :timer.hours(1))
|
Process.send_after(self(), :refresh_auth, :timer.minutes(5))
|
||||||
end
|
end
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
@@ -202,7 +202,7 @@ defmodule TeslaMate.Api do
|
|||||||
defp schedule_refresh(%Auth{} = auth) do
|
defp schedule_refresh(%Auth{} = auth) do
|
||||||
ms =
|
ms =
|
||||||
auth.expires_in
|
auth.expires_in
|
||||||
|> Kernel.*(0.9)
|
|> Kernel.*(0.5)
|
||||||
|> round()
|
|> round()
|
||||||
|> :timer.seconds()
|
|> :timer.seconds()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user