Initial Twitter strategy
This commit is contained in:
parent
8330fa308b
commit
2cf6544544
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
/deps
|
/deps
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
*.ez
|
*.ez
|
||||||
|
.DS_Store
|
||||||
|
14
CONTRIBUTING.md
Normal file
14
CONTRIBUTING.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Contributing to Ueberauth Twitter
|
||||||
|
|
||||||
|
## Pull Requests Welcome
|
||||||
|
1. Fork ueberauth_twitter
|
||||||
|
2. Create a topic branch
|
||||||
|
3. Make logically-grouped commits with clear commit messages
|
||||||
|
4. Push commits to your fork
|
||||||
|
5. Open a pull request against ueberauth_twitter/master
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
If you believe there to be a bug, please provide the maintainers with enough
|
||||||
|
detail to reproduce or a link to an app exhibiting unexpected behavior. For
|
||||||
|
help, please start with Stack Overflow.
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Sean Callan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
87
README.md
87
README.md
@ -2,35 +2,78 @@
|
|||||||
|
|
||||||
> Twitter strategy for Überauth.
|
> Twitter strategy for Überauth.
|
||||||
|
|
||||||
### Setup
|
_Note_: Sessions are required for this strategy.
|
||||||
|
|
||||||
Include the provider in your configuration for Überauth:
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
config :ueberauth, Ueberauth,
|
|
||||||
providers: [
|
|
||||||
twitter: [ { Ueberauth.Strategy.Twitter, [] } ]
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Then configure your provider:
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
|
|
||||||
client_id: System.get_env("TWITTER_API_KEY"),
|
|
||||||
client_secret: System.get_env("TWITTER_API_SECRET")
|
|
||||||
```
|
|
||||||
|
|
||||||
For an example implementation see the [Überauth Example](https://github.com/doomspork/ueberauth_example) application.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:
|
1. Setup your application at [Twitter Developers](https://dev.twitter.com/).
|
||||||
|
|
||||||
1. Add `:ueberauth_twitter` to your list of dependencies in `mix.exs`:
|
1. Add `:ueberauth_twitter` to your list of dependencies in `mix.exs`:
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
def deps do
|
def deps do
|
||||||
[{:ueberauth_twitter, "~> 0.1.0"}]
|
[{:ueberauth_twitter, "~> 0.1"},
|
||||||
|
{:oauth, github: "tim/erlang-oauth"}]
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
1. Add the strategy to your applications:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
def application do
|
||||||
|
[applications: [:ueberauth_twitter]]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Add Twitter to your Überauth configuration:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :ueberauth, Ueberauth,
|
||||||
|
providers: [
|
||||||
|
twitter: [{Ueberauth.Strategy.Twitter, []}]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Update your provider configuration:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
|
||||||
|
consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
|
||||||
|
consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Include the Überauth plug in your controller:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule MyApp.AuthController do
|
||||||
|
use MyApp.Web, :controller
|
||||||
|
plug Ueberauth
|
||||||
|
...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Create the request and callback routes if you haven't already:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
scope "/auth", MyApp do
|
||||||
|
pipe_through :browser
|
||||||
|
|
||||||
|
get "/:provider", AuthController, :request
|
||||||
|
get "/:provider/callback", AuthController, :callback
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
1. You controller needs to implement callbacks to deal with `Ueberauth.Auth` and `Ueberauth.Failure` responses.
|
||||||
|
|
||||||
|
For an example implementation see the [Überauth Example](https://github.com/ueberauth/ueberauth_example) application.
|
||||||
|
|
||||||
|
## Calling
|
||||||
|
|
||||||
|
Depending on the configured url you can initial the request through:
|
||||||
|
|
||||||
|
/auth/twitter
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Please see [LICENSE](https://github.com/ueberauth/ueberauth_twitter/blob/master/LICENSE) for licensing details.
|
||||||
|
|
||||||
|
1
config/config.exs
Normal file
1
config/config.exs
Normal file
@ -0,0 +1 @@
|
|||||||
|
use Mix.Config
|
123
lib/ueberauth/strategy/twitter.ex
Normal file
123
lib/ueberauth/strategy/twitter.ex
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
defmodule Ueberauth.Strategy.Twitter do
|
||||||
|
@moduledoc """
|
||||||
|
Twitter Strategy for Überauth.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ueberauth.Strategy, uid_field: :id_str
|
||||||
|
|
||||||
|
alias Ueberauth.Auth.Info
|
||||||
|
alias Ueberauth.Auth.Credentials
|
||||||
|
alias Ueberauth.Auth.Extra
|
||||||
|
alias Ueberauth.Strategy.Twitter
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Handles initial request for Twitter authentication.
|
||||||
|
"""
|
||||||
|
def handle_request!(conn) do
|
||||||
|
token = Twitter.OAuth.request_token!([], [redirect_uri: callback_url(conn)])
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_session(:twitter_token, token)
|
||||||
|
|> redirect!(Twitter.OAuth.authorize_url!(token))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Handles the callback from Twitter.
|
||||||
|
"""
|
||||||
|
def handle_callback!(%Plug.Conn{params: %{"oauth_verifier" => oauth_verifier}} = conn) do
|
||||||
|
token = get_session(conn, :twitter_token)
|
||||||
|
case Twitter.OAuth.access_token(token, oauth_verifier) do
|
||||||
|
{:ok, access_token} -> fetch_user(conn, access_token)
|
||||||
|
{:error, error} -> set_errors!(conn, [error(error.code, error.reason)])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def handle_callback!(conn) do
|
||||||
|
set_errors!(conn, [error("missing_code", "No code received")])
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def handle_cleanup!(conn) do
|
||||||
|
conn
|
||||||
|
|> put_private(:twitter_user, nil)
|
||||||
|
|> put_session(:twitter_token, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Fetches the uid field from the response.
|
||||||
|
"""
|
||||||
|
def uid(conn) do
|
||||||
|
uid_field =
|
||||||
|
conn
|
||||||
|
|> option(:uid_field)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
|
conn.private.twitter_user[uid_field]
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Includes the credentials from the twitter response.
|
||||||
|
"""
|
||||||
|
def credentials(conn) do
|
||||||
|
{token, _secret} = get_session(conn, :twitter_token
|
||||||
|
|
||||||
|
%Credentials{token: token}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Fetches the fields to populate the info section of the `Ueberauth.Auth` struct.
|
||||||
|
"""
|
||||||
|
def info(conn) do
|
||||||
|
user = conn.private.twitter_user
|
||||||
|
|
||||||
|
%Info{
|
||||||
|
email: user["email"],
|
||||||
|
image: user["profile_image_url"],
|
||||||
|
name: user["name"],
|
||||||
|
nickname: user["screen_name"],
|
||||||
|
description: user["description"],
|
||||||
|
urls: %{
|
||||||
|
Twitter: "https://twitter.com/#{user["screen_name"]}",
|
||||||
|
Website: user["url"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Stores the raw information (including the token) obtained from the twitter callback.
|
||||||
|
"""
|
||||||
|
def extra(conn) do
|
||||||
|
{token, _secret} = get_session(conn, :twitter_token)
|
||||||
|
|
||||||
|
%Extra{
|
||||||
|
raw_info: %{
|
||||||
|
token: token,
|
||||||
|
user: conn.private.twitter_user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_user(conn, token) do
|
||||||
|
params = [include_entities: false, skip_status: true, include_email: true]
|
||||||
|
case Twitter.OAuth.get("/1.1/account/verify_credentials.json", params, token) do
|
||||||
|
{:ok, {{_, 401, _}, _, _}} ->
|
||||||
|
set_errors!(conn, [error("token", "unauthorized")])
|
||||||
|
{:ok, {{_, status_code, _}, _, body}} when status_code in 200..399 ->
|
||||||
|
body = body |> List.to_string |> Poison.decode!
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_private(:twitter_token, token)
|
||||||
|
|> put_private(:twitter_user, body)
|
||||||
|
{:ok, {_, _, body}} ->
|
||||||
|
body = body |> List.to_string |> Poison.decode!
|
||||||
|
|
||||||
|
error = List.first(body["errors"])
|
||||||
|
set_errors!(conn, [error("token", error["message"])])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp option(conn, key) do
|
||||||
|
Dict.get(options(conn), key, Dict.get(default_options, key))
|
||||||
|
end
|
||||||
|
end
|
110
lib/ueberauth/strategy/twitter/oauth.ex
Normal file
110
lib/ueberauth/strategy/twitter/oauth.ex
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
defmodule Ueberauth.Strategy.Twitter.OAuth do
|
||||||
|
@moduledoc """
|
||||||
|
OAuth1 for Twitter.
|
||||||
|
|
||||||
|
Add `consumer_key` and `consumer_secret` to your configuration:
|
||||||
|
|
||||||
|
config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
|
||||||
|
consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
|
||||||
|
consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"),
|
||||||
|
redirect_uri: System.get_env("TWITTER_REDIRECT_URI")
|
||||||
|
"""
|
||||||
|
|
||||||
|
@defaults [access_token: "/oauth/access_token",
|
||||||
|
authorize_url: "/oauth/authorize",
|
||||||
|
request_token: "/oauth/request_token",
|
||||||
|
site: "https://api.twitter.com"]
|
||||||
|
|
||||||
|
def access_token({token, token_secret}, verifier, opts \\ []) do
|
||||||
|
opts
|
||||||
|
|> client
|
||||||
|
|> to_url(:access_token)
|
||||||
|
|> String.to_char_list
|
||||||
|
|> :oauth.get([oauth_verifier: verifier], consumer(client), token, token_secret)
|
||||||
|
|> decode_access_response
|
||||||
|
end
|
||||||
|
|
||||||
|
def access_token!(access_token, verifier, opts \\ []) do
|
||||||
|
case access_token(access_token, verifier, opts) do
|
||||||
|
{:ok, token} -> token
|
||||||
|
{:error, error} -> raise error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_url!({token, _token_secret}, opts \\ []) do
|
||||||
|
opts
|
||||||
|
|> client
|
||||||
|
|> to_url(:authorize_url, %{"oauth_token" => List.to_string(token)})
|
||||||
|
end
|
||||||
|
|
||||||
|
def client(opts \\ []) do
|
||||||
|
config = Application.get_env(:ueberauth, __MODULE__)
|
||||||
|
|
||||||
|
@defaults
|
||||||
|
|> Keyword.merge(config)
|
||||||
|
|> Keyword.merge(opts)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(url, access_token), do: get(url, [], access_token)
|
||||||
|
def get(url, params \\ [], {token, token_secret}) do
|
||||||
|
client
|
||||||
|
|> to_url(url)
|
||||||
|
|> String.to_char_list
|
||||||
|
|> :oauth.get(params, consumer(client), token, token_secret)
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_token(params \\ [], opts \\ []) do
|
||||||
|
client = client(opts)
|
||||||
|
params = Keyword.put_new(params, :oauth_callback, client.redirect_uri)
|
||||||
|
|
||||||
|
client
|
||||||
|
|> to_url(:request_token)
|
||||||
|
|> String.to_char_list
|
||||||
|
|> :oauth.get(params, consumer(client))
|
||||||
|
|> decode_request_response
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_token!(params \\ [], opts \\ []) do
|
||||||
|
case request_token(params, opts) do
|
||||||
|
{:ok, token} -> token
|
||||||
|
{:error, error} -> raise error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp consumer(client), do: {client.consumer_key, client.consumer_secret, :hmac_sha1}
|
||||||
|
|
||||||
|
defp decode_access_response({:ok, {{_, 200, _}, _, _} = resp}) do
|
||||||
|
params = :oauth.params_decode(resp)
|
||||||
|
token = :oauth.token(params)
|
||||||
|
token_secret = :oauth.token_secret(params)
|
||||||
|
|
||||||
|
{:ok, {token, token_secret}}
|
||||||
|
end
|
||||||
|
defp decode_access_response(error), do: {:error, error}
|
||||||
|
|
||||||
|
defp decode_request_response({:ok, {{_, 200, _}, _, _} = resp}) do
|
||||||
|
params = :oauth.params_decode(resp)
|
||||||
|
token = :oauth.token(params)
|
||||||
|
token_secret = :oauth.token_secret(params)
|
||||||
|
|
||||||
|
{:ok, {token, token_secret}}
|
||||||
|
end
|
||||||
|
defp decode_request_response(error), do: {:error, error}
|
||||||
|
|
||||||
|
defp endpoint("/" <> _path = endpoint, client), do: client.site <> endpoint
|
||||||
|
defp endpoint(endpoint, _client), do: endpoint
|
||||||
|
|
||||||
|
defp to_url(client, endpoint, params \\ nil) do
|
||||||
|
endpoint =
|
||||||
|
client
|
||||||
|
|> Map.get(endpoint, endpoint)
|
||||||
|
|> endpoint(client)
|
||||||
|
|
||||||
|
unless params == nil do
|
||||||
|
endpoint = endpoint <> "?" <> URI.encode_query(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
endpoint
|
||||||
|
end
|
||||||
|
end
|
2
lib/ueberauth_twitter.ex
Normal file
2
lib/ueberauth_twitter.ex
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
defmodule UeberauthTwitter do
|
||||||
|
end
|
52
mix.exs
Normal file
52
mix.exs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
defmodule UeberauthTwitter.Mixfile do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
@version "0.1.0"
|
||||||
|
@url "https://github.com/doomspork/ueberauth_twitter"
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[app: :ueberauth_twitter,
|
||||||
|
version: @version,
|
||||||
|
name: "Ueberauth Twitter Strategy",
|
||||||
|
package: package,
|
||||||
|
elixir: "~> 1.1",
|
||||||
|
build_embedded: Mix.env == :prod,
|
||||||
|
start_permanent: Mix.env == :prod,
|
||||||
|
source_url: @url,
|
||||||
|
homepage_url: @url,
|
||||||
|
description: description,
|
||||||
|
deps: deps,
|
||||||
|
docs: docs]
|
||||||
|
end
|
||||||
|
|
||||||
|
def application do
|
||||||
|
[applications: [:logger, :httpoison, :oauth, :ueberauth]]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp deps do
|
||||||
|
[{:ueberauth, "~> 0.1"},
|
||||||
|
{:oauth, github: "tim/erlang-oauth"},
|
||||||
|
{:httpoison, "~> 0.7"},
|
||||||
|
{:ex_doc, "~> 0.1", only: :dev},
|
||||||
|
{:earmark, ">= 0.0.0", only: :dev}]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp docs do
|
||||||
|
[extras: docs_extras, main: "extra-readme"]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp docs_extras do
|
||||||
|
["README.md"]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp description do
|
||||||
|
"An Uberauth strategy for Twitter authentication."
|
||||||
|
end
|
||||||
|
|
||||||
|
defp package do
|
||||||
|
[files: ["lib", "mix.exs", "README.md", "LICENSE"],
|
||||||
|
maintainers: ["Sean Callan"],
|
||||||
|
licenses: ["MIT"],
|
||||||
|
links: %{"GitHub": @url}]
|
||||||
|
end
|
||||||
|
end
|
15
mix.lock
Normal file
15
mix.lock
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
%{"certifi": {:hex, :certifi, "0.1.1"},
|
||||||
|
"earmark": {:hex, :earmark, "0.1.19"},
|
||||||
|
"ex_doc": {:hex, :ex_doc, "0.10.0"},
|
||||||
|
"hackney": {:hex, :hackney, "1.4.4"},
|
||||||
|
"httpoison": {:hex, :httpoison, "0.8.0"},
|
||||||
|
"idna": {:hex, :idna, "1.0.2"},
|
||||||
|
"mimerl": {:hex, :mimerl, "1.0.0"},
|
||||||
|
"mimetype_parser": {:hex, :mimetype_parser, "0.1.0"},
|
||||||
|
"oauth": {:git, "https://github.com/tim/erlang-oauth.git", "cd31addc828179983564fd57738f1680a4a4d1ff", []},
|
||||||
|
"oauth2": {:hex, :oauth2, "0.5.0"},
|
||||||
|
"oauther": {:hex, :oauther, "1.0.2"},
|
||||||
|
"plug": {:hex, :plug, "1.0.2"},
|
||||||
|
"poison": {:hex, :poison, "1.5.0"},
|
||||||
|
"ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"},
|
||||||
|
"ueberauth": {:hex, :ueberauth, "0.1.0"}}
|
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
@ -0,0 +1 @@
|
|||||||
|
ExUnit.start()
|
4
test/ueber_twitter_test.exs
Normal file
4
test/ueber_twitter_test.exs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
defmodule UeberauthTwitterTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest UeberauthTwitter
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user