There’s been a few questions on Github on how to use Pow on read-only platforms like Heroku. For production run the ETS cache is not recommended, and the Mnesia cache requires write access.
You can try it out by pulling the pow-invitation branch from github. It’s still WIP but it works. As always, feedback is most welcome and will help a lot!
This has been a highly requested feature, and I’m happy that we finally got a flexible solution in Pow.
What’s next?
WebAuthn
WebAuthn standard has been finalized! I believe Pow should have built-in 2FA/password-less authentication support, and this is something I hope to look at soon. I would be grateful for any input.
So far this has been a self-funded project, and I’ve been fortunate enough to have enough time to work full time on this. However I’ll probably get back to my startup life soon, so I’ve worked hard to streamline the codebase, and made it easy for others to join in.
The best way to help out is to open PR’s or helping with issues. When you become familiar with how Pow works you will be invited to join as maintainer. Thanks!
Security audit
Pow definitely needs some eyes on security practices. @griffinbyatt is one of the few people I’ve seen doing this. If there’s any person or company that’s willing to put some time into this, I would be deeply grateful.
Additionally, some of us could really use a separate umbrella step-by-step tutorial. Right now I am not even sure if some parts of the config belong to the Ecto app or the Phoenix app.
As an example: when I execute mix pow.phoenix.gen.templates it keeps telling me to add the web_module: ... snippet to my config even though both my apps have that in their config. So it quickly gets confusing for umbrella users.
I think this confusion can be eliminated by just having better mix helper messages, e.g. ignore :web_module instructions if the :web_module has already been set in config. This would probably go most of the way. You would only need to put the config in the Phoenix app.
So I played a bit with Pow and I can say I am very pleased with its modules and their docs.
Please do you think adding a feature such as disabling users so when the disabled users try to sign in they get a relevant message like “Sorry your account is disabled”. Or maybe this is not a too common feature ?
For now I extendend the users_context module to check if the the authenticating user is disabled and return nil in the authenticate/1 method.
Unfortunately doing that makes both a user entering wrong credentials and a disabled one entering right credentials, get the same error message.
So if I understand well the docs, I will have to create a custom session controller and edit web_app/router.ex. And also provide custom authorization + error handler plugs.
I just want to be sure that this the good way for doing things. But if you’re planing to add such a feature or to make its implementation even easier, it will be of course also fine!
You are the second dev to ask for it in two days, so there’s definitely good interest for it. However, I think it’s so trivial that it’s better to let the dev take charge and modify the flow. It helps ensure that developers also adds proper tests to their flow
It’s best to handle it at the plug level. The context module is responsible for interactions with the database, and I prefer that they have very limited logic.
I would add a plug module like this to handle locked accounts:
defmodule MyAppWeb.EnsureUserNotLockedPlug do
@moduledoc """
This plug ensures that a user isn't locked.
## Example
plug MyAppWeb.EnsureUserNotLockedPlug
"""
alias MyAppWeb.Router.Helpers, as: Routes
alias Phoenix.Controller
alias Plug.Conn
alias Pow.Plug
@doc false
@spec init(any()) :: any()
def init(opts), do: opts
@doc false
@spec call(Conn.t(), any()) :: Conn.t()
def call(conn, _opts) do
conn
|> Plug.current_user()
|> locked?()
|> maybe_halt(conn)
end
defp locked?(%{locked_at: locked_at}) when not is_nil(locked_at), do: true
defp locked?(_user), do: false
defp maybe_halt(true, conn) do
conn
|> Plug.clear_authenticated_user()
|> Controller.put_flash(:error, "Sorry, your account is locked.")
|> Controller.redirect(to: Routes.pow_session_path(conn, :new))
end
defp maybe_halt(_any, conn), do: conn
end
And then add the plug to your endpoint OR router depending what works better for your flow:
defmodule MyAppWeb.Endpoint do
# ...
plug Pow.Plug.Session, otp_app: :my_app_web
plug MyAppWeb.EnsureUserNotLockedPlug
# ...
end
defmodule MyAppWeb.Router do
# ...
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug MyAppWeb.EnsureUserNotLockedPlug
end
# ...
end
Yeah, it’s a typo, I meant defp locked?, but couldn’t update it because discourse doesn’t allow to fix typos anymore (just keep getting “too few changes to the body” error, maybe something you want to look at @AstonJ?).
Might be worth making the changes, copying the entire post - then typing anything in there and clicking save, then edit again and pasting what you actually wanted in there
Great that is fine for me. A Plug is really the right way to go. ^^
My other concern is about the reset_password extension. I think if an user account is locked, sending an email for password reset will be pointless. Any advice about how to prevent such a behavior ? I guess this time we should handle it in a custom reset_password controller.