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
There's several ways to do this. Let me go through a few options from minimal changes to most:
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{}) %>
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
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?
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 馃槃
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.
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 馃槃
2. Use controller callbacks to append assigns
Make a custom controller callbacks module that adds the assigns:
# config controller_callbacks: MyAppWeb.PowControllerCallbacksdefmodule 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.
Most helpful comment
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/2method:2. Use controller callbacks to append assigns
Make a custom controller callbacks module that adds the assigns:
3. Custom Controller
To understand how Pow controllers works, look at this Pow controller action:
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/1to accept the right response (Pow.Plug.authenticate_user/2has different return tuple toPow.Plug.create_user/2):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?