Use SSO Access Tokens

This commit is contained in:
Adrian Kumpf
2021-09-30 13:26:37 +02:00
parent ed4d738dbc
commit 00ce5c39ce
4 changed files with 63 additions and 82 deletions
+15 -17
View File
@@ -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)
-34
View File
@@ -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
+44 -27
View File
@@ -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
+4 -4
View File
@@ -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()