Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Help with email confirmation flow in custom controller #57

Closed
th31nitiate opened this issue Dec 28, 2018 · 5 comments
Closed

Help with email confirmation flow in custom controller #57

th31nitiate opened this issue Dec 28, 2018 · 5 comments

Comments

@th31nitiate
Copy link

I am fully loving pow so far but I am finding very hard to integrate or use with other libs due to the controller methods. When we try to implement a new flow its never easy because the custom controller methods lack guidance around how to to properly utilise these.

I had to create custom controller since I am using assigns in weird way in which I require them to generate the reg form and sign in forms. This is working, we now have a problem when it comes to email confirmation as this is not functional at all.

We manage to implement this based on a previous issue:

defmodule AppWeb.RegistrationController do
    use AppWeb, :controller
    alias PowEmailConfirmation.Phoenix.ControllerCallbacks, as: PowEmailConfirmationControllerCallbacks

    def new(conn, _params) do
      changeset = Pow.Plug.change_user(conn)
      form = create_form(App.Users.UserType, %App.Users.User{})

      render(conn, "new.html", changeset: changeset, form: form)
    end

    def create(conn, %{"user" => user_params}) do
      conn
      |> Pow.Plug.create_user(user_params)
      |> verify_confirmed()
    end

    defp verify_confirmed({:error, conn}), do: render(conn, "new.html")

    defp verify_confirmed({:ok, conn}) do
      user = Pow.Plug.current_user(conn)
  
      if confirmed?(user) do
        conn
        |> put_flash(:info, "Welcome!")
        |> redirect(to: Routes.page_path(conn, :index))
      else
        PowEmailConfirmationControllerCallbacks.send_confirmation_email(user, conn)
  
       {:ok, conn} = Pow.Plug.clear_authenticated_user(conn)
        # conn session has been reset and error can be returned
        render(conn, "new.html")
      end
    end
  
    defp confirmed?(%{email_confirmed_at: nil, email_confirmation_token: token}) when not is_nil(token), do: false
    defp confirmed?(_user), do: true
end

We mainly seem to be running in to issue around the expected return type. We have had to disable this feature for the time being as we are still in development. If there a better or easier way to pass assigns to Pow controllers ? Can you provide the best method to add or call the extension from here from the reg controller ?

The error we tend to see if the following: no function clause matching in AppWeb.RegistrationController.verify_confirmed/1 and this occurs on the following line: defp verify_confirmed({:error, conn}), do: render(conn, "new.html")

I like the following which is the initial example we went with as it provides a decent place to start building from:

defmodule AppWeb.RegistrationController do
    use AppWeb, :controller

    def new(conn, _params) do
      changeset = Pow.Plug.change_user(conn)
      form = create_form(App.Users.UserType, %App.Users.User{})
      render(conn, "new.html", changeset: changeset, form: form)
    end

    def create(conn, %{"user" => user_params}) do
      conn
      |> Pow.Plug.create_user(user_params)
      |> case do
        {:ok, user, conn} ->
          conn
          |> put_flash(:info, "Welcome!")
          |> redirect(to: Routes.page_path(conn, :index))

        {:error, changeset, conn} ->
          render(conn, "new.html", changeset: changeset)
      end
    end
end
@danschultzer
Copy link
Collaborator

danschultzer commented Dec 28, 2018

There's several ways to do this. Let me go through a few options from minimal changes to most:

1. Only update HTML templates

Use default pow controllers, and only change the HTML templates to call the create_form/2 method:

<h1>Register</h1>

<%= create_form(App.Users.UserType, %App.Users.User{}) %>

2. Use controller callbacks to append assigns

Make a custom controller callbacks module that adds the assigns:

# config
controller_callbacks: MyAppWeb.PowControllerCallbacks
defmodule MyAppWeb.PowControllerCallbacks do
  def before_respond(Pow.Phoenix.RegistrationController, :new, {:ok, conn}, _config) do
    conn = Plug.Conn.assign(conn, :form, create_form(App.Users.UserType, %App.Users.User{})))

    {:ok, conn}
  end

  # For extensions
  def before_process(controller, action, results, config), do: Pow.Extension.Phoenix.ControllerCallbacks.before_process(controller, action, results, config)
  def before_respond(controller, action, results, config), do: Pow.Extension.Phoenix.ControllerCallbacks.before_respond(controller, action, results, config)
end

3. Custom Controller

To understand how Pow controllers works, look at this Pow controller action:

  @doc """
  Handles the controller action call.
  If a `:controller_callbacks` module has been set in the configuration,
  then `before_process` and `before_respond` will be called on this module
  on all actions.
  """
  @spec action(atom(), Conn.t(), map()) :: Conn.t()
  def action(controller, %{private: private} = conn, params) do
    action    = private.phoenix_action
    config    = Plug.fetch_config(conn)
    callbacks = Config.get(config, :controller_callbacks)

    conn
    |> maybe_callback(callbacks, :before_process, controller, action, config)
    |> process_action(controller, action, params)
    |> maybe_callback(callbacks, :before_respond, controller, action, config)
    |> respond_action(controller, action)
  end

When you override the controllers this logic will be lost, and no extension callbacks will occur. So you'll have to add the logic yourself, like you've done. In your case you just have to update verify_confirmed/1 to accept the right response (Pow.Plug.authenticate_user/2 has different return tuple to Pow.Plug.create_user/2):

    defp verify_confirmed({:error, conn, _user}), do: render(conn, "new.html")

    defp verify_confirmed({:ok, conn, user}) do
      # ...
    end

There's a guide for custom controllers, and it may be good to add a section regarding how to enable extensions. Or maybe this reply should be a new guide to show the different ways that Pow can be customized?

@danschultzer
Copy link
Collaborator

Also, if you have more examples of customization please post them! It helps a lot to shape docs or api to make Pow easier to work with. Making customization easy is an essential part of Pow 😄

@th31nitiate
Copy link
Author

Thank you. I am going to try your suggestion and then when I understand it, I will try update docs and create a pull request.

@danschultzer
Copy link
Collaborator

I've updated the custom controller guide. I think a guide on controller callbacks could be good too, but not sure where that fits. In any case, feel free to open issue, PR or comment here if you got some ideas for how to improve Pow 😄

@popo63301
Copy link
Contributor

popo63301 commented Jun 21, 2019

2. Use controller callbacks to append assigns

Make a custom controller callbacks module that adds the assigns:

# config
controller_callbacks: MyAppWeb.PowControllerCallbacks
defmodule MyAppWeb.PowControllerCallbacks do
  def before_respond(Pow.Phoenix.RegistrationController, :new, {:ok, conn}, _config) do
    conn = Plug.Conn.assign(conn, :form, create_form(App.Users.UserType, %App.Users.User{})))

    {:ok, conn}
  end

  # For extensions
  def before_process(controller, action, results, config), do: Pow.Extension.Phoenix.ControllerCallbacks.before_process(controller, action, results, config)
  def before_response(controller, action, results, config), do: Pow.Extension.Phoenix.ControllerCallbacks.before_response(controller, action, results, config)
end

It's not before_response in the last function definition but rather before_respond. It's a small typo detected thanks to my VSCode IntelliSense but some people may not notice it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants