diff --git a/README.md b/README.md index 63beab2..c0cb1f2 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,13 @@ end config :ueberauth, Ueberauth, providers: [ weixin: {Ueberauth.Strategy.Weixin, - [appid: "YOUR_APPID", - secret: "YOUR_SECRET", - redirect_uri: "https://example.com/auth/weixin/callback", - uid_field: :unionid # default is :openid - ]} + [uid_field: :unionid # default is :openid]} ] + +config :ueberauth, Ueberauth.Strategy.Weixin.OAuth, + client_id: "YOUR_APPID", + client_secret: "YOUR_SECRET", + redirect_uri: "https://example.com/auth/weixin/callback" ``` ## Ueberauth.Auth struct diff --git a/lib/ueberauth/strategy/weixin.ex b/lib/ueberauth/strategy/weixin.ex index 2d54f6f..3682822 100644 --- a/lib/ueberauth/strategy/weixin.ex +++ b/lib/ueberauth/strategy/weixin.ex @@ -6,33 +6,24 @@ defmodule Ueberauth.Strategy.Weixin do use Ueberauth.Strategy, uid_field: :openid alias Ueberauth.Auth.{Credentials, Extra, Info} - - @auth_url "https://open.weixin.qq.com/connect/qrconnect" - @token_url "https://api.weixin.qq.com/sns/oauth2/access_token" + alias Ueberauth.Strategy.Weixin.OAuth def handle_request!(conn) do - appid = option(conn, :appid) - redirect_uri = option(conn, :redirect_uri) - - url = - "#{@auth_url}?appid=#{appid}&redirect_uri=#{redirect_uri}&response_type=code&scope=snsapi_login#wechat_redirect" - + url = OAuth.authorize_url!() redirect!(conn, url) end def handle_callback!(%Plug.Conn{params: %{"code" => code}} = conn) do - appid = option(conn, :appid) - secret = option(conn, :secret) + client = OAuth.get_token!(code: code) - url = - "#{@token_url}?appid=#{appid}&secret=#{secret}&code=#{code}&grant_type=authorization_code" - - case HTTPoison.get!(url).body |> Ueberauth.json_library().decode!() do - %{"access_token" => access_token, "openid" => openid} -> - fetch_user(conn, access_token, openid) + case OAuth.fetch_user(client) do + {:ok, user} -> + conn + |> put_private(:weixin_user, user) + |> put_private(:weixin_token, client.token) - %{"errcode" => errcode, "errmsg" => errmsg} -> - set_errors!(conn, [error(errcode, errmsg)]) + {:error, error} -> + set_errors!(conn, [error(error.code, error.reason)]) end end @@ -63,20 +54,6 @@ defmodule Ueberauth.Strategy.Weixin do } end - defp fetch_user(conn, token, openid) do - url = "https://api.weixin.qq.com/sns/userinfo?access_token=#{token}&openid=#{openid}" - - case HTTPoison.get!(url).body |> Ueberauth.json_library().decode!() do - %{"errcode" => errcode, "errmsg" => errmsg} -> - set_errors!(conn, [error(errcode, errmsg)]) - - body -> - conn - |> put_private(:weixin_token, token) - |> put_private(:weixin_user, body) - end - end - defp option(conn, key) do default = Keyword.get(default_options(), key) diff --git a/lib/ueberauth/strategy/weixin/oauth.ex b/lib/ueberauth/strategy/weixin/oauth.ex new file mode 100644 index 0000000..53502bd --- /dev/null +++ b/lib/ueberauth/strategy/weixin/oauth.ex @@ -0,0 +1,74 @@ +defmodule Ueberauth.Strategy.Weixin.OAuth do + @moduledoc false + + use OAuth2.Strategy + + def new do + :ueberauth + |> Application.fetch_env!(__MODULE__) + |> Keyword.merge(config()) + |> OAuth2.Client.new() + |> put_serializer("text/plain", Jason) + end + + defp config do + [ + strategy: __MODULE__, + site: "https://api.weixin.qq.com", + authorize_url: "https://open.weixin.qq.com/connect/qrconnect", + token_url: "https://api.weixin.qq.com/sns/oauth2/access_token" + ] + end + + def authorize_url!(params \\ []) do + new() + |> OAuth2.Client.authorize_url!(params) + end + + def get_token!(params \\ [], headers \\ []) do + new() + |> OAuth2.Client.get_token!(params, headers) + end + + def fetch_user(%{token: token} = client) do + params = %{ + access_token: token.access_token, + openid: token.other_params["openid"] + } + + case OAuth2.Client.get!(client, "/sns/userinfo", [], params: params) do + %{body: %{"errcode" => errcode, "errmsg" => errmsg}} -> + {:error, %{code: errcode, reason: errmsg}} + + %{body: body} -> + {:ok, body} + end + end + + @impl true + def authorize_url(client, params) do + client + |> put_param(:response_type, "code") + |> put_param(:scope, "snsapi_login") + |> put_param(:appid, client.client_id) + |> put_param(:redirect_uri, client.redirect_uri) + |> merge_params(params) + end + + @impl true + def get_token(client, params, headers) do + {code, params} = Keyword.pop(params, :code, client.params["code"]) + + unless code do + raise OAuth2.Error, reason: "Missing required key `code` for `#{inspect(__MODULE__)}`" + end + + client + |> put_param(:grant_type, "authorization_code") + |> put_param(:code, code) + |> put_param(:appid, client.client_id) + |> put_param(:secret, client.client_secret) + |> merge_params(params) + |> put_headers(headers) + end +end diff --git a/mix.exs b/mix.exs index 05792e1..00ea28c 100644 --- a/mix.exs +++ b/mix.exs @@ -27,8 +27,8 @@ defmodule UeberauthWeixin.MixProject do defp deps do [ {:ueberauth, "~> 0.6"}, - {:httpoison, "~> 1.0"}, {:jason, "~> 1.0"}, + {:oauth2, "~> 1.0"}, {:ex_doc, "~> 0.19", only: :dev, runtime: false} ] end diff --git a/mix.lock b/mix.lock index 9a87fa5..bf58c42 100644 --- a/mix.lock +++ b/mix.lock @@ -1,21 +1,21 @@ %{ - "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.20.0", "47ef7d09d990fa4a4373c96c4bdff846836f011e88dd7695d31f54ec035ff38e", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, + "oauth2": {:hex, :oauth2, "1.0.0", "7ae134c369bb9f7e96ed9404d12f34e938daa14745f1601876526599be4c80f2", [:mix], [{:hackney, "~> 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "plug": {:hex, :plug, "1.8.0", "9d2685cb007fe5e28ed9ac27af2815bc262b7817a00929ac10f56f169f43b977", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, "ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, }