Programming Phoenix LiveView: B03 (page 56-57) auth exercise

Dear Sophie.
I tried to do the “Authorization” exercise and have two questions:

  1. When trying to plug in an email-service, I found the function “&Routes.user_confirmation_url(conn, :confirm, &1)” in “user_confirmation_controller.ex”.
    I wasn’t able to find this function.
    Where is it located or how is it created dynamically?

  2. I added the “username” field without problem, but when I set it to unique and made it safe with “unsafe_validate_unique(:username, Pento.Repo)” some tests failed. I tried to fix them via modifying the fixture()-Function, but still two test concerning the uniqueness of the “email” failed.
    Is there an easy way to get the generated tests running, when adding a second unique field?

Thanks for your superb book.
I learned a lot from this exercise, especially the sending of the confirmation-email via Bamboo and Sendgrid.

Best wishes from Heiko

Hi @Chrichton! Let me tackle your questions one at a time:

  • The &Routes.user_confirmation_url/3 function is made available to you thanks to the Phoenix Auth generator. When you ran mix phx.gen.auth, it generated a set of routes for you web app, including this one defined in your app’s router: (This is generated code that was added to your router when you ran the auth generator command)…
scope "/", PentoWeb do
    pipe_through [:browser]
   ...
    get "/users/confirm", UserConfirmationController, :new
    post "/users/confirm", UserConfirmationController, :create
    get "/users/confirm/:token", UserConfirmationController, :confirm
  end

It maps three routes to various actions in the UserConfirmationController. If you run mix phx.routes from the command line, you’ll see the route helpers that are also generated to go along with these routes:

mix phx.routes

user_confirmation_path  GET     /users/confirm                         PentoWeb.UserConfirmationController :new
user_confirmation_path  POST    /users/confirm                         PentoWeb.UserConfirmationController :create
user_confirmation_path  GET     /users/confirm/:token                  PentoWeb.UserConfirmationController :confirm

This also gives you the Routes.user_confirmation_path/3 helper functions for free.

  • Updating the fixtures is the right way to get those tests passing I think.

Hi @SophieDeBenedetto,

thanks a lot for your detailed answer.
Unfortunately, I still have the following question:

I am not able to find the function: Routes.user_confirmation_url/3 in my Router.

Because it is called this way: &Routes.user_confirmation_url(conn, :confirm, &1),
I think it must be a function, that needs a Connection and a Atom and is turned in a function, that expects one parameter (a token?)

Where is this function in the codebase?


Maybe you would be so kind and answer another question?

The last exercise is: “If a logged in user visits the / route, make them redirect to the /guess route”

I solved this exercise by:

def log_in_user(conn, user, params \\ %{}) do
    token = Accounts.generate_user_session_token(user)
    user_return_to = "/guess"

    conn
    |> renew_session()
    |> put_session(:user_token, token)
    |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
    |> maybe_write_remember_me_cookie(token, params)
    |> redirect(to: user_return_to)
  end

Is this the correct way or is it possible to solve it inside the authentication-logic?

Thanks a lot for your effort,

Heiko

Hi again @Chrichton!

So the Routes.user_confirmation_url/3 isn’t defined as such in your router. It’s defined under the hood of Phoenix by virtue of the user_confirmation_path routes that were added to your router.ex file by the Phoenix Auth generator. The function itself is passed as an argument to the Accounts.deliver_user_confirmation_instructions/3 function in the UserConfirmationController controller. In that Accounts function, the Routes.user_confirmation_url/3 is finally invoked to return the user confirmation URL, with the correct user token, to be rendered in the email sent to the user. I would trace the code flow in the Accounts.deliver_user_confirmation_instructions/3 to get a better understanding of how the Routes.user_confirmation_url/3 function is being used.

Re: your exercise solution:

It looks like your solution will ensure that a user is redirected to the "/guess" route when the complete the log in process. But the challenge is actually asking you to ensure that if a user who is logged in visits /, that they get automatically redirected to /guess. You can solve this a few different ways.

  • Add a function plug to the :browser pipeline in the router.ex to redirect the user from / to /guess if they are logged in. Like this:
#router.ex
defmodule PentoWeb.Router do
  use PentoWeb, :router
  import Phoenix.Controller
  ...
  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, {PentoWeb.LayoutView, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :fetch_current_user
    plug :redirect_if_logged_in # add me!
  end
  
  ...
  
  def redirect_if_logged_in(conn, _opts) do
    if conn.assigns.current_user && conn.request_path == "/" do
      redirect(conn, to: "/guess")
    end
    conn
  end
end

This requires importing the Phoenix.Controller module into the router to get the redirect/2 function, which I don’t love.

You could also add some logic to the PageLive live view’s mount function to do a redirect if there is a logged in user. This is the live view that handles the / route. It would look something like this:

# lib/pento_web/live/page_live.ex
 def mount(_params, %{"user_token" => _}, socket) do
    {:ok, redirect(socket, to: "/guess")}
  end

  def mount(_params, session, socket) do
    {:ok, assign(socket, query: "", results: %{})}
  end

Here, you are checking to see if there is a logged in user based on whether or not the session argument given to mount has a "user_token"key present. If so, there is a logged in user and you can use theredirect` function to do a redirect.