Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

* [`PowAssent.Plug`] Now stores a session cookie instead of using `Plug.Session` to prevent SameSite policy issue in `form_post` flow
* [`PowAssent.Plug`] `PowAssent.Plug.callback/4` now adds the `userinfo` with claims to the user identity params
* [`PowAssent.Phoenix.ViewHelpers`] `PowAssent.Phoenix.ViewHelpers.authorization_link/3` now accepts block argument
* [`PowAssent.Phoenix.ViewHelpers`] `PowAssent.Phoenix.ViewHelpers.deauthorization_link/3` now accepts block argument
* [`PowAssent.Phoenix.ViewHelpers`] `PowAssent.Phoenix.ViewHelpers.authorization_links/2` now accepts link options or callback method as second argument

## v0.4.8 (2020-05-18)

Expand Down
79 changes: 61 additions & 18 deletions lib/pow_assent/phoenix/views/view_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,43 @@ defmodule PowAssent.Phoenix.ViewHelpers do
If a user is assigned to the conn, the authorized providers for a user will
be looked up with `PowAssent.Plug.providers_for_current_user/1`.
`deauthorization_link/2` will be used for any already authorized providers.

The second argument may be link options passed on to `authorization_link/2`
or `deauthorization_link/2` respectively. It may also be a method that
handles render callback as seen in the example below.

## Example

ViewHelpers.provider_links @conn, fn provider, providers_for_user ->
if provider in providers_for_user do
ViewHelpers.deauthorization_link @conn, provider do
Tag.content_tag(:span, "Remove \#{provider}", class: provider)
end
else
ViewHelpers.authorization_link @conn, provider do
Tag.content_tag(:span, "Sign in with \#{provider}", class: provider)
end
end
end
"""
@spec provider_links(Conn.t(), keyword()) :: [HTML.safe()]
def provider_links(conn, link_opts \\ []) do
available_providers = Plug.available_providers(conn)
providers_for_user = Plug.providers_for_current_user(conn)

available_providers
|> Enum.map(&{&1, &1 in providers_for_user})
|> Enum.map(fn
{provider, true} -> deauthorization_link(conn, provider, link_opts)
{provider, false} -> authorization_link(conn, provider, link_opts)
end)
@spec provider_links(Conn.t(), keyword() | ({atom(), boolean()} -> Phoenix.HTML.unsafe())) :: [HTML.safe()]
def provider_links(conn, link_opts_or_callback \\ []) do
providers_for_user = Plug.providers_for_current_user(conn)
callback = render_callback(link_opts_or_callback, conn)

conn
|> Plug.available_providers()
|> Enum.map(&callback.(&1, providers_for_user))
end

defp render_callback(callback, _conn) when is_function(callback), do: callback
defp render_callback(link_opts, conn) do
fn provider, providers_for_user ->
case provider in providers_for_user do
true -> deauthorization_link(conn, provider, link_opts)
false -> authorization_link(conn, provider, link_opts)
end
end
end

@doc """
Expand All @@ -37,15 +62,24 @@ defmodule PowAssent.Phoenix.ViewHelpers do
`:invited_user` is assigned to the conn, the invitation token will be passed
on through the URL query params.
"""
@spec authorization_link(Conn.t(), atom(), keyword()) :: HTML.safe()
def authorization_link(conn, provider, opts \\ []) do
@spec authorization_link(Conn.t(), atom(), keyword(), keyword()) :: HTML.safe()
def authorization_link(conn, provider, opts \\ [])
def authorization_link(conn, provider, do: contents),
do: authorization_link(conn, provider, contents, [])
def authorization_link(conn, provider, opts) do
msg = AuthorizationController.extension_messages(conn).login_with_provider(%{conn | params: %{"provider" => provider}})

authorization_link(conn, provider, msg, opts)
end
def authorization_link(conn, provider, opts, do: contents),
do: authorization_link(conn, provider, contents, opts)
def authorization_link(conn, provider, contents, opts) do
query_params = invitation_token_query_params(conn) ++ request_path_query_params(conn)

msg = AuthorizationController.extension_messages(conn).login_with_provider(%{conn | params: %{"provider" => provider}})
path = AuthorizationController.routes(conn).path_for(conn, AuthorizationController, :new, [provider], query_params)
opts = Keyword.merge(opts, to: path)

Link.link(msg, opts)
Link.link(contents, opts)
end

defp invitation_token_query_params(%{assigns: %{invited_user: %{invitation_token: token}}}), do: [invitation_token: token]
Expand All @@ -60,11 +94,20 @@ defmodule PowAssent.Phoenix.ViewHelpers do
The link is used to remove authorization with the provider.
"""
@spec deauthorization_link(Conn.t(), atom(), keyword()) :: HTML.safe()
def deauthorization_link(conn, provider, opts \\ []) do
msg = AuthorizationController.extension_messages(conn).remove_provider_authentication(%{conn | params: %{"provider" => provider}})
def deauthorization_link(conn, provider, opts \\ [])
def deauthorization_link(conn, provider, do: contents),
do: deauthorization_link(conn, provider, contents, [])
def deauthorization_link(conn, provider, opts) do
msg = AuthorizationController.extension_messages(conn).remove_provider_authentication(%{conn | params: %{"provider" => provider}})

deauthorization_link(conn, provider, msg, opts)
end
def deauthorization_link(conn, provider, opts, do: contents),
do: deauthorization_link(conn, provider, contents, opts)
def deauthorization_link(conn, provider, contents, opts) do
path = AuthorizationController.routes(conn).path_for(conn, AuthorizationController, :delete, [provider])
opts = Keyword.merge(opts, to: path, method: :delete)

Link.link(msg, opts)
Link.link(contents, opts)
end
end
74 changes: 68 additions & 6 deletions test/pow_assent/phoenix/views/view_helpers_test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
defmodule PowAssent.ViewHelpersTest do
defmodule PowAssent.Phoenix.ViewHelpersTest do
use PowAssent.Test.Phoenix.ConnCase

alias Plug.Conn
alias Phoenix.HTML.Link
alias Phoenix.HTML.{Link, Tag}
alias PowAssent.Phoenix.ViewHelpers
alias PowAssent.Test.{Phoenix.Router, Ecto.Users.User, RepoMock}

Expand All @@ -26,7 +26,7 @@ defmodule PowAssent.ViewHelpersTest do
{:ok, conn: conn}
end

test "provider_links/1", %{conn: conn} do
test "provider_links/2", %{conn: conn} do
[safe: iodata] = ViewHelpers.provider_links(conn)
assert {:safe, iodata} == Link.link("Sign in with Test provider", to: "/auth/test_provider/new")

Expand All @@ -36,13 +36,13 @@ defmodule PowAssent.ViewHelpersTest do
assert {:safe, iodata} == Link.link("Remove Test provider authentication", to: "/auth/test_provider", method: "delete")
end

test "provider_links/1 with link opts", %{conn: conn} do
test "provider_links/2 with link opts", %{conn: conn} do
[safe: iodata] = ViewHelpers.provider_links(conn, class: "example")

assert {:safe, iodata} == Link.link("Sign in with Test provider", to: "/auth/test_provider/new", class: "example")
end

test "provider_links/1 with request_path", %{conn: conn} do
test "provider_links/2 with request_path", %{conn: conn} do
[safe: iodata] =
conn
|> Conn.assign(:request_path, "/custom-url")
Expand All @@ -51,10 +51,72 @@ defmodule PowAssent.ViewHelpersTest do
assert {:safe, iodata} == Link.link("Sign in with Test provider", to: "/auth/test_provider/new?request_path=%2Fcustom-url")
end

test "provider_links/1 with invited_user", %{conn: conn} do
test "provider_links/2 with invited_user", %{conn: conn} do
conn = Conn.assign(conn, :invited_user, %PowAssent.Test.Invitation.Users.User{invitation_token: "token"})

[safe: iodata] = ViewHelpers.provider_links(conn)
assert {:safe, iodata} == Link.link("Sign in with Test provider", to: "/auth/test_provider/new?invitation_token=token")
end

test "provider_links/2 with callback", %{conn: conn} do
callback =
fn provider, providers_for_user ->
case provider in providers_for_user do
true ->
ViewHelpers.deauthorization_link(conn, :test_provider, class: "remove") do
Tag.content_tag(:span, "Provider remove", class: "test_provider")
end

false ->
ViewHelpers.authorization_link(conn, :test_provider, class: "auth") do
Tag.content_tag(:span, "Provider auth", class: "test_provider")
end
end
end

[safe: iodata] = ViewHelpers.provider_links(conn, callback)

assert {:safe, iodata} ==
(Link.link to: "/auth/test_provider/new", class: "auth" do
Tag.content_tag(:span, "Provider auth", class: "test_provider")
end)

[safe: iodata] =
conn
|> Pow.Plug.assign_current_user(%User{id: 1}, [])
|> ViewHelpers.provider_links(callback)

assert {:safe, iodata} ==
(Link.link to: "/auth/test_provider", class: "remove", method: "delete" do
Tag.content_tag(:span, "Provider remove", class: "test_provider")
end)
end

test "authorization_link/4 accepts blocks", %{conn: conn} do
{:safe, iodata} = ViewHelpers.authorization_link(conn, :test_provider) do
"Provider auth"
end

assert {:safe, iodata} == Link.link("Provider auth", to: "/auth/test_provider/new")

{:safe, iodata} = ViewHelpers.authorization_link(conn, :test_provider, class: "example") do
"Provider auth"
end

assert {:safe, iodata} == Link.link("Provider auth", to: "/auth/test_provider/new", class: "example")
end

test "deauthorization_link/4 accepts blocks", %{conn: conn} do
{:safe, iodata} = ViewHelpers.deauthorization_link(conn, :test_provider) do
"Provider remove"
end

assert {:safe, iodata} == Link.link("Provider remove", to: "/auth/test_provider", method: "delete")

{:safe, iodata} = ViewHelpers.deauthorization_link(conn, :test_provider, class: "example") do
"Provider remove"
end

assert {:safe, iodata} == Link.link("Provider remove", to: "/auth/test_provider", method: "delete", class: "example")
end
end