POW: Custom controller doubts

Yes you can make any controller to overwrite a specific route and its action.

I have overwritten Pow registration controller with just a regular controller. For my understanding , i kind of added made it look alike. When you ran mix pow.phx.gen.templates, it creates a pow/registration/files inside lib/templates. I did the same added pow/registration_controller inside lib/controllers.

Entire Code Block

I created a module and named it pow/.
scope "/" do
    pipe_through [:browser]
    resources "/registration", MyAppWeb.Pow.RegistrationController, singleton: true, only: [:create]
  end
controller:
defmodule MyAppWeb.Pow.RegistrationController do
  use MyAppWeb, :controller

  # def new(conn, _params) do
  #   # We'll leverage [`Pow.Plug`](Pow.Plug.html), but you can also follow the classic Phoenix way:
  #   # changeset = MyApp.Users.User.changeset(%MyApp.Users.User{}, %{})

  #   changeset = Pow.Plug.change_user(conn)

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

  def create(conn, %{"user" => user_params}) do
    # We'll leverage [`Pow.Plug`](Pow.Plug.html), but you can also follow the classic Phoenix way:
    # user =
    #   %MyApp.Users.User{}
    #   |> MyApp.Users.User.changeset(user_params)
    #   |> MyApp.Repo.insert()

    case MyApp.Users.create_user_using_referral(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "Welcome, Lets find your spouse!")
        |> redirect(to: Routes.dashboard_path(conn, :index))
      {:invalid_code, changeset} ->
        conn
        |> put_flash(:error, "You have entered an invalid code")
        |> render("new.html", changeset: changeset, action: Routes.registration_path(conn, :create))
      {:error, changeset} ->
        conn
        |> put_flash(:error, "Please correct the errors")
        |> render("new.html", changeset: changeset, action: Routes.registration_path(conn, :create))
    end
  end
end

And i used pow custom context as well.

defmodule MyApp.Users do
   use Pow.Ecto.Context,
        repo: MyApp.Repo,
       user: MyApp.Users.User

  def create_user_using_referral(params) do
    case params["referral_code"] do
      nil -> pow_create(params)
      "" -> pow_create(params)
      code ->
        case MyApp.Trial.get_by_referral_code(code) do
          nil ->
            changeset = User.changeset(%User{}, params)
            Ecto.Changeset.add_error(changeset, :referral_code, "Entered code is invalid", validation: :invalid)
            {:invalid_code, changeset}
          referral_code ->
            pow_create(params)
        end
    end
  end
end
2 Likes

You can both customize messages and redirect paths. The conn is passed along so you could even add some conditional based on, e.g. the @changeset assign for the message or route.

But the way @tenzil resolved it is what I prefer since itā€™s much more explicit, and it makes sense when you e.g. add controller test to it. One idea I have of Pow is that in most cases it should work more as a helper than a framework, so whenever you customize stuff you basically write it all out so itā€™s easy to test, understand and take control of rather than depending on the library.

I definitely want to ease these steps so please do let me know what could be improved!

2 Likes

@danschultzer, as i was creating user with pow_create(params), I am not able to signin the user after registration. How to signup the user after this? I am going to try with sync user function.

Sync user function worked!

defmodule MyAppWeb.Pow.User do
  @spec sync_user(Plug.Conn.t(), map()) :: Plug.Conn.t()
  def sync_user(conn, user) do
    config = Pow.Plug.fetch_config(conn)
    plug   = Pow.Plug.get_plug(config)

    plug.do_create(conn, user, config)
  end
end
1 Like

Hi @danschultzer, hope you are doing well. My custom registration controller is working fine, with and without referral codes. Now the thing is, i added few extensions ā€œreset password and confirmation with emailsā€. The setup is working fine for reset password. but it didnā€™t for confirmation. I googled and found out this link https://github.com/danschultzer/pow/blob/6518aaad2f1cbb680ac8ff8e8760021f00011717/lib/extensions/email_confirmation/phoenix/controllers/controller_callbacks.ex#L53-L60
and added this function. It worked fine. I send the email as well.

But the issue is, when i click the link. its not confirming the account, its throwing ā€œEmail couldnā€™t be confirmedā€

The expectation is, I need to register a unconfirmed account and send a confirmation email using which user can access the website.

It has changed since then: https://github.com/danschultzer/pow/blob/v1.0.19/lib/extensions/email_confirmation/phoenix/controllers/controller_callbacks.ex#L127-L140

If you use 1.0.19 and up, then all tokens need to be signed before they can be used.

1 Like

Thank you @danschultzer for the quick response.

Yes, i recently updated it, like 2 days ago.

I updated few lines to get it work for me.

@spec send_confirmation_email(map(), Conn.t()) :: any()
  def send_confirmation_email(user, conn) do
    url               = confirmation_url(conn, user)
    unconfirmed_user  = %{user | email: user.unconfirmed_email || user.email}
    email             = Mailer.email_confirmation(conn, unconfirmed_user, url)

    Pow.Phoenix.Mailer.deliver(conn, email)
  end

  defp confirmation_url(conn, user) do
    token = PowEmailConfirmation.Plug.sign_confirmation_token(conn, user)
    ConfirmationController.routes(conn).url_for(conn, ConfirmationController, :show, [token])
  end

Now i got ā€œThe email address has been confirmedā€ and got redirected to registration edit page.
How to register/signup an unconfirmed email, and redirect to a different/custom url post confirmation.

You would need to add a custom controller action for that. The logic is super simple though: pow/lib/extensions/email_confirmation/phoenix/controllers/confirmation_controller.ex at v1.0.19 Ā· pow-auth/pow Ā· GitHub

You just have to load the user by the token with PowEmailConfirmation.Plug.load_user_by_token/2 and then call PowEmailConfirmation.Plug.Plug.confirm_email/2 to confirm it.

Hi @danschultzer, i gone through the code. Looks like unconfirmed_email carries nil value on user creation in my custom registration controller. So i added a check to update unconfirmed_email.
Now its working as expected. After registration, confirmation link is sent. post clicking the confirmation link, the account is confirmed.

def changeset(user_or_changeset, attrs) do
    user_or_changeset
    |> pow_changeset(attrs)
    |> pow_extension_changeset(attrs)
    |> Ecto.Changeset.validate_required([:fname, :lname, :dob, :gender])
    |> set_age()
    |> unconfirm_email
  end

  defp unconfirm_email(%Ecto.Changeset{valid?: true, changes: %{email: email}} = changeset) do
    put_change(changeset, :unconfirmed_email, email)
  end 
    

1 Like