Skip to content
Closed
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
291 changes: 161 additions & 130 deletions lib/pow_assent/phoenix/html/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,161 +2,192 @@
# Credo will complain about unless statement but we want this first
# credo:disable-for-next-line
unless Pow.dependency_vsn_match?(:phoenix, "< 1.7.0") do
defmodule PowAssent.Phoenix.HTML.CoreComponents do
@moduledoc false
use Phoenix.Component
defmodule PowAssent.Phoenix.HTML.CoreComponents do
@moduledoc false
use Phoenix.Component

alias PowAssent.{Phoenix.AuthorizationController, Plug}
alias PowAssent.{Phoenix.AuthorizationController, Plug}

@doc """
Renders a list of authorization links for all configured providers.
@doc """
Renders a list of authorization links for all configured providers.

The list of providers will be fetched from the PowAssent configuration, and
`authorization_link/1` will be called on each.
The list of providers will be fetched from the PowAssent configuration, and
`authorization_link/1` will be called on each.

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/1` will be used for any already authorized providers.
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/1` will be used for any already authorized providers.

## Examples
## Examples

<.provider_links conn={@conn} />
<.provider_links conn={@conn} />

<.provider_links conn={@conn}>
<:authorization_link class="text-green-500">
Sign in with <%= @provider %>
<:/authorization_link>
<.provider_links conn={@conn}>
<:authorization_link class="text-green-500">
Sign in with <%= @provider %>
<:/authorization_link>

<:deauthorization_link class="text-red-500">
Remove <%= @provider %> authentication
<:/deauthorization_link>
</.provider_links>
"""
attr :conn, :any, required: true, doc: "the conn"

slot :authorization_link, doc: "attributes and inner content for the authorization link" do
attr :class, :string, doc: "Additional classes added to the `.link` tag"
end

slot :deauthorization_link, doc: "attributes and inner content for the deuathorization link" do
attr :class, :string, doc: "Additional classes added to the `.link` tag"
end

def provider_links(assigns) do
providers = Plug.available_providers(assigns.conn)
providers_for_user = Plug.providers_for_current_user(assigns.conn)

assigns =
assign(
assigns,
class: %{
authorization: (for %{class: class} <- assigns.authorization_link, do: class),
deauthorization: (for %{class: class} <- assigns.deauthorization_link, do: class)
},
label: %{
authorization: Enum.reject(assigns.authorization_link, &is_nil(&1.inner_block)),
deauthorization: Enum.reject(assigns.deauthorization_link, &is_nil(&1.inner_block))
},
providers: providers,
providers_for_user: providers_for_user
)

~H"""
<%= for provider <- @providers do %><.authorization_link
:if={provider not in @providers_for_user}
conn={@conn}
provider={provider}
{@class.authorization != [] && [class: @class.authorization] || []}
>
<%= @label.authorization != [] && render_slot(@label.authorization, provider) || sign_in_with_provider_label(@conn, provider) %>
</.authorization_link><.deauthorization_link
:if={provider in @providers_for_user}
conn={@conn}
provider={provider}
{@class.deauthorization != [] && [class: @class.deauthorization] || []}
>
<%= @label.deauthorization != [] && render_slot(@label.deauthorization, provider) || remove_provider_authentication_label(@conn, provider) %>
</.deauthorization_link><% end %>
<:deauthorization_link class="text-red-500">
Remove <%= @provider %> authentication
<:/deauthorization_link>
</.provider_links>
"""
end

defp sign_in_with_provider_label(conn, provider) do
AuthorizationController.extension_messages(conn).login_with_provider(%{conn | params: %{"provider" => provider}})
end

defp remove_provider_authentication_label(conn, provider) do
AuthorizationController.extension_messages(conn).remove_provider_authentication(%{conn | params: %{"provider" => provider}})
end

@doc """
Renders an authorization link for a provider.

The link is used to sign up or register a user using a provider. If
`:invited_user` is assigned to the conn, the invitation token will be passed
on through the URL query params.

## Examples

<.authorization_link conn={@conn} provider="github" />

<.authorization_link conn={@conn} provider="github">Sign in with Github</.authorization_link>
"""
attr :conn, :any, required: true, doc: "the conn"
attr :provider, :any, required: true, doc: "the provider"

attr :rest,
:global,
include: ~w(csrf_token download hreflang referrerpolicy rel target type),
doc: "
attr(:conn, :any, required: true, doc: "the conn")

slot :authorization_link, doc: "attributes and inner content for the authorization link" do
attr(:class, :string, doc: "Additional classes added to the `.link` tag")
end

slot :deauthorization_link, doc: "attributes and inner content for the deuathorization link" do
attr(:class, :string, doc: "Additional classes added to the `.link` tag")
end

def provider_links(assigns) do
providers = Plug.available_providers(assigns.conn)
providers_for_user = Plug.providers_for_current_user(assigns.conn)

assigns =
assign(
assigns,
class: %{
authorization: for(%{class: class} <- assigns.authorization_link, do: class),
deauthorization: for(%{class: class} <- assigns.deauthorization_link, do: class)
},
label: %{
authorization: Enum.reject(assigns.authorization_link, &is_nil(&1.inner_block)),
deauthorization: Enum.reject(assigns.deauthorization_link, &is_nil(&1.inner_block))
},
providers: providers,
providers_for_user: providers_for_user
)

~H"""
<%= for provider <- @providers do %><.authorization_link
:if={provider not in @providers_for_user}
conn={@conn}
provider={provider}
{@class.authorization != [] && [class: @class.authorization] || []}
>
<%= @label.authorization != [] && render_slot(@label.authorization, provider) || sign_in_with_provider_label(@conn, provider) %>
</.authorization_link><.deauthorization_link
:if={provider in @providers_for_user}
conn={@conn}
provider={provider}
{@class.deauthorization != [] && [class: @class.deauthorization] || []}
>
<%= @label.deauthorization != [] && render_slot(@label.deauthorization, provider) || remove_provider_authentication_label(@conn, provider) %>
</.deauthorization_link><% end %>
"""
end

defp sign_in_with_provider_label(conn, provider) do
AuthorizationController.extension_messages(conn).login_with_provider(%{
conn
| params: %{"provider" => provider}
})
end

defp remove_provider_authentication_label(conn, provider) do
AuthorizationController.extension_messages(conn).remove_provider_authentication(%{
conn
| params: %{"provider" => provider}
})
end

@doc """
Renders an authorization link for a provider.

The link is used to sign up or register a user using a provider. If
`:invited_user` is assigned to the conn, the invitation token will be passed
on through the URL query params.

## Examples

<.authorization_link conn={@conn} provider="github" />

<.authorization_link conn={@conn} provider="github">Sign in with Github</.authorization_link>
"""
attr(:conn, :any, required: true, doc: "the conn")
attr(:provider, :any, required: true, doc: "the provider")

attr(
:rest,
:global,
include: ~w(csrf_token download hreflang referrerpolicy rel target type),
doc: "
Additional attributes added to the `.link` tag.
"
)

slot :inner_block
slot(:inner_block)

def authorization_link(assigns) do
query_params = invitation_token_query_params(assigns.conn) ++ request_path_query_params(assigns.conn)
path = AuthorizationController.routes(assigns.conn).path_for(assigns.conn, AuthorizationController, :new, [assigns.provider], query_params)
assigns = assign(assigns, navigate: path)
def authorization_link(assigns) do
query_params =
invitation_token_query_params(assigns.conn) ++ request_path_query_params(assigns.conn)

~H"""
<.link navigate={@navigate} {@rest}><%= render_slot(@inner_block) || sign_in_with_provider_label(@conn, @provider) %></.link>
"""
end
path =
AuthorizationController.routes(assigns.conn).path_for(
assigns.conn,
AuthorizationController,
:new,
[assigns.provider],
query_params
)

defp invitation_token_query_params(%{assigns: %{invited_user: %{invitation_token: token}}}), do: [invitation_token: token]
defp invitation_token_query_params(_conn), do: []
assigns = assign(assigns, navigate: path)

defp request_path_query_params(%{assigns: %{request_path: request_path}}), do: [request_path: request_path]
defp request_path_query_params(_conn), do: []
~H"""
<.link navigate={@navigate} {@rest}><%= render_slot(@inner_block) || sign_in_with_provider_label(@conn, @provider) %></.link>
"""
end

@doc """
Renders a deauthorization link for a provider.
defp invitation_token_query_params(%{assigns: %{invited_user: %{invitation_token: token}}}),
do: [invitation_token: token]

The link is used to remove authorization with the provider.
defp invitation_token_query_params(_conn), do: []

## Examples
defp request_path_query_params(%{assigns: %{request_path: request_path}}),
do: [request_path: request_path]

<.deauthorization_link conn={@conn} provider="github">
defp request_path_query_params(_conn), do: []

<.deauthorization_link conn={@conn} provider="github">Remove Github authentication</.deauthorization_link>
"""
attr :conn, :any, required: true, doc: "the conn"
attr :provider, :any, required: true, doc: "the provider"
@doc """
Renders a deauthorization link for a provider.

attr :rest,
:global,
include: ~w(csrf_token download hreflang referrerpolicy rel target type),
doc: "Additional attributes added to the `.link` tag."
The link is used to remove authorization with the provider.

slot :inner_block
## Examples

def deauthorization_link(assigns) do
path = AuthorizationController.routes(assigns.conn).path_for(assigns.conn, AuthorizationController, :delete, [assigns.provider])
assigns = assign(assigns, navigate: path)
<.deauthorization_link conn={@conn} provider="github">

~H"""
<.link navigate={@navigate} method="delete" {@rest}><%= render_slot(@inner_block) || remove_provider_authentication_label(@conn, @provider) %></.link>
<.deauthorization_link conn={@conn} provider="github">Remove Github authentication</.deauthorization_link>
"""
attr(:conn, :any, required: true, doc: "the conn")
attr(:provider, :any, required: true, doc: "the provider")

attr(
:rest,
:global,
include: ~w(csrf_token download hreflang referrerpolicy rel target type),
doc: "Additional attributes added to the `.link` tag."
)

slot(:inner_block)

def deauthorization_link(assigns) do
path =
AuthorizationController.routes(assigns.conn).path_for(
assigns.conn,
AuthorizationController,
:delete,
[assigns.provider]
)

assigns = assign(assigns, navigate: path)

~H"""
<.link href={@navigate} method="delete" {@rest}><%= render_slot(@inner_block) || remove_provider_authentication_label(@conn, @provider) %></.link>
"""
end
end
end
end
13 changes: 13 additions & 0 deletions lib/pow_assent/user_identities/user_identity.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule PowAssent.UserIdentities.UserIdentity do
@moduledoc """
PowAssent user identity schema.
"""
use Ecto.Schema
use PowAssent.Ecto.UserIdentities.Schema, user: PowAssent.Users.User

schema "user_identities" do
pow_assent_user_identity_fields()

timestamps()
end
end
13 changes: 13 additions & 0 deletions lib/pow_assent/users/user.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule PowAssent.Users.User do
@moduledoc """
Pow user schema.
"""
use Ecto.Schema
use Pow.Ecto.Schema

schema "users" do
pow_user_fields()

timestamps()
end
end
Loading