├── config ├── test.exs └── config.exs ├── test ├── test_helper.exs ├── ueber_twitter_test.exs └── strategy │ └── twitter │ └── oauth_test.exs ├── .github ├── CODEOWNERS └── workflows │ ├── release.yml │ └── ci.yml ├── lib ├── ueberauth_twitter.ex └── ueberauth │ └── strategy │ ├── twitter │ ├── internal.ex │ └── oauth.ex │ └── twitter.ex ├── .formatter.exs ├── CONTRIBUTING.md ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── mix.exs ├── README.md └── mix.lock /config/test.exs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @ueberauth/developers 2 | -------------------------------------------------------------------------------- /lib/ueberauth_twitter.ex: -------------------------------------------------------------------------------- 1 | defmodule UeberauthTwitter do 2 | @moduledoc false 3 | end 4 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /test/ueber_twitter_test.exs: -------------------------------------------------------------------------------- 1 | defmodule UeberauthTwitterTest do 2 | use ExUnit.Case 3 | doctest UeberauthTwitter 4 | end 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Ueberauth Twitter 2 | 3 | ## Pull Requests Welcome 4 | 1. Fork ueberauth_twitter 5 | 2. Create a topic branch 6 | 3. Make logically-grouped commits with clear commit messages 7 | 4. Push commits to your fork 8 | 5. Open a pull request against ueberauth_twitter/master 9 | 10 | ## Issues 11 | 12 | If you believe there to be a bug, please provide the maintainers with enough 13 | detail to reproduce or a link to an app exhibiting unexpected behavior. For 14 | help, please start with Stack Overflow. 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | ueberauth_twitter-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | 28 | # Misc. 29 | .DS_Store 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Hexpm Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | name: Publish 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Elixir 14 | uses: erlef/setup-elixir@v1 15 | with: 16 | elixir-version: '1.11' 17 | otp-version: '22.3' 18 | - name: Restore dependencies cache 19 | uses: actions/cache@v2 20 | with: 21 | path: deps 22 | key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} 23 | restore-keys: ${{ runner.os }}-mix- 24 | - name: Install dependencies 25 | run: | 26 | mix local.rebar --force 27 | mix local.hex --force 28 | mix deps.get 29 | - name: Run Hex Publish 30 | run: mix hex.publish --yes 31 | env: 32 | HEX_API_KEY: ${{ secrets.HEX_API_KEY }} 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## v0.4.1 - 2021-07-19 11 | 12 | * Protect against CSRF attacks by adding a state parameter 13 | * Bump version of Ueberauth to `0.7` 14 | 15 | ## v0.3.0 - 2019-03-15 16 | 17 | * Use JSON library from Ueberauth 18 | * Bump version of Ueberauth to `0.6` 19 | 20 | ## v0.2.4 - 2017-03-15 21 | 22 | * Add Poison dependency 23 | * Improve OAuth error handling 24 | 25 | ## v0.2.3 - 2016-08-15 26 | 27 | * Fix Elixir 1.3 warnings 28 | 29 | ## v0.2.2 - 2015-12-20 30 | 31 | * Credentials now include token and secret 32 | 33 | ## v0.2.1 - 2015-12-12 34 | 35 | * Fix missing parenthesis 36 | * Update `ueberauth` and `plug` 37 | 38 | ## v0.2.0 - 2015-11-28 39 | 40 | * Bump revision to follow ueberauth 0.2.0 41 | 42 | ## v0.1.0 - 2015-11-24 43 | 44 | * Initial Twitter OAuth strategy 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sean Callan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | push: 7 | branches: 8 | - 'master' 9 | jobs: 10 | Test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Code 14 | uses: actions/checkout@v1 15 | 16 | - name: Set up Elixir 17 | uses: erlef/setup-elixir@v1 18 | with: 19 | elixir-version: '1.11' 20 | otp-version: '22.3' 21 | 22 | - name: Install Dependencies 23 | run: | 24 | mix local.rebar --force 25 | mix local.hex --force 26 | mix deps.get 27 | 28 | - name: Run Tests 29 | run: mix test 30 | 31 | Linting: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout Code 35 | uses: actions/checkout@v1 36 | 37 | - name: Set up Elixir 38 | uses: erlef/setup-elixir@v1 39 | with: 40 | elixir-version: '1.11' 41 | otp-version: '22.3' 42 | 43 | - name: Install Dependencies 44 | run: | 45 | mix local.rebar --force 46 | mix local.hex --force 47 | mix deps.get 48 | 49 | - name: Run Formatter 50 | run: mix format --check-formatted 51 | 52 | - name: Run Credo 53 | run: mix credo 54 | -------------------------------------------------------------------------------- /test/strategy/twitter/oauth_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ueberauth.Strategy.Twitter.OAuthTest do 2 | use ExUnit.Case, async: true 3 | 4 | alias Ueberauth.Strategy.Twitter.OAuth 5 | 6 | setup do 7 | Application.put_env(:ueberauth, OAuth, 8 | consumer_key: "consumer_key", 9 | consumer_secret: "consumer_secret" 10 | ) 11 | 12 | :ok 13 | end 14 | 15 | test "access_token!/2: raises an appropriate error on auth failure" do 16 | assert_raise RuntimeError, ~r/401/i, fn -> 17 | OAuth.access_token!({"badtoken", "badsecret"}, "badverifier") 18 | end 19 | end 20 | 21 | test "access_token!/2 raises an appropriate error on network failure" do 22 | assert_raise RuntimeError, ~r/nxdomain/i, fn -> 23 | OAuth.access_token!({"token", "secret"}, "verifier", site: "https://bogusapi.twitter.com") 24 | end 25 | end 26 | 27 | test "request_token!/2: raises an appropriate error on auth failure" do 28 | assert_raise RuntimeError, ~r/401/i, fn -> 29 | OAuth.request_token!([], redirect_uri: "some/uri") 30 | end 31 | end 32 | 33 | test "request_token!/2: raises an appropriate error on network failure" do 34 | assert_raise RuntimeError, ~r/nxdomain/i, fn -> 35 | OAuth.request_token!([], site: "https://bogusapi.twitter.com", redirect_uri: "some/uri") 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/ueberauth/strategy/twitter/internal.ex: -------------------------------------------------------------------------------- 1 | defmodule Ueberauth.Strategy.Twitter.OAuth.Internal do 2 | @moduledoc """ 3 | A layer to handle OAuth signing, etc. 4 | """ 5 | 6 | def get(url, extraparams, {consumer_key, consumer_secret, _}, token \\ "", token_secret \\ "") do 7 | creds = 8 | OAuther.credentials( 9 | consumer_key: consumer_key, 10 | consumer_secret: consumer_secret, 11 | token: token, 12 | token_secret: token_secret 13 | ) 14 | 15 | {header, params} = 16 | "get" 17 | |> OAuther.sign(url, extraparams, creds) 18 | |> OAuther.header() 19 | 20 | response = HTTPoison.get(url, [header, {"Accept", "application/json"}], params: params) 21 | 22 | response 23 | |> decode_body() 24 | end 25 | 26 | def decode_body({:ok, response}) do 27 | content_type = 28 | Enum.find_value(response.headers, fn 29 | {"content-type", val} -> val 30 | _ -> nil 31 | end) 32 | 33 | case content_type do 34 | "application/json" <> _ -> 35 | json_body = Ueberauth.json_library().decode!(response.body) 36 | json_response = %{response | body: json_body} 37 | {:ok, json_response} 38 | 39 | _ -> 40 | {:ok, response} 41 | end 42 | end 43 | 44 | def decode_body(other), do: other 45 | 46 | def params_decode(resp) do 47 | URI.decode_query(resp) 48 | end 49 | 50 | def token(params) do 51 | params["oauth_token"] 52 | end 53 | 54 | def token_secret(params) do 55 | params["oauth_token_secret"] 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule UeberauthTwitter.Mixfile do 2 | use Mix.Project 3 | 4 | @source_url "https://github.com/ueberauth/ueberauth_twitter" 5 | @version "0.4.1" 6 | 7 | def project do 8 | [ 9 | app: :ueberauth_twitter, 10 | version: @version, 11 | name: "Ueberauth Twitter Strategy", 12 | package: package(), 13 | elixir: "~> 1.1", 14 | build_embedded: Mix.env() == :prod, 15 | start_permanent: Mix.env() == :prod, 16 | source_url: @source_url, 17 | homepage_url: @source_url, 18 | deps: deps(), 19 | docs: docs() 20 | ] 21 | end 22 | 23 | def application do 24 | [applications: [:logger, :httpoison, :oauther, :ueberauth]] 25 | end 26 | 27 | defp deps do 28 | [ 29 | {:httpoison, "~> 1.0"}, 30 | {:oauther, "~> 1.1"}, 31 | {:ueberauth, "~> 0.7"}, 32 | 33 | # dev/test dependencies 34 | {:earmark, ">= 0.0.0", only: :dev}, 35 | {:ex_doc, "~> 0.18", only: :dev}, 36 | {:credo, "~> 0.8", only: [:dev, :test]} 37 | ] 38 | end 39 | 40 | defp docs do 41 | [ 42 | extras: [ 43 | "CHANGELOG.md": [], 44 | "CONTRIBUTING.md": [title: "Contributing"], 45 | "LICENSE.md": [title: "License"], 46 | "README.md": [title: "Overview"] 47 | ], 48 | main: "readme", 49 | source_url: @source_url, 50 | source_ref: "v#{@version}", 51 | formatters: ["html"] 52 | ] 53 | end 54 | 55 | defp package do 56 | [ 57 | description: "An Uberauth strategy for Twitter authentication.", 58 | files: ["lib", "mix.exs", "README.md", "LICENSE.md"], 59 | maintainers: ["Sean Callan"], 60 | licenses: ["MIT"], 61 | links: %{ 62 | Changelog: "https://hexdocs.pm/ueberauth_twitter/changelog.html", 63 | GitHub: @source_url 64 | } 65 | ] 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Überauth Twitter 2 | 3 | [![Module Version](https://img.shields.io/hexpm/v/ueberauth_twitter.svg)](https://hex.pm/packages/ueberauth_twitter) 4 | [![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/ueberauth_twitter/) 5 | [![Total Download](https://img.shields.io/hexpm/dt/ueberauth_twitter.svg)](https://hex.pm/packages/ueberauth_twitter) 6 | [![License](https://img.shields.io/hexpm/l/ueberauth_twitter.svg)](https://github.com/ueberauth/ueberauth_twitter/blob/master/LICENSE.md) 7 | [![Last Updated](https://img.shields.io/github/last-commit/ueberauth/ueberauth_twitter.svg)](https://github.com/ueberauth/ueberauth_twitter/commits/master) 8 | 9 | > Twitter strategy for Überauth. 10 | 11 | _Note_: Sessions are required for this strategy. 12 | 13 | ## Installation 14 | 15 | 1. Setup your application at [Twitter Developers](https://dev.twitter.com/). 16 | 17 | 2. Add `:ueberauth_twitter` to your list of dependencies in `mix.exs`: 18 | 19 | ```elixir 20 | def deps do 21 | [ 22 | {:ueberauth_twitter, "~> 0.4"} 23 | ] 24 | end 25 | ``` 26 | 27 | 3. Add Twitter to your Überauth configuration: 28 | 29 | ```elixir 30 | config :ueberauth, Ueberauth, 31 | providers: [ 32 | twitter: {Ueberauth.Strategy.Twitter, []} 33 | ] 34 | ``` 35 | 36 | 4. Update your provider configuration: 37 | 38 | ```elixir 39 | config :ueberauth, Ueberauth.Strategy.Twitter.OAuth, 40 | consumer_key: System.get_env("TWITTER_CONSUMER_KEY"), 41 | consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET") 42 | ``` 43 | 44 | 5. Include the Überauth plug in your controller: 45 | 46 | ```elixir 47 | defmodule MyApp.AuthController do 48 | use MyApp.Web, :controller 49 | plug Ueberauth 50 | ... 51 | end 52 | ``` 53 | 54 | 6. Create the request and callback routes if you haven't already: 55 | 56 | ```elixir 57 | scope "/auth", MyApp do 58 | pipe_through :browser 59 | 60 | get "/:provider", AuthController, :request 61 | get "/:provider/callback", AuthController, :callback 62 | end 63 | ``` 64 | 65 | 7. Your controller needs to implement callbacks to deal with `Ueberauth.Auth` 66 | and `Ueberauth.Failure` responses. 67 | 68 | For an example implementation see the [Überauth Example](https://github.com/ueberauth/ueberauth_example) application. 69 | 70 | ## Calling 71 | 72 | Depending on the configured url you can initiate the request through: 73 | 74 | /auth/twitter 75 | 76 | ## Development mode 77 | 78 | As noted when registering your application on the Twitter Developer site, you need to explicitly specify the `oauth_callback` url. While in development, this is an example url you need to enter. 79 | 80 | Website - http://127.0.0.1 81 | Callback URL - http://127.0.0.1:4000/auth/twitter/callback 82 | 83 | ## Copyright and License 84 | 85 | Copyright (c) 2015 Sean Callan 86 | 87 | This library is released under the MIT License. See the [LICENSE.md](./LICENSE.md) file 88 | for further details. 89 | -------------------------------------------------------------------------------- /lib/ueberauth/strategy/twitter.ex: -------------------------------------------------------------------------------- 1 | defmodule Ueberauth.Strategy.Twitter do 2 | @moduledoc """ 3 | Twitter Strategy for Überauth. 4 | """ 5 | 6 | use Ueberauth.Strategy, uid_field: :id_str 7 | 8 | alias Ueberauth.Auth.Credentials 9 | alias Ueberauth.Auth.Extra 10 | alias Ueberauth.Auth.Info 11 | alias Ueberauth.Strategy.Twitter.OAuth 12 | 13 | @doc """ 14 | Handles initial request for Twitter authentication. 15 | """ 16 | def handle_request!(conn) do 17 | params = with_state_param([], conn) 18 | token = OAuth.request_token!([], redirect_uri: callback_url(conn, params)) 19 | 20 | conn 21 | |> put_session(:twitter_token, token) 22 | |> redirect!(OAuth.authorize_url!(token)) 23 | end 24 | 25 | @doc """ 26 | Handles the callback from Twitter. 27 | """ 28 | def handle_callback!(%Plug.Conn{params: %{"oauth_verifier" => oauth_verifier}} = conn) do 29 | token = get_session(conn, :twitter_token) 30 | 31 | case OAuth.access_token(token, oauth_verifier) do 32 | {:ok, access_token} -> fetch_user(conn, access_token) 33 | {:error, error} -> set_errors!(conn, [error(error.code, error.reason)]) 34 | end 35 | end 36 | 37 | @doc false 38 | def handle_callback!(conn) do 39 | set_errors!(conn, [error("missing_code", "No code received")]) 40 | end 41 | 42 | @doc false 43 | def handle_cleanup!(conn) do 44 | conn 45 | |> put_private(:twitter_user, nil) 46 | |> put_session(:twitter_token, nil) 47 | end 48 | 49 | @doc """ 50 | Fetches the uid field from the response. 51 | """ 52 | def uid(conn) do 53 | uid_field = 54 | conn 55 | |> option(:uid_field) 56 | |> to_string 57 | 58 | conn.private.twitter_user[uid_field] 59 | end 60 | 61 | @doc """ 62 | Includes the credentials from the twitter response. 63 | """ 64 | def credentials(conn) do 65 | {token, secret} = conn.private.twitter_token 66 | 67 | %Credentials{token: token, secret: secret} 68 | end 69 | 70 | @doc """ 71 | Fetches the fields to populate the info section of the `Ueberauth.Auth` struct. 72 | """ 73 | def info(conn) do 74 | user = conn.private.twitter_user 75 | 76 | %Info{ 77 | email: user["email"], 78 | image: user["profile_image_url_https"], 79 | name: user["name"], 80 | nickname: user["screen_name"], 81 | description: user["description"], 82 | location: user["location"], 83 | urls: %{ 84 | Twitter: "https://twitter.com/#{user["screen_name"]}", 85 | Website: user["url"] 86 | } 87 | } 88 | end 89 | 90 | @doc """ 91 | Stores the raw information (including the token) obtained from the twitter callback. 92 | """ 93 | def extra(conn) do 94 | {token, _secret} = get_session(conn, :twitter_token) 95 | 96 | %Extra{ 97 | raw_info: %{ 98 | token: token, 99 | user: conn.private.twitter_user 100 | } 101 | } 102 | end 103 | 104 | defp fetch_user(conn, token) do 105 | params = [{"include_entities", false}, {"skip_status", true}, {"include_email", true}] 106 | 107 | case OAuth.get("/1.1/account/verify_credentials.json", params, token) do 108 | {:ok, %{status_code: 401, body: _, headers: _}} -> 109 | set_errors!(conn, [error("token", "unauthorized")]) 110 | 111 | {:ok, %{status_code: status_code, body: body, headers: _}} when status_code in 200..399 -> 112 | conn 113 | |> put_private(:twitter_token, token) 114 | |> put_private(:twitter_user, body) 115 | 116 | {:ok, %{status_code: _, body: body, headers: _}} -> 117 | error = List.first(body["errors"]) 118 | set_errors!(conn, [error("token", error["message"])]) 119 | end 120 | end 121 | 122 | defp option(conn, key) do 123 | default = Keyword.get(default_options(), key) 124 | 125 | conn 126 | |> options 127 | |> Keyword.get(key, default) 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /lib/ueberauth/strategy/twitter/oauth.ex: -------------------------------------------------------------------------------- 1 | defmodule Ueberauth.Strategy.Twitter.OAuth do 2 | @moduledoc """ 3 | OAuth1 for Twitter. 4 | 5 | Add `consumer_key` and `consumer_secret` to your configuration: 6 | 7 | config :ueberauth, Ueberauth.Strategy.Twitter.OAuth, 8 | consumer_key: System.get_env("TWITTER_CONSUMER_KEY"), 9 | consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"), 10 | redirect_uri: System.get_env("TWITTER_REDIRECT_URI") 11 | """ 12 | 13 | alias Ueberauth.Strategy.Twitter.OAuth.Internal 14 | 15 | @defaults [ 16 | access_token: "/oauth/access_token", 17 | authorize_url: "/oauth/authorize", 18 | request_token: "/oauth/request_token", 19 | site: "https://api.twitter.com" 20 | ] 21 | 22 | defmodule ApiError do 23 | @moduledoc "Raised on OAuth API errors." 24 | 25 | defexception [:message, :code] 26 | 27 | def message(%{code: nil} = e), do: e.message 28 | 29 | def message(e) do 30 | "#{e.message} (Code #{e.code})" 31 | end 32 | end 33 | 34 | def access_token({token, token_secret}, verifier, opts \\ []) do 35 | opts 36 | |> client() 37 | |> to_url(:access_token) 38 | |> Internal.get([{"oauth_verifier", verifier}], consumer(client()), token, token_secret) 39 | |> decode_response 40 | end 41 | 42 | def access_token!(access_token, verifier, opts \\ []) do 43 | case access_token(access_token, verifier, opts) do 44 | {:ok, token} -> token 45 | {:error, error} -> raise error 46 | end 47 | end 48 | 49 | def authorize_url!({token, _token_secret}, opts \\ []) do 50 | opts 51 | |> client() 52 | |> to_url(:authorize_url, %{"oauth_token" => token}) 53 | end 54 | 55 | def client(opts \\ []) do 56 | config = Application.get_env(:ueberauth, __MODULE__) 57 | 58 | @defaults 59 | |> Keyword.merge(config) 60 | |> Keyword.merge(opts) 61 | |> Enum.into(%{}) 62 | end 63 | 64 | def get(url, access_token), do: get(url, [], access_token) 65 | 66 | def get(url, params, {token, token_secret}) do 67 | client() 68 | |> to_url(url) 69 | |> Internal.get(params, consumer(client()), token, token_secret) 70 | end 71 | 72 | def request_token(params \\ [], opts \\ []) do 73 | client = client(opts) 74 | params = [{"oauth_callback", client.redirect_uri} | params] 75 | 76 | client 77 | |> to_url(:request_token) 78 | |> Internal.get(params, consumer(client)) 79 | |> decode_response 80 | end 81 | 82 | def request_token!(params \\ [], opts \\ []) do 83 | case request_token(params, opts) do 84 | {:ok, token} -> token 85 | {:error, error} -> raise error 86 | end 87 | end 88 | 89 | defp consumer(client), do: {client.consumer_key, client.consumer_secret, :hmac_sha1} 90 | 91 | defp decode_response({:ok, %{status_code: 200, body: body}}) do 92 | params = Internal.params_decode(body) 93 | token = Internal.token(params) 94 | token_secret = Internal.token_secret(params) 95 | 96 | {:ok, {token, token_secret}} 97 | end 98 | 99 | defp decode_response({:ok, %{status_code: 401, body: body}}) do 100 | {:error, "401: #{inspect(body)}"} 101 | end 102 | 103 | defp decode_response({:ok, %{body: %{"errors" => [error | _]}}}) do 104 | {:error, %ApiError{message: error["message"], code: error["code"]}} 105 | end 106 | 107 | defp decode_response({:error, %{reason: reason}}) do 108 | {:error, "#{reason}"} 109 | end 110 | 111 | defp decode_response(error) do 112 | {:error, error} 113 | end 114 | 115 | defp endpoint("/" <> _path = endpoint, client), do: client.site <> endpoint 116 | defp endpoint(endpoint, _client), do: endpoint 117 | 118 | defp to_url(client, endpoint, params \\ nil) do 119 | endpoint = 120 | client 121 | |> Map.get(endpoint, endpoint) 122 | |> endpoint(client) 123 | 124 | endpoint = 125 | if params do 126 | endpoint <> "?" <> URI.encode_query(params) 127 | else 128 | endpoint 129 | end 130 | 131 | endpoint 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, 3 | "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, 4 | "credo": {:hex, :credo, "0.10.2", "03ad3a1eff79a16664ed42fc2975b5e5d0ce243d69318060c626c34720a49512", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "539596b6774069260d5938aa73042a2f5157e1c0215aa35f5a53d83889546d14"}, 5 | "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm", "000aaeff08919e95e7aea13e4af7b2b9734577b3e6a7c50ee31ee88cab6ec4fb"}, 6 | "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"}, 7 | "ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"}, 8 | "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [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", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, 9 | "httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "e9d994aea63fab9e29307920492ab95f87339b56fbc5c8c4b1f65ea20d3ba9a4"}, 10 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, 11 | "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, 12 | "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, 13 | "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, 14 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, 15 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, 16 | "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, 17 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, 18 | "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, 19 | "oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm", "9374f4302045321874cccdc57eb975893643bd69c3b22bf1312dab5f06e5788e"}, 20 | "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, 21 | "plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"}, 22 | "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, 23 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, 24 | "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, 25 | "ueberauth": {:hex, :ueberauth, "0.7.0", "9c44f41798b5fa27f872561b6f7d2bb0f10f03fdd22b90f454232d7b087f4b75", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2efad9022e949834f16cc52cd935165049d81fa9e925690f91035c2e4b58d905"}, 26 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, 27 | } 28 | --------------------------------------------------------------------------------