Creating custom controller callbacks with Pow

I’m working on an app that uses Pow to handle user authentication. I was able to get it working by following the documentation, but I ran into an issue that I don’t see a clear solution for.

The application is for a subscription service where new users are given a free trial after signing up. I use Pow to create the user, which works fine. Now, I’m trying to associate the newly created user with a subscription using the controller callback feature from the Phoenix controllers section of the README. Here’s my implementation so far:

defmodule AddSubscription.Phoenix.ControllerCallbacks do
  use Pow.Extension.Phoenix.ControllerCallbacks.Base

  require Logger

  @impl true
  def before_respond(Pow.Phoenix.RegistrationController, :create, {:ok, user, conn}, _config) do
    Logger.debug("user registration before_respond: #{inspect(user)}")
    with {:ok, user} <- Trumpet.insert_subscription(%{user_id: user.id}) do
      {:ok, user, conn}
    end
  end
end

I’ve also written a controller test to exercise the behavior works as intended:

defmodule Pow.Phoenix.RegistrationControllerTest do
  use TrumpetWeb.ConnCase

  describe "POST /registration" do
    test "creates a user with a subscription", %{conn: conn} do
      email = "wembley.gl@gmail.com"
      post(
        conn,
        "/registration",
        %{
          user: %{
            email: email,
            password: "test1234567890",
            confirm_password: "test1234567890",
          }
        }
      )
      user = Trumpet.get_user_by_email_with_subscription(email)
      assert user.subscription
      assert Trumpet.Subscription.trial?(user.subscription)
    end
  end
end

The user creation works as expected, but the controller callback isn’t firing, so the assertion for user.subscription fails. I’ve taken a look at the Extensions section for Pow, from here, and looked at the implementation for other extensions, like PowPasswordReset, and it looks like an extension should be implemented as a library application, is that right? Also, how do I configure Pow to register an extension once it’s created? I apologize if this is in the documentation and I’m missing it.

The callbacks is the wrong place to deal with this. You would want it to be inserted in a transaction so if the subscription insert fails, the user won’t be created. There’re multiple ways to deal with it, but if all users inserted needs to have a subscription created along it, then I would keep it simple and just deal with it in the user changeset:

defmodule MyApp.Users.User do
  use Ecto.Schema
  use Pow.Ecto.Schema

  schema "users" do
    has_one :subscription, MyApp.Users.Subscription

    pow_user_fields()

    timestamps()
  end

  def changeset(user_or_changeset, attrs) do
    user
    |> pow_changeset(attrs)
    |> maybe_add_subscription()
  end

  defp maybe_add_subscription(changeset) do
    case Ecto.get_meta(changeset.data, :state) do
      :built  -> add_subscription(changeset)
      :loaded -> changeset
    end
  end
  
  defp add_subscription(changeset) do
    Ecto.Changeset.cast_assoc(changeset, :subscription, with: &MyApp.Users.Subscription.changeset/2)
  end
end

If you need more control over the flow, then I would either set up a custom controller and/or custom context module.

(Also, as you guessed the controller callbacks module you’ve set up is only for custom extensions)

1 Like

Thanks for the quick response. You make a valid point. I was caught up trying to leverage the functionality in Pow, that I forgot that I was able to make modifications to the User changeset directly, since all I did to add the Pow support was add pow_user_fields and the various use macros to bring in functions.

1 Like

Hi @danschultzer, I was planning on adding a referral code in Login form, i generated the templates and added referral_code as text field, in the generated registration form. I have few doubts with the custom controllers
(The ask is): I want to add this referral code only on registration controller, in a transactional way. if code given , then i check presence of code and finally create user. If code not given, i directly create user.
I use pow_assent. I have read about invitation extension but i want to go with my own.

Doubts:

  1. I want to modify only create action of registration controller. Do i need to modify the routes as well?
  2. Do i need to add all action for both registration, session controllers and point their routes as mentioned in https://hexdocs.pm/pow/1.0.11/custom_controllers.html#routes.
  3. Is there a way to overwrite only create action of registration controller?

There is a duplicate thread that I answered this in: POW: Custom controller doubts

1 Like

Yes, looks like there was a network issue, i deleted this thread before creating a new one. but i think elixir-forum runs thread deletion in a background job.

Thanks again @danschultzer, I will try those steps and yet you know how it went in POW: Custom controller doubts