mirror of
https://github.com/fly-apps/live_beats.git
synced 2025-01-28 07:18:07 +00:00
129 lines
3.5 KiB
Elixir
129 lines
3.5 KiB
Elixir
defmodule LiveBeats.Github do
|
|
def authorize_url() do
|
|
state = random_string()
|
|
"https://github.com/login/oauth/authorize?client_id=#{client_id()}&state=#{state}&scope=user:email"
|
|
end
|
|
|
|
def exchange_access_token(opts) do
|
|
code = Keyword.fetch!(opts, :code)
|
|
state = Keyword.fetch!(opts, :state)
|
|
|
|
state
|
|
|> fetch_exchange_response(code)
|
|
|> fetch_user_info()
|
|
|> fetch_emails()
|
|
end
|
|
|
|
defp fetch_exchange_response(state, code) do
|
|
resp =
|
|
http(
|
|
"github.com",
|
|
"POST",
|
|
"/login/oauth/access_token",
|
|
[state: state, code: code, client_secret: secret()],
|
|
[{"accept", "application/json"}]
|
|
)
|
|
|
|
with {:ok, resp} <- resp,
|
|
%{"access_token" => token} <- Jason.decode!(resp) do
|
|
{:ok, token}
|
|
else
|
|
{:error, _reason} = err -> err
|
|
%{} = resp -> {:error, {:bad_response, resp}}
|
|
end
|
|
end
|
|
|
|
defp fetch_user_info({:error, _reason} = error), do: error
|
|
defp fetch_user_info({:ok, token}) do
|
|
resp =
|
|
http(
|
|
"api.github.com",
|
|
"GET",
|
|
"/user",
|
|
[],
|
|
[{"accept", "application/vnd.github.v3+json"}, {"Authorization", "token #{token}"}]
|
|
)
|
|
case resp do
|
|
{:ok, info} -> {:ok, %{info: Jason.decode!(info), token: token}}
|
|
{:error, _reason} = err -> err
|
|
end
|
|
end
|
|
|
|
defp fetch_emails({:error, _} = err), do: err
|
|
defp fetch_emails({:ok, user}) do
|
|
resp =
|
|
http(
|
|
"api.github.com",
|
|
"GET",
|
|
"/user/emails",
|
|
[],
|
|
[{"accept", "application/vnd.github.v3+json"}, {"Authorization", "token #{user.token}"}]
|
|
)
|
|
case resp do
|
|
{:ok, info} ->
|
|
emails = Jason.decode!(info)
|
|
{:ok, Map.merge(user, %{primary_email: primary_email(emails), emails: emails})}
|
|
|
|
{:error, _reason} = err ->
|
|
err
|
|
end
|
|
end
|
|
|
|
def random_string do
|
|
binary = <<
|
|
System.system_time(:nanosecond)::64,
|
|
:erlang.phash2({node(), self()})::16,
|
|
:erlang.unique_integer()::16
|
|
>>
|
|
|
|
binary
|
|
|> Base.url_encode64()
|
|
|> String.replace(["/", "+"], "-")
|
|
end
|
|
|
|
defp client_id, do: LiveBeats.config([:github, :client_id])
|
|
defp secret, do: LiveBeats.config([:github, :client_secret])
|
|
|
|
defp http(host, method, path, query, headers, body \\ "") do
|
|
{:ok, conn} = Mint.HTTP.connect(:https, host, 443)
|
|
|
|
path = path <> "?" <> URI.encode_query([{:client_id, client_id()} | query])
|
|
|
|
{:ok, conn, ref} =
|
|
Mint.HTTP.request(
|
|
conn,
|
|
method,
|
|
path,
|
|
headers,
|
|
body
|
|
)
|
|
|
|
receive_resp(conn, ref, nil, nil, false)
|
|
end
|
|
|
|
defp receive_resp(conn, ref, status, data, done?) do
|
|
receive do
|
|
message ->
|
|
{:ok, conn, responses} = Mint.HTTP.stream(conn, message)
|
|
|
|
{new_status, new_data, done?} =
|
|
Enum.reduce(responses, {status, data, done?}, fn
|
|
{:status, ^ref, new_status}, {_old_status, data, done?} -> {new_status, data, done?}
|
|
{:headers, ^ref, _headers}, acc -> acc
|
|
{:data, ^ref, binary}, {status, nil, done?} -> {status, binary, done?}
|
|
{:data, ^ref, binary}, {status, data, done?} -> {status, data <> binary, done?}
|
|
{:done, ^ref}, {status, data, _done?} -> {status, data, true}
|
|
end)
|
|
|
|
cond do
|
|
done? and new_status == 200 -> {:ok, new_data}
|
|
done? -> {:error, {new_status, new_data}}
|
|
!done? -> receive_resp(conn, ref, new_status, new_data, done?)
|
|
end
|
|
end
|
|
end
|
|
|
|
defp primary_email(emails) do
|
|
Enum.find(emails, fn email -> email["primary"] end)["email"] || Enum.at(emails, 0)
|
|
end
|
|
end
|