Packages
pow_assent
0.4.4
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
0.1.0-rc.2
0.1.0-rc.1
0.1.0-rc.0
0.1.0-alpha.12
0.1.0-alpha.11
0.1.0-alpha.10
0.1.0-alpha.9
0.1.0-alpha.8
0.1.0-alpha.7
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
retired
0.1.0-alpha.1
0.1.0-alpha
Multi-provider support for Pow
Current section
Files
Jump to
Current section
Files
lib/pow_assent/plug.ex
defmodule PowAssent.Plug do
@moduledoc """
Plug helper methods.
If you wish to configure PowAssent through the Pow plug interface rather than
environment config, please add PowAssent config with `:pow_assent` config:
plug Pow.Plug.Session,
repo: MyApp.Repo,
user: MyApp.User,
pow_assent: [
http_adapter: PowAssent.HTTPAdapter.Mint,
json_library: Poison,
user_identities_context: MyApp.UserIdentities
]
"""
alias Plug.Conn
alias PowAssent.{Config, Operations}
alias Pow.Plug
@doc """
Calls the authorize_url method for the provider strategy.
A generated authorization URL will be returned. If `:session_params` is
returned from the provider, it'll be added to the connection as private key
`:pow_assent_session_params`.
If `:nonce` is set to `true` in the provider configuration, a randomly
generated nonce will be added to the configuration.
"""
@spec authorize_url(Conn.t(), binary(), binary()) :: {:ok, binary(), Conn.t()} | {:error, any(), Conn.t()}
def authorize_url(conn, provider, redirect_uri) do
{strategy, provider_config} = get_provider_config(conn, provider, redirect_uri)
provider_config
|> maybe_gen_nonce()
|> strategy.authorize_url()
|> maybe_put_session_params(conn)
end
defp maybe_gen_nonce(config) do
case Config.get(config, :nonce, nil) do
true -> Config.put(config, :nonce, gen_nonce())
_any -> config
end
end
defp gen_nonce() do
16
|> :crypto.strong_rand_bytes()
|> Base.encode64(padding: false)
end
defp maybe_put_session_params({:ok, %{url: url, session_params: params}}, conn) do
{:ok, url, Conn.put_private(conn, :pow_assent_session_params, params)}
end
defp maybe_put_session_params({:ok, %{url: url}}, conn), do: {:ok, url, conn}
defp maybe_put_session_params({:error, error}, conn), do: {:error, error, conn}
@doc """
Calls the callback method for the provider strategy.
Returns the user identity params and user params fetched from the provider.
`:session_params` will be added to the provider config if
`:pow_assent_session_params` is present as a private key in the connection.
"""
@spec callback(Conn.t(), binary(), map(), binary()) :: {:ok, map(), map(), Conn.t()} | {:error, any(), Conn.t()}
def callback(conn, provider, params, redirect_uri) do
{strategy, provider_config} = get_provider_config(conn, provider, redirect_uri)
provider_config
|> maybe_set_session_params_config(conn)
|> strategy.callback(params)
|> parse_callback_response(provider, conn)
end
defp maybe_set_session_params_config(config, %{private: %{pow_assent_session_params: params}}), do: Config.put(config, :session_params, params)
defp maybe_set_session_params_config(config, _conn), do: config
defp parse_callback_response({:ok, %{user: user} = response}, provider, conn) do
other_params = Map.drop(response, [:user])
user
|> normalize_username()
|> split_user_identity_params()
|> handle_user_identity_params(other_params, provider, conn)
end
defp parse_callback_response({:error, error}, _provider, conn), do: {:error, error, conn}
defp normalize_username(%{"preferred_username" => username} = params) do
params
|> Map.delete("preferred_username")
|> Map.put("username", username)
end
defp normalize_username(params), do: params
defp split_user_identity_params(%{"sub" => uid} = params) do
user_params = Map.delete(params, "sub")
{%{"uid" => uid}, user_params}
end
defp handle_user_identity_params({user_identity_params, user_params}, other_params, provider, conn) do
user_identity_params = Map.put(user_identity_params, "provider", provider)
other_params = for {key, value} <- other_params, into: %{}, do: {Atom.to_string(key), value}
user_identity_params =
user_identity_params
|> Map.put("provider", provider)
|> Map.merge(other_params)
{:ok, user_identity_params, user_params, conn}
end
@doc """
Authenticates a user with provider and provider user params.
If successful, a new session will be created.
"""
@spec authenticate(Conn.t(), map()) :: {:ok, Conn.t()} | {:error, Conn.t()}
def authenticate(conn, %{"provider" => provider, "uid" => uid}) do
config = fetch_config(conn)
provider
|> Operations.get_user_by_provider_uid(uid, config)
|> case do
nil -> {:error, conn}
user -> {:ok, Plug.get_plug(config).do_create(conn, user, config)}
end
end
# TODO: Remove by 0.4.0
@doc false
@deprecated "Use `upsert_identity/2` instead"
@spec create_identity(Conn.t(), map()) :: {:ok, map(), Conn.t()} | {:error, {:bound_to_different_user, map()} | map(), Conn.t()}
def create_identity(conn, user_identity_params), do: upsert_identity(conn, user_identity_params)
@doc """
Will upsert identity for the current user.
If successful, a new session will be created.
"""
@spec upsert_identity(Conn.t(), map()) :: {:ok, map(), Conn.t()} | {:error, {:bound_to_different_user, map()} | map(), Conn.t()}
def upsert_identity(conn, user_identity_params) do
config = fetch_config(conn)
user = Pow.Plug.current_user(conn)
user
|> Operations.upsert(user_identity_params, config)
|> case do
{:ok, user_identity} -> {:ok, user_identity, Plug.get_plug(config).do_create(conn, user, config)}
{:error, error} -> {:error, error, conn}
end
end
@doc """
Create a user with the provider and provider user params.
If successful, a new session will be created.
"""
@spec create_user(Conn.t(), map(), map(), map() | nil) :: {:ok, map(), Conn.t()} | {:error, {:bound_to_different_user | :invalid_user_id_field, map()} | map(), Conn.t()}
def create_user(conn, user_identity_params, user_params, user_id_params \\ nil) do
config = fetch_config(conn)
user_identity_params
|> Operations.create_user(user_params, user_id_params, config)
|> case do
{:ok, user} -> {:ok, user, Plug.get_plug(config).do_create(conn, user, config)}
{:error, error} -> {:error, error, conn}
end
end
@doc """
Deletes the associated user identity for the current user and provider.
"""
@spec delete_identity(Conn.t(), binary()) :: {:ok, map(), Conn.t()} | {:error, {:no_password, map()}, Conn.t()}
def delete_identity(conn, provider) do
config = fetch_config(conn)
conn
|> Pow.Plug.current_user()
|> Operations.delete(provider, config)
|> case do
{:ok, results} -> {:ok, results, conn}
{:error, error} -> {:error, error, conn}
end
end
@doc """
Lists associated providers for the user.
"""
@spec providers_for_current_user(Conn.t()) :: [atom()]
def providers_for_current_user(conn) do
config = fetch_config(conn)
conn
|> Pow.Plug.current_user()
|> get_all_providers_for_user(config)
|> Enum.map(&String.to_existing_atom(&1.provider))
end
defp get_all_providers_for_user(nil, _config), do: []
defp get_all_providers_for_user(user, config), do: Operations.all(user, config)
@doc """
Lists available providers for connection.
"""
@spec available_providers(Conn.t() | Config.t()) :: [atom()]
def available_providers(%Conn{} = conn) do
conn
|> fetch_config()
|> available_providers()
end
def available_providers(config) do
config
|> Config.get_providers()
|> Keyword.keys()
end
defp fetch_config(conn) do
config = Pow.Plug.fetch_config(conn)
config
|> Keyword.take([:otp_app, :plug, :repo, :user])
|> Keyword.merge(Keyword.get(config, :pow_assent, []))
end
defp get_provider_config(%Conn{} = conn, provider, redirect_uri) do
conn
|> fetch_config()
|> get_provider_config(provider, redirect_uri)
end
defp get_provider_config(config, provider, redirect_uri) do
config = Config.get_provider_config(config, provider)
strategy = config[:strategy]
provider_config =
config
|> Keyword.delete(:strategy)
|> Config.put(:redirect_uri, redirect_uri)
{strategy, provider_config}
end
end