From f1552dc3c37660a7e17d647e1b7142a895e42f2c Mon Sep 17 00:00:00 2001 From: Sasha Gerrand Date: Mon, 20 Jan 2025 14:16:09 +0000 Subject: [PATCH 1/3] Test against current Elixir and Erlang/OTP GA versions The current generally available versions of Elixir and Erlang/OTP are 1.18.1 and 27.2, respectfully: - https://github.com/elixir-lang/elixir/releases/tag/v1.18.1 - https://github.com/erlang/otp/releases/tag/OTP-27.2 --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e40ae5..8f2101b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,6 +40,9 @@ jobs: strategy: matrix: version: + - otp: 27.2 + elixir: 1.18 + os: ubuntu-latest - otp: 26.0 elixir: 1.16.0 os: ubuntu-latest From 4187529c8e06ed4f77df0c2d7046a8f30ae0ee76 Mon Sep 17 00:00:00 2001 From: Sasha Gerrand Date: Mon, 20 Jan 2025 14:18:21 +0000 Subject: [PATCH 2/3] Fix typo in comment --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f2101b..3185be2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,7 +48,7 @@ jobs: os: ubuntu-latest - otp: 22.0 elixir: 1.12.0 - # It's necessary to run on ubunto 20.04 for OTP 20 - 25 + # It's necessary to run on Ubuntu 20.04 for OTP 20 - 25 # See https://github.com/erlef/setup-beam os: ubuntu-20.04 runs-on: ${{ matrix.version.os }} From 804000c9d9a38757b84a8d983abb2bd30789f046 Mon Sep 17 00:00:00 2001 From: Sasha Gerrand Date: Mon, 20 Jan 2025 14:30:37 +0000 Subject: [PATCH 3/3] Fix compilation warnings with `mix format --migrate` See: https://elixir-lang.org/blog/2024/12/19/elixir-v1-18-0-released/#mix-format---migrate --- config/config.exs | 2 +- lib/mix/pow_assent.ex | 22 +- .../ecto/pow_assent.ecto.gen.migration.ex | 20 +- .../tasks/ecto/pow_assent.ecto.gen.schema.ex | 16 +- lib/mix/tasks/ecto/pow_assent.ecto.install.ex | 29 +- .../pow_assent.phoenix.gen.templates.ex | 8 +- .../phoenix/pow_assent.phoenix.install.ex | 40 +-- lib/mix/tasks/pow_assent.install.ex | 11 +- lib/pow_assent/config.ex | 5 +- lib/pow_assent/ecto/schema.ex | 67 +++- .../ecto/user_identities/context.ex | 84 +++-- lib/pow_assent/ecto/user_identities/schema.ex | 3 +- .../ecto/user_identities/schema/migration.ex | 5 +- .../ecto/user_identities/schema/module.ex | 4 +- lib/pow_assent/operations.ex | 25 +- .../controllers/authorization_controller.ex | 168 +++++++--- .../reauthorization_plug_handler.ex | 23 +- .../controllers/registration_controller.ex | 49 ++- .../phoenix/controllers/registration_html.ex | 50 ++- .../phoenix/controllers/view_helpers.ex | 102 +++--- .../phoenix/html/core_components.ex | 293 +++++++++-------- lib/pow_assent/phoenix/messages.ex | 20 +- lib/pow_assent/phoenix/router.ex | 23 +- lib/pow_assent/plug.ex | 139 +++++--- lib/pow_assent/plug/reauthorization.ex | 23 +- mix.exs | 4 - .../pow_assent.ecto.gen.migration_test.exs | 8 +- .../ecto/pow_assent.ecto.gen.schema_test.exs | 8 +- .../ecto/pow_assent.ecto.install_test.exs | 32 +- .../pow_assent.phoenix.gen.templates_test.exs | 4 +- .../pow_assent.phoenix.install_test.exs | 29 +- test/mix/tasks/pow_assent.install_test.exs | 32 +- test/pow_assent/config_test.exs | 23 +- test/pow_assent/ecto/schema_test.exs | 124 ++++++-- .../ecto/user_identities/context_test.exs | 98 ++++-- .../ecto/user_identities/schema_test.exs | 17 +- .../authorization_controller_test.exs | 299 +++++++++++++----- .../registration_controller_test.exs | 70 +++- .../phoenix/html/core_components_test.exs | 27 +- .../reauthorization_plug_handler_test.exs | 14 +- test/pow_assent/plug_test.exs | 219 ++++++++++--- .../pow_assent/plugs/reauthorization_test.exs | 46 ++- .../priv/migrations/2_add_name_to_users.exs | 2 +- ...ccess_refresh_token_to_user_identities.exs | 4 +- .../5_add_name_to_user_identities.exs | 2 +- test/support/ecto/user.ex | 2 +- .../ecto/user_identities/user_identity.ex | 1 + test/support/ets_cache_mock.ex | 12 +- .../email_confirmation/phoenix/endpoint.ex | 19 +- .../email_confirmation/phoenix/layouts.ex | 2 +- .../email_confirmation/repo_mock.ex | 27 +- .../extensions/email_confirmation/user.ex | 4 +- .../email_confirmation/user_identity.ex | 1 + .../extensions/invitation/phoenix/endpoint.ex | 19 +- .../extensions/invitation/repo_mock.ex | 4 +- test/support/extensions/invitation/user.ex | 6 +- .../extensions/invitation/user_identity.ex | 1 + test/support/mix/test_case.ex | 20 +- .../phoenix/components/layouts.ex | 2 +- .../no_registration/phoenix/endpoint.ex | 19 +- .../support/no_registration/phoenix/router.ex | 12 +- test/support/phoenix/components/layouts.ex | 2 +- test/support/phoenix/endpoint.ex | 19 +- test/support/phoenix/messages.ex | 1 + test/support/phoenix/router.ex | 22 +- test/support/phoenix/routes.ex | 1 + .../reauthorization/phoenix/endpoint.ex | 19 +- .../support/reauthorization/phoenix/router.ex | 16 +- test/support/repo_mock.ex | 70 +++- test/support/strategies/oauth2_test_case.ex | 36 ++- test/support/test_provider.ex | 51 ++- .../with_custom_changeset/phoenix/endpoint.ex | 19 +- .../with_custom_changeset/repo_mock.ex | 13 +- .../with_custom_changeset/user_identity.ex | 7 +- 74 files changed, 1886 insertions(+), 834 deletions(-) diff --git a/config/config.exs b/config/config.exs index 8b7f7cc..c4f4b17 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,5 +1,5 @@ import Config -if Mix.env == :test do +if Mix.env() == :test do import_config "test.exs" end diff --git a/lib/mix/pow_assent.ex b/lib/mix/pow_assent.ex index 59d7a31..58add80 100644 --- a/lib/mix/pow_assent.ex +++ b/lib/mix/pow_assent.ex @@ -8,16 +8,26 @@ defmodule Mix.PowAssent do def validate_schema_args!([schema, plural | _rest] = args, task) do cond do not schema_valid?(schema) -> - raise_invalid_schema_args_error!("Expected the schema argument, #{inspect schema}, to be a valid module name", task) + raise_invalid_schema_args_error!( + "Expected the schema argument, #{inspect(schema)}, to be a valid module name", + task + ) + not plural_valid?(plural) -> - raise_invalid_schema_args_error!("Expected the plural argument, #{inspect plural}, to be all lowercase using snake_case convention", task) + raise_invalid_schema_args_error!( + "Expected the plural argument, #{inspect(plural)}, to be all lowercase using snake_case convention", + task + ) + true -> schema_options_from_args(args) end end + def validate_schema_args!([_schema | _rest], task) do raise_invalid_schema_args_error!("Invalid arguments", task) end + def validate_schema_args!([], _task), do: schema_options_from_args() defp schema_valid?(schema), do: schema =~ ~r/^[A-Z]\w*(\.[A-Z]\w*)*$/ @@ -35,6 +45,10 @@ defmodule Mix.PowAssent do end defp schema_options_from_args(_opts \\ []) - defp schema_options_from_args([schema, plural | _rest]), do: %{schema_name: schema, schema_plural: plural} - defp schema_options_from_args(_any), do: %{schema_name: "UserIdentities.UserIdentity", schema_plural: "user_identities"} + + defp schema_options_from_args([schema, plural | _rest]), + do: %{schema_name: schema, schema_plural: plural} + + defp schema_options_from_args(_any), + do: %{schema_name: "UserIdentities.UserIdentity", schema_plural: "user_identities"} end diff --git a/lib/mix/tasks/ecto/pow_assent.ecto.gen.migration.ex b/lib/mix/tasks/ecto/pow_assent.ecto.gen.migration.ex index e111700..d8dd049 100644 --- a/lib/mix/tasks/ecto/pow_assent.ecto.gen.migration.ex +++ b/lib/mix/tasks/ecto/pow_assent.ecto.gen.migration.ex @@ -50,10 +50,22 @@ defmodule Mix.Tasks.PowAssent.Ecto.Gen.Migration do |> Enum.each(&create_migration_file/1) end - defp create_migration_file(%{repo: repo, binary_id: binary_id, users_table: users_table, schema_plural: schema_plural}) do - context_base = Pow.app_base(Pow.otp_app()) - schema = UserIdentitiesMigration.new(context_base, schema_plural, repo: repo, binary_id: binary_id, users_table: users_table) - content = UserIdentitiesMigration.gen(schema) + defp create_migration_file(%{ + repo: repo, + binary_id: binary_id, + users_table: users_table, + schema_plural: schema_plural + }) do + context_base = Pow.app_base(Pow.otp_app()) + + schema = + UserIdentitiesMigration.new(context_base, schema_plural, + repo: repo, + binary_id: binary_id, + users_table: users_table + ) + + content = UserIdentitiesMigration.gen(schema) Migration.create_migration_file(repo, schema.migration_name, content) end diff --git a/lib/mix/tasks/ecto/pow_assent.ecto.gen.schema.ex b/lib/mix/tasks/ecto/pow_assent.ecto.gen.schema.ex index 2f35673..759a0e7 100644 --- a/lib/mix/tasks/ecto/pow_assent.ecto.gen.schema.ex +++ b/lib/mix/tasks/ecto/pow_assent.ecto.gen.schema.ex @@ -41,13 +41,15 @@ defmodule Mix.Tasks.PowAssent.Ecto.Gen.Schema do |> Map.merge(config) end - defp create_schema_file(%{binary_id: binary_id, schema_name: schema_name, schema_plural: schema_plural} = config) do - context_app = Map.get(config, :context_app) || Pow.otp_app() + defp create_schema_file( + %{binary_id: binary_id, schema_name: schema_name, schema_plural: schema_plural} = config + ) do + context_app = Map.get(config, :context_app) || Pow.otp_app() context_base = Pow.app_base(context_app) - schema = SchemaModule.new(context_base, schema_name, schema_plural, binary_id: binary_id) - content = SchemaModule.gen(schema) - dir_name = dir_name(schema_name) - file_name = file_name(schema.module) + schema = SchemaModule.new(context_base, schema_name, schema_plural, binary_id: binary_id) + content = SchemaModule.gen(schema) + dir_name = dir_name(schema_name) + file_name = file_name(schema.module) context_app |> Pow.context_lib_path(dir_name) @@ -84,7 +86,7 @@ defmodule Mix.Tasks.PowAssent.Ecto.Gen.Schema do |> File.exists?() |> case do false -> path - _ -> Mix.raise("schema file can't be created, there is already a schema file in #{path}.") + _ -> Mix.raise("schema file can't be created, there is already a schema file in #{path}.") end end end diff --git a/lib/mix/tasks/ecto/pow_assent.ecto.install.ex b/lib/mix/tasks/ecto/pow_assent.ecto.install.ex index 96d8e8d..f047364 100644 --- a/lib/mix/tasks/ecto/pow_assent.ecto.install.ex +++ b/lib/mix/tasks/ecto/pow_assent.ecto.install.ex @@ -51,6 +51,7 @@ defmodule Mix.Tasks.PowAssent.Ecto.Install do config end + defp maybe_run_gen_migration(config, _args), do: config defp maybe_run_gen_schema(%{schema: true} = config, args) do @@ -58,15 +59,20 @@ defmodule Mix.Tasks.PowAssent.Ecto.Install do config end + defp maybe_run_gen_schema(config, _args), do: config defp parse_structure(config) do - context_app = Map.get(config, :context_app) || Pow.otp_app() + context_app = Map.get(config, :context_app) || Pow.otp_app() context_base = Pow.app_base(context_app) user_module = Module.concat([context_base, "Users.User"]) user_file = Path.join(["lib", "#{context_app}", "users", "user.ex"]) - Map.put(config, :structure, %{context_app: context_app, user_module: user_module, user_file: user_file}) + Map.put(config, :structure, %{ + context_app: context_app, + user_module: user_module, + user_file: user_file + }) end defp print_shell_instructions(%{structure: structure} = config) do @@ -80,7 +86,7 @@ defmodule Mix.Tasks.PowAssent.Ecto.Install do config :error -> - Mix.raise "Couldn't install PowAssent! Did you run this inside your Ecto app?" + Mix.raise("Couldn't install PowAssent! Did you run this inside your Ecto app?") end end @@ -96,17 +102,16 @@ defmodule Mix.Tasks.PowAssent.Ecto.Install do needle: "use Pow.Ecto.Schema" } ], - instructions: - """ - Add the `PowAssent.Ecto.Schema` macro to #{Path.relative_to_cwd(file)} after `use Pow.Ecto.Schema`: + instructions: """ + Add the `PowAssent.Ecto.Schema` macro to #{Path.relative_to_cwd(file)} after `use Pow.Ecto.Schema`: - defmodule #{inspect(structure.user_module)} do - use Ecto.Schema - use Pow.Ecto.Schema - use PowAssent.Ecto.Schema + defmodule #{inspect(structure.user_module)} do + use Ecto.Schema + use Pow.Ecto.Schema + use PowAssent.Ecto.Schema - # ... - """ + # ... + """ } end end diff --git a/lib/mix/tasks/phoenix/pow_assent.phoenix.gen.templates.ex b/lib/mix/tasks/phoenix/pow_assent.phoenix.gen.templates.ex index 6e04b6a..be6b0c3 100644 --- a/lib/mix/tasks/phoenix/pow_assent.phoenix.gen.templates.ex +++ b/lib/mix/tasks/phoenix/pow_assent.phoenix.gen.templates.ex @@ -29,13 +29,13 @@ defmodule Mix.Tasks.PowAssent.Phoenix.Gen.Templates do end @templates [ - {"registration", ~w(add_user_id)}, + {"registration", ~w(add_user_id)} ] defp create_template_files({config, _parsed, _invalid}) do - structure = Phoenix.parse_structure(config) - web_module = structure[:web_module] - web_prefix = structure[:web_prefix] + structure = Phoenix.parse_structure(config) + web_module = structure[:web_module] + web_prefix = structure[:web_prefix] Enum.each(@templates, fn {name, actions} -> Phoenix.create_template_module(Elixir.PowAssent, name, web_module, web_prefix) diff --git a/lib/mix/tasks/phoenix/pow_assent.phoenix.install.ex b/lib/mix/tasks/phoenix/pow_assent.phoenix.install.ex index 0e6890e..0079939 100644 --- a/lib/mix/tasks/phoenix/pow_assent.phoenix.install.ex +++ b/lib/mix/tasks/phoenix/pow_assent.phoenix.install.ex @@ -55,7 +55,7 @@ defmodule Mix.Tasks.PowAssent.Phoenix.Install do config :error -> - Mix.raise "Couldn't install PowAssent! Did you run this inside your Phoenix app?" + Mix.raise("Couldn't install PowAssent! Did you run this inside your Phoenix app?") end end @@ -111,33 +111,32 @@ defmodule Mix.Tasks.PowAssent.Phoenix.Install do needle: "pow_routes()" } ], - instructions: - """ - Update `#{Path.relative_to_cwd(file)}` with the PowAssent routes: + instructions: """ + Update `#{Path.relative_to_cwd(file)}` with the PowAssent routes: - defmodule #{inspect(structure.web_module)}.Router do - use #{inspect(structure.web_module)}, :router - use Pow.Phoenix.Router - #{router_use_content} + defmodule #{inspect(structure.web_module)}.Router do + use #{inspect(structure.web_module)}, :router + use Pow.Phoenix.Router + #{router_use_content} - # ... + # ... - #{router_pipeline_content} + #{router_pipeline_content} - # ... + # ... - #{router_scope_content} + #{router_scope_content} - scope "/" do - pipe_through :browser - - pow_routes() - #{router_routes_macro} - end + scope "/" do + pipe_through :browser - # ... + pow_routes() + #{router_routes_macro} end - """ + + # ... + end + """ } end @@ -146,5 +145,6 @@ defmodule Mix.Tasks.PowAssent.Phoenix.Install do config end + defp maybe_run_gen_templates(config, _args), do: config end diff --git a/lib/mix/tasks/pow_assent.install.ex b/lib/mix/tasks/pow_assent.install.ex index f7a7c3e..a03acb6 100644 --- a/lib/mix/tasks/pow_assent.install.ex +++ b/lib/mix/tasks/pow_assent.install.ex @@ -37,14 +37,13 @@ defmodule Mix.Tasks.PowAssent.Install do defp no_umbrella! do if Project.umbrella?() do - Mix.raise( - """ - mix #{@mix_task} has to be used inside an application directory, but this is an umbrella project. + Mix.raise(""" + mix #{@mix_task} has to be used inside an application directory, but this is an umbrella project. - Run mix pow_assent.ecto.install inside your Ecto application directory to create schema module and migrations. + Run mix pow_assent.ecto.install inside your Ecto application directory to create schema module and migrations. - Run mix pow_assent.phoenix.install in your Phoenix application directory for configuration instructions. - """) + Run mix pow_assent.phoenix.install in your Phoenix application directory for configuration instructions. + """) end :ok diff --git a/lib/pow_assent/config.ex b/lib/pow_assent/config.ex index 873496c..f637a5b 100644 --- a/lib/pow_assent/config.ex +++ b/lib/pow_assent/config.ex @@ -19,7 +19,7 @@ defmodule PowAssent.Config do def get(config, key, default \\ nil) do case Keyword.get(config, key, :not_found) do :not_found -> get_env_config(config, key, default) - value -> value + value -> value end end @@ -35,7 +35,7 @@ defmodule PowAssent.Config do config |> Keyword.get(:otp_app) |> case do - nil -> Application.get_all_env(env_key) + nil -> Application.get_all_env(env_key) otp_app -> Application.get_env(otp_app, env_key, []) end |> Keyword.get(key, default) @@ -83,6 +83,7 @@ defmodule PowAssent.Config do deep_merge(left, right) end) end + defp deep_merge(_left, right), do: right @doc """ diff --git a/lib/pow_assent/ecto/schema.ex b/lib/pow_assent/ecto/schema.ex index b8cc25e..0aae232 100644 --- a/lib/pow_assent/ecto/schema.ex +++ b/lib/pow_assent/ecto/schema.ex @@ -38,19 +38,44 @@ defmodule PowAssent.Ecto.Schema do alias Pow.UUID alias PowAssent.Config - @callback user_identity_changeset(Schema.t() | Changeset.t(), Schema.t(), map(), map() | nil) :: Changeset.t() + @callback user_identity_changeset(Schema.t() | Changeset.t(), Schema.t(), map(), map() | nil) :: + Changeset.t() @doc false defmacro __using__(_config) do quote do @behaviour unquote(__MODULE__) - @spec user_identity_changeset(Schema.t() | Changeset.t(), Schema.t(), map(), map() | nil) :: Changeset.t() - def user_identity_changeset(user_or_changeset, user_identity, attrs, user_id_attrs), do: pow_assent_user_identity_changeset(user_or_changeset, user_identity, attrs, user_id_attrs) - - @spec pow_assent_user_identity_changeset(Schema.t() | Changeset.t(), Schema.t(), map(), map() | nil) :: Changeset.t() - def pow_assent_user_identity_changeset(user_or_changeset, user_identity, attrs, user_id_attrs) do - unquote(__MODULE__).changeset(user_or_changeset, user_identity, attrs, user_id_attrs, @pow_config) + @spec user_identity_changeset(Schema.t() | Changeset.t(), Schema.t(), map(), map() | nil) :: + Changeset.t() + def user_identity_changeset(user_or_changeset, user_identity, attrs, user_id_attrs), + do: + pow_assent_user_identity_changeset( + user_or_changeset, + user_identity, + attrs, + user_id_attrs + ) + + @spec pow_assent_user_identity_changeset( + Schema.t() | Changeset.t(), + Schema.t(), + map(), + map() | nil + ) :: Changeset.t() + def pow_assent_user_identity_changeset( + user_or_changeset, + user_identity, + attrs, + user_id_attrs + ) do + unquote(__MODULE__).changeset( + user_or_changeset, + user_identity, + attrs, + user_id_attrs, + @pow_config + ) end unquote(__MODULE__).__has_many__() @@ -62,7 +87,9 @@ defmodule PowAssent.Ecto.Schema do @doc false defmacro __has_many__ do quote do - @pow_assocs {:has_many, :user_identities, unquote(__MODULE__).__user_identities_module__(__MODULE__), foreign_key: :user_id, on_delete: :delete_all} + @pow_assocs {:has_many, :user_identities, + unquote(__MODULE__).__user_identities_module__(__MODULE__), + foreign_key: :user_id, on_delete: :delete_all} end end @@ -86,7 +113,8 @@ defmodule PowAssent.Ecto.Schema do Only `Pow.Ecto.Schema.Changeset.user_id_field_changeset/3` is used for validation as password is not required. """ - @spec changeset(Schema.t() | Changeset.t(), Schema.t(), map(), map() | nil, Config.t()) :: Changeset.t() + @spec changeset(Schema.t() | Changeset.t(), Schema.t(), map(), map() | nil, Config.t()) :: + Changeset.t() def changeset(user_or_changeset, user_identity, attrs, user_id_attrs, _config) do user_or_changeset |> Changeset.change() @@ -97,24 +125,35 @@ defmodule PowAssent.Ecto.Schema do |> Changeset.cast_assoc(:user_identities) end - defp maybe_accept_invitation(%Changeset{data: %user_mod{invitation_token: token, invitation_accepted_at: nil} = changeset}) when not is_nil(token) do + defp maybe_accept_invitation(%Changeset{ + data: %user_mod{invitation_token: token, invitation_accepted_at: nil} = changeset + }) + when not is_nil(token) do accepted_at = Pow.Ecto.Schema.__timestamp_for__(user_mod, :invitation_accepted_at) Changeset.change(changeset, invitation_accepted_at: accepted_at) end + defp maybe_accept_invitation(changeset), do: changeset - defp user_id_field_changeset(changeset, attrs, nil), do: changeset.data.__struct__.pow_user_id_field_changeset(changeset, attrs) - defp user_id_field_changeset(changeset, _attrs, user_id_attrs), do: changeset.data.__struct__.pow_user_id_field_changeset(changeset, user_id_attrs) + defp user_id_field_changeset(changeset, attrs, nil), + do: changeset.data.__struct__.pow_user_id_field_changeset(changeset, attrs) - defp maybe_email_confirmation_changeset(%Changeset{data: %{unconfirmed_email: _any}} = changeset, attrs) do + defp user_id_field_changeset(changeset, _attrs, user_id_attrs), + do: changeset.data.__struct__.pow_user_id_field_changeset(changeset, user_id_attrs) + + defp maybe_email_confirmation_changeset( + %Changeset{data: %{unconfirmed_email: _any}} = changeset, + attrs + ) do email = Changeset.get_change(changeset, :email) case email_verified?(email, attrs) do - true -> confirm_email_changeset(changeset) + true -> confirm_email_changeset(changeset) false -> email_confirmation_token_changeset(changeset) end end + defp maybe_email_confirmation_changeset(changeset, _attrs), do: changeset defp confirm_email_changeset(%Changeset{data: %user_mod{}} = changeset) do diff --git a/lib/pow_assent/ecto/user_identities/context.ex b/lib/pow_assent/ecto/user_identities/context.ex index 1ba46f5..5a92fb3 100644 --- a/lib/pow_assent/ecto/user_identities/context.ex +++ b/lib/pow_assent/ecto/user_identities/context.ex @@ -71,9 +71,12 @@ defmodule PowAssent.Ecto.UserIdentities.Context do def get_user_by_provider_uid(provider, uid), do: pow_assent_get_user_by_provider_uid(provider, uid) + def upsert(user, user_identity_params), do: pow_assent_upsert(user, user_identity_params) + def create_user(user_identity_params, user_params, user_id_params), do: pow_assent_create_user(user_identity_params, user_params, user_id_params) + def delete(user, provider), do: pow_assent_delete(user, provider) def all(user), do: pow_assent_all(user) @@ -86,7 +89,12 @@ defmodule PowAssent.Ecto.UserIdentities.Context do end def pow_assent_create_user(user_identity_params, user_params, user_id_params) do - unquote(__MODULE__).create_user(user_identity_params, user_params, user_id_params, @pow_config) + unquote(__MODULE__).create_user( + user_identity_params, + user_params, + user_id_params, + @pow_config + ) end def pow_assent_delete(user, provider) do @@ -103,7 +111,9 @@ defmodule PowAssent.Ecto.UserIdentities.Context do # TODO: Remove by 0.4.0 @deprecated "Please use `pow_assent_upsert/2` instead" - defdelegate pow_assent_create(user, user_identity_params), to: __MODULE__, as: :pow_assent_upsert + defdelegate pow_assent_create(user, user_identity_params), + to: __MODULE__, + as: :pow_assent_upsert defoverridable unquote(__MODULE__) end @@ -117,6 +127,7 @@ defmodule PowAssent.Ecto.UserIdentities.Context do @spec get_user_by_provider_uid(binary(), binary() | integer(), Config.t()) :: user() | nil def get_user_by_provider_uid(provider, uid, config) when is_integer(uid), do: get_user_by_provider_uid(provider, Integer.to_string(uid), config) + def get_user_by_provider_uid(provider, uid, config) do opts = repo_opts(config, [:prefix]) @@ -131,7 +142,10 @@ defmodule PowAssent.Ecto.UserIdentities.Context do # TODO: Remove by 0.4.0 @doc false @deprecated "Use `upsert/3` instead" - @spec create(user(), user_identity_params(), Config.t()) :: {:ok, user_identity()} | {:error, {:bound_to_different_user, changeset()}} | {:error, changeset()} + @spec create(user(), user_identity_params(), Config.t()) :: + {:ok, user_identity()} + | {:error, {:bound_to_different_user, changeset()}} + | {:error, changeset()} def create(user, user_identity_params, config), do: upsert(user, user_identity_params, config) @doc """ @@ -142,15 +156,18 @@ defmodule PowAssent.Ecto.UserIdentities.Context do Repo module will be fetched from config. """ - @spec upsert(user(), user_identity_params(), Config.t()) :: {:ok, user_identity()} | {:error, {:bound_to_different_user, changeset()}} | {:error, changeset()} + @spec upsert(user(), user_identity_params(), Config.t()) :: + {:ok, user_identity()} + | {:error, {:bound_to_different_user, changeset()}} + | {:error, changeset()} def upsert(user, user_identity_params, config) do - params = convert_params(user_identity_params) + params = convert_params(user_identity_params) {uid_provider_params, additional_params} = Map.split(params, ["uid", "provider"]) user |> get_for_user(uid_provider_params, config) |> case do - nil -> insert_identity(user, params, config) + nil -> insert_identity(user, params, config) identity -> update_identity(identity, additional_params, config) end |> user_identity_bound_different_user_error() @@ -158,10 +175,11 @@ defmodule PowAssent.Ecto.UserIdentities.Context do defp user_identity_bound_different_user_error({:error, %{errors: errors} = changeset}) do case unique_constraint_error?(errors, :uid_provider) do - true -> {:error, {:bound_to_different_user, changeset}} + true -> {:error, {:bound_to_different_user, changeset}} false -> {:error, changeset} end end + defp user_identity_bound_different_user_error(any), do: any defp convert_params(params) when is_map(params) do @@ -171,7 +189,10 @@ defmodule PowAssent.Ecto.UserIdentities.Context do end defp convert_param({:uid, value}), do: convert_param({"uid", value}) - defp convert_param({"uid", value}) when is_integer(value), do: convert_param({"uid", Integer.to_string(value)}) + + defp convert_param({"uid", value}) when is_integer(value), + do: convert_param({"uid", Integer.to_string(value)}) + defp convert_param({key, value}) when is_atom(key), do: {Atom.to_string(key), value} defp convert_param({key, value}) when is_binary(key), do: {key, value} @@ -192,7 +213,11 @@ defmodule PowAssent.Ecto.UserIdentities.Context do defp get_for_user(user, %{"uid" => uid, "provider" => provider}, config) do user_identity = Ecto.build_assoc(user, :user_identities).__struct__ - repo!(config).get_by(user_identity, [user_id: user.id, provider: provider, uid: uid], repo_opts(config, [:prefix])) + repo!(config).get_by( + user_identity, + [user_id: user.id, provider: provider, uid: uid], + repo_opts(config, [:prefix]) + ) end @doc """ @@ -200,9 +225,12 @@ defmodule PowAssent.Ecto.UserIdentities.Context do User schema module and repo module will be fetched from config. """ - @spec create_user(user_identity_params(), user_params(), user_id_params() | nil, Config.t()) :: {:ok, user()} | {:error, {:bound_to_different_user | :invalid_user_id_field, changeset()}} | {:error, changeset()} + @spec create_user(user_identity_params(), user_params(), user_id_params() | nil, Config.t()) :: + {:ok, user()} + | {:error, {:bound_to_different_user | :invalid_user_id_field, changeset()}} + | {:error, changeset()} def create_user(user_identity_params, user_params, user_id_params, config) do - params = convert_params(user_identity_params) + params = convert_params(user_identity_params) user_mod = user!(config) user_mod @@ -213,30 +241,34 @@ defmodule PowAssent.Ecto.UserIdentities.Context do |> invalid_user_id_error(config) end - defp user_user_identity_bound_different_user_error({:error, %{changes: %{user_identities: [%{errors: errors}]}} = changeset}) do + defp user_user_identity_bound_different_user_error( + {:error, %{changes: %{user_identities: [%{errors: errors}]}} = changeset} + ) do case unique_constraint_error?(errors, :uid_provider) do - true -> {:error, {:bound_to_different_user, changeset}} + true -> {:error, {:bound_to_different_user, changeset}} false -> {:error, changeset} end end + defp user_user_identity_bound_different_user_error(any), do: any defp unique_constraint_error?(errors, field) do Enum.find_value(errors, false, fn {^field, {_msg, [constraint: :unique, constraint_name: _name]}} -> true - _any -> false + _any -> false end) end defp invalid_user_id_error({:error, %{errors: errors} = changeset}, config) do - user_mod = user!(config) + user_mod = user!(config) user_id_field = user_mod.pow_user_id_field() Enum.find_value(errors, {:error, changeset}, fn {^user_id_field, _error} -> {:error, {:invalid_user_id_field, changeset}} - _any -> false + _any -> false end) end + defp invalid_user_id_error(any, _config), do: any @doc """ @@ -247,23 +279,27 @@ defmodule PowAssent.Ecto.UserIdentities.Context do @spec delete(user(), binary(), Config.t()) :: {:ok, {number(), nil}} | {:error, {:no_password, changeset()}} def delete(user, provider, config) do - user = repo!(config).preload(user, :user_identities, repo_opts(config, [:prefix]) ++ [force: true]) + user = + repo!(config).preload(user, :user_identities, repo_opts(config, [:prefix]) ++ [force: true]) user.user_identities |> Enum.split_with(&(&1.provider == provider)) |> maybe_delete(user, config) end - defp maybe_delete({user_identities, rest}, %{password_hash: password_hash} = user, config) when length(rest) > 0 or not is_nil(password_hash) do - opts = repo_opts(config, [:prefix]) + defp maybe_delete({user_identities, rest}, %{password_hash: password_hash} = user, config) + when length(rest) > 0 or not is_nil(password_hash) do + opts = repo_opts(config, [:prefix]) + results = user |> Ecto.assoc(:user_identities) - |> where([i], i.id in ^Enum.map(user_identities, &(&1.id))) + |> where([i], i.id in ^Enum.map(user_identities, & &1.id)) |> repo!(config).delete_all(opts) {:ok, results} end + defp maybe_delete(_any, user, _config) do changeset = user @@ -292,15 +328,19 @@ defmodule PowAssent.Ecto.UserIdentities.Context do |> user!() |> user_identity_schema_mod() end + defp user_identity_schema_mod(user_mod) when is_atom(user_mod) do - association = user_mod.__schema__(:association, :user_identities) || raise_no_user_identity_error() + association = + user_mod.__schema__(:association, :user_identities) || raise_no_user_identity_error() association.queryable end @spec raise_no_user_identity_error :: no_return defp raise_no_user_identity_error do - Config.raise_error("The `:user` configuration option doesn't have a `:user_identities` association.") + Config.raise_error( + "The `:user` configuration option doesn't have a `:user_identities` association." + ) end defp repo_opts(config, opts) do diff --git a/lib/pow_assent/ecto/user_identities/schema.ex b/lib/pow_assent/ecto/user_identities/schema.ex index d52a549..e902f42 100644 --- a/lib/pow_assent/ecto/user_identities/schema.ex +++ b/lib/pow_assent/ecto/user_identities/schema.ex @@ -44,7 +44,8 @@ defmodule PowAssent.Ecto.UserIdentities.Schema do @pow_assent_config unquote(config) @spec changeset(Ecto.Schema.t() | Changeset.t(), map()) :: Changeset.t() - def changeset(user_identity_or_changeset, attrs), do: pow_assent_changeset(user_identity_or_changeset, attrs) + def changeset(user_identity_or_changeset, attrs), + do: pow_assent_changeset(user_identity_or_changeset, attrs) defoverridable unquote(__MODULE__) diff --git a/lib/pow_assent/ecto/user_identities/schema/migration.ex b/lib/pow_assent/ecto/user_identities/schema/migration.ex index 714016c..4dd4af3 100644 --- a/lib/pow_assent/ecto/user_identities/schema/migration.ex +++ b/lib/pow_assent/ecto/user_identities/schema/migration.ex @@ -10,9 +10,9 @@ defmodule PowAssent.Ecto.UserIdentities.Schema.Migration do """ @spec new(atom(), binary(), Config.t()) :: map() def new(context_base, schema_plural, config \\ []) do - attrs = attrs(config) + attrs = attrs(config) indexes = Fields.indexes(config) - config = Keyword.merge(config, attrs: attrs, indexes: indexes) + config = Keyword.merge(config, attrs: attrs, indexes: indexes) Migration.new(context_base, schema_plural, config) end @@ -35,6 +35,7 @@ defmodule PowAssent.Ecto.UserIdentities.Schema.Migration do {String.to_atom("#{name}_id"), {:references, users_table}, field_options, migration_options} end + defp attr_from_assoc(_assoc, _opts), do: nil @doc false diff --git a/lib/pow_assent/ecto/user_identities/schema/module.ex b/lib/pow_assent/ecto/user_identities/schema/module.ex index 5e3a943..38bb740 100644 --- a/lib/pow_assent/ecto/user_identities/schema/module.ex +++ b/lib/pow_assent/ecto/user_identities/schema/module.ex @@ -36,8 +36,8 @@ defmodule PowAssent.Ecto.UserIdentities.Schema.Module do """ @spec new(atom(), binary(), binary(), Config.t()) :: map() def new(context_base, schema_name, schema_plural, config \\ []) do - module = Module.concat([context_base, schema_name]) - binary_id = config[:binary_id] + module = Module.concat([context_base, schema_name]) + binary_id = config[:binary_id] user_module = Module.concat([context_base, "Users.User"]) %{ diff --git a/lib/pow_assent/operations.ex b/lib/pow_assent/operations.ex index f16fae0..7683076 100644 --- a/lib/pow_assent/operations.ex +++ b/lib/pow_assent/operations.ex @@ -19,14 +19,15 @@ defmodule PowAssent.Operations do def get_user_by_provider_uid(provider, uid, config) do case context_module(config) do Context -> Context.get_user_by_provider_uid(provider, uid, config) - module -> module.get_user_by_provider_uid(provider, uid) + module -> module.get_user_by_provider_uid(provider, uid) end end # TODO: Remove by 0.4.0 @doc false @deprecated "Use `upsert/3` instead" - @spec create(map(), map(), Config.t()) :: {:ok, map()} | {:error, {:bound_to_different_user, map()}} | {:error, map()} | no_return + @spec create(map(), map(), Config.t()) :: + {:ok, map()} | {:error, {:bound_to_different_user, map()}} | {:error, map()} | no_return def create(user, user_identity_params, config), do: upsert(user, user_identity_params, config) @doc """ @@ -35,11 +36,12 @@ defmodule PowAssent.Operations do This calls `Pow.Ecto.UserIdentities.Context.upsert/3` or `upsert/2` on a custom context module. """ - @spec upsert(map(), map(), Config.t()) :: {:ok, map()} | {:error, {:bound_to_different_user, map()}} | {:error, map()} | no_return + @spec upsert(map(), map(), Config.t()) :: + {:ok, map()} | {:error, {:bound_to_different_user, map()}} | {:error, map()} | no_return def upsert(user, user_identity_params, config) do case context_module(config) do Context -> Context.upsert(user, user_identity_params, config) - module -> module.upsert(user, user_identity_params) + module -> module.upsert(user, user_identity_params) end end @@ -49,11 +51,15 @@ defmodule PowAssent.Operations do This calls `Pow.Ecto.UserIdentities.Context.create_user/4` or `create_user/3` on a custom context module. """ - @spec create_user(map(), map(), map() | nil, Config.t()) :: {:ok, map()} | {:error, {:bound_to_different_user | :invalid_user_id_field, map()}} | {:error, map()} | no_return + @spec create_user(map(), map(), map() | nil, Config.t()) :: + {:ok, map()} + | {:error, {:bound_to_different_user | :invalid_user_id_field, map()}} + | {:error, map()} + | no_return def create_user(user_identity_params, user_params, user_id_params, config) do case context_module(config) do Context -> Context.create_user(user_identity_params, user_params, user_id_params, config) - module -> module.create_user(user_identity_params, user_params, user_id_params) + module -> module.create_user(user_identity_params, user_params, user_id_params) end end @@ -78,11 +84,12 @@ defmodule PowAssent.Operations do This calls `Pow.Ecto.UserIdentities.Context.delete/3` or `delete/2` on a custom context module. """ - @spec delete(map(), binary(), Config.t()) :: {:ok, {number(), nil}} | {:error, {:no_password, map()}} | no_return + @spec delete(map(), binary(), Config.t()) :: + {:ok, {number(), nil}} | {:error, {:no_password, map()}} | no_return def delete(user, provider, config) do case context_module(config) do Context -> Context.delete(user, provider, config) - module -> module.delete(user, provider) + module -> module.delete(user, provider) end end @@ -96,7 +103,7 @@ defmodule PowAssent.Operations do def all(user, config) do case context_module(config) do Context -> Context.all(user, config) - module -> module.all(user) + module -> module.all(user) end end diff --git a/lib/pow_assent/phoenix/controllers/authorization_controller.ex b/lib/pow_assent/phoenix/controllers/authorization_controller.ex index 953b3d7..a76eee4 100644 --- a/lib/pow_assent/phoenix/controllers/authorization_controller.ex +++ b/lib/pow_assent/phoenix/controllers/authorization_controller.ex @@ -10,13 +10,13 @@ defmodule PowAssent.Phoenix.AuthorizationController do alias PowAssent.{Phoenix.AuthorizationController, Phoenix.RegistrationController, Plug} alias PowEmailConfirmation.Phoenix.ControllerCallbacks, as: EmailConfirmationCallbacks - plug :require_authenticated when action in [:delete] - plug :assign_callback_url when action in [:new, :callback] - plug :init_session when action in [:new, :callback] - plug :assign_request_path when action in [:callback] - plug :load_session_params when action in [:callback] - plug :set_registration_option when action in [:callback] - plug :load_user_by_invitation_token when action in [:callback] + plug(:require_authenticated when action in [:delete]) + plug(:assign_callback_url when action in [:new, :callback]) + plug(:init_session when action in [:new, :callback]) + plug(:assign_request_path when action in [:callback]) + plug(:load_session_params when action in [:callback]) + plug(:set_registration_option when action in [:callback]) + plug(:load_user_by_invitation_token when action in [:callback]) @spec process_new(Conn.t(), map()) :: {:ok, binary(), Conn.t()} | {:error, any(), Conn.t()} def process_new(conn, %{"provider" => provider}) do @@ -31,33 +31,45 @@ defmodule PowAssent.Phoenix.AuthorizationController do |> maybe_store_invitation_token() |> redirect(external: url) end + def respond_new({:error, error, conn}), do: handle_strategy_error(conn, error) defp maybe_store_session_params(%{private: %{pow_assent_session_params: params}} = conn), do: Plug.put_session(conn, :session_params, params) + defp maybe_store_session_params(conn), do: conn defp maybe_store_request_path(%{params: %{"request_path" => request_path}} = conn), do: Plug.put_session(conn, :request_path, request_path) + defp maybe_store_request_path(conn), do: conn defp maybe_store_invitation_token(%{params: %{"invitation_token" => token}} = conn), do: Plug.put_session(conn, :invitation_token, token) + defp maybe_store_invitation_token(conn), do: conn - @spec process_callback(Conn.t(), map()) :: {:ok, Conn.t()} | {:error, Conn.t()} | {:error, {atom(), map()} | map(), Conn.t()} + @spec process_callback(Conn.t(), map()) :: + {:ok, Conn.t()} | {:error, Conn.t()} | {:error, {atom(), map()} | map(), Conn.t()} def process_callback(conn, %{"provider" => provider} = params) do Plug.callback_upsert(conn, provider, params, conn.assigns.callback_url) end - @spec respond_callback({:ok, Conn.t()} | {:error, Conn.t()} | {:error, {atom(), map()} | map(), Conn.t()}) :: Conn.t() - def respond_callback({:ok, %{private: %{pow_assent_callback_state: {:ok, :create_user}}} = conn}) do + @spec respond_callback( + {:ok, Conn.t()} + | {:error, Conn.t()} + | {:error, {atom(), map()} | map(), Conn.t()} + ) :: Conn.t() + def respond_callback( + {:ok, %{private: %{pow_assent_callback_state: {:ok, :create_user}}} = conn} + ) do trigger_registration_email_confirmation_controller_callback(conn, fn conn -> conn |> put_flash(:info, extension_messages(conn).user_has_been_created(conn)) |> redirect(to: routes(conn).after_registration_path(conn)) end) end + def respond_callback({:ok, conn}) do trigger_session_email_confirmation_controller_callback(conn, fn conn -> conn @@ -65,31 +77,68 @@ defmodule PowAssent.Phoenix.AuthorizationController do |> redirect(to: routes(conn).after_sign_in_path(conn)) end) end - def respond_callback({:error, %{private: %{pow_assent_callback_state: {:error, :strategy}, pow_assent_callback_error: error}} = conn}), - do: handle_strategy_error(conn, error) - def respond_callback({:error, %{private: %{pow_assent_callback_error: {:bound_to_different_user, _changeset}}} = conn}) do + + def respond_callback( + {:error, + %{ + private: %{ + pow_assent_callback_state: {:error, :strategy}, + pow_assent_callback_error: error + } + } = conn} + ), + do: handle_strategy_error(conn, error) + + def respond_callback( + {:error, + %{private: %{pow_assent_callback_error: {:bound_to_different_user, _changeset}}} = conn} + ) do conn |> put_flash(:error, extension_messages(conn).account_already_bound_to_other_user(conn)) |> redirect(to: routes(conn).session_path(conn, :new)) end - def respond_callback({:error, %{private: %{pow_assent_callback_error: {:invalid_user_id_field, changeset}}} = conn}) do + + def respond_callback( + {:error, + %{private: %{pow_assent_callback_error: {:invalid_user_id_field, changeset}}} = conn} + ) do trigger_registration_email_confirmation_controller_callback(conn, fn conn -> - params = Map.fetch!(conn.private, :pow_assent_callback_params) + params = Map.fetch!(conn.private, :pow_assent_callback_params) provider = Map.fetch!(conn.params, "provider") conn |> Plug.put_session(:callback_params, %{provider => params}) |> Plug.put_session(:changeset, changeset) - |> redirect(to: routes(conn).path_for(conn, RegistrationController, :add_user_id, [provider])) + |> redirect( + to: routes(conn).path_for(conn, RegistrationController, :add_user_id, [provider]) + ) end) end - def respond_callback({:error, %{private: %{pow_assent_callback_state: {:error, :create_user}, pow_assent_registration: false}} = conn}) do + + def respond_callback( + {:error, + %{ + private: %{ + pow_assent_callback_state: {:error, :create_user}, + pow_assent_registration: false + } + } = conn} + ) do conn |> put_flash(:error, extension_messages(conn).could_not_sign_in(conn)) |> redirect(to: routes(conn).session_path(conn, :new)) end - def respond_callback({:error, %{private: %{pow_assent_callback_state: {:error, :create_user}, pow_assent_callback_error: changeset}} = conn}) do - Logger.error("Unexpected error inserting user: #{inspect changeset}") + + def respond_callback( + {:error, + %{ + private: %{ + pow_assent_callback_state: {:error, :create_user}, + pow_assent_callback_error: changeset + } + } = conn} + ) do + Logger.error("Unexpected error inserting user: #{inspect(changeset)}") conn |> put_flash(:error, extension_messages(conn).could_not_sign_in(conn)) @@ -97,7 +146,7 @@ defmodule PowAssent.Phoenix.AuthorizationController do end defp trigger_registration_email_confirmation_controller_callback(conn, callback) do - config = PowPlug.fetch_config(conn) + config = PowPlug.fetch_config(conn) %{user: user} = conn.private[:pow_assent_callback_params] cond do @@ -106,11 +155,15 @@ defmodule PowAssent.Phoenix.AuthorizationController do extension_enabled?(config, PowEmailConfirmation) -> Pow.Phoenix.RegistrationController - |> EmailConfirmationCallbacks.before_respond(:create, to_email_confirmation_res(conn), config) + |> EmailConfirmationCallbacks.before_respond( + :create, + to_email_confirmation_res(conn), + config + ) |> case do - {:ok, _user, conn} -> callback.(conn) + {:ok, _user, conn} -> callback.(conn) {:error, _changeset, conn} -> callback.(conn) - {:halt, conn} -> conn + {:halt, conn} -> conn end true -> @@ -118,9 +171,17 @@ defmodule PowAssent.Phoenix.AuthorizationController do end end - defp to_email_confirmation_res(%{private: %{pow_assent_callback_state: {:error, _method}, pow_assent_callback_error: {_type, changeset}}} = conn) do + defp to_email_confirmation_res( + %{ + private: %{ + pow_assent_callback_state: {:error, _method}, + pow_assent_callback_error: {_type, changeset} + } + } = conn + ) do {:error, changeset, conn} end + defp to_email_confirmation_res(%{private: %{pow_assent_callback_state: {:ok, _method}}} = conn) do {:ok, PowPlug.current_user(conn), conn} end @@ -141,7 +202,7 @@ defmodule PowAssent.Phoenix.AuthorizationController do Pow.Phoenix.SessionController |> EmailConfirmationCallbacks.before_respond(:create, {:ok, conn}, config) |> case do - {:ok, conn} -> callback.(conn) + {:ok, conn} -> callback.(conn) {:halt, conn} -> conn end end @@ -157,55 +218,74 @@ defmodule PowAssent.Phoenix.AuthorizationController do |> put_flash(:info, extension_messages(conn).authentication_has_been_removed(conn)) |> redirect(to: after_delete_path(conn)) end + def respond_delete({:error, {:no_password, _changeset}, conn}) do conn - |> put_flash(:error, extension_messages(conn).identity_cannot_be_removed_missing_user_password(conn)) + |> put_flash( + :error, + extension_messages(conn).identity_cannot_be_removed_missing_user_password(conn) + ) |> redirect(to: after_delete_path(conn)) end defp after_delete_path(conn), do: routes(conn).registration_path(conn, :edit) defp assign_callback_url(conn, _opts) do - url = routes(conn).url_for(conn, AuthorizationController, :callback, [conn.params["provider"]]) + url = + routes(conn).url_for(conn, AuthorizationController, :callback, [conn.params["provider"]]) assign(conn, :callback_url, url) end defp init_session(conn, _opts), do: Plug.init_session(conn) - defp assign_request_path(%{private: %{pow_assent_session: %{request_path: request_path}}} = conn, _opts) do + defp assign_request_path( + %{private: %{pow_assent_session: %{request_path: request_path}}} = conn, + _opts + ) do conn |> Plug.delete_session(:request_path) |> Conn.assign(:request_path, request_path) end + defp assign_request_path(conn, _opts), do: conn - defp load_session_params(%{private: %{pow_assent_session: %{session_params: params}}} = conn, _opts) do + defp load_session_params( + %{private: %{pow_assent_session: %{session_params: params}}} = conn, + _opts + ) do conn |> Conn.put_private(:pow_assent_session_params, params) |> Plug.delete_session(:session_params) end + defp load_session_params(conn, _opts), do: conn - defp set_registration_option(%{private: %{pow_assent_registration: _any}} = conn, _opts), do: conn - defp set_registration_option(conn, _opts), do: Conn.put_private(conn, :pow_assent_registration, registration_path?(conn)) + defp set_registration_option(%{private: %{pow_assent_registration: _any}} = conn, _opts), + do: conn + + defp set_registration_option(conn, _opts), + do: Conn.put_private(conn, :pow_assent_registration, registration_path?(conn)) # TODO: Force verified routes when Phoenix 1.7 is required if Code.ensure_loaded?(Phoenix.VerifiedRoutes) do - defp registration_path?(conn) do - Enum.any?(conn.private.phoenix_router.__routes__(), fn route -> - route.plug == RegistrationController and route.plug_opts == :create - end) - end + defp registration_path?(conn) do + Enum.any?(conn.private.phoenix_router.__routes__(), fn route -> + route.plug == RegistrationController and route.plug_opts == :create + end) + end else - defp registration_path?(conn) do - [conn.private.phoenix_router, Helpers] - |> Module.concat() - |> function_exported?(:pow_assent_registration_path, 3) - end + defp registration_path?(conn) do + [conn.private.phoenix_router, Helpers] + |> Module.concat() + |> function_exported?(:pow_assent_registration_path, 3) + end end - defp load_user_by_invitation_token(%{private: %{pow_assent_session: %{invitation_token: token}}} = conn, _opts) do + defp load_user_by_invitation_token( + %{private: %{pow_assent_session: %{invitation_token: token}}} = conn, + _opts + ) do conn = Plug.delete_session(conn, :invitation_token) conn @@ -220,6 +300,7 @@ defmodule PowAssent.Phoenix.AuthorizationController do PowPlug.assign_current_user(conn, user, config) end end + defp load_user_by_invitation_token(conn, _opts), do: conn defp handle_strategy_error(conn, error) do @@ -230,7 +311,8 @@ defmodule PowAssent.Phoenix.AuthorizationController do |> 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_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}") diff --git a/lib/pow_assent/phoenix/controllers/reauthorization_plug_handler.ex b/lib/pow_assent/phoenix/controllers/reauthorization_plug_handler.ex index f710b6c..f0fca98 100644 --- a/lib/pow_assent/phoenix/controllers/reauthorization_plug_handler.ex +++ b/lib/pow_assent/phoenix/controllers/reauthorization_plug_handler.ex @@ -29,10 +29,15 @@ defmodule PowAssent.Phoenix.ReauthorizationPlugHandler do defp request?(%{method: method, request_path: path}, method, expected_path), do: compare_paths(path, expected_path) + defp request?(_conn, _method, _expected_path), do: false - defp compare_paths(path_1, path_2) when is_binary(path_1), do: compare_paths(URI.parse(path_1), path_2) - defp compare_paths(path_1, path_2) when is_binary(path_2), do: compare_paths(path_1, URI.parse(path_2)) + defp compare_paths(path_1, path_2) when is_binary(path_1), + do: compare_paths(URI.parse(path_1), path_2) + + defp compare_paths(path_1, path_2) when is_binary(path_2), + do: compare_paths(path_1, URI.parse(path_2)) + defp compare_paths(%{path: path}, %{path: path}), do: true defp compare_paths(_, _), do: false @@ -50,7 +55,15 @@ defmodule PowAssent.Phoenix.ReauthorizationPlugHandler do check_conn!(conn, config) params = Map.take(conn.params, ["request_path"]) - path = AuthorizationController.routes(conn).path_for(conn, AuthorizationController, :new, [provider], params) + + path = + AuthorizationController.routes(conn).path_for( + conn, + AuthorizationController, + :new, + [provider], + params + ) Controller.redirect(conn, to: path) end @@ -74,6 +87,8 @@ defmodule PowAssent.Phoenix.ReauthorizationPlugHandler do @spec raise_missing_phoenix_router(Config.t()) :: no_return() defp raise_missing_phoenix_router(config) do - Config.raise_error("Please use #{inspect config[:reauthorization_plug]} plug in your Phoenix router rather than endpoint when used with the #{inspect __MODULE__} handler.") + Config.raise_error( + "Please use #{inspect(config[:reauthorization_plug])} plug in your Phoenix router rather than endpoint when used with the #{inspect(__MODULE__)} handler." + ) end end diff --git a/lib/pow_assent/phoenix/controllers/registration_controller.ex b/lib/pow_assent/phoenix/controllers/registration_controller.ex index 6ccbbdf..5cbc3ad 100644 --- a/lib/pow_assent/phoenix/controllers/registration_controller.ex +++ b/lib/pow_assent/phoenix/controllers/registration_controller.ex @@ -8,10 +8,10 @@ defmodule PowAssent.Phoenix.RegistrationController do alias PowAssent.Plug alias PowEmailConfirmation.Phoenix.ControllerCallbacks, as: EmailConfirmationCallbacks - plug :init_session - plug :load_params_from_session - plug :assign_changeset when action in [:add_user_id] - plug :assign_create_path + plug(:init_session) + plug(:load_params_from_session) + plug(:assign_changeset when action in [:add_user_id]) + plug(:assign_create_path) @spec process_add_user_id(Conn.t(), map()) :: {:ok, map(), Conn.t()} def process_add_user_id(conn, _params) do @@ -22,7 +22,7 @@ defmodule PowAssent.Phoenix.RegistrationController do def respond_add_user_id({:ok, changeset, conn}), do: render_add_user_id(conn, changeset) defp render_add_user_id(conn, changeset) do - params = Map.fetch!(conn.private, :pow_assent_callback_params) + params = Map.fetch!(conn.private, :pow_assent_callback_params) provider = Map.fetch!(conn.params, "provider") conn @@ -32,7 +32,14 @@ defmodule PowAssent.Phoenix.RegistrationController do end @spec process_create(Conn.t(), map()) :: {:ok, map(), Conn.t()} | {:error, map(), Conn.t()} - def process_create(%{private: %{pow_assent_callback_params: %{user_identity: user_identity_params, user: user_params}}} = conn, %{"user" => user_id_params}) do + def process_create( + %{ + private: %{ + pow_assent_callback_params: %{user_identity: user_identity_params, user: user_params} + } + } = conn, + %{"user" => user_id_params} + ) do Plug.create_user(conn, user_identity_params, user_params, user_id_params) end @@ -46,14 +53,20 @@ defmodule PowAssent.Phoenix.RegistrationController do |> redirect(to: routes(conn).after_registration_path(conn)) end) end + def respond_create({:error, {:bound_to_different_user, _changeset}, conn}) do conn |> put_flash(:error, extension_messages(conn).account_already_bound_to_other_user(conn)) |> redirect(to: routes(conn).registration_path(conn, :new)) end + def respond_create({:error, {:invalid_user_id_field, changeset}, conn}) do - maybe_trigger_email_confirmed_controller_callback({:error, changeset, conn}, &respond_create/1) + maybe_trigger_email_confirmed_controller_callback( + {:error, changeset, conn}, + &respond_create/1 + ) end + def respond_create({:error, changeset, conn}), do: render_add_user_id(conn, changeset) defp maybe_trigger_email_confirmed_controller_callback({:ok, _user, conn} = resp, callback) do @@ -61,22 +74,35 @@ defmodule PowAssent.Phoenix.RegistrationController do maybe_trigger_email_confirmed_controller_callback(resp, callback, config) end - defp maybe_trigger_email_confirmed_controller_callback({:error, _changeset, conn} = resp, callback) do + + defp maybe_trigger_email_confirmed_controller_callback( + {:error, _changeset, conn} = resp, + callback + ) do config = PowPlug.fetch_config(conn) maybe_trigger_email_confirmed_controller_callback(resp, callback, config) end + defp maybe_trigger_email_confirmed_controller_callback(resp, callback, config) do config |> ExtensionConfig.extensions() |> Enum.member?(PowEmailConfirmation) |> case do - true -> EmailConfirmationCallbacks.before_respond(Pow.Phoenix.RegistrationController, :create, resp, config) - false -> resp + true -> + EmailConfirmationCallbacks.before_respond( + Pow.Phoenix.RegistrationController, + :create, + resp, + config + ) + + false -> + resp end |> case do {:halt, conn} -> conn - resp -> callback.(resp) + resp -> callback.(resp) end end @@ -102,6 +128,7 @@ defmodule PowAssent.Phoenix.RegistrationController do |> assign(:changeset, changeset) |> Plug.delete_session(:changeset) end + defp assign_changeset(conn, _opts), do: conn defp assign_create_path(conn, _opts) do diff --git a/lib/pow_assent/phoenix/controllers/registration_html.ex b/lib/pow_assent/phoenix/controllers/registration_html.ex index 6d4839e..07d0c37 100644 --- a/lib/pow_assent/phoenix/controllers/registration_html.ex +++ b/lib/pow_assent/phoenix/controllers/registration_html.ex @@ -4,35 +4,33 @@ defmodule PowAssent.Phoenix.RegistrationHTML do # 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 - template :add_user_id, :html, - """ -
- <.header class="text-center"> - Register - + if !Pow.dependency_vsn_match?(:phoenix, "< 1.7.0") do + template(:add_user_id, :html, """ +
+ <.header class="text-center"> + Register + - <.simple_form :let={f} for={<%= "@changeset" %>} as={:user} action={<%= "@action" %>} phx-update="ignore"> - <.error :if={<%= "@changeset.action" %>}>Oops, something went wrong! Please check the errors below. - <.input field={<%= "f[\#{__user_id_field__("@changeset", :key)}]" %>} type={<%= __user_id_field__("@changeset", :type) %>} label={<%= __user_id_field__("@changeset", :label) %>} required /> + <.simple_form :let={f} for={<%= "@changeset" %>} as={:user} action={<%= "@action" %>} phx-update="ignore"> + <.error :if={<%= "@changeset.action" %>}>Oops, something went wrong! Please check the errors below. + <.input field={<%= "f[\#{__user_id_field__("@changeset", :key)}]" %>} type={<%= __user_id_field__("@changeset", :type) %>} label={<%= __user_id_field__("@changeset", :label) %>} required /> - <:actions> - <.button phx-disable-with="Registering..." class="w-full"> - Register - - - -
- """ + <:actions> + <.button phx-disable-with="Registering..." class="w-full"> + Register + + + +
+ """) else - # TODO: Remove when Phoenix 1.7 required - template :add_user_id, :html, - """ -

Register

+ # TODO: Remove when Phoenix 1.7 required + template(:add_user_id, :html, """ +

Register

- <%= render_form([ - {:text, {:changeset, :pow_user_id_field}} - ]) %> - """ + <%= render_form([ + {:text, {:changeset, :pow_user_id_field}} + ]) %> + """) end end diff --git a/lib/pow_assent/phoenix/controllers/view_helpers.ex b/lib/pow_assent/phoenix/controllers/view_helpers.ex index b7d9076..894405a 100644 --- a/lib/pow_assent/phoenix/controllers/view_helpers.ex +++ b/lib/pow_assent/phoenix/controllers/view_helpers.ex @@ -1,49 +1,75 @@ # 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 false - alias PowAssent.Plug - - alias Phoenix.{HTML, HTML.Link} - alias PowAssent.Phoenix.AuthorizationController - - @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) - end + defmodule PowAssent.Phoenix.ViewHelpers do + @moduledoc false + alias PowAssent.Plug - @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) + alias Phoenix.{HTML, HTML.Link} + alias PowAssent.Phoenix.AuthorizationController - 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) + @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) - Link.link(msg, opts) - end + 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) + end + + @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) + + 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) - defp invitation_token_query_params(%{assigns: %{invited_user: %{invitation_token: token}}}), do: [invitation_token: token] - defp invitation_token_query_params(_conn), do: [] + Link.link(msg, opts) + end - defp request_path_query_params(%{assigns: %{request_path: request_path}}), do: [request_path: request_path] - defp request_path_query_params(_conn), do: [] + defp invitation_token_query_params(%{assigns: %{invited_user: %{invitation_token: token}}}), + do: [invitation_token: token] - @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}}) - path = AuthorizationController.routes(conn).path_for(conn, AuthorizationController, :delete, [provider]) - opts = Keyword.merge(opts, to: path, method: :delete) + defp invitation_token_query_params(_conn), do: [] - Link.link(msg, opts) + defp request_path_query_params(%{assigns: %{request_path: request_path}}), + do: [request_path: request_path] + + defp request_path_query_params(_conn), do: [] + + @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} + }) + + path = + AuthorizationController.routes(conn).path_for(conn, AuthorizationController, :delete, [ + provider + ]) + + opts = Keyword.merge(opts, to: path, method: :delete) + + Link.link(msg, opts) + end end end -end diff --git a/lib/pow_assent/phoenix/html/core_components.ex b/lib/pow_assent/phoenix/html/core_components.ex index 7f577bf..69cc232 100644 --- a/lib/pow_assent/phoenix/html/core_components.ex +++ b/lib/pow_assent/phoenix/html/core_components.ex @@ -1,162 +1,193 @@ # 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 +if !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} + 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> - - """ - 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) %> - <.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) %> - <% end %> + <:deauthorization_link class="text-red-500"> + Remove <%= @provider %> authentication + <:/deauthorization_link> + """ - 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 - """ - 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) %> + <.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) %> + <% 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 + """ + 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) %> - """ - 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) %> + """ + 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 - """ - 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) %> + <.deauthorization_link conn={@conn} provider="github">Remove Github authentication """ + 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) %> + """ + end end end -end diff --git a/lib/pow_assent/phoenix/messages.ex b/lib/pow_assent/phoenix/messages.ex index 08cdc09..fd6c881 100644 --- a/lib/pow_assent/phoenix/messages.ex +++ b/lib/pow_assent/phoenix/messages.ex @@ -53,13 +53,19 @@ defmodule PowAssent.Phoenix.Messages do Message for when provider account already exists for another user. """ def account_already_bound_to_other_user(conn), - do: interpolate("The %{provider} account is already bound to another user.", provider: Naming.humanize(conn.params["provider"])) + do: + interpolate("The %{provider} account is already bound to another user.", + provider: Naming.humanize(conn.params["provider"]) + ) @doc """ Message for when provider identity has been deleted for user. """ def authentication_has_been_removed(conn), - do: interpolate("Authentication with %{provider} has been removed", provider: Naming.humanize(conn.params["provider"])) + do: + interpolate("Authentication with %{provider} has been removed", + provider: Naming.humanize(conn.params["provider"]) + ) @doc """ Message for when user password is required to delete provider identity. @@ -77,13 +83,17 @@ defmodule PowAssent.Phoenix.Messages do """ # 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"])) + do: + interpolate("Sign in with %{provider}", provider: Naming.humanize(conn.params["provider"])) @doc """ Message for provider identity deletion button. """ def remove_provider_authentication(conn), - do: interpolate("Remove %{provider} authentication", provider: Naming.humanize(conn.params["provider"])) + do: + interpolate("Remove %{provider} authentication", + provider: Naming.humanize(conn.params["provider"]) + ) # Simple mock method for interpolations defp interpolate(msg, opts) do @@ -91,7 +101,7 @@ defmodule PowAssent.Phoenix.Messages do token = "%{#{key}}" case String.contains?(msg, token) do - true -> String.replace(msg, token, to_string(value), global: false) + true -> String.replace(msg, token, to_string(value), global: false) false -> msg end end) diff --git a/lib/pow_assent/phoenix/router.ex b/lib/pow_assent/phoenix/router.ex index c3b92ed..ca28300 100644 --- a/lib/pow_assent/phoenix/router.ex +++ b/lib/pow_assent/phoenix/router.ex @@ -43,7 +43,14 @@ defmodule PowAssent.Phoenix.Router do """ defmacro __using__(_opts \\ []) do quote do - import unquote(__MODULE__), only: [pow_assent_routes: 0, pow_assent_authorization_routes: 0, pow_assent_authorization_post_callback_routes: 0, pow_assent_registration_routes: 0, pow_assent_scope: 1] + import unquote(__MODULE__), + only: [ + pow_assent_routes: 0, + pow_assent_authorization_routes: 0, + pow_assent_authorization_post_callback_routes: 0, + pow_assent_registration_routes: 0, + pow_assent_scope: 1 + ] end end @@ -71,8 +78,12 @@ defmodule PowAssent.Phoenix.Router do defmacro pow_assent_authorization_routes do quote location: :keep do pow_assent_scope do - Router.pow_resources "/:provider", AuthorizationController, singleton: true, only: [:new, :delete] - Router.pow_route :get, "/:provider/callback", AuthorizationController, :callback + Router.pow_resources("/:provider", AuthorizationController, + singleton: true, + only: [:new, :delete] + ) + + Router.pow_route(:get, "/:provider/callback", AuthorizationController, :callback) end end end @@ -82,7 +93,7 @@ defmodule PowAssent.Phoenix.Router do quote location: :keep do pow_assent_scope do scope "/", as: "post" do - Router.pow_route :post, "/:provider/callback", AuthorizationController, :callback + Router.pow_route(:post, "/:provider/callback", AuthorizationController, :callback) end end end @@ -92,8 +103,8 @@ defmodule PowAssent.Phoenix.Router do defmacro pow_assent_registration_routes do quote location: :keep do pow_assent_scope do - Router.pow_route :get, "/:provider/add-user-id", RegistrationController, :add_user_id - Router.pow_route :post, "/:provider/create", RegistrationController, :create + Router.pow_route(:get, "/:provider/add-user-id", RegistrationController, :add_user_id) + Router.pow_route(:post, "/:provider/create", RegistrationController, :create) end end end diff --git a/lib/pow_assent/plug.ex b/lib/pow_assent/plug.ex index 3f20a46..890bfa5 100644 --- a/lib/pow_assent/plug.ex +++ b/lib/pow_assent/plug.ex @@ -29,7 +29,8 @@ defmodule PowAssent.Plug do If `:nonce` is set to `true` in the PowAssent provider configuration, a randomly generated nonce will be added to the provider configuration. """ - @spec authorize_url(Conn.t(), binary(), binary()) :: {:ok, binary(), Conn.t()} | {:error, any(), Conn.t()} + @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) @@ -55,6 +56,7 @@ defmodule PowAssent.Plug do 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} @@ -91,7 +93,8 @@ defmodule PowAssent.Plug do to `{:error, :create_user}` with `nil` value for `:pow_assent_callback_error`. """ - @spec callback_upsert(Conn.t(), binary(), map(), binary()) :: {:ok, Conn.t()} | {:error, Conn.t()} + @spec callback_upsert(Conn.t(), binary(), map(), binary()) :: + {:ok, Conn.t()} | {:error, Conn.t()} def callback_upsert(conn, provider, params, redirect_uri) do conn |> callback(provider, params, redirect_uri) @@ -111,15 +114,26 @@ defmodule PowAssent.Plug do defp handle_callback({:ok, user_identity_params, user_params, conn}) do conn |> Conn.put_private(:pow_assent_callback_state, {:ok, :strategy}) - |> Conn.put_private(:pow_assent_callback_params, %{user_identity: user_identity_params, user: user_params}) + |> Conn.put_private(:pow_assent_callback_params, %{ + user_identity: user_identity_params, + user: user_params + }) end - defp handle_callback({:error, error, conn}) do + + defp handle_callback({:error, error, conn}) do conn |> Conn.put_private(:pow_assent_callback_state, {:error, :strategy}) |> Conn.put_private(:pow_assent_callback_error, error) end - defp maybe_authenticate(%{private: %{pow_assent_callback_state: {:ok, :strategy}, pow_assent_callback_params: params}} = conn) do + defp maybe_authenticate( + %{ + private: %{ + pow_assent_callback_state: {:ok, :strategy}, + pow_assent_callback_params: params + } + } = conn + ) do user_identity_params = Map.fetch!(params, :user_identity) case Plug.current_user(conn) do @@ -127,7 +141,7 @@ defmodule PowAssent.Plug do conn |> authenticate(user_identity_params) |> case do - {:ok, conn} -> conn + {:ok, conn} -> conn {:error, conn} -> conn end @@ -135,9 +149,17 @@ defmodule PowAssent.Plug do conn end end + defp maybe_authenticate(conn), do: conn - defp maybe_upsert_user_identity(%{private: %{pow_assent_callback_state: {:ok, :strategy}, pow_assent_callback_params: params}} = conn) do + defp maybe_upsert_user_identity( + %{ + private: %{ + pow_assent_callback_state: {:ok, :strategy}, + pow_assent_callback_params: params + } + } = conn + ) do user_identity_params = Map.fetch!(params, :user_identity) case Plug.current_user(conn) do @@ -158,6 +180,7 @@ defmodule PowAssent.Plug do end end end + defp maybe_upsert_user_identity(conn), do: conn defp maybe_create_user(conn) do @@ -171,8 +194,17 @@ defmodule PowAssent.Plug do |> Conn.put_private(:pow_assent_callback_state, {:error, :create_user}) |> Conn.put_private(:pow_assent_callback_error, nil) end - defp maybe_create_user(nil, %{private: %{pow_assent_callback_state: {:ok, :strategy}, pow_assent_callback_params: params}} = conn) do - user_params = Map.fetch!(params, :user) + + defp maybe_create_user( + nil, + %{ + private: %{ + pow_assent_callback_state: {:ok, :strategy}, + pow_assent_callback_params: params + } + } = conn + ) do + user_params = Map.fetch!(params, :user) user_identity_params = Map.fetch!(params, :user_identity) conn @@ -187,6 +219,7 @@ defmodule PowAssent.Plug do |> Conn.put_private(:pow_assent_callback_error, changeset) end end + defp maybe_create_user(_user, conn), do: conn @doc """ @@ -197,7 +230,8 @@ defmodule PowAssent.Plug do `: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()} + @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) @@ -207,7 +241,9 @@ defmodule PowAssent.Plug do |> 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, %{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 @@ -221,6 +257,7 @@ defmodule PowAssent.Plug do |> 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 @@ -228,6 +265,7 @@ defmodule PowAssent.Plug do |> Map.delete("preferred_username") |> Map.put("username", username) end + defp normalize_username(params), do: params defp split_user_identity_params(%{"sub" => uid} = params) do @@ -236,9 +274,14 @@ defmodule PowAssent.Plug do {%{"uid" => uid}, user_params} end - defp handle_user_identity_params({user_identity_params, user_params}, other_params, provider, conn) do + 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} + other_params = for {key, value} <- other_params, into: %{}, do: {Atom.to_string(key), value} user_identity_params = user_identity_params @@ -261,12 +304,14 @@ defmodule PowAssent.Plug do provider |> Operations.get_user_by_provider_uid(uid, config) |> case do - nil -> {:error, conn} + nil -> {:error, conn} user -> {:ok, create_session(conn, user, provider, config)} end end - defp create_session(conn, user, %{"provider" => provider}, config), do: create_session(conn, user, provider, config) + defp create_session(conn, user, %{"provider" => provider}, config), + do: create_session(conn, user, provider, config) + defp create_session(conn, user, provider, config) when is_binary(provider) do conn = Plug.create(conn, user) @@ -297,7 +342,8 @@ defmodule PowAssent.Plug do # 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()} + @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 """ @@ -306,16 +352,20 @@ defmodule PowAssent.Plug do If successful, a new session will be created. After session has been created the callbacks stored with `put_create_session_callback/2` will run. """ - @spec upsert_identity(Conn.t(), map()) :: {:ok, map(), Conn.t()} | {:error, {:bound_to_different_user, map()} | map(), Conn.t()} + @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 = Plug.current_user(conn) + user = Plug.current_user(conn) user |> Operations.upsert(user_identity_params, config) |> case do - {:ok, user_identity} -> {:ok, user_identity, create_session(conn, user, user_identity_params, config)} - {:error, error} -> {:error, error, conn} + {:ok, user_identity} -> + {:ok, user_identity, create_session(conn, user, user_identity_params, config)} + + {:error, error} -> + {:error, error, conn} end end @@ -325,14 +375,16 @@ defmodule PowAssent.Plug do If successful, a new session will be created. After session has been created the callbacks stored with `put_create_session_callback/2` will run. """ - @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()} + @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, create_session(conn, user, user_identity_params, config)} + {:ok, user} -> {:ok, user, create_session(conn, user, user_identity_params, config)} {:error, error} -> {:error, error, conn} end end @@ -350,7 +402,8 @@ defmodule PowAssent.Plug do @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()} + @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) @@ -358,7 +411,7 @@ defmodule PowAssent.Plug do |> Plug.current_user() |> Operations.delete(provider, config) |> case do - {:ok, results} -> {:ok, results, conn} + {:ok, results} -> {:ok, results, conn} {:error, error} -> {:error, error, conn} end end @@ -388,6 +441,7 @@ defmodule PowAssent.Plug do |> fetch_config() |> available_providers() end + def available_providers(config) do config |> Config.get_providers() @@ -413,10 +467,12 @@ defmodule PowAssent.Plug do |> fetch_config() |> get_provider_config(provider, redirect_uri) end + defp get_provider_config(config, provider, redirect_uri) do - provider = provider_to_atom!(provider) - config = Config.get_provider_config(config, provider) - strategy = config[:strategy] + provider = provider_to_atom!(provider) + config = Config.get_provider_config(config, provider) + strategy = config[:strategy] + provider_config = config |> Keyword.delete(:strategy) @@ -430,6 +486,7 @@ defmodule PowAssent.Plug do rescue ArgumentError -> Config.raise_no_provider_config_error(provider) end + defp provider_to_atom!(provider) when is_atom(provider), do: provider @cookie_key "auth_session" @@ -468,22 +525,22 @@ defmodule PowAssent.Plug do """ @spec init_session(Conn.t()) :: Conn.t() def init_session(conn) do - config = fetch_config(conn) - pow_config = Plug.fetch_config(conn) + config = fetch_config(conn) + pow_config = Plug.fetch_config(conn) {key, conn} = client_store_fetch(conn, config, pow_config) - value = get_session_value(key, config, pow_config) || default_value(conn) + value = get_session_value(key, config, pow_config) || default_value(conn) conn |> maybe_client_store_delete(config) |> Conn.put_private(@private_session_key, value) - |> Conn.register_before_send(& put_session_value(&1, config, pow_config)) + |> Conn.register_before_send(&put_session_value(&1, config, pow_config)) end defp client_store_fetch(conn, config, pow_config) do conn = Conn.fetch_cookies(conn) with token when is_binary(token) <- conn.cookies[cookie_key(config)], - {:ok, token} <- Plug.verify_token(conn, signing_salt(), token, pow_config) do + {:ok, token} <- Plug.verify_token(conn, signing_salt(), token, pow_config) do {token, conn} else _any -> {nil, conn} @@ -501,6 +558,7 @@ defmodule PowAssent.Plug do end defp get_session_value(nil, _config, _pow_config), do: nil + defp get_session_value(key, config, pow_config) do {store, store_config} = store(config, pow_config) @@ -517,7 +575,7 @@ defmodule PowAssent.Plug do defp store(config, pow_config) do case Config.get(config, :session_store, default_store(pow_config)) do {store, store_config} -> {store, store_config} - store -> {store, []} + store -> {store, []} end end @@ -534,7 +592,7 @@ defmodule PowAssent.Plug do conn = Conn.fetch_cookies(conn) case Map.has_key?(conn.cookies, cookie_key(config)) do - true -> client_store_delete(conn, config) + true -> client_store_delete(conn, config) false -> conn end end @@ -551,14 +609,21 @@ defmodule PowAssent.Plug do |> Keyword.put_new(:path, "/") end - defp put_session_value(%{private: %{@private_session_info_key => :write, @private_session_key => session}} = conn, config, pow_config) when session != %{} do + defp put_session_value( + %{private: %{@private_session_info_key => :write, @private_session_key => session}} = + conn, + config, + pow_config + ) + when session != %{} do {store, store_config} = store(config, pow_config) - key = UUID.generate() + key = UUID.generate() store.put(store_config, key, session) client_store_put(conn, key, config, pow_config) end + defp put_session_value(conn, _config, _pow_config), do: conn defp client_store_put(conn, token, config, pow_config) do @@ -602,7 +667,7 @@ defmodule PowAssent.Plug do @spec merge_provider_config(Conn.t(), binary() | atom(), Keyword.t()) :: Conn.t() def merge_provider_config(conn, provider, new_config) do pow_config = Pow.Plug.fetch_config(conn) - provider = provider_to_atom!(provider) + provider = provider_to_atom!(provider) pow_assent_config = conn diff --git a/lib/pow_assent/plug/reauthorization.ex b/lib/pow_assent/plug/reauthorization.ex index 6f3bb52..df94008 100644 --- a/lib/pow_assent/plug/reauthorization.ex +++ b/lib/pow_assent/plug/reauthorization.ex @@ -48,7 +48,7 @@ defmodule PowAssent.Plug.Reauthorization do @spec init(Config.t()) :: {Config.t(), {module(), Config.t()}} def init(config) do handler = get_handler(config) - config = Keyword.delete(config, :handler) + config = Keyword.delete(config, :handler) {config, handler} end @@ -60,7 +60,7 @@ defmodule PowAssent.Plug.Reauthorization do |> Kernel.||(raise_no_handler()) |> case do {handler, config} -> {handler, config} - handler -> {handler, []} + handler -> {handler, []} end {handler, Keyword.put(config, :reauthorization_plug, __MODULE__)} @@ -88,7 +88,7 @@ defmodule PowAssent.Plug.Reauthorization do |> handler.reauthorize(provider, handler_config) |> Conn.halt() - clear_reauthorization?(conn, {handler, handler_config}) -> + clear_reauthorization?(conn, {handler, handler_config}) -> clear_cookie(conn, config) true -> @@ -97,7 +97,10 @@ defmodule PowAssent.Plug.Reauthorization do end defp store_reauthorization_provider(conn, provider, config) do - Conn.register_before_send(conn, &Conn.put_resp_cookie(&1, cookie_key(config), provider, cookie_opts(config))) + Conn.register_before_send( + conn, + &Conn.put_resp_cookie(&1, cookie_key(config), provider, cookie_opts(config)) + ) end defp cookie_key(config) do @@ -116,7 +119,7 @@ defmodule PowAssent.Plug.Reauthorization do end defp get_reauthorization_provider(conn, {handler, handler_config}, config) do - with :ok <- check_should_reauthorize(conn, {handler, handler_config}), + with :ok <- check_should_reauthorize(conn, {handler, handler_config}), {:ok, provider} <- fetch_provider_from_cookie(conn, config) do provider else @@ -126,7 +129,7 @@ defmodule PowAssent.Plug.Reauthorization do defp check_should_reauthorize(conn, {handler, handler_config}) do case handler.reauthorize?(conn, handler_config) do - true -> :ok + true -> :ok false -> :error end end @@ -139,9 +142,9 @@ defmodule PowAssent.Plug.Reauthorization do provider -> config |> Plug.available_providers() - |> Enum.any?(&Atom.to_string(&1) == provider) + |> Enum.any?(&(Atom.to_string(&1) == provider)) |> case do - true -> {:ok, provider} + true -> {:ok, provider} false -> :error end end @@ -156,6 +159,8 @@ defmodule PowAssent.Plug.Reauthorization do @spec raise_no_handler :: no_return defp raise_no_handler do - Config.raise_error("No :handler configuration option provided. It's required to set this when using #{inspect __MODULE__}.") + Config.raise_error( + "No :handler configuration option provided. It's required to set this when using #{inspect(__MODULE__)}." + ) end end diff --git a/mix.exs b/mix.exs index 85fe817..2684a55 100644 --- a/mix.exs +++ b/mix.exs @@ -33,19 +33,15 @@ defmodule PowAssent.MixProject do [ {:pow, "~> 1.0.29"}, {:assent, "~> 0.2.8"}, - {:ecto, "~> 2.2 or ~> 3.0"}, {:phoenix, ">= 1.3.0 and < 1.8.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}, - {:phoenix_ecto, "~> 4.0", only: [:dev, :test]}, {:credo, "~> 1.1", only: [:dev, :test]}, {:jason, "~> 1.0", only: [:dev, :test]}, - {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, - {:ecto_sql, "~> 3.1", only: :test}, {:postgrex, "~> 0.14", only: :test}, {:floki, ">= 0.30.0", only: :test}, diff --git a/test/mix/tasks/ecto/pow_assent.ecto.gen.migration_test.exs b/test/mix/tasks/ecto/pow_assent.ecto.gen.migration_test.exs index b3b6439..a762420 100644 --- a/test/mix/tasks/ecto/pow_assent.ecto.gen.migration_test.exs +++ b/test/mix/tasks/ecto/pow_assent.ecto.gen.migration_test.exs @@ -34,9 +34,11 @@ defmodule Mix.Tasks.PowAssent.Ecto.Gen.MigrationTest do File.cd!(context.tmp_path, fn -> Migration.run(context.options) - assert_raise Mix.Error, "migration can't be created, there is already a migration file with name CreateUserIdentities.", fn -> - Migration.run(context.options) - end + assert_raise Mix.Error, + "migration can't be created, there is already a migration file with name CreateUserIdentities.", + fn -> + Migration.run(context.options) + end end) end diff --git a/test/mix/tasks/ecto/pow_assent.ecto.gen.schema_test.exs b/test/mix/tasks/ecto/pow_assent.ecto.gen.schema_test.exs index 04cb92c..4f1e580 100644 --- a/test/mix/tasks/ecto/pow_assent.ecto.gen.schema_test.exs +++ b/test/mix/tasks/ecto/pow_assent.ecto.gen.schema_test.exs @@ -38,9 +38,11 @@ defmodule Mix.Tasks.PowAssent.Ecto.Gen.SchemaTest do File.cd!(context.tmp_path, fn -> Schema.run([]) - assert_raise Mix.Error, "schema file can't be created, there is already a schema file in lib/pow_assent/user_identities/user_identity.ex.", fn -> - Schema.run([]) - end + assert_raise Mix.Error, + "schema file can't be created, there is already a schema file in lib/pow_assent/user_identities/user_identity.ex.", + fn -> + Schema.run([]) + end end) end end diff --git a/test/mix/tasks/ecto/pow_assent.ecto.install_test.exs b/test/mix/tasks/ecto/pow_assent.ecto.install_test.exs index 6a13a99..6f81379 100644 --- a/test/mix/tasks/ecto/pow_assent.ecto.install_test.exs +++ b/test/mix/tasks/ecto/pow_assent.ecto.install_test.exs @@ -38,9 +38,11 @@ defmodule Mix.Tasks.PowAssent.Ecto.InstallTest do File.cd!(context.tmp_path, fn -> File.rm_rf!(context.paths.user_path) - assert_raise Mix.Error, "Couldn't install PowAssent! Did you run this inside your Ecto app?", fn -> - Install.run(context.options) - end + assert_raise Mix.Error, + "Couldn't install PowAssent! Did you run this inside your Ecto app?", + fn -> + Install.run(context.options) + end assert_received {:mix_shell, :error, ["Could not find the following file(s)" <> msg]} assert msg =~ context.paths.user_path @@ -57,7 +59,10 @@ defmodule Mix.Tasks.PowAssent.Ecto.InstallTest do assert msg =~ context.paths.user_path assert_received {:mix_shell, :info, ["To complete please do the following:" <> msg]} - assert msg =~ "Add the `PowAssent.Ecto.Schema` macro to lib/pow_assent/users/user.ex after `use Pow.Ecto.Schema`:" + + assert msg =~ + "Add the `PowAssent.Ecto.Schema` macro to lib/pow_assent/users/user.ex after `use Pow.Ecto.Schema`:" + assert msg =~ "use PowAssent.Ecto.Schema" end) end @@ -91,7 +96,9 @@ defmodule Mix.Tasks.PowAssent.Ecto.InstallTest do end end """) + File.mkdir!("ecto_dep") + File.write!("ecto_dep/mix.exs", """ defmodule EctoDep.MixProject do use Mix.Project @@ -108,13 +115,16 @@ defmodule Mix.Tasks.PowAssent.Ecto.InstallTest do Mix.Project.in_project(:missing_top_level_ecto_dep, ".", fn _ -> # Insurance that we do test for top level ecto inclusion assert Enum.any?(Mix.Pow.__dependencies__([]), fn - %{app: :ecto_sql} -> true - _ -> false - end), "Ecto not loaded by dependency" - - assert_raise Mix.Error, "mix pow_assent.ecto.install can only be run inside an application directory that has :ecto or :ecto_sql as dependency", fn -> - Install.run([]) - end + %{app: :ecto_sql} -> true + _ -> false + end), + "Ecto not loaded by dependency" + + assert_raise Mix.Error, + "mix pow_assent.ecto.install can only be run inside an application directory that has :ecto or :ecto_sql as dependency", + fn -> + Install.run([]) + end end) end) end diff --git a/test/mix/tasks/phoenix/pow_assent.phoenix.gen.templates_test.exs b/test/mix/tasks/phoenix/pow_assent.phoenix.gen.templates_test.exs index 30fdc33..cade895 100644 --- a/test/mix/tasks/phoenix/pow_assent.phoenix.gen.templates_test.exs +++ b/test/mix/tasks/phoenix/pow_assent.phoenix.gen.templates_test.exs @@ -12,7 +12,7 @@ defmodule Mix.Tasks.PowAssent.Phoenix.Gen.TemplatesTest do Templates.run([]) templates_path = Path.join(["lib", "pow_assent_web", "controllers", "pow_assent"]) - expected_dirs = Map.keys(@expected_template_files) + expected_dirs = Map.keys(@expected_template_files) expected_files = Enum.map(expected_dirs, &"#{&1}.ex") assert expected_dirs -- ls(templates_path) == [] @@ -25,7 +25,7 @@ defmodule Mix.Tasks.PowAssent.Phoenix.Gen.TemplatesTest do end for base_name <- expected_dirs do - content = templates_path |> Path.join(base_name <> ".ex") |> File.read!() + content = templates_path |> Path.join(base_name <> ".ex") |> File.read!() module_name = base_name |> Macro.camelize() |> String.replace_suffix("Html", "HTML") assert content =~ "defmodule PowAssentWeb.PowAssent.#{module_name} do" diff --git a/test/mix/tasks/phoenix/pow_assent.phoenix.install_test.exs b/test/mix/tasks/phoenix/pow_assent.phoenix.install_test.exs index 17404f8..698a79b 100644 --- a/test/mix/tasks/phoenix/pow_assent.phoenix.install_test.exs +++ b/test/mix/tasks/phoenix/pow_assent.phoenix.install_test.exs @@ -3,7 +3,7 @@ defmodule Mix.Tasks.PowAssent.Phoenix.InstallTest do alias Mix.Tasks.PowAssent.Phoenix.Install - @options [] + @options [] @success_msg "PowAssent has been installed in your Phoenix app!" test "default", context do @@ -67,9 +67,11 @@ defmodule Mix.Tasks.PowAssent.Phoenix.InstallTest do File.cd!(context.tmp_path, fn -> File.rm_rf!(context.paths.router_path) - assert_raise Mix.Error, "Couldn't install PowAssent! Did you run this inside your Phoenix app?", fn -> - Install.run(@options) - end + assert_raise Mix.Error, + "Couldn't install PowAssent! Did you run this inside your Phoenix app?", + fn -> + Install.run(@options) + end assert_received {:mix_shell, :error, ["Could not find the following file(s)" <> msg]} assert msg =~ context.paths.router_path @@ -134,7 +136,9 @@ defmodule Mix.Tasks.PowAssent.Phoenix.InstallTest do end end """) + File.mkdir!("dep") + File.write!("dep/mix.exs", """ defmodule PhoenixDep.MixProject do use Mix.Project @@ -153,13 +157,16 @@ defmodule Mix.Tasks.PowAssent.Phoenix.InstallTest do Mix.Project.in_project(:missing_top_level_phoenix_dep, ".", fn _ -> # Insurance that we do test for top level phoenix inclusion assert Enum.any?(Mix.Pow.__dependencies__([]), fn - %{app: :phoenix} -> true - _ -> false - end), "Phoenix not loaded by dependency" - - assert_raise Mix.Error, "mix pow_assent.phoenix.install can only be run inside an application directory that has :phoenix as dependency", fn -> - Install.run(@options) - end + %{app: :phoenix} -> true + _ -> false + end), + "Phoenix not loaded by dependency" + + assert_raise Mix.Error, + "mix pow_assent.phoenix.install can only be run inside an application directory that has :phoenix as dependency", + fn -> + Install.run(@options) + end end) end) end diff --git a/test/mix/tasks/pow_assent.install_test.exs b/test/mix/tasks/pow_assent.install_test.exs index 613a56a..6b67c93 100644 --- a/test/mix/tasks/pow_assent.install_test.exs +++ b/test/mix/tasks/pow_assent.install_test.exs @@ -32,9 +32,11 @@ defmodule Mix.Tasks.PowAssent.InstallTest do """) Mix.Project.in_project(:umbrella, ".", fn _ -> - assert_raise Mix.Error, ~r/mix pow_assent.install has to be used inside an application directory/, fn -> - Install.run([]) - end + assert_raise Mix.Error, + ~r/mix pow_assent.install has to be used inside an application directory/, + fn -> + Install.run([]) + end end) end) end @@ -45,17 +47,23 @@ defmodule Mix.Tasks.PowAssent.InstallTest do Install.run(~w(UserIdentities.UserIdentity)) end - assert_raise Mix.Error, ~r/Expected the schema argument, "useridentities.useridentity", to be a valid module name/, fn -> - Install.run(~w(useridentities.useridentity useridentities)) - end + assert_raise Mix.Error, + ~r/Expected the schema argument, "useridentities.useridentity", to be a valid module name/, + fn -> + Install.run(~w(useridentities.useridentity useridentities)) + end - assert_raise Mix.Error, ~r/Expected the plural argument, "UserIdentities", to be all lowercase using snake_case convention/, fn -> - Install.run(~w(UserIdentities.UserIdentity UserIdentities)) - end + assert_raise Mix.Error, + ~r/Expected the plural argument, "UserIdentities", to be all lowercase using snake_case convention/, + fn -> + Install.run(~w(UserIdentities.UserIdentity UserIdentities)) + end - assert_raise Mix.Error, ~r/Expected the plural argument, "useridentities:", to be all lowercase using snake_case convention/, fn -> - Install.run(~w(UserIdentities.UserIdentity useridentities:)) - end + assert_raise Mix.Error, + ~r/Expected the plural argument, "useridentities:", to be all lowercase using snake_case convention/, + fn -> + Install.run(~w(UserIdentities.UserIdentity useridentities:)) + end end) end end diff --git a/test/pow_assent/config_test.exs b/test/pow_assent/config_test.exs index 991271e..e03f6f3 100644 --- a/test/pow_assent/config_test.exs +++ b/test/pow_assent/config_test.exs @@ -13,19 +13,19 @@ defmodule PowAssent.ConfigTest do end test "get_providers/1" do - Application.put_env(:pow_assent, :providers, [provider1: [], provider2: []]) + Application.put_env(:pow_assent, :providers, provider1: [], provider2: []) assert Config.get_providers([]) == [provider1: [], provider2: []] end test "merge_provider_config/2" do - Application.put_env(:pow_assent, :providers, [ + Application.put_env(:pow_assent, :providers, provider1: [ a: 1, b: 2, authorization_params: [c: 3, d: 4], strategy: PowAssent.Test.TestProvider ] - ]) + ) new_config = [ a: 2, @@ -49,14 +49,19 @@ defmodule PowAssent.ConfigTest do end test "get_provider_config/2" do - Application.put_env(:pow_assent, :providers, [provider1: [a: 1], provider2: [b: 2]]) + Application.put_env(:pow_assent, :providers, provider1: [a: 1], provider2: [b: 2]) assert Config.get_provider_config([], :provider2) == [b: 2] - assert_raise PowAssent.Config.ConfigError, "No provider configuration available for non_existent.", fn -> - Config.get_provider_config([], :non_existent) - end + assert_raise PowAssent.Config.ConfigError, + "No provider configuration available for non_existent.", + fn -> + Config.get_provider_config([], :non_existent) + end - assert Config.get_provider_config([http_adapter: HTTPAdapter, json_adapter: JSONAdapter, jwt_adapter: JWTAdapter], :provider1) == - [http_adapter: HTTPAdapter, json_adapter: JSONAdapter, jwt_adapter: JWTAdapter, a: 1] + assert Config.get_provider_config( + [http_adapter: HTTPAdapter, json_adapter: JSONAdapter, jwt_adapter: JWTAdapter], + :provider1 + ) == + [http_adapter: HTTPAdapter, json_adapter: JSONAdapter, jwt_adapter: JWTAdapter, a: 1] end end diff --git a/test/pow_assent/ecto/schema_test.exs b/test/pow_assent/ecto/schema_test.exs index 13e2119..5fcf1ee 100644 --- a/test/pow_assent/ecto/schema_test.exs +++ b/test/pow_assent/ecto/schema_test.exs @@ -42,7 +42,14 @@ defmodule PowAssent.Ecto.SchemaTest do assert user_identity.errors[:provider] == {"can't be blank", [validation: :required]} assert changeset.errors[:name] == {"can't be blank", [validation: :required]} - changeset = User.user_identity_changeset(%User{}, @user_identity, %{email: "test@example.com", name: "John Doe"}, nil) + changeset = + User.user_identity_changeset( + %User{}, + @user_identity, + %{email: "test@example.com", name: "John Doe"}, + nil + ) + assert changeset.valid? assert changeset.changes[:name] == "John Doe" end @@ -55,16 +62,26 @@ defmodule PowAssent.Ecto.SchemaTest do |> Repo.insert() assert {:error, changeset} = - %User{email: "john.doe@example.com", name: "John Doe"} - |> User.user_identity_changeset(@user_identity, %{}, nil) - |> Repo.insert() + %User{email: "john.doe@example.com", name: "John Doe"} + |> User.user_identity_changeset(@user_identity, %{}, nil) + |> Repo.insert() assert [user_identity] = changeset.changes.user_identities - assert user_identity.errors[:uid_provider] == {"has already been taken", [constraint: :unique, constraint_name: "user_identities_uid_provider_index"]} + + assert user_identity.errors[:uid_provider] == + {"has already been taken", + [constraint: :unique, constraint_name: "user_identities_uid_provider_index"]} end test "uses case insensitive value for user id" do - changeset = User.user_identity_changeset(%User{}, @user_identity, %{email: "Test@EXAMPLE.com", name: "John Doe"}, nil) + changeset = + User.user_identity_changeset( + %User{}, + @user_identity, + %{email: "Test@EXAMPLE.com", name: "John Doe"}, + nil + ) + assert changeset.valid? assert Ecto.Changeset.get_field(changeset, :email) == "test@example.com" end @@ -74,16 +91,21 @@ defmodule PowAssent.Ecto.SchemaTest do @moduledoc false use Ecto.Schema use Pow.Ecto.Schema, user_id_field: :username + use Pow.Extension.Ecto.Schema, extensions: [PowEmailConfirmation] + use PowAssent.Ecto.Schema @ecto_derive_inspect_for_redacted_fields false schema "users" do - has_many :user_identities, PowAssent.Test.Ecto.UserIdentities.UserIdentity, foreign_key: :user_id, on_delete: :delete_all + has_many(:user_identities, PowAssent.Test.Ecto.UserIdentities.UserIdentity, + foreign_key: :user_id, + on_delete: :delete_all + ) - field :email, :string + field(:email, :string) pow_user_fields() @@ -106,23 +128,51 @@ defmodule PowAssent.Ecto.SchemaTest do refute changeset.changes[:email_confirmed_at] refute changeset.changes[:email_confirmation_token] - changeset = UserConfirmEmail.user_identity_changeset(%UserConfirmEmail{}, @user_identity, provider_params, %{email: "foo@example.com"}) + changeset = + UserConfirmEmail.user_identity_changeset( + %UserConfirmEmail{}, + @user_identity, + provider_params, + %{email: "foo@example.com"} + ) + assert changeset.changes[:email] refute changeset.changes[:email_confirmed_at] assert changeset.changes[:email_confirmation_token] - changeset = UserConfirmEmail.user_identity_changeset(%UserConfirmEmail{}, @user_identity, provider_params, %{email: "test@example.com"}) + changeset = + UserConfirmEmail.user_identity_changeset( + %UserConfirmEmail{}, + @user_identity, + provider_params, + %{email: "test@example.com"} + ) + assert changeset.changes[:email] assert changeset.changes[:email_confirmed_at] refute changeset.changes[:email_confirmation_token] - changeset = UserConfirmEmail.user_identity_changeset(%UserConfirmEmail{}, @user_identity, provider_params, nil) + changeset = + UserConfirmEmail.user_identity_changeset( + %UserConfirmEmail{}, + @user_identity, + provider_params, + nil + ) + assert changeset.changes[:email] assert changeset.changes[:name] == "John Doe" assert changeset.changes[:email_confirmed_at] refute changeset.changes[:email_confirmation_token] - changeset = UserConfirmEmail.user_identity_changeset(%UserConfirmEmail{}, @user_identity, Map.delete(provider_params, :email_verified), nil) + changeset = + UserConfirmEmail.user_identity_changeset( + %UserConfirmEmail{}, + @user_identity, + Map.delete(provider_params, :email_verified), + nil + ) + assert changeset.changes[:email] assert changeset.changes[:name] == "John Doe" refute changeset.changes[:email_confirmed_at] @@ -132,12 +182,26 @@ defmodule PowAssent.Ecto.SchemaTest do test "sets :email_confirmed_at when provided as attrs and :email is not user id field" do provider_params = %{email: "test@example.com", email_verified: true} - changeset = UsernameUserWithEmail.user_identity_changeset(%UsernameUserWithEmail{}, @user_identity, Map.delete(provider_params, :email_verified), %{username: "john.doe"}) + changeset = + UsernameUserWithEmail.user_identity_changeset( + %UsernameUserWithEmail{}, + @user_identity, + Map.delete(provider_params, :email_verified), + %{username: "john.doe"} + ) + assert changeset.changes[:email] refute changeset.changes[:email_confirmed_at] assert changeset.changes[:email_confirmation_token] - changeset = UsernameUserWithEmail.user_identity_changeset(%UsernameUserWithEmail{}, @user_identity, provider_params, %{username: "john.doe"}) + changeset = + UsernameUserWithEmail.user_identity_changeset( + %UsernameUserWithEmail{}, + @user_identity, + provider_params, + %{username: "john.doe"} + ) + assert changeset.changes[:username] assert changeset.changes[:email] assert changeset.changes[:email_confirmed_at] @@ -147,13 +211,31 @@ defmodule PowAssent.Ecto.SchemaTest do describe "user_identity_changeset/4 with PowInvitation" do test "sets :invitation_accepted_at when is invited user" do - changeset = InvitationUser.user_identity_changeset(%InvitationUser{}, @user_identity, %{}, %{email: "test@example.com"}) + changeset = + InvitationUser.user_identity_changeset(%InvitationUser{}, @user_identity, %{}, %{ + email: "test@example.com" + }) + refute changeset.changes[:invitation_accepted_at] - changeset = InvitationUser.user_identity_changeset(%InvitationUser{invitation_token: "token", invitation_accepted_at: DateTime.utc_now()}, @user_identity, %{}, %{email: "test@example.com"}) + changeset = + InvitationUser.user_identity_changeset( + %InvitationUser{invitation_token: "token", invitation_accepted_at: DateTime.utc_now()}, + @user_identity, + %{}, + %{email: "test@example.com"} + ) + refute changeset.changes[:invitation_accepted_at] - changeset = InvitationUser.user_identity_changeset(%InvitationUser{invitation_token: "token"}, @user_identity, %{}, %{email: "test@example.com"}) + changeset = + InvitationUser.user_identity_changeset( + %InvitationUser{invitation_token: "token"}, + @user_identity, + %{}, + %{email: "test@example.com"} + ) + assert changeset.changes[:invitation_accepted_at] end end @@ -167,9 +249,11 @@ defmodule PowAssent.Ecto.SchemaTest do @ecto_derive_inspect_for_redacted_fields false schema "users" do - has_many :user_identities, + has_many( + :user_identities, MyApp.UserIdentities.UserIdentity, on_delete: :nothing + ) pow_user_fields() @@ -188,6 +272,8 @@ defmodule PowAssent.Ecto.SchemaTest do user = %PowAssent.NoContextUser{} assert Map.has_key?(user, :user_identities) - assert %{queryable: PowAssent.UserIdentities.UserIdentity} = PowAssent.NoContextUser.__schema__(:association, :user_identities) + + assert %{queryable: PowAssent.UserIdentities.UserIdentity} = + PowAssent.NoContextUser.__schema__(:association, :user_identities) end end diff --git a/test/pow_assent/ecto/user_identities/context_test.exs b/test/pow_assent/ecto/user_identities/context_test.exs index 2079c07..ef022a0 100644 --- a/test/pow_assent/ecto/user_identities/context_test.exs +++ b/test/pow_assent/ecto/user_identities/context_test.exs @@ -20,7 +20,10 @@ defmodule PowAssent.Test.Ecto.Users.UserWithCustomChangesetUserIdentities do @ecto_derive_inspect_for_redacted_fields false schema "users" do - has_many :user_identities, PowAssent.Test.WithCustomChangeset.UserIdentities.UserIdentity, foreign_key: :user_id, on_delete: :delete_all + has_many(:user_identities, PowAssent.Test.WithCustomChangeset.UserIdentities.UserIdentity, + foreign_key: :user_id, + on_delete: :delete_all + ) pow_user_fields() timestamps() @@ -33,10 +36,21 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do alias Ecto.Changeset alias PowAssent.Ecto.UserIdentities.Context - alias PowAssent.Test.Ecto.{Repo, Users.User, Users.UserWithCustomChangesetUserIdentities, Users.UserWithoutUserIdentities} + + alias PowAssent.Test.Ecto.{ + Repo, + Users.User, + Users.UserWithCustomChangesetUserIdentities, + Users.UserWithoutUserIdentities + } @config [repo: Repo, user: User] - @user_identity_params %{"provider" => "test_provider", "uid" => "1", "token" => %{"access_token" => "access_token"}, "userinfo" => %{"name" => "John Doe"}} + @user_identity_params %{ + "provider" => "test_provider", + "uid" => "1", + "token" => %{"access_token" => "access_token"}, + "userinfo" => %{"name" => "John Doe"} + } @user_identity %{provider: @user_identity_params["provider"], uid: @user_identity_params["uid"]} describe "get_user_by_provider_uid/2" do @@ -62,9 +76,14 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do end test "requires user has :user_identities assoc" do - assert_raise PowAssent.Config.ConfigError, "The `:user` configuration option doesn't have a `:user_identities` association.", fn -> - Context.get_user_by_provider_uid("test_provider", "2", repo: Repo, user: UserWithoutUserIdentities) - end + assert_raise PowAssent.Config.ConfigError, + "The `:user` configuration option doesn't have a `:user_identities` association.", + fn -> + Context.get_user_by_provider_uid("test_provider", "2", + repo: Repo, + user: UserWithoutUserIdentities + ) + end end end @@ -81,7 +100,9 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do end test "inserts with valid params", %{user: user} do - assert {:ok, user_identity} = Context.upsert(user, @user_identity_params, @config_with_access_token) + assert {:ok, user_identity} = + Context.upsert(user, @user_identity_params, @config_with_access_token) + assert user_identity.provider == "test_provider" assert user_identity.uid == "1" end @@ -94,12 +115,21 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do end test "updates with valid params", %{user: user} do - assert {:ok, prev_user_identity} = Context.upsert(user, @user_identity_params, @config_with_access_token) + assert {:ok, prev_user_identity} = + Context.upsert(user, @user_identity_params, @config_with_access_token) + assert prev_user_identity.access_token refute prev_user_identity.refresh_token assert prev_user_identity.name - params = Map.merge(@user_identity_params, %{"token" => %{"access_token" => "changed_access_token", "refresh_token" => "refresh_token"}, "userinfo" => %{"name" => "John Doe Jr"}}) + params = + Map.merge(@user_identity_params, %{ + "token" => %{ + "access_token" => "changed_access_token", + "refresh_token" => "refresh_token" + }, + "userinfo" => %{"name" => "John Doe Jr"} + }) assert {:ok, user_identity} = Context.upsert(user, params, @config_with_access_token) assert prev_user_identity.id == user_identity.id @@ -125,7 +155,8 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do |> Changeset.cast_assoc(:user_identities) |> Repo.insert!() - assert {:error, {:bound_to_different_user, _changeset}} = Context.upsert(user, @user_identity_params, @config_with_access_token) + assert {:error, {:bound_to_different_user, _changeset}} = + Context.upsert(user, @user_identity_params, @config_with_access_token) end end @@ -144,7 +175,14 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do end test "with valid params with access token" do - assert {:ok, user} = Context.create_user(@user_identity_params, @user_params, nil, @config_with_access_token) + assert {:ok, user} = + Context.create_user( + @user_identity_params, + @user_params, + nil, + @config_with_access_token + ) + user = Repo.preload(user, :user_identities, force: true) assert [user_identity] = user.user_identities @@ -170,11 +208,18 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do |> Changeset.change(email: "test-2@example.com", user_identities: [@user_identity]) |> Repo.insert!() - assert {:error, {:bound_to_different_user, _changeset}} = Context.create_user(@user_identity_params, @user_params, nil, @config) + assert {:error, {:bound_to_different_user, _changeset}} = + Context.create_user(@user_identity_params, @user_params, nil, @config) end test "when user id field is missing" do - assert {:error, {:invalid_user_id_field, _changeset}} = Context.create_user(@user_identity_params, Map.delete(@user_params, :email), nil, @config) + assert {:error, {:invalid_user_id_field, _changeset}} = + Context.create_user( + @user_identity_params, + Map.delete(@user_params, :email), + nil, + @config + ) end end @@ -182,7 +227,10 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do setup do user = %User{} - |> Changeset.change(email: "test@example.com", user_identities: [@user_identity, %{provider: "test_provider", uid: "2"}]) + |> Changeset.change( + email: "test@example.com", + user_identities: [@user_identity, %{provider: "test_provider", uid: "2"}] + ) |> Repo.insert!() {:ok, user: user} @@ -191,7 +239,10 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do test "requires password hash or other identity", %{user: user} do assert {:error, {:no_password, _changeset}} = Context.delete(user, "test_provider", @config) - Repo.insert!(Ecto.build_assoc(user, :user_identities, %{provider: "another_provider", uid: "1"})) + Repo.insert!( + Ecto.build_assoc(user, :user_identities, %{provider: "another_provider", uid: "1"}) + ) + assert {:ok, {2, nil}} = Context.delete(user, "test_provider", @config) user = %{user | password_hash: "password"} @@ -202,15 +253,26 @@ defmodule PowAssent.Ecto.UserIdentities.ContextTest do test "all/2 retrieves" do user = %User{} - |> Changeset.change(email: "test@example.com", user_identities: [%{provider: "test_provider", uid: "1"}, %{provider: "other_provider", uid: "1"}]) + |> Changeset.change( + email: "test@example.com", + user_identities: [ + %{provider: "test_provider", uid: "1"}, + %{provider: "other_provider", uid: "1"} + ] + ) |> Repo.insert!() second_user = %User{} - |> Changeset.change(email: "test-2@example.com", user_identities: [%{provider: "test_provider", uid: "2"}]) + |> Changeset.change( + email: "test-2@example.com", + user_identities: [%{provider: "test_provider", uid: "2"}] + ) |> Repo.insert!() assert [%{provider: "test_provider", uid: "2"}] = Context.all(second_user, @config) - assert [%{provider: "test_provider", uid: "1"}, %{provider: "other_provider", uid: "1"}] = Context.all(user, @config) + + assert [%{provider: "test_provider", uid: "1"}, %{provider: "other_provider", uid: "1"}] = + Context.all(user, @config) end end diff --git a/test/pow_assent/ecto/user_identities/schema_test.exs b/test/pow_assent/ecto/user_identities/schema_test.exs index 213b1b0..8f9fd9b 100644 --- a/test/pow_assent/ecto/user_identities/schema_test.exs +++ b/test/pow_assent/ecto/user_identities/schema_test.exs @@ -14,7 +14,8 @@ defmodule PowAssent.Ecto.UserIdentities.SchemaTest do alias PowAssent.Test.Ecto.{Repo, UserIdentities.UserIdentity, Users.User} test "raises error during compile when there's no `:user` configuration" do - assert unquote(module_raised_with) =~ "No :user configuration option found for user identity schema module." + assert unquote(module_raised_with) =~ + "No :user configuration option found for user identity schema module." end test "pow_assent_user_identity_fields/1" do @@ -47,7 +48,9 @@ defmodule PowAssent.Ecto.UserIdentities.SchemaTest do |> UserIdentity.changeset(Map.put(@valid_params, :user_id, 2)) |> Repo.insert() - assert changeset.errors[:user] == {"does not exist", [constraint: :assoc, constraint_name: "user_identities_user_id_fkey"]} + assert changeset.errors[:user] == + {"does not exist", + [constraint: :assoc, constraint_name: "user_identities_user_id_fkey"]} end test "requires unique uid and provider" do @@ -57,11 +60,13 @@ defmodule PowAssent.Ecto.UserIdentities.SchemaTest do |> Repo.insert() assert {:error, changeset} = - %UserIdentity{} - |> UserIdentity.changeset(@valid_params) - |> Repo.insert() + %UserIdentity{} + |> UserIdentity.changeset(@valid_params) + |> Repo.insert() - assert changeset.errors[:uid_provider] == {"has already been taken", [constraint: :unique, constraint_name: "user_identities_uid_provider_index"]} + assert changeset.errors[:uid_provider] == + {"has already been taken", + [constraint: :unique, constraint_name: "user_identities_uid_provider_index"]} end end end diff --git a/test/pow_assent/phoenix/controllers/authorization_controller_test.exs b/test/pow_assent/phoenix/controllers/authorization_controller_test.exs index 79a171c..3e99ac8 100644 --- a/test/pow_assent/phoenix/controllers/authorization_controller_test.exs +++ b/test/pow_assent/phoenix/controllers/authorization_controller_test.exs @@ -1,7 +1,9 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do use PowAssent.Test.Phoenix.ConnCase - import PowAssent.Test.TestProvider, only: [set_oauth2_test_endpoints: 1, put_oauth2_env: 0, put_oauth2_env: 1] + import PowAssent.Test.TestProvider, + only: [set_oauth2_test_endpoints: 1, put_oauth2_env: 0, put_oauth2_env: 1] + import ExUnit.CaptureLog alias Plug.Conn @@ -55,13 +57,16 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do put_oauth2_env(strategy: FailAuthorizeURL) assert capture_log(fn -> - conn = get(conn, ~p"/auth/#{@provider}/new") + conn = get(conn, ~p"/auth/#{@provider}/new") + + assert redirected_to(conn) == ~p"/session/new" + + assert get_flash(conn, :error) == + "Something went wrong, and you couldn't be signed in. Please try again." - assert redirected_to(conn) == ~p"/session/new" - assert get_flash(conn, :error) == "Something went wrong, and you couldn't be signed in. Please try again." - refute conn.resp_cookies["pow_assent_auth_session"] - refute get_pow_assent_session(conn, :session_params) - end) =~ "Strategy failed with error: fail" + refute conn.resp_cookies["pow_assent_auth_session"] + refute get_pow_assent_session(conn, :session_params) + end) =~ "Strategy failed with error: fail" end end @@ -75,20 +80,26 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do end test "with failed token response", %{conn: conn} do - TestServer.add("/oauth/token", to: fn conn -> - conn - |> put_resp_content_type("application/json") - |> send_resp(401, Jason.encode!(%{error: "invalid_client"})) - end) + TestServer.add("/oauth/token", + to: fn conn -> + conn + |> put_resp_content_type("application/json") + |> send_resp(401, Jason.encode!(%{error: "invalid_client"})) + end + ) + + log = + capture_log(fn -> + conn = get(conn, ~p"/auth/#{@provider}/callback?#{@callback_params}") + + assert redirected_to(conn) == ~p"/session/new" - log = capture_log(fn -> - conn = get(conn, ~p"/auth/#{@provider}/callback?#{@callback_params}") + assert get_flash(conn, :error) == + "Something went wrong, and you couldn't be signed in. Please try again." - assert redirected_to(conn) == ~p"/session/new" - assert get_flash(conn, :error) == "Something went wrong, and you couldn't be signed in. Please try again." - refute conn.resp_cookies["pow_assent_auth_session"] - refute get_pow_assent_session(conn, :session_params) - end) + refute conn.resp_cookies["pow_assent_auth_session"] + refute get_pow_assent_session(conn, :session_params) + end) assert log =~ "Strategy failed with error: An invalid response was received." assert log =~ "HTTP Adapter: Assent.HTTPAdapter.Httpc" @@ -98,14 +109,18 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do test "with timeout", %{conn: conn} do TestServer.stop() - log = capture_log(fn -> - conn = get(conn, ~p"/auth/#{@provider}/callback?#{@callback_params}") + log = + capture_log(fn -> + conn = get(conn, ~p"/auth/#{@provider}/callback?#{@callback_params}") - assert redirected_to(conn) == ~p"/session/new" - assert get_flash(conn, :error) == "Something went wrong, and you couldn't be signed in. Please try again." - refute conn.resp_cookies["pow_assent_auth_session"] - refute get_pow_assent_session(conn, :session_params) - end) + assert redirected_to(conn) == ~p"/session/new" + + assert get_flash(conn, :error) == + "Something went wrong, and you couldn't be signed in. Please try again." + + refute conn.resp_cookies["pow_assent_auth_session"] + refute get_pow_assent_session(conn, :session_params) + end) assert log =~ "Strategy failed with error: The server was unreachable." assert log =~ "HTTP Adapter: Assent.HTTPAdapter.Httpc" @@ -114,13 +129,20 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do test "with invalid state", %{conn: conn} do assert capture_log(fn -> - conn = get(conn, ~p"/auth/#{@provider}/callback?#{Map.put(@callback_params, :state, "invalid")}") + conn = + get( + conn, + ~p"/auth/#{@provider}/callback?#{Map.put(@callback_params, :state, "invalid")}" + ) + + assert redirected_to(conn) == ~p"/session/new" - assert redirected_to(conn) == ~p"/session/new" - assert get_flash(conn, :error) == "Something went wrong, and you couldn't be signed in. Please try again." - refute conn.resp_cookies["pow_assent_auth_session"] - refute get_pow_assent_session(conn, :session_params) - end) =~ "Strategy failed with error: CSRF detected with param key \"state\"" + assert get_flash(conn, :error) == + "Something went wrong, and you couldn't be signed in. Please try again." + + refute conn.resp_cookies["pow_assent_auth_session"] + refute get_pow_assent_session(conn, :session_params) + end) =~ "Strategy failed with error: CSRF detected with param key \"state\"" end test "when identity exists authenticates", %{conn: conn} do @@ -142,13 +164,16 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do |> Conn.put_private(:pow_assent_registration, false) |> get(~p"/auth/#{@provider}/callback?#{@callback_params}") - assert redirected_to(conn) == "/session_created" + assert redirected_to(conn) == "/session_created" - refute conn.resp_cookies["pow_assent_auth_session"] - refute get_pow_assent_session(conn, :session_params) + refute conn.resp_cookies["pow_assent_auth_session"] + refute get_pow_assent_session(conn, :session_params) end - test "with current user session when identity doesn't exist creates identity", %{conn: conn, user: user} do + test "with current user session when identity doesn't exist creates identity", %{ + conn: conn, + user: user + } do set_oauth2_test_endpoints(user: %{sub: "new_identity"}) conn = @@ -162,7 +187,10 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do refute get_pow_assent_session(conn, :session_params) end - test "with current user session when identity identity already bound to another user", %{conn: conn, user: user} do + test "with current user session when identity identity already bound to another user", %{ + conn: conn, + user: user + } do set_oauth2_test_endpoints(user: %{sub: "identity_taken"}) conn = @@ -171,7 +199,10 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do |> get(~p"/auth/#{@provider}/callback?#{@callback_params}") assert redirected_to(conn) == ~p"/session/new" - assert get_flash(conn, :error) == "The Test provider account is already bound to another user." + + assert get_flash(conn, :error) == + "The Test provider account is already bound to another user." + refute conn.resp_cookies["pow_assent_auth_session"] refute get_pow_assent_session(conn, :session_params) end @@ -194,13 +225,16 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do set_oauth2_test_endpoints(user: %{sub: "new_user", name: ""}) assert capture_log(fn -> - conn = get(conn, ~p"/auth/#{@provider}/callback?#{@callback_params}") + conn = get(conn, ~p"/auth/#{@provider}/callback?#{@callback_params}") + + assert redirected_to(conn) == ~p"/session/new" + + assert get_flash(conn, :error) == + "Something went wrong, and you couldn't be signed in. Please try again." - assert redirected_to(conn) == ~p"/session/new" - assert get_flash(conn, :error) == "Something went wrong, and you couldn't be signed in. Please try again." - refute conn.resp_cookies["pow_assent_auth_session"] - refute get_pow_assent_session(conn, :session_params) - end) =~ "Unexpected error inserting user: #Ecto.Changeset %{user_identity: user_identity, user: user}} = get_pow_assent_session(conn, :callback_params) - assert user_identity == %{"provider" => "test_provider", "uid" => "new_user", "token" => %{"access_token" => "access_token"}, "userinfo" => %{"email" => "", "name" => "John Doe", "sub" => "new_user"}} + + assert %{"test_provider" => %{user_identity: user_identity, user: user}} = + get_pow_assent_session(conn, :callback_params) + + assert user_identity == %{ + "provider" => "test_provider", + "uid" => "new_user", + "token" => %{"access_token" => "access_token"}, + "userinfo" => %{"email" => "", "name" => "John Doe", "sub" => "new_user"} + } + assert user == %{"name" => "John Doe", "email" => ""} refute get_pow_assent_session(conn, :session_params) assert get_pow_assent_session(conn, :callback_params) @@ -225,8 +268,21 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do assert redirected_to(conn) == ~p"/auth/test_provider/add-user-id" assert conn.resp_cookies["pow_assent_auth_session"] - assert %{"test_provider" => %{user_identity: user_identity, user: user}} = get_pow_assent_session(conn, :callback_params) - assert user_identity == %{"provider" => "test_provider", "uid" => "new_user", "token" => %{"access_token" => "access_token"}, "userinfo" => %{"email" => "taken@example.com", "name" => "John Doe", "sub" => "new_user"}} + + assert %{"test_provider" => %{user_identity: user_identity, user: user}} = + get_pow_assent_session(conn, :callback_params) + + assert user_identity == %{ + "provider" => "test_provider", + "uid" => "new_user", + "token" => %{"access_token" => "access_token"}, + "userinfo" => %{ + "email" => "taken@example.com", + "name" => "John Doe", + "sub" => "new_user" + } + } + assert user == %{"name" => "John Doe", "email" => "taken@example.com"} refute get_pow_assent_session(conn, :session_params) assert get_pow_assent_session(conn, :callback_params) @@ -242,7 +298,10 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do |> get(~p"/auth/#{@provider}/callback?#{@callback_params}") assert redirected_to(conn) == ~p"/session/new" - assert get_flash(conn, :error) == "Something went wrong, and you couldn't be signed in. Please try again." + + assert get_flash(conn, :error) == + "Something went wrong, and you couldn't be signed in. Please try again." + refute conn.resp_cookies["pow_assent_auth_session"] refute get_pow_assent_session(conn, :session_params) end @@ -252,7 +311,10 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do conn = conn - |> Conn.put_private(:pow_assent_session, Map.put(@pow_assent_session, :request_path, "/custom-uri")) + |> Conn.put_private( + :pow_assent_session, + Map.put(@pow_assent_session, :request_path, "/custom-uri") + ) |> get(~p"/auth/#{@provider}/callback?#{@callback_params}") assert redirected_to(conn) == "/custom-uri" @@ -289,6 +351,7 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do end alias PowAssent.Test.EmailConfirmation.Phoenix.Endpoint, as: EmailConfirmationEndpoint + describe "GET /auth/:provider/callback with PowEmailConfirmation" do @pow_assent_session %{session_params: %{state: "token"}} @@ -304,21 +367,37 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do test "when user doesn't exist", %{conn: conn} do set_oauth2_test_endpoints(user: %{sub: "new_user", email: "foo@example.com"}) - conn = Phoenix.ConnTest.dispatch(conn, EmailConfirmationEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + conn = + Phoenix.ConnTest.dispatch( + conn, + EmailConfirmationEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) refute Pow.Plug.current_user(conn) assert redirected_to(conn) == "/registration_created" - assert get_flash(conn, :info) == "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." + + assert get_flash(conn, :info) == + "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." assert_received {:mail_mock, mail} mail.html =~ "http://example.com/confirm-email/" end test "when user doesn't exist and provider e-mail is verified", %{conn: conn} do - set_oauth2_test_endpoints(user: %{sub: "new_user", email: "foo@example.com", email_verified: true}) + set_oauth2_test_endpoints( + user: %{sub: "new_user", email: "foo@example.com", email_verified: true} + ) - conn = Phoenix.ConnTest.dispatch(conn, EmailConfirmationEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + conn = + Phoenix.ConnTest.dispatch( + conn, + EmailConfirmationEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) assert redirected_to(conn) == "/registration_created" assert user = Pow.Plug.current_user(conn) @@ -330,26 +409,63 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do test "when user doesn't exist and provider e-mail taken", %{conn: conn} do set_oauth2_test_endpoints(user: %{sub: "new_user", email: "taken@example.com"}) - conn = Phoenix.ConnTest.dispatch(conn, EmailConfirmationEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + conn = + Phoenix.ConnTest.dispatch( + conn, + EmailConfirmationEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) refute Pow.Plug.current_user(conn) assert redirected_to(conn) == "/registration_created" - assert get_flash(conn, :info) == "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." + + assert get_flash(conn, :info) == + "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." refute_received {:mail_mock, _mail} end - test "when user doesn't exist and provider e-mail taken and provider e-mail is verified", %{conn: conn} do - set_oauth2_test_endpoints(user: %{sub: "new_user", email: "taken@example.com", email_verified: true}) + test "when user doesn't exist and provider e-mail taken and provider e-mail is verified", %{ + conn: conn + } do + set_oauth2_test_endpoints( + user: %{sub: "new_user", email: "taken@example.com", email_verified: true} + ) - conn = Phoenix.ConnTest.dispatch(conn, EmailConfirmationEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + conn = + Phoenix.ConnTest.dispatch( + conn, + EmailConfirmationEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) assert redirected_to(conn) == ~p"/auth/test_provider/add-user-id" assert conn.resp_cookies["pow_assent_auth_session"] - assert %{"test_provider" => %{user_identity: user_identity, user: user}} = get_pow_assent_session(conn, :callback_params) - assert user_identity == %{"provider" => "test_provider", "uid" => "new_user", "token" => %{"access_token" => "access_token"}, "userinfo" => %{"email" => "taken@example.com", "email_verified" => true, "name" => "John Doe", "sub" => "new_user"}} - assert user == %{"name" => "John Doe", "email" => "taken@example.com", "email_verified" => true} + + assert %{"test_provider" => %{user_identity: user_identity, user: user}} = + get_pow_assent_session(conn, :callback_params) + + assert user_identity == %{ + "provider" => "test_provider", + "uid" => "new_user", + "token" => %{"access_token" => "access_token"}, + "userinfo" => %{ + "email" => "taken@example.com", + "email_verified" => true, + "name" => "John Doe", + "sub" => "new_user" + } + } + + assert user == %{ + "name" => "John Doe", + "email" => "taken@example.com", + "email_verified" => true + } + refute get_pow_assent_session(conn, :session_params) assert get_pow_assent_session(conn, :callback_params) assert get_pow_assent_session(conn, :changeset) @@ -358,12 +474,20 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do test "when user exists with unconfirmed e-mail", %{conn: conn} do set_oauth2_test_endpoints(user: %{sub: "existing_user-missing_email_confirmation"}) - conn = Phoenix.ConnTest.dispatch(conn, EmailConfirmationEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + conn = + Phoenix.ConnTest.dispatch( + conn, + EmailConfirmationEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) refute Pow.Plug.current_user(conn) assert redirected_to(conn) == "/session_created" - assert get_flash(conn, :info) == "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." + + assert get_flash(conn, :info) == + "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." assert_received {:mail_mock, mail} mail.html =~ "http://example.com/confirm-email/" @@ -371,17 +495,22 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do end alias PowAssent.Test.Invitation.Phoenix.Endpoint, as: InvitationEndpoint + describe "GET /auth/:provider/callback as authentication with invitation" do test "with invitation_token updates user as accepted invitation", %{conn: conn} do set_oauth2_test_endpoints(user: %{sub: "new_identity"}) signed_token = sign_invitation_token(conn, "token") - session = %{session_params: %{state: "token"}, invitation_token: signed_token} + session = %{session_params: %{state: "token"}, invitation_token: signed_token} conn = conn |> Conn.put_private(:pow_assent_session, session) - |> Phoenix.ConnTest.dispatch(InvitationEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + |> Phoenix.ConnTest.dispatch( + InvitationEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) assert redirected_to(conn) == "/session_created" assert get_flash(conn, :info) == "signed_in_test_provider" @@ -394,6 +523,7 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do end alias PowAssent.Test.NoRegistration.Phoenix.Endpoint, as: NoRegistrationEndpoint + describe "GET /auth/:provider/callback as authentication with missing registration routes" do @pow_assent_session %{session_params: %{state: "token"}} @@ -403,19 +533,26 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do conn = conn |> Conn.put_private(:pow_assent_session, @pow_assent_session) - |> Phoenix.ConnTest.dispatch(NoRegistrationEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + |> Phoenix.ConnTest.dispatch( + NoRegistrationEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) refute Pow.Plug.current_user(conn) refute conn.resp_cookies["pow_assent_auth_session"] refute get_pow_assent_session(conn, :session_params) assert redirected_to(conn) == ~p"/session/new" - assert get_flash(conn, :error) == "Something went wrong, and you couldn't be signed in. Please try again." + + assert get_flash(conn, :error) == + "Something went wrong, and you couldn't be signed in. Please try again." end end alias PowAssent.Test.WithCustomChangeset.Phoenix.Endpoint, as: WithCustomChangesetEndpoint alias PowAssent.Test.WithCustomChangeset.Users.User, as: WithCustomChangesetUser + describe "GET /auth/:provider/callback recording strategy params" do setup %{conn: conn} do user = %WithCustomChangesetUser{id: 1} @@ -430,7 +567,11 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do conn = conn |> Pow.Plug.assign_current_user(user, []) - |> Phoenix.ConnTest.dispatch(WithCustomChangesetEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + |> Phoenix.ConnTest.dispatch( + WithCustomChangesetEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) assert redirected_to(conn) == "/session_created" end @@ -438,7 +579,13 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do test "with new user", %{conn: conn} do set_oauth2_test_endpoints(user: %{sub: "new_user"}) - conn = Phoenix.ConnTest.dispatch(conn, WithCustomChangesetEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + conn = + Phoenix.ConnTest.dispatch( + conn, + WithCustomChangesetEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) assert redirected_to(conn) == "/registration_created" refute conn.resp_cookies["pow_assent_auth_session"] @@ -452,7 +599,13 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do test "when identity exists updates identity", %{conn: conn} do set_oauth2_test_endpoints(user: %{sub: "existing_user"}) - conn = Phoenix.ConnTest.dispatch(conn, WithCustomChangesetEndpoint, :get, ~p"/auth/#{@provider}/callback?#{@callback_params}") + conn = + Phoenix.ConnTest.dispatch( + conn, + WithCustomChangesetEndpoint, + :get, + ~p"/auth/#{@provider}/callback?#{@callback_params}" + ) assert redirected_to(conn) == "/session_created" refute conn.resp_cookies["pow_assent_auth_session"] @@ -469,7 +622,9 @@ defmodule PowAssent.Phoenix.AuthorizationControllerTest do |> delete(~p"/auth/#{@provider}") assert redirected_to(conn) == ~p"/registration/edit" - assert get_flash(conn, :error) == "Authentication cannot be removed until you've entered a password for your account." + + assert get_flash(conn, :error) == + "Authentication cannot be removed until you've entered a password for your account." end test "when password not set but has multiple identities", %{conn: conn} do diff --git a/test/pow_assent/phoenix/controllers/registration_controller_test.exs b/test/pow_assent/phoenix/controllers/registration_controller_test.exs index 8be29ef..ff4f1c6 100644 --- a/test/pow_assent/phoenix/controllers/registration_controller_test.exs +++ b/test/pow_assent/phoenix/controllers/registration_controller_test.exs @@ -7,7 +7,12 @@ defmodule PowAssent.Phoenix.RegistrationControllerTest do @provider "test_provider" @token_params %{"access_token" => "access_token"} - @user_identity_params %{"provider" => @provider, "uid" => "new_user", "token" => @token_params, "userinfo" => %{"sub" => "new_user", "name" => "John Doe"}} + @user_identity_params %{ + "provider" => @provider, + "uid" => "new_user", + "token" => @token_params, + "userinfo" => %{"sub" => "new_user", "name" => "John Doe"} + } @user_params %{"name" => "John Doe"} setup %{conn: conn} do @@ -53,10 +58,21 @@ defmodule PowAssent.Phoenix.RegistrationControllerTest do end test "shows with changeset stored in session", %{conn: conn} do - {:error, {:invalid_user_id_field, changeset}} = Context.create_user(@user_identity_params, Map.put(@user_params, "email", "taken@example.com"), nil, repo: PowAssent.Test.RepoMock, user: PowAssent.Test.Ecto.Users.User) + {:error, {:invalid_user_id_field, changeset}} = + Context.create_user( + @user_identity_params, + Map.put(@user_params, "email", "taken@example.com"), + nil, + repo: PowAssent.Test.RepoMock, + user: PowAssent.Test.Ecto.Users.User + ) + conn = conn - |> Conn.put_private(:pow_assent_session, %{changeset: changeset, callback_params: provider_params()}) + |> Conn.put_private(:pow_assent_session, %{ + changeset: changeset, + callback_params: provider_params() + }) |> get(~p"/auth/#{@provider}/add-user-id") assert conn.resp_cookies["pow_assent_auth_session"] @@ -117,7 +133,8 @@ defmodule PowAssent.Phoenix.RegistrationControllerTest do test "with identity already bound to another user", %{conn: conn} do params = provider_params(user_identity_params: %{"uid" => "identity_taken"}) - conn = + + conn = conn |> Conn.put_private(:pow_assent_session, %{callback_params: params}) |> post(~p"/auth/#{@provider}/create", @valid_params) @@ -125,18 +142,28 @@ defmodule PowAssent.Phoenix.RegistrationControllerTest do refute conn.resp_cookies["pow_assent_auth_session"] refute conn.private[:pow_assent_session][:callback_params] assert redirected_to(conn) == ~p"/registration/new" - assert get_flash(conn, :error) == "The Test provider account is already bound to another user." + + assert get_flash(conn, :error) == + "The Test provider account is already bound to another user." end end alias PowAssent.Test.EmailConfirmation.Phoenix.Endpoint, as: EmailConfirmationEndpoint alias PowAssent.Test.EmailConfirmation.Users.User, as: EmailConfirmationUser + describe "POST /auth/:provider/create with PowEmailConfirmation" do @valid_params %{user: %{email: "foo@example.com"}} @taken_params %{user: %{email: "taken@example.com"}} test "with email from user", %{conn: conn} do - conn = Phoenix.ConnTest.dispatch(conn, EmailConfirmationEndpoint, :post, ~p"/auth/#{@provider}/create", @valid_params) + conn = + Phoenix.ConnTest.dispatch( + conn, + EmailConfirmationEndpoint, + :post, + ~p"/auth/#{@provider}/create", + @valid_params + ) refute conn.resp_cookies["pow_assent_auth_session"] refute conn.private[:pow_assent_session][:callback_params] @@ -144,7 +171,9 @@ defmodule PowAssent.Phoenix.RegistrationControllerTest do refute Pow.Plug.current_user(conn) assert redirected_to(conn) == "/registration_created" - assert get_flash(conn, :info) == "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." + + assert get_flash(conn, :info) == + "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." assert user = Process.get({EmailConfirmationUser, :inserted}) assert user.email == "foo@example.com" @@ -156,7 +185,14 @@ defmodule PowAssent.Phoenix.RegistrationControllerTest do end test "with taken email", %{conn: conn} do - conn = Phoenix.ConnTest.dispatch(conn, EmailConfirmationEndpoint, :post, ~p"/auth/#{@provider}/create", @taken_params) + conn = + Phoenix.ConnTest.dispatch( + conn, + EmailConfirmationEndpoint, + :post, + ~p"/auth/#{@provider}/create", + @taken_params + ) refute conn.resp_cookies["pow_assent_auth_session"] refute conn.private[:pow_assent_session][:callback_params] @@ -164,16 +200,26 @@ defmodule PowAssent.Phoenix.RegistrationControllerTest do refute Pow.Plug.current_user(conn) assert redirected_to(conn) == "/registration_created" - assert get_flash(conn, :info) == "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." + + assert get_flash(conn, :info) == + "You'll need to confirm your e-mail before you can sign in. An e-mail confirmation link has been sent to you." refute_received {:mail_mock, _mail} end end alias PowAssent.Test.WithCustomChangeset.Phoenix.Endpoint, as: WithCustomChangesetEndpoint + describe "POST /auth/:provider/create recording strategy params" do test "records", %{conn: conn} do - conn = Phoenix.ConnTest.dispatch(conn, WithCustomChangesetEndpoint, :post, ~p"/auth/#{@provider}/create", %{user: %{email: "foo@example.com"}}) + conn = + Phoenix.ConnTest.dispatch( + conn, + WithCustomChangesetEndpoint, + :post, + ~p"/auth/#{@provider}/create", + %{user: %{email: "foo@example.com"}} + ) refute conn.resp_cookies["pow_assent_auth_session"] refute conn.private[:pow_assent_session][:callback_params] @@ -187,7 +233,9 @@ defmodule PowAssent.Phoenix.RegistrationControllerTest do end defp provider_params(opts \\ []) do - user_identity_params = Map.merge(@user_identity_params, Keyword.get(opts, :user_identity_params, %{})) + user_identity_params = + Map.merge(@user_identity_params, Keyword.get(opts, :user_identity_params, %{})) + user_params = Map.merge(@user_params, Keyword.get(opts, :user_params, %{})) %{@provider => %{user_identity: user_identity_params, user: user_params}} diff --git a/test/pow_assent/phoenix/html/core_components_test.exs b/test/pow_assent/phoenix/html/core_components_test.exs index dcf3556..5308a4c 100644 --- a/test/pow_assent/phoenix/html/core_components_test.exs +++ b/test/pow_assent/phoenix/html/core_components_test.exs @@ -50,7 +50,7 @@ defmodule PowAssent.Phoenix.HTML.CoreComponentsTest do end assert render_component(&template.(&1), %{conn: conn}) == - render_component(&expected.(&1)) + render_component(&expected.(&1)) end test "provider_links/1 with slot assigns", %{conn: conn} do @@ -75,7 +75,7 @@ defmodule PowAssent.Phoenix.HTML.CoreComponentsTest do end assert render_component(&template.(&1), %{conn: conn}) == - render_component(&expected.(&1)) + render_component(&expected.(&1)) end test "provider_links/1 with slot inner block", %{conn: conn} do @@ -100,7 +100,7 @@ defmodule PowAssent.Phoenix.HTML.CoreComponentsTest do end assert render_component(&template.(&1), %{conn: conn}) == - render_component(&expected.(&1)) + render_component(&expected.(&1)) end test "provider_links/1 with request_path", %{conn: conn} do @@ -123,11 +123,14 @@ defmodule PowAssent.Phoenix.HTML.CoreComponentsTest do end assert render_component(&template.(&1), %{conn: conn}) == - render_component(&expected.(&1)) + render_component(&expected.(&1)) end test "provider_links/1 with invited_user", %{conn: conn} do - conn = Conn.assign(conn, :invited_user, %PowAssent.Test.Invitation.Users.User{invitation_token: "token"}) + conn = + Conn.assign(conn, :invited_user, %PowAssent.Test.Invitation.Users.User{ + invitation_token: "token" + }) template = fn assigns -> ~H""" @@ -146,7 +149,7 @@ defmodule PowAssent.Phoenix.HTML.CoreComponentsTest do end assert render_component(&template.(&1), %{conn: conn}) == - render_component(&expected.(&1)) + render_component(&expected.(&1)) end test "authorization_link/1 with assigns", %{conn: conn} do @@ -163,11 +166,11 @@ defmodule PowAssent.Phoenix.HTML.CoreComponentsTest do end assert render_component(&template.(&1), %{conn: conn}) == - render_component(&expected.(&1)) + render_component(&expected.(&1)) end test "authorization_link/1 with inner block", %{conn: conn} do - template = fn assigns -> + template = fn assigns -> ~H""" Authorize @@ -184,7 +187,7 @@ defmodule PowAssent.Phoenix.HTML.CoreComponentsTest do end assert render_component(&template.(&1), %{conn: conn}) == - render_component(&expected.(&1)) + render_component(&expected.(&1)) end test "deauthorization_link/1 with assigns", %{conn: conn} do @@ -201,11 +204,11 @@ defmodule PowAssent.Phoenix.HTML.CoreComponentsTest do end assert render_component(&template.(&1), %{conn: conn}) == - render_component(&expected.(&1)) + render_component(&expected.(&1)) end test "deauthorization_link/1 with inner block", %{conn: conn} do - template = fn assigns -> + template = fn assigns -> ~H""" Deauthorize @@ -222,6 +225,6 @@ defmodule PowAssent.Phoenix.HTML.CoreComponentsTest do end assert render_component(&template.(&1), %{conn: conn}) == - render_component(&expected.(&1)) + render_component(&expected.(&1)) end end diff --git a/test/pow_assent/phoenix/reauthorization_plug_handler_test.exs b/test/pow_assent/phoenix/reauthorization_plug_handler_test.exs index bc60bd2..0febfaa 100644 --- a/test/pow_assent/phoenix/reauthorization_plug_handler_test.exs +++ b/test/pow_assent/phoenix/reauthorization_plug_handler_test.exs @@ -59,12 +59,14 @@ defmodule PowAssent.Phoenix.ReauthorizationPlugHandlerTest do end test "requires conn.private.phoenix_controller", %{conn: conn} do - assert_raise ConfigError, "Please use PowAssent.Plug.Reauthorization plug in your Phoenix router rather than endpoint when used with the PowAssent.Phoenix.ReauthorizationPlugHandler handler.", fn -> - opts = Reauthorization.init(handler: ReauthorizationPlugHandler) + assert_raise ConfigError, + "Please use PowAssent.Plug.Reauthorization plug in your Phoenix router rather than endpoint when used with the PowAssent.Phoenix.ReauthorizationPlugHandler handler.", + fn -> + opts = Reauthorization.init(handler: ReauthorizationPlugHandler) - conn - |> Plug.put_config([]) - |> Reauthorization.call(opts) - end + conn + |> Plug.put_config([]) + |> Reauthorization.call(opts) + end end end diff --git a/test/pow_assent/plug_test.exs b/test/pow_assent/plug_test.exs index 64f3a78..0e88c1b 100644 --- a/test/pow_assent/plug_test.exs +++ b/test/pow_assent/plug_test.exs @@ -7,7 +7,8 @@ defmodule PowAssent.PlugTest do alias PowAssent.{Plug, Store.SessionCache} alias PowAssent.Test.{Ecto.UserIdentities.UserIdentity, Ecto.Users.User, EtsCacheMock, RepoMock} - import PowAssent.Test.TestProvider, only: [set_oauth2_test_endpoints: 1, put_oauth2_env: 0, put_oauth2_env: 1] + import PowAssent.Test.TestProvider, + only: [set_oauth2_test_endpoints: 1, put_oauth2_env: 0, put_oauth2_env: 1] @default_config [ plug: PowSession, @@ -34,7 +35,14 @@ defmodule PowAssent.PlugTest do end test "uses nonce from config", %{conn: conn} do - put_oauth2_env(base_url: "http://localhost:8888", nonce: "nonce", strategy: Assent.Strategy.OIDC, openid_configuration: %{"authorization_endpoint" => "http://localhost:8888/oauth/authorize"}) + put_oauth2_env( + base_url: "http://localhost:8888", + nonce: "nonce", + strategy: Assent.Strategy.OIDC, + openid_configuration: %{ + "authorization_endpoint" => "http://localhost:8888/oauth/authorize" + } + ) assert {:ok, url, conn} = Plug.authorize_url(conn, "test_provider", "https://example.com/") @@ -44,7 +52,14 @@ defmodule PowAssent.PlugTest do end test "uses generated nonce when nonce in config set to true", %{conn: conn} do - put_oauth2_env(base_url: "http://localhost:8888", nonce: true, strategy: Assent.Strategy.OIDC, openid_configuration: %{"authorization_endpoint" => "http://localhost:8888/oauth/authorize"}) + put_oauth2_env( + base_url: "http://localhost:8888", + nonce: true, + strategy: Assent.Strategy.OIDC, + openid_configuration: %{ + "authorization_endpoint" => "http://localhost:8888/oauth/authorize" + } + ) assert {:ok, url, conn} = Plug.authorize_url(conn, "test_provider", "https://example.com/") @@ -65,24 +80,65 @@ defmodule PowAssent.PlugTest do end test "returns user params", %{conn: conn} do - set_oauth2_test_endpoints(access_token_assert_fn: fn conn -> - {:ok, body, _conn} = Conn.read_body(conn, []) - params = URI.decode_query(body) + set_oauth2_test_endpoints( + access_token_assert_fn: fn conn -> + {:ok, body, _conn} = Conn.read_body(conn, []) + params = URI.decode_query(body) + + assert params["redirect_uri"] == "https://example.com/" + end + ) + + assert {:ok, user_identity_params, user_params, _conn} = + Plug.callback( + conn, + "test_provider", + %{"code" => "access_token"}, + "https://example.com/" + ) + + assert user_identity_params == %{ + "provider" => "test_provider", + "uid" => "new_user", + "token" => %{"access_token" => "access_token"}, + "userinfo" => %{ + "sub" => "new_user", + "name" => "John Doe", + "email" => "test@example.com" + } + } - assert params["redirect_uri"] == "https://example.com/" - end) - - assert {:ok, user_identity_params, user_params, _conn} = Plug.callback(conn, "test_provider", %{"code" => "access_token"}, "https://example.com/") - assert user_identity_params == %{"provider" => "test_provider", "uid" => "new_user", "token" => %{"access_token" => "access_token"}, "userinfo" => %{"sub" => "new_user", "name" => "John Doe", "email" => "test@example.com"}} assert user_params == %{"name" => "John Doe", "email" => "test@example.com"} end test "returns user params with preferred username as username", %{conn: conn} do set_oauth2_test_endpoints(user: %{preferred_username: "john.doe"}) - assert {:ok, user_identity_params, user_params, _conn} = Plug.callback(conn, "test_provider", %{"code" => "access_token"}, "https://example.com/") - assert user_identity_params == %{"provider" => "test_provider", "uid" => "new_user", "token" => %{"access_token" => "access_token"}, "userinfo" => %{"sub" => "new_user", "name" => "John Doe", "email" => "test@example.com", "preferred_username" => "john.doe"}} - assert user_params == %{"username" => "john.doe", "name" => "John Doe", "email" => "test@example.com"} + assert {:ok, user_identity_params, user_params, _conn} = + Plug.callback( + conn, + "test_provider", + %{"code" => "access_token"}, + "https://example.com/" + ) + + assert user_identity_params == %{ + "provider" => "test_provider", + "uid" => "new_user", + "token" => %{"access_token" => "access_token"}, + "userinfo" => %{ + "sub" => "new_user", + "name" => "John Doe", + "email" => "test@example.com", + "preferred_username" => "john.doe" + } + } + + assert user_params == %{ + "username" => "john.doe", + "name" => "John Doe", + "email" => "test@example.com" + } end end @@ -101,7 +157,11 @@ defmodule PowAssent.PlugTest do end test "calls create session callback", %{conn: init_conn} do - init_conn = Plug.put_create_session_callback(init_conn, &Conn.put_private(&1, :callback_called, {&2, &3})) + init_conn = + Plug.put_create_session_callback( + init_conn, + &Conn.put_private(&1, :callback_called, {&2, &3}) + ) assert {:error, conn} = Plug.authenticate(init_conn, @new_user_params) refute conn.private[:callback_called] @@ -139,31 +199,40 @@ defmodule PowAssent.PlugTest do end test "with identity already taken", %{conn: conn} do - assert {:error, {:bound_to_different_user, _changeset}, conn} = Plug.upsert_identity(conn, @identity_taken_params) + assert {:error, {:bound_to_different_user, _changeset}, conn} = + Plug.upsert_identity(conn, @identity_taken_params) assert Pow.Plug.current_user(conn) == @user refute fetch_pow_session_id(conn) end test "calls create session callback", %{conn: init_conn} do - init_conn = Plug.put_create_session_callback(init_conn, &Conn.put_private(&1, :callback_called, {&2, &3})) + init_conn = + Plug.put_create_session_callback( + init_conn, + &Conn.put_private(&1, :callback_called, {&2, &3}) + ) assert {:ok, _user_identity, conn} = Plug.upsert_identity(init_conn, @new_identity_params) assert {"test_provider", _config} = conn.private[:callback_called] - assert {:ok, _user_identity, conn} = Plug.upsert_identity(init_conn, @existing_identity_params) + assert {:ok, _user_identity, conn} = + Plug.upsert_identity(init_conn, @existing_identity_params) + assert {"test_provider", _config} = conn.private[:callback_called] - assert {:error, {:bound_to_different_user, _changeset}, conn} = Plug.upsert_identity(init_conn, @identity_taken_params) + assert {:error, {:bound_to_different_user, _changeset}, conn} = + Plug.upsert_identity(init_conn, @identity_taken_params) + refute conn.private[:callback_called] end end describe "create_user/3" do - @user_identity_attrs %{"provider" => "test_provider", "uid" => "new_user"} + @user_identity_attrs %{"provider" => "test_provider", "uid" => "new_user"} @user_identity_attrs_taken %{"provider" => "test_provider", "uid" => "identity_taken"} - @user_attrs %{"name" => "John Doe", "email" => "test@example.com"} - @user_attrs_no_user_id %{"name" => "John Doe"} + @user_attrs %{"name" => "John Doe", "email" => "test@example.com"} + @user_attrs_no_user_id %{"name" => "John Doe"} test "creates user", %{conn: conn} do assert {:ok, user, conn} = Plug.create_user(conn, @user_identity_attrs, @user_attrs) @@ -173,31 +242,50 @@ defmodule PowAssent.PlugTest do end test "with missing user id", %{conn: conn} do - assert {:error, {:invalid_user_id_field, _changeset}, conn} = Plug.create_user(conn, @user_identity_attrs, @user_attrs_no_user_id) + assert {:error, {:invalid_user_id_field, _changeset}, conn} = + Plug.create_user(conn, @user_identity_attrs, @user_attrs_no_user_id) + refute fetch_pow_session_id(conn) end test "with identity already taken", %{conn: conn} do - assert {:error, {:bound_to_different_user, _changeset}, conn} = Plug.create_user(conn, @user_identity_attrs_taken, @user_attrs) + assert {:error, {:bound_to_different_user, _changeset}, conn} = + Plug.create_user(conn, @user_identity_attrs_taken, @user_attrs) + refute fetch_pow_session_id(conn) end test "calls create session callback", %{conn: init_conn} do - init_conn = Plug.put_create_session_callback(init_conn, &Conn.put_private(&1, :callback_called, {&2, &3})) + init_conn = + Plug.put_create_session_callback( + init_conn, + &Conn.put_private(&1, :callback_called, {&2, &3}) + ) assert {:ok, _user, conn} = Plug.create_user(init_conn, @user_identity_attrs, @user_attrs) assert {"test_provider", _config} = conn.private[:callback_called] - assert {:error, {:invalid_user_id_field, _changeset}, conn} = Plug.create_user(init_conn, @user_identity_attrs, @user_attrs_no_user_id) + assert {:error, {:invalid_user_id_field, _changeset}, conn} = + Plug.create_user(init_conn, @user_identity_attrs, @user_attrs_no_user_id) + refute conn.private[:callback_called] - assert {:error, {:bound_to_different_user, _changeset}, conn} = Plug.create_user(init_conn, @user_identity_attrs_taken, @user_attrs) + assert {:error, {:bound_to_different_user, _changeset}, conn} = + Plug.create_user(init_conn, @user_identity_attrs_taken, @user_attrs) + refute conn.private[:callback_called] end end describe "delete_identity/3" do - @user %User{id: 1, password_hash: "", user_identities: [%UserIdentity{id: 1, provider: "test_provider"}, %UserIdentity{id: 2, provider: "other_provider"}]} + @user %User{ + id: 1, + password_hash: "", + user_identities: [ + %UserIdentity{id: 1, provider: "test_provider"}, + %UserIdentity{id: 2, provider: "other_provider"} + ] + } test "deletes", %{conn: conn} do conn = Pow.Plug.assign_current_user(conn, @user, @default_config) @@ -206,9 +294,11 @@ defmodule PowAssent.PlugTest do end test "with error", %{conn: conn} do - conn = Pow.Plug.assign_current_user(conn, Map.put(@user, :password_hash, nil), @default_config) + conn = + Pow.Plug.assign_current_user(conn, Map.put(@user, :password_hash, nil), @default_config) - assert {:error, {:no_password, _changeset}, _conn} = Plug.delete_identity(conn, "test_provider") + assert {:error, {:no_password, _changeset}, _conn} = + Plug.delete_identity(conn, "test_provider") end end @@ -241,7 +331,14 @@ defmodule PowAssent.PlugTest do end @cookie_key "auth_session" - @custom_cookie_opts [domain: "domain.com", max_age: 1, path: "/path", http_only: false, secure: true, extra: "SameSite=Lax"] + @custom_cookie_opts [ + domain: "domain.com", + max_age: 1, + path: "/path", + http_only: false, + secure: true, + extra: "SameSite=Lax" + ] describe "init_session/1" do test "initializes new session", %{conn: conn} do @@ -291,7 +388,12 @@ defmodule PowAssent.PlugTest do |> Plug.init_session() |> Conn.send_resp(200, "") - assert conn.resp_cookies["pow_assent_" <> @cookie_key] == %{max_age: 0, path: "/", universal_time: {{1970, 1, 1}, {0, 0, 0}}} + assert conn.resp_cookies["pow_assent_" <> @cookie_key] == %{ + max_age: 0, + path: "/", + universal_time: {{1970, 1, 1}, {0, 0, 0}} + } + assert conn.private[:pow_assent_session] == %{a: 1} assert get_from_cache(conn, id) == :not_found end @@ -317,13 +419,20 @@ defmodule PowAssent.PlugTest do |> Plug.init_session() |> Conn.send_resp(200, "") - assert conn.resp_cookies["test_app_" <> @cookie_key] == %{max_age: 0, path: "/", universal_time: {{1970, 1, 1}, {0, 0, 0}}} + assert conn.resp_cookies["test_app_" <> @cookie_key] == %{ + max_age: 0, + path: "/", + universal_time: {{1970, 1, 1}, {0, 0, 0}} + } + assert conn.private[:pow_assent_session] == %{a: 1} end test "with custom cookie options", %{conn: init_conn} do - config = Keyword.put(@default_config, :pow_assent, auth_session_cookie_opts: @custom_cookie_opts) - conn = + config = + Keyword.put(@default_config, :pow_assent, auth_session_cookie_opts: @custom_cookie_opts) + + conn = init_conn |> Conn.put_private(:pow_config, config) |> Plug.init_session() @@ -332,14 +441,14 @@ defmodule PowAssent.PlugTest do |> Conn.send_resp(200, "") assert %{ - value: id, - domain: "domain.com", - extra: "SameSite=Lax", - http_only: false, - max_age: 1, - path: "/path", - secure: true - } = conn.resp_cookies["pow_assent_" <> @cookie_key] + value: id, + domain: "domain.com", + extra: "SameSite=Lax", + http_only: false, + max_age: 1, + path: "/path", + secure: true + } = conn.resp_cookies["pow_assent_" <> @cookie_key] conn = init_conn @@ -349,14 +458,14 @@ defmodule PowAssent.PlugTest do |> Conn.send_resp(200, "") assert conn.resp_cookies["pow_assent_" <> @cookie_key] == %{ - max_age: 0, - universal_time: {{1970, 1, 1}, {0, 0, 0}}, - domain: "domain.com", - extra: "SameSite=Lax", - http_only: false, - path: "/path", - secure: true - } + max_age: 0, + universal_time: {{1970, 1, 1}, {0, 0, 0}}, + domain: "domain.com", + extra: "SameSite=Lax", + http_only: false, + path: "/path", + secure: true + } end test "pulls `:cache_store_backend` from Pow environment config", %{conn: conn} do @@ -401,7 +510,13 @@ defmodule PowAssent.PlugTest do config = Pow.Plug.fetch_config(conn) - assert config[:pow_assent][:providers][:test_provider][:authorization_params] == [scope: "user:read user:write", b: 2, a: 2, c: 3] + assert config[:pow_assent][:providers][:test_provider][:authorization_params] == [ + scope: "user:read user:write", + b: 2, + a: 2, + c: 3 + ] + assert {:ok, url, _conn} = Plug.authorize_url(conn, :test_provider, "http://localhost:4000") assert url =~ "http://localhost:8888/oauth/authorize?" diff --git a/test/pow_assent/plugs/reauthorization_test.exs b/test/pow_assent/plugs/reauthorization_test.exs index f4316d5..962d72f 100644 --- a/test/pow_assent/plugs/reauthorization_test.exs +++ b/test/pow_assent/plugs/reauthorization_test.exs @@ -24,7 +24,14 @@ defmodule PowAssent.Plug.ReauthorizationTest do end @cookie_key "reauthorization_provider" - @custom_cookie_opts [domain: "domain.com", max_age: 1, path: "/path", http_only: false, secure: true, extra: "SameSite=Lax"] + @custom_cookie_opts [ + domain: "domain.com", + max_age: 1, + path: "/path", + http_only: false, + secure: true, + extra: "SameSite=Lax" + ] @default_config [ plug: PowSession, user: User, @@ -50,9 +57,11 @@ defmodule PowAssent.Plug.ReauthorizationTest do end test "init/1 requires handler", %{conn: conn} do - assert_raise ConfigError, "No :handler configuration option provided. It's required to set this when using PowAssent.Plug.Reauthorization.", fn -> - init_plug(conn, Keyword.delete(@plug_opts, :handler)) - end + assert_raise ConfigError, + "No :handler configuration option provided. It's required to set this when using PowAssent.Plug.Reauthorization.", + fn -> + init_plug(conn, Keyword.delete(@plug_opts, :handler)) + end end describe "call/2" do @@ -101,7 +110,9 @@ defmodule PowAssent.Plug.ReauthorizationTest do assert cookie.max_age == -1 end - test "when in reauthorization condition with cookie set with prepended `:otp_app`", %{conn: conn} do + test "when in reauthorization condition with cookie set with prepended `:otp_app`", %{ + conn: conn + } do conn = conn |> PowPlug.put_config(@default_config ++ [otp_app: :test_app]) @@ -157,7 +168,10 @@ defmodule PowAssent.Plug.ReauthorizationTest do end test "with custom cookie options", %{conn: init_conn} do - config = Keyword.put(@default_config, :pow_assent, reauthorization_cookie_opts: @custom_cookie_opts) + config = + Keyword.put(@default_config, :pow_assent, + reauthorization_cookie_opts: @custom_cookie_opts + ) conn = init_conn @@ -166,19 +180,20 @@ defmodule PowAssent.Plug.ReauthorizationTest do |> run_callback() assert %{ - domain: "domain.com", - extra: "SameSite=Lax", - http_only: false, - max_age: 1, - path: "/path", - secure: true - } = conn.resp_cookies[@cookie_key] + domain: "domain.com", + extra: "SameSite=Lax", + http_only: false, + max_age: 1, + path: "/path", + secure: true + } = conn.resp_cookies[@cookie_key] end end defp with_reauthorization_condition(conn), do: Conn.put_private(conn, :reauthorize?, true) - defp with_clear_reauthorization_condition(conn), do: Conn.put_private(conn, :clear_reauthorization?, true) + defp with_clear_reauthorization_condition(conn), + do: Conn.put_private(conn, :clear_reauthorization?, true) defp with_reauthorization_cookie(conn, provider \\ "test_provider", key \\ @cookie_key) do cookies = Map.new([{key, provider}]) @@ -187,7 +202,8 @@ defmodule PowAssent.Plug.ReauthorizationTest do end defp run_callback(conn) do - assert {:ok, conn} = Plug.authenticate(conn, %{"provider" => "test_provider", "uid" => "existing_user"}) + assert {:ok, conn} = + Plug.authenticate(conn, %{"provider" => "test_provider", "uid" => "existing_user"}) Conn.send_resp(conn, 200, "") end diff --git a/test/support/ecto/priv/migrations/2_add_name_to_users.exs b/test/support/ecto/priv/migrations/2_add_name_to_users.exs index cbe5ee2..bded72b 100644 --- a/test/support/ecto/priv/migrations/2_add_name_to_users.exs +++ b/test/support/ecto/priv/migrations/2_add_name_to_users.exs @@ -3,7 +3,7 @@ defmodule PowAssent.Test.Ecto.Repo.Migrations.AddNameToUsers do def change do alter table(:users) do - add :name, :string + add(:name, :string) end end end diff --git a/test/support/ecto/priv/migrations/4_add_access_refresh_token_to_user_identities.exs b/test/support/ecto/priv/migrations/4_add_access_refresh_token_to_user_identities.exs index e974e41..4df73a4 100644 --- a/test/support/ecto/priv/migrations/4_add_access_refresh_token_to_user_identities.exs +++ b/test/support/ecto/priv/migrations/4_add_access_refresh_token_to_user_identities.exs @@ -3,8 +3,8 @@ defmodule PowAssent.Test.Ecto.Repo.Migrations.AddAccessRefreshTokenToUserIdentit def change do alter table(:user_identities) do - add :access_token, :string - add :refresh_token, :string + add(:access_token, :string) + add(:refresh_token, :string) end end end diff --git a/test/support/ecto/priv/migrations/5_add_name_to_user_identities.exs b/test/support/ecto/priv/migrations/5_add_name_to_user_identities.exs index 154738f..b7eea24 100644 --- a/test/support/ecto/priv/migrations/5_add_name_to_user_identities.exs +++ b/test/support/ecto/priv/migrations/5_add_name_to_user_identities.exs @@ -3,7 +3,7 @@ defmodule PowAssent.Test.Ecto.Repo.Migrations.AddNameToToUserIdentities do def change do alter table(:user_identities) do - add :name, :string + add(:name, :string) end end end diff --git a/test/support/ecto/user.ex b/test/support/ecto/user.ex index 51fcfd7..c72a953 100644 --- a/test/support/ecto/user.ex +++ b/test/support/ecto/user.ex @@ -5,7 +5,7 @@ defmodule PowAssent.Test.Ecto.Users.User do use PowAssent.Ecto.Schema schema "users" do - field :name, :string + field(:name, :string) pow_user_fields() diff --git a/test/support/ecto/user_identities/user_identity.ex b/test/support/ecto/user_identities/user_identity.ex index 62d568b..ee658ae 100644 --- a/test/support/ecto/user_identities/user_identity.ex +++ b/test/support/ecto/user_identities/user_identity.ex @@ -1,6 +1,7 @@ defmodule PowAssent.Test.Ecto.UserIdentities.UserIdentity do @moduledoc false use Ecto.Schema + use PowAssent.Ecto.UserIdentities.Schema, user: PowAssent.Test.Ecto.Users.User diff --git a/test/support/ets_cache_mock.ex b/test/support/ets_cache_mock.ex index 45e617c..83d7245 100644 --- a/test/support/ets_cache_mock.ex +++ b/test/support/ets_cache_mock.ex @@ -11,7 +11,7 @@ defmodule PowAssent.Test.EtsCacheMock do |> :ets.lookup(ets_key) |> case do [{^ets_key, value} | _rest] -> value - [] -> :not_found + [] -> :not_found end end @@ -22,10 +22,12 @@ defmodule PowAssent.Test.EtsCacheMock do end def put(config, record_or_records) do - records = List.wrap(record_or_records) - ets_records = Enum.map(records, fn {key, value} -> - {ets_key(config, key), value} - end) + records = List.wrap(record_or_records) + + ets_records = + Enum.map(records, fn {key, value} -> + {ets_key(config, key), value} + end) :ets.insert(@tab, ets_records) end diff --git a/test/support/extensions/email_confirmation/phoenix/endpoint.ex b/test/support/extensions/email_confirmation/phoenix/endpoint.ex index 48783ad..e76cd56 100644 --- a/test/support/extensions/email_confirmation/phoenix/endpoint.ex +++ b/test/support/extensions/email_confirmation/phoenix/endpoint.ex @@ -2,23 +2,25 @@ defmodule PowAssent.Test.EmailConfirmation.Phoenix.Endpoint do @moduledoc false use Phoenix.Endpoint, otp_app: :pow_assent - plug Plug.RequestId - plug Plug.Logger + plug(Plug.RequestId) + plug(Plug.Logger) - plug Plug.Parsers, + plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library() + ) - plug Plug.MethodOverride - plug Plug.Head + plug(Plug.MethodOverride) + plug(Plug.Head) - plug Plug.Session, + plug(Plug.Session, store: :cookie, key: "_binaryid_key", signing_salt: "secret" + ) - plug Pow.Plug.Session, + plug(Pow.Plug.Session, user: PowAssent.Test.EmailConfirmation.Users.User, routes_backend: PowAssent.Test.Phoenix.Routes, messages_backend: PowAssent.Test.Phoenix.Messages, @@ -26,6 +28,7 @@ defmodule PowAssent.Test.EmailConfirmation.Phoenix.Endpoint do repo: PowAssent.Test.EmailConfirmation.RepoMock, extensions: [PowEmailConfirmation], otp_app: :pow_assent + ) - plug PowAssent.Test.Phoenix.Router + plug(PowAssent.Test.Phoenix.Router) end diff --git a/test/support/extensions/email_confirmation/phoenix/layouts.ex b/test/support/extensions/email_confirmation/phoenix/layouts.ex index 934619f..c070c37 100644 --- a/test/support/extensions/email_confirmation/phoenix/layouts.ex +++ b/test/support/extensions/email_confirmation/phoenix/layouts.ex @@ -2,5 +2,5 @@ defmodule PowAssent.Test.EmailConfirmation.Phoenix.Layouts do @moduledoc false use PowAssent.Test.Phoenix.Web, :html - embed_templates "../../../phoenix/components/layouts/*.html" + embed_templates("../../../phoenix/components/layouts/*.html") end diff --git a/test/support/extensions/email_confirmation/repo_mock.ex b/test/support/extensions/email_confirmation/repo_mock.ex index 4b7785c..0e6ea25 100644 --- a/test/support/extensions/email_confirmation/repo_mock.ex +++ b/test/support/extensions/email_confirmation/repo_mock.ex @@ -5,13 +5,32 @@ defmodule PowAssent.Test.EmailConfirmation.RepoMock do alias PowAssent.Test.RepoMock def one(query, _opts) do - case inspect(query) =~ "left_join: u1 in assoc(u0, :user)" and inspect(query) =~ "where: u0.provider == ^\"test_provider\" and u0.uid == ^\"existing_user-missing_email_confirmation\"" do - true -> %User{id: 1, email: "test@example.com", email_confirmation_token: "token", email_confirmed_at: nil} - false -> nil + case inspect(query) =~ "left_join: u1 in assoc(u0, :user)" and + inspect(query) =~ + "where: u0.provider == ^\"test_provider\" and u0.uid == ^\"existing_user-missing_email_confirmation\"" do + true -> + %User{ + id: 1, + email: "test@example.com", + email_confirmation_token: "token", + email_confirmed_at: nil + } + + false -> + nil end end - def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "existing_user-missing_email_confirmation"], _opts), do: %UserIdentity{user_id: 1, provider: "test_provider", uid: "existing_user-missing_email_confirmation"} + def get_by( + UserIdentity, + [user_id: 1, provider: "test_provider", uid: "existing_user-missing_email_confirmation"], + _opts + ), + do: %UserIdentity{ + user_id: 1, + provider: "test_provider", + uid: "existing_user-missing_email_confirmation" + } defdelegate insert(changeset, opts), to: RepoMock diff --git a/test/support/extensions/email_confirmation/user.ex b/test/support/extensions/email_confirmation/user.ex index c4d5a61..ec4ab90 100644 --- a/test/support/extensions/email_confirmation/user.ex +++ b/test/support/extensions/email_confirmation/user.ex @@ -2,12 +2,14 @@ defmodule PowAssent.Test.EmailConfirmation.Users.User do @moduledoc false use Ecto.Schema use Pow.Ecto.Schema + use Pow.Extension.Ecto.Schema, extensions: [PowEmailConfirmation] + use PowAssent.Ecto.Schema schema "users" do - field :name, :string + field(:name, :string) pow_user_fields() diff --git a/test/support/extensions/email_confirmation/user_identity.ex b/test/support/extensions/email_confirmation/user_identity.ex index 855a4f9..edc8d5f 100644 --- a/test/support/extensions/email_confirmation/user_identity.ex +++ b/test/support/extensions/email_confirmation/user_identity.ex @@ -1,6 +1,7 @@ defmodule PowAssent.Test.EmailConfirmation.UserIdentities.UserIdentity do @moduledoc false use Ecto.Schema + use PowAssent.Ecto.UserIdentities.Schema, user: PowAssent.Test.EmailConfirmation.Users.User diff --git a/test/support/extensions/invitation/phoenix/endpoint.ex b/test/support/extensions/invitation/phoenix/endpoint.ex index c4be828..214a330 100644 --- a/test/support/extensions/invitation/phoenix/endpoint.ex +++ b/test/support/extensions/invitation/phoenix/endpoint.ex @@ -2,29 +2,32 @@ defmodule PowAssent.Test.Invitation.Phoenix.Endpoint do @moduledoc false use Phoenix.Endpoint, otp_app: :pow_assent - plug Plug.RequestId - plug Plug.Logger + plug(Plug.RequestId) + plug(Plug.Logger) - plug Plug.Parsers, + plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library() + ) - plug Plug.MethodOverride - plug Plug.Head + plug(Plug.MethodOverride) + plug(Plug.Head) - plug Plug.Session, + plug(Plug.Session, store: :cookie, key: "_binaryid_key", signing_salt: "secret" + ) - plug Pow.Plug.Session, + plug(Pow.Plug.Session, user: PowAssent.Test.Invitation.Users.User, routes_backend: PowAssent.Test.Phoenix.Routes, messages_backend: PowAssent.Test.Phoenix.Messages, mailer_backend: PowAssent.Test.Phoenix.MailerMock, repo: PowAssent.Test.Invitation.RepoMock, otp_app: :pow_assent + ) - plug PowAssent.Test.Phoenix.Router + plug(PowAssent.Test.Phoenix.Router) end diff --git a/test/support/extensions/invitation/repo_mock.ex b/test/support/extensions/invitation/repo_mock.ex index ba47da4..c80c6c8 100644 --- a/test/support/extensions/invitation/repo_mock.ex +++ b/test/support/extensions/invitation/repo_mock.ex @@ -7,7 +7,9 @@ defmodule PowAssent.Test.Invitation.RepoMock do @user %User{id: 1, email: "test@example.com"} def get_by(User, [invitation_token: "token"], _opts), do: %{@user | invitation_token: "token"} - def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "new_identity"], _opts), do: nil + + def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "new_identity"], _opts), + do: nil defdelegate insert(changeset, opts), to: RepoMock diff --git a/test/support/extensions/invitation/user.ex b/test/support/extensions/invitation/user.ex index 89eaba3..f440371 100644 --- a/test/support/extensions/invitation/user.ex +++ b/test/support/extensions/invitation/user.ex @@ -2,15 +2,19 @@ defmodule PowAssent.Test.Invitation.Users.User do @moduledoc false use Ecto.Schema use Pow.Ecto.Schema + use Pow.Extension.Ecto.Schema, extensions: [PowInvitation] + use PowAssent.Ecto.Schema schema "users" do - has_many :user_identities, + has_many( + :user_identities, PowAssent.Test.Invitation.UserIdentities.UserIdentity, on_delete: :delete_all, foreign_key: :user_id + ) pow_user_fields() diff --git a/test/support/extensions/invitation/user_identity.ex b/test/support/extensions/invitation/user_identity.ex index 146c0f9..c5ce46a 100644 --- a/test/support/extensions/invitation/user_identity.ex +++ b/test/support/extensions/invitation/user_identity.ex @@ -1,6 +1,7 @@ defmodule PowAssent.Test.Invitation.UserIdentities.UserIdentity do @moduledoc false use Ecto.Schema + use PowAssent.Ecto.UserIdentities.Schema, user: PowAssent.Test.Invitation.Users.User diff --git a/test/support/mix/test_case.ex b/test/support/mix/test_case.ex index 914b0d3..033bf43 100644 --- a/test/support/mix/test_case.ex +++ b/test/support/mix/test_case.ex @@ -20,9 +20,9 @@ defmodule PowAssent.Test.Mix.TestCase do setup context do current_shell = Mix.shell() - on_exit fn -> + on_exit(fn -> Mix.shell(current_shell) - end + end) Mix.shell(Mix.Shell.Process) @@ -53,7 +53,12 @@ defmodule PowAssent.Test.Mix.TestCase do user_path: Path.join([context_path, "users", "user.ex"]) } - Map.merge(context, %{repo: Repo, context_module: context_module, web_module: web_module, paths: paths}) + Map.merge(context, %{ + repo: Repo, + context_module: context_module, + web_module: web_module, + paths: paths + }) end defp init_pow_phoenix_app_dir(context) do @@ -79,7 +84,8 @@ defmodule PowAssent.Test.Mix.TestCase do # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "\#{config_env()}.exs" - """) + """ + ) File.mkdir_p!(context.paths.web_path) @@ -109,7 +115,8 @@ defmodule PowAssent.Test.Mix.TestCase do plug Plug.Session, @session_options plug #{context.web_module}.Router end - """) + """ + ) File.write!( context.paths.router_path, @@ -132,7 +139,8 @@ defmodule PowAssent.Test.Mix.TestCase do get "/", PageController, :index end end - """) + """ + ) PowInstallTask.run(["-r", context.repo]) diff --git a/test/support/no_registration/phoenix/components/layouts.ex b/test/support/no_registration/phoenix/components/layouts.ex index e7e1c7a..cd9cdf2 100644 --- a/test/support/no_registration/phoenix/components/layouts.ex +++ b/test/support/no_registration/phoenix/components/layouts.ex @@ -2,5 +2,5 @@ defmodule PowAssent.Test.Reauthorization.Phoenix.Layouts do @moduledoc false use PowAssent.Test.Phoenix.Web, :html - embed_templates "../../../phoenix/components/layouts/*.html" + embed_templates("../../../phoenix/components/layouts/*.html") end diff --git a/test/support/no_registration/phoenix/endpoint.ex b/test/support/no_registration/phoenix/endpoint.ex index 952b5c6..5dcf1a7 100644 --- a/test/support/no_registration/phoenix/endpoint.ex +++ b/test/support/no_registration/phoenix/endpoint.ex @@ -2,28 +2,31 @@ defmodule PowAssent.Test.NoRegistration.Phoenix.Endpoint do @moduledoc false use Phoenix.Endpoint, otp_app: :pow_assent - plug Plug.RequestId - plug Plug.Logger + plug(Plug.RequestId) + plug(Plug.Logger) - plug Plug.Parsers, + plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library() + ) - plug Plug.MethodOverride - plug Plug.Head + plug(Plug.MethodOverride) + plug(Plug.Head) - plug Plug.Session, + plug(Plug.Session, store: :cookie, key: "_binaryid_key", signing_salt: "secret" + ) - plug Pow.Plug.Session, + plug(Pow.Plug.Session, user: PowAssent.Test.Ecto.Users.User, routes_backend: PowAssent.Test.Phoenix.Routes, messages_backend: PowAssent.Test.Phoenix.Messages, otp_app: :pow_assent, repo: PowAssent.Test.RepoMock + ) - plug PowAssent.Test.NoRegistration.Phoenix.Router + plug(PowAssent.Test.NoRegistration.Phoenix.Router) end diff --git a/test/support/no_registration/phoenix/router.ex b/test/support/no_registration/phoenix/router.ex index 8d3663d..d93f500 100644 --- a/test/support/no_registration/phoenix/router.ex +++ b/test/support/no_registration/phoenix/router.ex @@ -5,15 +5,15 @@ defmodule PowAssent.Test.NoRegistration.Phoenix.Router do use PowAssent.Phoenix.Router pipeline :browser do - plug :accepts, ["html"] - plug :fetch_session - plug :fetch_flash - plug :protect_from_forgery - plug :put_secure_browser_headers + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:protect_from_forgery) + plug(:put_secure_browser_headers) end scope "/" do - pipe_through :browser + pipe_through(:browser) pow_routes() pow_assent_authorization_routes() diff --git a/test/support/phoenix/components/layouts.ex b/test/support/phoenix/components/layouts.ex index 48f4f4e..9cb6c8b 100644 --- a/test/support/phoenix/components/layouts.ex +++ b/test/support/phoenix/components/layouts.ex @@ -2,5 +2,5 @@ defmodule PowAssent.Test.Phoenix.Layouts do @moduledoc false use PowAssent.Test.Phoenix.Web, :html - embed_templates "layouts/*.html" + embed_templates("layouts/*.html") end diff --git a/test/support/phoenix/endpoint.ex b/test/support/phoenix/endpoint.ex index 3a73005..f55bceb 100644 --- a/test/support/phoenix/endpoint.ex +++ b/test/support/phoenix/endpoint.ex @@ -2,28 +2,31 @@ defmodule PowAssent.Test.Phoenix.Endpoint do @moduledoc false use Phoenix.Endpoint, otp_app: :pow_assent - plug Plug.RequestId - plug Plug.Logger + plug(Plug.RequestId) + plug(Plug.Logger) - plug Plug.Parsers, + plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library() + ) - plug Plug.MethodOverride - plug Plug.Head + plug(Plug.MethodOverride) + plug(Plug.Head) - plug Plug.Session, + plug(Plug.Session, store: :cookie, key: "_binaryid_key", signing_salt: "secret" + ) - plug Pow.Plug.Session, + plug(Pow.Plug.Session, user: PowAssent.Test.Ecto.Users.User, routes_backend: PowAssent.Test.Phoenix.Routes, messages_backend: PowAssent.Test.Phoenix.Messages, otp_app: :pow_assent, repo: PowAssent.Test.RepoMock + ) - plug PowAssent.Test.Phoenix.Router + plug(PowAssent.Test.Phoenix.Router) end diff --git a/test/support/phoenix/messages.ex b/test/support/phoenix/messages.ex index fa42e07..283ad16 100644 --- a/test/support/phoenix/messages.ex +++ b/test/support/phoenix/messages.ex @@ -1,6 +1,7 @@ defmodule PowAssent.Test.Phoenix.Messages do @moduledoc false use Pow.Phoenix.Messages + use Pow.Extension.Phoenix.Messages, extensions: [ PowAssent, diff --git a/test/support/phoenix/router.ex b/test/support/phoenix/router.ex index a1df235..bab39b8 100644 --- a/test/support/phoenix/router.ex +++ b/test/support/phoenix/router.ex @@ -9,28 +9,28 @@ defmodule PowAssent.Test.Phoenix.Router do extensions: [PowEmailConfirmation] pipeline :browser do - plug :accepts, ["html"] - plug :fetch_session - plug :fetch_flash - plug :protect_from_forgery - plug :put_secure_browser_headers + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:protect_from_forgery) + plug(:put_secure_browser_headers) end pipeline :skip_csrf_protection do - plug :accepts, ["html"] - plug :fetch_session - plug :fetch_flash - plug :put_secure_browser_headers + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:put_secure_browser_headers) end scope "/" do - pipe_through :skip_csrf_protection + pipe_through(:skip_csrf_protection) pow_assent_authorization_post_callback_routes() end scope "/" do - pipe_through :browser + pipe_through(:browser) pow_routes() pow_assent_routes() diff --git a/test/support/phoenix/routes.ex b/test/support/phoenix/routes.ex index 54fd138..6d58a05 100644 --- a/test/support/phoenix/routes.ex +++ b/test/support/phoenix/routes.ex @@ -4,6 +4,7 @@ defmodule PowAssent.Test.Phoenix.Routes do def after_sign_in_path(%{assigns: %{request_path: request_path}}) when is_binary(request_path), do: request_path + def after_sign_in_path(_conn), do: "/session_created" def after_registration_path(_conn), do: "/registration_created" diff --git a/test/support/reauthorization/phoenix/endpoint.ex b/test/support/reauthorization/phoenix/endpoint.ex index e87057c..11eb499 100644 --- a/test/support/reauthorization/phoenix/endpoint.ex +++ b/test/support/reauthorization/phoenix/endpoint.ex @@ -2,23 +2,25 @@ defmodule PowAssent.Test.Reauthorization.Phoenix.Endpoint do @moduledoc false use Phoenix.Endpoint, otp_app: :pow_assent - plug Plug.RequestId - plug Plug.Logger + plug(Plug.RequestId) + plug(Plug.Logger) - plug Plug.Parsers, + plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library() + ) - plug Plug.MethodOverride - plug Plug.Head + plug(Plug.MethodOverride) + plug(Plug.Head) - plug Plug.Session, + plug(Plug.Session, store: :cookie, key: "_binaryid_key", signing_salt: "secret" + ) - plug Pow.Plug.Session, + plug(Pow.Plug.Session, user: PowAssent.Test.Ecto.Users.User, routes_backend: PowAssent.Test.Phoenix.Routes, messages_backend: PowAssent.Test.Phoenix.Messages, @@ -29,6 +31,7 @@ defmodule PowAssent.Test.Reauthorization.Phoenix.Endpoint do test_provider: [] ] ] + ) - plug PowAssent.Test.Reauthorization.Phoenix.Router + plug(PowAssent.Test.Reauthorization.Phoenix.Router) end diff --git a/test/support/reauthorization/phoenix/router.ex b/test/support/reauthorization/phoenix/router.ex index 44d53f8..a7990a5 100644 --- a/test/support/reauthorization/phoenix/router.ex +++ b/test/support/reauthorization/phoenix/router.ex @@ -5,17 +5,19 @@ defmodule PowAssent.Test.Reauthorization.Phoenix.Router do use PowAssent.Phoenix.Router pipeline :browser do - plug :accepts, ["html"] - plug :fetch_session - plug :fetch_flash - plug :protect_from_forgery - plug :put_secure_browser_headers - plug PowAssent.Plug.Reauthorization, + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:protect_from_forgery) + plug(:put_secure_browser_headers) + + plug(PowAssent.Plug.Reauthorization, handler: PowAssent.Phoenix.ReauthorizationPlugHandler + ) end scope "/" do - pipe_through :browser + pipe_through(:browser) pow_routes() pow_assent_routes() diff --git a/test/support/repo_mock.ex b/test/support/repo_mock.ex index 1bc062d..b8b5db2 100644 --- a/test/support/repo_mock.ex +++ b/test/support/repo_mock.ex @@ -4,35 +4,69 @@ defmodule PowAssent.Test.RepoMock do alias PowAssent.Test.Ecto.{UserIdentities.UserIdentity, Users.User} def one(query, _opts) do - case inspect(query) =~ "left_join: u1 in assoc(u0, :user)" and inspect(query) =~ "where: u0.provider == ^\"test_provider\" and u0.uid == ^\"existing_user\"" do - true -> %User{id: 1, email: "test@example.com"} + case inspect(query) =~ "left_join: u1 in assoc(u0, :user)" and + inspect(query) =~ + "where: u0.provider == ^\"test_provider\" and u0.uid == ^\"existing_user\"" do + true -> %User{id: 1, email: "test@example.com"} false -> nil end end - def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "existing_user"], _opts), do: %UserIdentity{user_id: 1, provider: "test_provider", uid: "existing_user"} - def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "new_identity"], _opts), do: nil - def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "identity_taken"], _opts), do: nil + def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "existing_user"], _opts), + do: %UserIdentity{user_id: 1, provider: "test_provider", uid: "existing_user"} + + def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "new_identity"], _opts), + do: nil + + def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "identity_taken"], _opts), + do: nil @spec insert(%{action: any, valid?: boolean}, any) :: {:error, %{action: :insert, valid?: false}} | {:ok, %{id: 1}} def insert(%{changes: %{provider: "test_provider", uid: "identity_taken"}} = changeset, _opts) do - changeset = Ecto.Changeset.add_error(changeset, :uid_provider, "has already been taken", constraint: :unique, constraint_name: "user_identities_uid_provider_index") + changeset = + Ecto.Changeset.add_error(changeset, :uid_provider, "has already been taken", + constraint: :unique, + constraint_name: "user_identities_uid_provider_index" + ) {:error, %{changeset | action: :insert}} end + def insert(%{changes: %{email: "taken@example.com"}} = changeset, _opts) do - changeset = Ecto.Changeset.add_error(changeset, :email, "has already been taken", constraint: :unique, constraint_name: "users_email_index") + changeset = + Ecto.Changeset.add_error(changeset, :email, "has already been taken", + constraint: :unique, + constraint_name: "users_email_index" + ) {:error, %{changeset | action: :insert}} end - def insert(%{valid?: true, changes: %{user_identities: [%{changes: %{provider: "test_provider", uid: "identity_taken"}} = user_identity_changeset]}} = changeset, _opts) do - user_identity_changeset = Ecto.Changeset.add_error(user_identity_changeset, :uid_provider, "has already been taken", constraint: :unique, constraint_name: "user_identities_uid_provider_index") + + def insert( + %{ + valid?: true, + changes: %{ + user_identities: [ + %{changes: %{provider: "test_provider", uid: "identity_taken"}} = + user_identity_changeset + ] + } + } = changeset, + _opts + ) do + user_identity_changeset = + Ecto.Changeset.add_error(user_identity_changeset, :uid_provider, "has already been taken", + constraint: :unique, + constraint_name: "user_identities_uid_provider_index" + ) + user_identity_changeset = %{user_identity_changeset | action: :insert} - changeset = Ecto.Changeset.put_change(changeset, :user_identities, [user_identity_changeset]) + changeset = Ecto.Changeset.put_change(changeset, :user_identities, [user_identity_changeset]) {:error, %{changeset | action: :insert}} end + def insert(%{valid?: true, data: %mod{}} = changeset, _opts) do struct = %{Ecto.Changeset.apply_changes(changeset) | id: :inserted} @@ -41,6 +75,7 @@ defmodule PowAssent.Test.RepoMock do {:ok, struct} end + def insert(%{valid?: false} = changeset, _opts), do: {:error, %{changeset | action: :insert}} def update(%{valid?: true, data: %mod{}} = changeset, _opts) do @@ -54,12 +89,21 @@ defmodule PowAssent.Test.RepoMock do def get_by!(struct, [id: id], _opts), do: Process.get({struct, id}) - def preload(%User{id: :multiple_identities} = user, :user_identities, force: true), do: %{user | user_identities: [%UserIdentity{id: 1, provider: "test_provider"}, %UserIdentity{id: 2, provider: "other_provider"}]} - def preload(user, :user_identities, force: true), do: %{user | user_identities: [%UserIdentity{id: 1, provider: "test_provider"}]} + def preload(%User{id: :multiple_identities} = user, :user_identities, force: true), + do: %{ + user + | user_identities: [ + %UserIdentity{id: 1, provider: "test_provider"}, + %UserIdentity{id: 2, provider: "other_provider"} + ] + } + + def preload(user, :user_identities, force: true), + do: %{user | user_identities: [%UserIdentity{id: 1, provider: "test_provider"}]} def delete_all(query, _opts) do case inspect(query) =~ "where: u0.user_id == ^1, where: u0.id in ^[1]" do - true -> {1, nil} + true -> {1, nil} false -> {0, nil} end end diff --git a/test/support/strategies/oauth2_test_case.ex b/test/support/strategies/oauth2_test_case.ex index b4395b2..7ac8f74 100644 --- a/test/support/strategies/oauth2_test_case.ex +++ b/test/support/strategies/oauth2_test_case.ex @@ -6,7 +6,12 @@ defmodule PowAssent.Test.OAuth2TestCase do TestServer.start(scheme: :https) params = %{"code" => "test", "state" => "test"} - config = [client_secret: "secret", base_url: TestServer.url(), session_params: %{state: "test"}] + + config = [ + client_secret: "secret", + base_url: TestServer.url(), + session_params: %{state: "test"} + ] {:ok, callback_params: params, config: config} end @@ -25,14 +30,17 @@ defmodule PowAssent.Test.OAuth2TestCase do def add_oauth2_access_token_endpoint(opts \\ [], assert_fn \\ nil) do access_token = Keyword.get(opts, :access_token, "access_token") token_params = Keyword.get(opts, :params, %{access_token: access_token}) - uri = Keyword.get(opts, :uri, "/oauth/token") - status_code = Keyword.get(opts, :status_code, 200) + uri = Keyword.get(opts, :uri, "/oauth/token") + status_code = Keyword.get(opts, :status_code, 200) - TestServer.add(uri, via: :post, to: fn conn -> - if assert_fn, do: assert_fn.(conn) + TestServer.add(uri, + via: :post, + to: fn conn -> + if assert_fn, do: assert_fn.(conn) - send_json_resp(conn, token_params, status_code) - end) + send_json_resp(conn, token_params, status_code) + end + ) end @spec add_oauth2_user_endpoint(map(), Keyword.t(), function() | nil) :: :ok @@ -45,15 +53,17 @@ defmodule PowAssent.Test.OAuth2TestCase do @spec add_oauth2_api_endpoint(binary(), map(), Keyword.t(), function() | nil) :: :ok def add_oauth2_api_endpoint(uri, response, opts \\ [], assert_fn \\ nil) do access_token = Keyword.get(opts, :access_token, "access_token") - status_code = Keyword.get(opts, :status_code, 200) + status_code = Keyword.get(opts, :status_code, 200) - TestServer.add(uri, to: fn conn -> - if assert_fn, do: assert_fn.(conn) + TestServer.add(uri, + to: fn conn -> + if assert_fn, do: assert_fn.(conn) - assert_bearer_token_in_header(conn, access_token) + assert_bearer_token_in_header(conn, access_token) - send_json_resp(conn, response, status_code) - end) + send_json_resp(conn, response, status_code) + end + ) end defp assert_bearer_token_in_header(conn, token) do diff --git a/test/support/test_provider.ex b/test/support/test_provider.ex index 80a94c2..5fa6656 100644 --- a/test/support/test_provider.ex +++ b/test/support/test_provider.ex @@ -25,9 +25,18 @@ defmodule PowAssent.Test.TestProvider do put_oauth2_env() token_params = Keyword.get(opts, :token, %{"access_token" => "access_token"}) - user_params = Map.merge(%{sub: "new_user", name: "John Doe", email: "test@example.com"}, Keyword.get(opts, :user, %{})) - OAuth2TestCase.add_oauth2_access_token_endpoint([params: token_params], opts[:access_token_assert_fn]) + user_params = + Map.merge( + %{sub: "new_user", name: "John Doe", email: "test@example.com"}, + Keyword.get(opts, :user, %{}) + ) + + OAuth2TestCase.add_oauth2_access_token_endpoint( + [params: token_params], + opts[:access_token_assert_fn] + ) + OAuth2TestCase.add_oauth2_user_endpoint(user_params) end @@ -36,24 +45,32 @@ defmodule PowAssent.Test.TestProvider do url = Keyword.get(config, :base_url) || TestServer.url() %{host: host} = URI.parse(url) - httpc_opts = Keyword.get(config, :base_url) || [ - ssl: [ - verify: :verify_peer, - depth: 99, - cacerts: TestServer.x509_suite().cacerts, - verify_fun: {&:ssl_verify_hostname.verify_fun/3, check_hostname: to_charlist(host)}, - customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)] - ] - ] + httpc_opts = + Keyword.get(config, :base_url) || + [ + ssl: [ + verify: :verify_peer, + depth: 99, + cacerts: TestServer.x509_suite().cacerts, + verify_fun: {&:ssl_verify_hostname.verify_fun/3, check_hostname: to_charlist(host)}, + customize_hostname_check: [ + match_fun: :public_key.pkix_verify_hostname_match_fun(:https) + ] + ] + ] Application.put_env(:pow_assent, :pow_assent, providers: [ - test_provider: Keyword.merge([ - client_id: "client_id", - client_secret: "abc123", - base_url: url, - strategy: __MODULE__ - ], config) + test_provider: + Keyword.merge( + [ + client_id: "client_id", + client_secret: "abc123", + base_url: url, + strategy: __MODULE__ + ], + config + ) ], http_adapter: {Assent.HTTPAdapter.Httpc, httpc_opts} ) diff --git a/test/support/with_custom_changeset/phoenix/endpoint.ex b/test/support/with_custom_changeset/phoenix/endpoint.ex index 6f88c8e..a8466ca 100644 --- a/test/support/with_custom_changeset/phoenix/endpoint.ex +++ b/test/support/with_custom_changeset/phoenix/endpoint.ex @@ -2,29 +2,32 @@ defmodule PowAssent.Test.WithCustomChangeset.Phoenix.Endpoint do @moduledoc false use Phoenix.Endpoint, otp_app: :pow_assent - plug Plug.RequestId - plug Plug.Logger + plug(Plug.RequestId) + plug(Plug.Logger) - plug Plug.Parsers, + plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library() + ) - plug Plug.MethodOverride - plug Plug.Head + plug(Plug.MethodOverride) + plug(Plug.Head) - plug Plug.Session, + plug(Plug.Session, store: :cookie, key: "_binaryid_key", signing_salt: "secret" + ) - plug Pow.Plug.Session, + plug(Pow.Plug.Session, user: PowAssent.Test.WithCustomChangeset.Users.User, routes_backend: PowAssent.Test.Phoenix.Routes, messages_backend: PowAssent.Test.Phoenix.Messages, mailer_backend: PowAssent.Test.Phoenix.MailerMock, repo: PowAssent.Test.WithCustomChangeset.RepoMock, otp_app: :pow_assent + ) - plug PowAssent.Test.Phoenix.Router + plug(PowAssent.Test.Phoenix.Router) end diff --git a/test/support/with_custom_changeset/repo_mock.ex b/test/support/with_custom_changeset/repo_mock.ex index 97b90d0..71d7cc4 100644 --- a/test/support/with_custom_changeset/repo_mock.ex +++ b/test/support/with_custom_changeset/repo_mock.ex @@ -5,14 +5,19 @@ defmodule PowAssent.Test.WithCustomChangeset.RepoMock do alias PowAssent.Test.WithCustomChangeset.{UserIdentities.UserIdentity, Users.User} def one(query, _opts) do - case inspect(query) =~ "left_join: u1 in assoc(u0, :user)" and inspect(query) =~ "where: u0.provider == ^\"test_provider\" and u0.uid == ^\"existing_user\"" do - true -> %User{id: 1, email: "test@example.com"} + case inspect(query) =~ "left_join: u1 in assoc(u0, :user)" and + inspect(query) =~ + "where: u0.provider == ^\"test_provider\" and u0.uid == ^\"existing_user\"" do + true -> %User{id: 1, email: "test@example.com"} false -> nil end end - def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "new_identity"], _opts), do: nil - def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "existing_user"], _opts), do: %UserIdentity{user_id: 1, provider: "test_provider", uid: "existing_user"} + def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "new_identity"], _opts), + do: nil + + def get_by(UserIdentity, [user_id: 1, provider: "test_provider", uid: "existing_user"], _opts), + do: %UserIdentity{user_id: 1, provider: "test_provider", uid: "existing_user"} defdelegate insert(changeset, opts), to: RepoMock diff --git a/test/support/with_custom_changeset/user_identity.ex b/test/support/with_custom_changeset/user_identity.ex index 25f0543..9ecd8b2 100644 --- a/test/support/with_custom_changeset/user_identity.ex +++ b/test/support/with_custom_changeset/user_identity.ex @@ -1,14 +1,15 @@ defmodule PowAssent.Test.WithCustomChangeset.UserIdentities.UserIdentity do @moduledoc false use Ecto.Schema + use PowAssent.Ecto.UserIdentities.Schema, user: PowAssent.Test.WithCustomChangeset.Users.User schema "user_identities" do - field :access_token, :string - field :refresh_token, :string + field(:access_token, :string) + field(:refresh_token, :string) - field :name, :string + field(:name, :string) pow_assent_user_identity_fields()