Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Elixir 1.16, Phoenix 1.7, Phonix HTML 4.0 #248

Merged
merged 4 commits into from
Feb 17, 2024
Merged
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
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ jobs:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: 25.0
elixir-version: 1.14.0
otp-version: 26.0
elixir-version: 1.16.0
- run: mix deps.get
- run: mix compile --warnings-as-errors
- run: mix credo --strict --ignore design.tagtodo,readability.maxlinelength
Expand All @@ -40,8 +40,8 @@ jobs:
strategy:
matrix:
version:
- otp: 25.0
elixir: 1.14.0
- otp: 26.0
elixir: 1.16.0
os: ubuntu-latest
- otp: 22.0
elixir: 1.12.0
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1
with:
otp-version: 24.0
elixir-version: 1.12
otp-version: 26.0
elixir-version: 1.16
- run: mix deps.get
- run: mix hex.publish --yes
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## v0.4.18 (TBA)

Now supports Phoenix HTML 4.0.

`PowAssent.Phoenix.ViewHelpers` has been replaced with `PowAssent.Phoenix.HTML.CoreComponents` for Phoenix 1.7. The following functions be used in place of the previous view helper functions:

- `PowAssent.Phoenix.HTML.CoreComponents.provider_links/1`
- `PowAssent.Phoenix.HTML.CoreComponents.authorization_link/1`
- `PowAssent.Phoenix.HTML.CoreComponents.deauthorization_link/1`

### Enhancements

* [`PowAssent.Phoenix.HTML.CoreComponents`] added with template functions for authorization links

### Deprecations

* [`PowAssent.Phoenix.ViewHelpers`] has been deprecated and removed from Phoenix 1.7

## v0.4.17 (2023-03-28)

### Enhancements
Expand Down
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,27 @@ Otherwise, Pow will raise an error about missing template when the user id field

### Provider links

You can use `PowAssent.Phoenix.ViewHelpers.provider_links/1` to add provider links to your template files:
You can use helpers from `PowAssent.Phoenix.HTML.CoreComponents` to render provider links:

```elixir
<%= for link <- PowAssent.Phoenix.ViewHelpers.provider_links(@conn),
do: content_tag(:span, link) %>
# Minimal
<PowAssent.Phoenix.HTML.CoreComponents.provider_links conn={@conn} />

# With styling
<div class="space-y-4 bg-white mt-10">
<PowAssent.Phoenix.HTML.CoreComponents.provider_links conn={@conn}>
<:authorization_link :let={provider} class="block rounded-lg bg-zinc-600 hover:bg-zinc-700 py-2 px-3 text-sm font-semibold leading-6 text-white w-full text-center">
<%= Phoenix.Naming.humanize(provider) %> <span aria-hidden="true">→</span>
</:authorization_link>
</PowAssent.Phoenix.HTML.CoreComponents.provider_links>
</div>
```

This can be used in the `WEB_PATH/controllers/pow/session_html/new.html.heex`, `WEB_PATH/controllers/pow/registration_html/new.html.heex` and `WEB_PATH/controllers/pow/registration_html/edit.html.heex` templates.
This can be used in the `WEB_PATH/controllers/pow/session_html/new.html.heex`, `WEB_PATH/controllers/pow/registration_html/new.html.heex` and `WEB_PATH/controllers/pow/registration_html/edit.html.heex` templates. You may want to import or alias the module in your web module.

By default "Sign in with PROVIDER" link is shown. A "Remove PROVIDER authentication" link will be shown instead if the user is signed in and the user already have authorized with the provider.
By default "Sign in with PROVIDER" link is shown. A "Remove PROVIDER authentication" link will be shown instead if the user is signed in and the user already is authorized with the provider.

You can also call `PowAssent.Phoenix.ViewHelpers.authorization_link/2` and `PowAssent.Phoenix.ViewHelpers.deauthorization_link/2` to generate a link for a single provider.
You can also use `<.authorization_link conn={@conn} provider={provider} />` and `<.deauthorization_link conn={@conn} provider={provider} />` to generate a link for a single provider.

### Setting up a provider

Expand Down Expand Up @@ -145,7 +154,7 @@ config :my_app, :pow_assent,
providers: [
example: [
client_id: "REPLACE_WITH_CLIENT_ID",
site: "https://server.example.com",
base_url: "https://server.example.com",
authorization_params: [scope: "user:read user:write"],
nonce: true,
strategy: Assent.Strategy.OIDC
Expand Down
2 changes: 1 addition & 1 deletion lib/mix/tasks/ecto/pow_assent.ecto.gen.schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ defmodule Mix.Tasks.PowAssent.Ecto.Gen.Schema do
defp dir_name(schema_name) do
schema_name
|> String.split(".")
|> Enum.slice(0..-2)
|> Enum.slice(0..-2//1)
|> Enum.join(".")
|> Macro.underscore()
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,16 @@ defmodule PowAssent.Phoenix.AuthorizationController do
defp load_user_by_invitation_token(conn, _opts), do: conn

defp handle_strategy_error(conn, error) do
Logger.error("Strategy failed with error: #{inspect error}")
log_strategy_error(error)

conn
|> put_flash(:error, extension_messages(conn).could_not_sign_in(conn))
|> redirect(to: routes(conn).session_path(conn, :new))
end

defp log_strategy_error(error) when is_exception(error), do: log_strategy_error(Exception.message(error))

defp log_strategy_error(error) when is_binary(error) do
Logger.error("Strategy failed with error: #{error}")
end
end
29 changes: 4 additions & 25 deletions lib/pow_assent/phoenix/controllers/view_helpers.ex
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
# TODO: Remove this when Phoenix 1.7+ is required
if Pow.dependency_vsn_match?(:phoenix, "< 1.7.0") do
defmodule PowAssent.Phoenix.ViewHelpers do
@moduledoc """
View helpers to render authorization links.
"""
@moduledoc false
alias PowAssent.Plug

alias Phoenix.{HTML, HTML.Link}
alias PowAssent.Phoenix.AuthorizationController

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

The list of providers will be fetched from the PowAssent configuration, and
`authorization_link/2` 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/2` will be used for any already authorized providers.
"""
@spec provider_links(Conn.t(), keyword()) :: [HTML.safe()]
def provider_links(conn, link_opts \\ []) do
available_providers = Plug.available_providers(conn)
Expand All @@ -30,13 +20,6 @@ defmodule PowAssent.Phoenix.ViewHelpers do
end)
end

@doc """
Generates 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.
"""
@spec authorization_link(Conn.t(), atom(), keyword()) :: HTML.safe()
def authorization_link(conn, provider, opts \\ []) do
query_params = invitation_token_query_params(conn) ++ request_path_query_params(conn)
Expand All @@ -54,11 +37,6 @@ defmodule PowAssent.Phoenix.ViewHelpers do
defp request_path_query_params(%{assigns: %{request_path: request_path}}), do: [request_path: request_path]
defp request_path_query_params(_conn), do: []

@doc """
Generates a provider deauthorization link.

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}})
Expand All @@ -68,3 +46,4 @@ defmodule PowAssent.Phoenix.ViewHelpers do
Link.link(msg, opts)
end
end
end
162 changes: 162 additions & 0 deletions lib/pow_assent/phoenix/html/core_components.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# TODO: Remove conditional when LiveView for Phoenix 1.7+ is required
# 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

alias PowAssent.{Phoenix.AuthorizationController, Plug}

@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.

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

<.provider_links conn={@conn} />

<.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 %>
"""
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

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)

~H"""
<.link navigate={@navigate} {@rest}><%= render_slot(@inner_block) || sign_in_with_provider_label(@conn, @provider) %></.link>
"""
end

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

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

@doc """
Renders a deauthorization link for a provider.

The link is used to remove authorization with the provider.

## Examples

<.deauthorization_link conn={@conn} provider="github">

<.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 navigate={@navigate} method="delete" {@rest}><%= render_slot(@inner_block) || remove_provider_authentication_label(@conn, @provider) %></.link>
"""
end
end
end
1 change: 1 addition & 0 deletions lib/pow_assent/phoenix/messages.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ defmodule PowAssent.Phoenix.Messages do
@doc """
Message for provider login button.
"""
# TODO: Change function name to `log_in_with_provider` or `sign_in_with_provider`.
def login_with_provider(conn),
do: interpolate("Sign in with %{provider}", provider: Naming.humanize(conn.params["provider"]))

Expand Down
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ defmodule PowAssent.MixProject do
defp deps do
[
{:pow, "~> 1.0.29"},
{:assent, "~> 0.1.2 or ~> 0.2.0"},
{:assent, "~> 0.2.8"},

{:ecto, "~> 2.2 or ~> 3.0"},
{:phoenix, ">= 1.3.0 and < 1.8.0"},
{:phoenix_html, ">= 2.0.0 and <= 4.0.0"},
{:phoenix_html, ">= 2.0.0 and <= 5.0.0", optional: true},
{:plug, ">= 1.5.0 and < 2.0.0", optional: true},
{:phoenix_live_view, ">= 0.18.0", optional: true},

Expand Down
Loading
Loading