implement github oauth and display user's name and avatar from it

This commit is contained in:
Silas 2021-11-12 20:20:40 -05:00
parent dfaa328af4
commit 540519222d
Signed by: silentsilas
GPG Key ID: 4199EFB7DAA34349
24 changed files with 226 additions and 181 deletions

1
.gitignore vendored
View File

@ -32,5 +32,6 @@ npm-debug.log
# we ignore priv/static. You may want to comment
# this depending on your deployment strategy.
/priv/static/
/priv/cert/
dev.secret.exs

View File

@ -5,16 +5,29 @@ To start your Phoenix server:
* Install dependencies with `mix deps.get`
* Create and migrate your database with `mix ecto.setup`
* Install Node.js dependencies with `npm install` inside the `assets` directory
* Run `mix phx.gen.cert` to use HTTPS during development, required for OAuth
* Start Phoenix endpoint with `mix phx.server`
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
## Learn more
## Setting up OAuth
* Official website: https://www.phoenixframework.org/
* Guides: https://hexdocs.pm/phoenix/overview.html
* Docs: https://hexdocs.pm/phoenix
* Forum: https://elixirforum.com/c/phoenix-forum
* Source: https://github.com/phoenixframework/phoenix
Add the OAuth providers you'll be using to the providers array.
```
use Mix.Config
config :ueberauth, Ueberauth,
providers: [
github: {Ueberauth.Strategy.Github, []}
]
```
You'll need to visit each provider's site and set up OAuth. This usually entails creating an OAuth App to generate a client_id, and generating a client_secret. Then create a `dev.secret.exs` file in your config folder and add your client_id & secret to your provider's ueberauth config.
```
config :ueberauth, Ueberauth.Strategy.Github.OAuth,
client_id: "some_client_id",
client_secret: "some_secret_key"
```

View File

@ -26,6 +26,11 @@ config :logger, :console,
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
config :ueberauth, Ueberauth,
providers: [
github: {Ueberauth.Strategy.Github, [default_scope: "user:email", allow_private_emails: true]}
]
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

View File

@ -17,6 +17,12 @@ config :entendu, Entendu.Repo,
# with webpack to recompile .js and .css sources.
config :entendu, EntenduWeb.Endpoint,
http: [port: System.get_env("PORT", "4000")],
https: [
port: 4001,
cipher_suite: :strong,
certfile: "priv/cert/selfsigned.pem",
keyfile: "priv/cert/selfsigned_key.pem"
],
debug_errors: true,
code_reloader: true,
check_origin: false,

View File

@ -6,12 +6,6 @@ defmodule Entendu.Application do
use Application
def start(_type, _args) do
# topologies = [
# chat: [
# strategy: Cluster.Strategy.Gossip
# ]
# ]
topologies = [
k8s_entendu: [
strategy: Elixir.Cluster.Strategy.Kubernetes.DNS,

View File

@ -0,0 +1,47 @@
defmodule Entendu.UserFromAuth do
@moduledoc """
Retrieve the user information from an auth request
"""
require Logger
require Jason
alias Ueberauth.Auth
def find_or_create(%Auth{} = auth) do
{:ok, basic_info(auth)}
end
# github does it this way
defp avatar_from_auth(%{info: %{urls: %{avatar_url: image}}}), do: image
# facebook does it this way
defp avatar_from_auth(%{info: %{image: image}}), do: image
# default case if nothing matches
defp avatar_from_auth(auth) do
Logger.warn("#{auth.provider} needs to find an avatar URL!")
Logger.debug(Jason.encode!(auth))
nil
end
defp basic_info(auth) do
IO.inspect(auth)
%{id: auth.uid, name: name_from_auth(auth), avatar: avatar_from_auth(auth)}
end
defp name_from_auth(auth) do
if auth.info.name do
auth.info.name
else
name =
[auth.info.first_name, auth.info.last_name]
|> Enum.filter(&(&1 != nil and &1 != ""))
if Enum.empty?(name) do
auth.info.nickname
else
Enum.join(name, " ")
end
end
end
end

View File

@ -0,0 +1,41 @@
defmodule EntenduWeb.AuthController do
@moduledoc """
Auth controller responsible for handling Ueberauth responses
"""
use EntenduWeb, :controller
plug Ueberauth
alias Ueberauth.Strategy.Helpers
alias Entendu.UserFromAuth
def delete(conn, _params) do
conn
|> put_flash(:info, "You have been logged out!")
|> clear_session()
|> redirect(to: "/")
end
def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do
conn
|> put_flash(:error, "Failed to authenticate.")
|> redirect(to: "/")
end
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
case UserFromAuth.find_or_create(auth) do
{:ok, user} ->
conn
|> put_flash(:info, "Successfully authenticated.")
|> put_session(:current_user, user)
|> configure_session(renew: true)
|> redirect(to: "/")
{:error, reason} ->
conn
|> put_flash(:error, reason)
|> redirect(to: "/")
end
end
end

View File

@ -0,0 +1,11 @@
defmodule EntenduWeb.LinkController do
@moduledoc """
Controller responsible for managing links responses
"""
use EntenduWeb, :controller
def just(conn, _params) do
render(conn, "just.html")
end
end

View File

@ -0,0 +1,11 @@
defmodule EntenduWeb.PageController do
@moduledoc """
Static page controller
"""
use EntenduWeb, :controller
def index(conn, _params) do
render(conn, "index.html", current_user: get_session(conn, :current_user))
end
end

View File

@ -1,13 +0,0 @@
defmodule EntenduWeb.LinkLive.For do
use EntenduWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, socket |> assign(:step, 1)}
end
def handle_event("next_step", _, %{ assigns: %{ step: step } } = socket) do
socket = socket |> assign(:step, step + 1)
{:noreply, socket}
end
end

View File

@ -1,4 +0,0 @@
<h1>Create Link</h1>
<%= live_component @socket, EntenduWeb.LinkLive.For.Step1Component, id: "step1", test: "test" %>
On Step: <%= @step %>

View File

@ -1,46 +0,0 @@
defmodule EntenduWeb.LinkLive.Index do
use EntenduWeb, :live_view
alias Entendu.Links
alias Entendu.Links.Link
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, :links, list_links())}
end
@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Link")
|> assign(:link, Links.get_link!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Link")
|> assign(:link, %Link{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Links")
|> assign(:link, nil)
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
link = Links.get_link!(id)
{:ok, _} = Links.delete_link(link)
{:noreply, assign(socket, :links, list_links())}
end
defp list_links do
Links.list_links()
end
end

View File

@ -1,37 +0,0 @@
<h1>Listing Links</h1>
<%= if @live_action in [:new, :edit] do %>
<%= live_modal @socket, EntenduWeb.LinkLive.FormComponent,
id: @link.id || :new,
title: @page_title,
action: @live_action,
link: @link,
return_to: Routes.link_index_path(@socket, :index) %>
<% end %>
<table>
<thead>
<tr>
<th>Expires</th>
<th>Burn after reading</th>
<th></th>
</tr>
</thead>
<tbody id="links">
<%= for link <- @links do %>
<tr id="link-<%= link.id %>">
<td><%= link.expires %></td>
<td><%= link.burn_after_reading %></td>
<td>
<span><%= live_redirect "Show", to: Routes.link_show_path(@socket, :show, link) %></span>
<span><%= live_patch "Edit", to: Routes.link_index_path(@socket, :edit, link) %></span>
<span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: link.id, data: [confirm: "Are you sure?"] %></span>
</td>
</tr>
<% end %>
</tbody>
</table>
<span><%= live_patch "New Link", to: Routes.link_index_path(@socket, :new) %></span>

View File

@ -1,4 +0,0 @@
defmodule EntenduWeb.LinkLive.For.Step1Component do
use EntenduWeb, :live_component
end

View File

@ -1,3 +0,0 @@
This is step 1. <%= @test %>
<button phx-click="next_step">Next Step</button>

View File

@ -1,39 +0,0 @@
defmodule EntenduWeb.PageLive do
use EntenduWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, query: "", results: %{})}
end
@impl true
def handle_event("suggest", %{"q" => query}, socket) do
{:noreply, assign(socket, results: search(query), query: query)}
end
@impl true
def handle_event("search", %{"q" => query}, socket) do
case search(query) do
%{^query => vsn} ->
{:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")}
_ ->
{:noreply,
socket
|> put_flash(:error, "No dependencies found matching \"#{query}\"")
|> assign(results: %{}, query: query)}
end
end
defp search(query) do
if not EntenduWeb.Endpoint.config(:code_reloader) do
raise "action disabled when not in development"
end
for {app, desc, vsn} <- Application.started_applications(),
app = to_string(app),
String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
into: %{},
do: {app, vsn}
end
end

View File

@ -1,18 +0,0 @@
<section class="phx-hero">
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
<p>Peace of mind from prototype to production</p>
<form phx-change="suggest" phx-submit="search">
<input type="text" name="q" value="<%= @query %>" placeholder="Live dependency search" list="results" autocomplete="off"/>
<datalist id="results">
<%= for {app, _vsn} <- @results do %>
<option value="<%= app %>"><%= app %></option>
<% end %>
</datalist>
<button type="submit" phx-disable-with="Searching...">Go to Hexdocs</button>
</form>
</section>
<section class="row">
<a href="/links">Links</a>
</section>

View File

@ -17,8 +17,16 @@ defmodule EntenduWeb.Router do
scope "/", EntenduWeb do
pipe_through :browser
live "/", PageLive, :index
live "/for", LinkLive.For
get "/", PageController, :index
get "/just", LinkController, :just
end
scope "/auth", EntenduWeb do
pipe_through :browser
get "/:provider", AuthController, :request
get "/:provider/callback", AuthController, :callback
delete "/logout", AuthController, :delete
end
# Other scopes may use custom stacks.

View File

@ -0,0 +1,6 @@
<section class="phx-hero">
<h2>Create a secret!</h2>
<p>There will be a form here</p>
<p>And a file input here</p>
<p>Then a next button</p>
</section>

View File

@ -0,0 +1,49 @@
<section class="phx-hero">
<h1>Überauth + Phoenix Example</h1>
<p>
This is an application to show an example of how to wire up
<a href="https://github.com/ueberauth/ueberauth">Überauth</a> with
<a href="https://github.com/phoenixframework/phoenix">Phoenix</a>.
</p>
<%= if @current_user do %>
<h2>Welcome, <%= @current_user.name %>!</h2>
<div>
<img src="<%= @current_user.avatar %>" />
</div>
<%= link "Logout", to: Routes.auth_path(@conn, :delete), method: "delete", class: "button" %>
<br>
<% else %>
<ul style="list-style: none">
<li>
<a class="button" href="<%= Routes.auth_path(@conn, :request, "github") %>">
<i class="fa fa-github"></i>
Sign in with GitHub
</a>
</li>
<li>
<a class="button" href="<%= Routes.auth_path(@conn, :request, "facebook") %>">
<i class="fa fa-facebook"></i>
Sign in with Facebook
</a>
</li>
<li>
<a class="button" href="<%= Routes.auth_path(@conn, :request, "google") %>">
<i class="fa fa-google"></i>
Sign in with Google
</a>
</li>
<li>
<a class="button" href="<%= Routes.auth_path(@conn, :request, "slack") %>">
<i class="fa fa-slack"></i>
Sign in with Slack
</a>
</li>
<li>
<a class="button" href="<%= Routes.auth_path(@conn, :request, "twitter") %>">
<i class="fa fa-twitter"></i>
Sign in with Twitter
</a>
</li>
</ul>
<% end %>
</section>

View File

@ -0,0 +1,3 @@
defmodule EntenduWeb.LinkView do
use EntenduWeb, :view
end

View File

@ -0,0 +1,3 @@
defmodule EntenduWeb.PageView do
use EntenduWeb, :view
end

View File

@ -48,7 +48,8 @@ defmodule Entendu.MixProject do
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:libcluster, "~> 3.2"},
{:ueberauth, "~> 0.6.3"}
{:ueberauth, "~> 0.7.0"},
{:ueberauth_github, "~> 0.8.1"}
]
end

View File

@ -1,4 +1,5 @@
%{
"certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
@ -10,10 +11,16 @@
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.31.0", "f05ee8a8e6a3ced4e62beeb2c79a63bc8e12ab98fbaaf6e6a3d9b76b1278e23f", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "b05afa372f5c345a5bf240ac25ea1f0f3d5fcfd7490ac0beeb4a203f9444891e"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"libcluster": {:hex, :libcluster, "3.3.0", "f7d45ff56d88e9fb4c30aee662480cbab69ebc0e7f7da4ad8d01b1e4f7492da8", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "ecdcdc88334ec8eb18b10a13a1d5f22a3319a970b5b1e66cfe71c7719a4ab6cc"},
"mime": {:hex, :mime, "2.0.1", "0de4c81303fe07806ebc2494d5321ce8fb4df106e34dd5f9d787b637ebadc256", [:mix], [], "hexpm", "7a86b920d2aedce5fb6280ac8261ac1a739ae6c1a1ad38f5eadf910063008942"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"oauth2": {:hex, :oauth2, "2.0.0", "338382079fe16c514420fa218b0903f8ad2d4bfc0ad0c9f988867dfa246731b0", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "881b8364ac7385f9fddc7949379cbe3f7081da37233a1aa7aab844670a91e7e7"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.5.10", "3ee7d5c17ff9626d72d374d8fc8909bf00f4323fd15549fbe3abbbd38b5299c8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9c2eaa5a8fe5a412610c6aa84ccdb6f3e92f333d4df7fbaeb0d5a157dbfb48d"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.3.0", "2c69a452c2e0ee8c93345ae1cdc1696ef4877ff9cbb15c305def41960c3c4ebf", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0ac491924217550c8f42c81c1f390b5d81517d12ceaf9abf3e701156760a848e"},
"phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
@ -26,8 +33,11 @@
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"postgrex": {:hex, :postgrex, "0.15.10", "2809dee1b1d76f7cbabe570b2a9285c2e7b41be60cf792f5f2804a54b838a067", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "1560ca427542f6b213f8e281633ae1a3b31cdbcd84ebd7f50628765b8f6132be"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
"ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
"ueberauth": {:hex, :ueberauth, "0.7.0", "9c44f41798b5fa27f872561b6f7d2bb0f10f03fdd22b90f454232d7b087f4b75", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2efad9022e949834f16cc52cd935165049d81fa9e925690f91035c2e4b58d905"},
"ueberauth_github": {:hex, :ueberauth_github, "0.8.1", "0be487b5afc29bc805fa5e31636f37c8f09d5159ef73fc08c4c7a98c9cfe2c18", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "143d6130b945ea9bdbd0ef94987f40788f1d7e8090decbfc0722773155e7a74a"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
}