Coder Social home page Coder Social logo

Comments (5)

danschultzer avatar danschultzer commented on May 18, 2024

I was traveling these last weeks, but finally I'm able to answer this thoroughly! Hopefully this won't be too late of a response 😄

You've probably figured out a lot of this yourself by digging in the source code, but just to make it clear for everyone, this is where the callback is used: https://github.com/danschultzer/pow/blob/master/lib/pow/phoenix/controllers/controller.ex#L74-L92

  @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

I've been thinking if there's a good way of making Pow nearly plug n' play for JSON response. You could to some degree make it work with controller callbacks as you suggest, but there are redirects that happens in plug methods too so I think the whole flow in Pow has to be thought through. I would love any suggestions for this.

But let's go ahead with how to achieve JSON responses with Pow in its current state. In an API, you wouldn't want to deal with redirects, so we'll set up a custom controller that uses the underlying plug methods directly (the plug methods can be seen in the process methods in the SessionController):

defmodule MyAppWeb.SessionController do
  use MyAppWeb, :controller

  alias Pow.Plug

  def create(conn, %{"user" => user_params}) do
    conn
    |> Plug.authenticate_user(user_params)
    |> case do
      {:ok, conn} -> # authenticated, conn has been set with session
      {:error, _conn} -> # error occurred, conn remains unchanged
    end
  end

  def delete(conn, params) do
    conn
    |> Plug.clear_authenticated_user()
    |> case do
      {:ok, conn} -> # session in conn has been removed
    end
  end
end

And then I would just replicate the logic in extensions (in this case with email confirmation):

  alias PowEmailConfirmation.Phoenix.ControllerCallbacks, as: PowEmailConfirmationControllerCallbacks

  def create(conn, %{"user" => user_params}) do
    conn
    |> Plug.authenticate_user(user_params)
    |> verify_confirmed()
  end

  defp verify_confirmed({:error, conn}), do: # error occurred, conn remains unchanged

  defp verify_confirmed({:ok, conn}) do
    user = Plug.current_user(conn)

    if confirmed?(user) do
      # user is confirmed and conn can be used
    else
      PowEmailConfirmationControllerCallbacks.send_confirmation_email(user, conn)

      {:ok, conn} = Plug.clear_authenticated_user(conn)

      # conn session has been reset and error can be returned
    end
  end

  defp confirmed?(%{email_confirmed_at: nil, email_confirmation_token: token}) when not is_nil(token), do: false
  defp confirmed?(_user), do: true

As the above highlights, I think there's room for making it easier working with extensions. All suggestions are welcome! 😄

from pow.

danschultzer avatar danschultzer commented on May 18, 2024

A quick thought! If redirects isn't a problem, then you could actually just switch out all responses in a custom controller callbacks using before_response. The only thing you've to do is to return {:halt, conn} to prevent the response method from being called in the controller.

I think there's just one place where emails are sent out as part of the response, but it's pretty easily handled. All the extension controller callbacks only redirects, e.g. email confirmation just redirects to pow_session_path(conn, :new) with and :error flash

Errors won't be clear this way though. I think the best way is to refactor the flow in Pow to make it trivial for developers to handle pow errors in the connection.

from pow.

mwojtul avatar mwojtul commented on May 18, 2024

This is great, thanks! Perfect timing actually. I had gone down the path of using a custom controller previously but I'm going to play around with implementing it using before_response too and see where I end up. Curious to see how clear/unclear the errors will be because simply switching out the responses sounds nice and clean in and of itself.

I'll keep an eye out for ways to improve the flow to better handle errors and potentially avoid redirecting too 😄

from pow.

danschultzer avatar danschultzer commented on May 18, 2024

Cool! Custom controllers are probably the better approach, but here's an example of how the controller callbacks could look like:

defmodule MyAppWeb.PowControllerCallbacks do
  alias Pow.Extension.Phoenix.ControllerCallbacks

  def before_process(controller, action, results, config),
    do: ControllerCallbacks.before_process(controller, action, results, config)
 
  def before_response(controller, action, results, config) do
    controller
    |> ControllerCallbacks.before_response(action, results, config)
    |> handle_response(controller, action)
  end

  # Custom response
  defp handle_response({:ok, user, conn}, Pow.Phoenix.RegistrationController, :create) do
    conn = Phoenix.Controller.json(conn, %{id: user.id})

    {:halt, conn}
  end

  # All other responses should just be handled by default in Pow
  defp handle_response(result, _controller, _action), do: result
end

As you can see, it'll call extension controller callbacks before the custom response.

Remember to add it to your config:

config :my_app, :pow,
  controller_callbacks: MyAppWeb.PowControllerCallbacks

from pow.

danschultzer avatar danschultzer commented on May 18, 2024

Here's some more information: #57 (comment)

from pow.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.