pow
pow copied to clipboard
Making `PowEmailConfirmation` play nice with custom controllers
Hi,
Thanks for all your hard work on this great library! I got user email confirmation up and running in just a couple of hours thanks to pow 😁
I wanted to share my experience, both for future users who might encounter my same issue, and as a feature suggestion.
I use a custom controller for registration with pow. When I was setting up PowEmailConfirmation
I ran into an issue when a user clicked on the confirmation link that is sent after updating their email (though if I had been using a custom session controller as well, this issue would also appear when a user first confirms their email):
** (UndefinedFunctionError) function MyAppWeb.Router.Helpers.pow_registration_path/3 is undefined or private
This occurs because after PowEmailConfirmation.Phoenix.ConfirmationController
finishes confirming the token, it calls redirect_to/1
(https://github.com/danschultzer/pow/blob/e4b97c3dffbe287e19bde74e2be029644a0e4038/lib/extensions/email_confirmation/phoenix/controllers/confirmation_controller.ex#L25), which in turn tries to call Router.Helpers.pow_registration_path/3
through a function routes(conn).registration_path/2
which I don't totally understand.
If you, like me, don't want to write a ConfirmationController
yourself, you can fix this issue with an ugly hack in your Router
:
resources "/user", RegistrationController, only: [:show, :edit, :update], singleton: true
# miserable hack to comply with `PowEmailConfirmation`s expectations
resources "/uuser", RegistrationController, only: [:edit], singleton: true, as: :pow_registration
This essential "redirects" :pow_registration_path
to RegistrationController
while also allowing you to keep using Routes.registration_path
elsewhere in your application. (You'd do the same thing for SessionController
:new
if you had a custom SessionController
.)
The downside is that after a user confirms their updated email, they'll find themselves at "myapp.com/uuser" instead of "myapp.com/user".
As a feature request: it would be great if we could somehow inform pow that we're using a custom controller for registration/session (or maybe there already is? I don't really understand routes(conn).registration_path
...)
There's a much simpler way, as the registration_path/2
calls the callback routes module. You can create a custom routes module and then just use your registration path:
defmodule MyAppWeb.Pow.Routes do
use Pow.Phoenix.Routes
alias MyAppWeb.Router.Helpers, as: Routes
def registration_path(conn, verb), do: Routes.registration_path(conn, verb)
end
An (uglier) alternative is to set use the pow alias in the scope for the registration routes:
resources "/user", RegistrationController, only: [:show, :edit, :update], singleton: true
scope "/", as: :pow do
resources "/user", RegistrationController, only: [:show, :edit, :update], singleton: true
end
The docs could need an update regarding mix with extensions. Ideally custom controllers mean that you take full control over the flow though and don't use any the extensions either, but I do understand it's not really the best for all cases. I'm thinking of introducing mix tasks to produce custom controllers which will produce something akin to mix phx.gen.auth
where you will be able to understand how it's all connected.
"I'm thinking of introducing mix tasks to produce custom controllers which will produce something akin to mix phx.gen.auth where you will be able to understand how it's all connected." I think that would be awesome, I do hope that won't cause a lot of work. In fact you would be translating back to general code a lot of the stuff you got incorporated in Pow.
In fact you would be translating back to general code a lot of the stuff you got incorporated in Pow.
Yeah, it's one of the things I'm trying to figure out. I would probably still leverage Pow quite a bit focusing on making it super easy customizing flow this way. phx.gen.auth
introduces code injection in mix tasks, so I feel that now I can finally do with Pow what I originally wanted to do 😄 (back then there was no "official" library that did code injection so I assumed it was just a bad idea)