{@thread.name}
talha-azeem:
How do we maintain the current user, like in liveviews we fetch it from the session.
ZachDaniel:
You can use the
load_from_session
plug in your router, which should be documented
ZachDaniel:
and then you can use the live session
ZachDaniel:
For instance, we do this in ash_hq around our liveviews
ZachDaniel:
ash_authentication_live_session :main,
on_mount: [
{AshHqWeb.LiveUserAuth, :live_user_optional},
{AshHqWeb.InitAssigns, :default}
],
session: {AshAuthentication.Phoenix.LiveSession, :generate_session, []},
root_layout: {AshHqWeb.LayoutView, :root} do
...
end
talha-azeem:
so i will add the on_mount one?
talha-azeem:
in my liveviews?
ZachDaniel:
Have you done this kind of thing with liveview before?
ZachDaniel:
It might be worth reading their documentation on this stuff too
ZachDaniel:
The
load_from_session
plug is documented in the getting started guide for ash_authentication_phoenix, and will make a
current_user
assign available
ZachDaniel:
the
on_mount
is something you write yourself
ZachDaniel:
but just adding
ash_authentication_live_session
will set the
current_user
assign
ZachDaniel:
So the
{AshHqWeb.LiveUserAuth, :live_user_optional}
is something I wrote for
AshHq
specifically
talha-azeem:
yes i have but at that time i had to write a custom live helper to fetch the current user from session and assign it to the socket and called that function in every mount of liveview.
ZachDaniel:
Ah, yeah so thats been updated
ZachDaniel:
and now you can use
on_mount
hooks like the one I mentioned
ZachDaniel:
so
ash_authentication_live_session
will set
current_user
assign, and then you can add an
on_mount
hook to do things like require that the user is there for certain routes
ZachDaniel:
i.e
ash_authentication_live_session :main,
on_mount: [
{MyApp.LiveUserAuth, :live_user_optional},
] do
live "/unsecured_route", ...
end
ash_authentication_live_session :main,
on_mount: [
{MyApp.LiveUserAuth, :live_user_required},
] do
live "/secured_route", ...
end
ZachDaniel:
This is what it looks like in
AshHq
defmodule AshHqWeb.LiveUserAuth do
@moduledoc """
Helpers for authenticating users in liveviews
"""
import Phoenix.Component
use AshHqWeb, :verified_routes
def on_mount(:live_user_optional, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:cont, assign(socket, :current_user, nil)}
end
end
def on_mount(:live_user_required, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/sign-in")}
end
end
end
ZachDaniel:
That makes sure that the assigns I want are always there (
current_user
) and also handles requiring a user for some routes
talha-azeem:
What i understood from on_mount live user required one is making sure that the current user is present.
ZachDaniel:
Correct
ZachDaniel:
This example is from the getting started guide for ash authentication phoenix:
defmodule MyAppWeb.Router do
use MyAppWeb, :router
use AshAuthentication.Phoenix.Router
pipeline :browser do
# ...
plug(:load_from_session) # <- this line will load the user from the session
end
pipeline :api do
# ...
plug(:load_from_bearer)
end
# This stuff is the authentication routes
scope "/", MyAppWeb do
pipe_through :browser
sign_in_route
sign_out_route AuthController
auth_routes_for MyApp.Accounts.User, to: AuthController
end
scope "/", MyAppWeb do
ash_authentication_session <opts> do
#<- this sets the `current_user` assign if the user is logged in, for any liveviews inside
live ....
end
end
end
talha-azeem:
Now i understood it. I am sorry. I couldn’t find it in the docs. 😅
ZachDaniel:
no problem 👍
talha-azeem:
If i want the relationships of user to be loaded then?
ZachDaniel:
Put that in your
on_mount
hook
ZachDaniel:
and use
Accounts.load(user, [:relationships, ...])
talha-azeem:
so that i have to do through plug.
ZachDaniel:
You can do it in the
on_mount
hook like I showed above, just add logic to load relationships
ZachDaniel:
def on_mount(:live_user_optional, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, assign(socket, :current_user, MyApp.Accounts.load!(socket.assigns.current_user, [:foo, :bar])}
else
{:cont, assign(socket, :current_user, nil)}
end
end
def on_mount(:live_user_required, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, assign(socket, :current_user, MyApp.Accounts.load!(socket.assigns.current_user, [:foo, :bar])}
else
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/sign-in")}
end
end
ZachDaniel:
Like that
talha-azeem:
yeah i got it 😄
talha-azeem:
or i can add another on_mount just to load the relations
ZachDaniel:
yep!
ZachDaniel:
That would be a good way to do it
talha-azeem:
in list we provide the relations that we need to load, right?
talha-azeem:
is it different from the Ash.Query.load?
ZachDaniel:
They are exactly the same 👍
talha-azeem:
MyApp.Accounts.load!(socket.assigns, :current_user, [:teams])
if i do this it gives me error
ZachDaniel:
That isn’t how that works
ZachDaniel:
the first argument to
load!
is the ash record
ZachDaniel:
and the second argument is the stuff you want to load
ZachDaniel:
MyApp.Accounts.load!(socket.assigns.current_user, [:teams])
talha-azeem:
oh, i picked the one you wrote above in the
on_mount
hook
ZachDaniel:
Oh
ZachDaniel:
sorry 🙂
talha-azeem:
no, don’t be.
ZachDaniel:
I’ve fixed the example
talha-azeem:
its just i am new 😅
ZachDaniel:
Doesn’t help when I give you bad code samples 😆
talha-azeem:
it still gives me the error
talha-azeem:
* No read action exists for Dummy.Accounts.Team when: loading relationship teams
I have this defined in Team resource:
actions do
defaults [:create, :read, :update, :destroy]
end
ZachDaniel:
Can I see the whole resource?
talha-azeem:
defmodule Dummy.Accounts.Team do
use Ash.Resource, data_layer: AshPostgres.DataLayer
postgres do
repo(Dummy.Repo)
table("teams")
end
actions do
defaults [:create, :read, :update, :destroy]
end
attributes do
uuid_primary_key :id
attribute :name, :string
timestamps()
end
relationships do
many_to_many :users, Dummy.Accounts.User do
through Dummy.Accounts.TeamJoinedUser
source_attribute_on_join_resource :team_id
destination_attribute_on_join_resource :user_id
end
end
end
ZachDaniel:
You sure you’ve saved it and recompiled and everything?
ZachDaniel:
That looks right to me
talha-azeem:
yes, i have auto save enabled
ZachDaniel:
and you restarted your server?
ZachDaniel:
Sorry, just being thorough because that looks right to me
talha-azeem:
Yup
talha-azeem:
same error
ZachDaniel:
Can I see your code where you’re loading it?
talha-azeem:
yes gimme a sec
talha-azeem:
def on_mount(:load_assocs_current_user, _params, _session, socket) do
{:cont, assign(socket, :current_user, Dummy.Accounts.load!(socket.assigns.current_user, [:teams]))}
end
added this is auth plug
ZachDaniel:
Ah
ZachDaniel:
That is fine, but I think the issue is probably on your join resource
ZachDaniel:
Dummy.Accounts.TeamJoinedUser
also needs
defaults [:read, :create, :update, :destroy]
talha-azeem:
yeah that was the issue
talha-azeem:
Am i missing something here?
talha-azeem:
no function clause matching in Plug.Conn.assign/3
It is giving me this error.
ZachDaniel:
That shouldn’t be calling
Plug.Conn.assign
ZachDaniel:
it should be from
Phoenix.Component
I believe
ZachDaniel:
did you try to put the
on_mount
in a plug?
talha-azeem:
nope
ZachDaniel:
can I see the whole module
talha-azeem:
defmodule DummyWeb.User.TeamLive.Index do
use DummyWeb, :live_view
alias Dummy.Accounts
on_mount {Dummy.AuthPlug, :load_assocs_current_user}
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end
@impl true
def handle_event("show-team-details", %{"id" => team_id}, socket) do
# team = Accounts.get_team!(id)
# {:noreply, socket}
user_id = socket.assigns.current_user.id
{:noreply, redirect(socket, to: "<path>")}
end
@impl true
def handle_params(params, _uri, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
def apply_action(socket, :index, _params) do
assign(socket, page_title: "All Teams")
end
# def apply_action(socket, :team_details, _params) do
# assign(socket, page_title: "Team Details")
# end
def apply_action(socket, :new, _params) do
# form =
# Accounts.Team
# |> AshPhoenix.Form.for_create(:create,
# api: Team,
# forms: [auto?: true]
# )
# |> IO.inspect(label: "here in team form => ")
socket
|> assign(page_title: "New Team")
# |> assign(form: form, page_title: "New Team")
end
end
ZachDaniel:
on_mount {Dummy.AuthPlug, :load_assocs_current_user}
I didn’t even know you could do this
talha-azeem:
really?
ZachDaniel:
Yeah, I only ever did them in the live_session
talha-azeem:
I got to know about them recently myself tho.
ZachDaniel:
anyway, can I see
AuthPlug
?
ZachDaniel:
Because it looks like you did something like
import Plug
ZachDaniel:
and thats not what you want to do
talha-azeem:
when i implemented
mix phx.gen.auth
it gave an example there for this.
talha-azeem:
defmodule DummyWeb.AuthPlug do
use AshAuthentication.Plug, otp_app: :dummy_app
use DummyWeb, :verified_routes
def handle_success(conn, _activity, user, token) do
if is_api_request?(conn) do
conn
|> send_resp(200, Jason.encode!(%{
authentication: %{
success: true,
token: token
}
}))
else
conn
|> store_in_session(user)
|> send_resp(200, EEx.eval_string("""
<h2>Welcome back <%= @user.email %></h2>
""", user: user))
end
end
def handle_failure(conn, _activity, _reason) do
if is_api_request?(conn) do
conn
|> send_resp(401, Jason.encode!(%{
authentication: %{
success: false
}
}))
else
conn
|> send_resp(401, "<h2>Incorrect email or password</h2>")
end
end
defp is_api_request?(conn), do: "application/json" in get_req_header(conn, "accept")
def on_mount(:live_user_required, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/sign-in")}
end
end
def on_mount(:load_assocs_current_user, _params, _session, socket) do
{:cont, assign(socket, :current_user, Dummy.Accounts.load!(socket.assigns.current_user, [:teams]))}
end
end
ZachDaniel:
Yeah, so because you put that in your
AuthPlug
, when you say
assign
its calling the imported
Plug.Conn.assign
ZachDaniel:
But there is a different assign that you are supposed to use with liveview sockets
ZachDaniel:
Phoenix.Component.assign
ZachDaniel:
For example, the one we use in ash_hq
ZachDaniel:
defmodule AshHqWeb.LiveUserAuth do
@moduledoc """
Helpers for authenticating users in liveviews
"""
import Phoenix.Component
use AshHqWeb, :verified_routes
def on_mount(:live_user_optional, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:cont, assign(socket, :current_user, nil)}
end
end
def on_mount(:live_user_required, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/sign-in")}
end
end
end
ZachDaniel:
See how that does
import Phoenix.Component
(not
use AshAuthentication.Plug, otp_app: :dummy_app
)
talha-azeem:
yes i got it
talha-azeem:
that was the issue.
ZachDaniel:
👍
talha-azeem:
oh so i shouldn’t use the ash auth plug here?
ZachDaniel:
Put it in its own module like I show above
talha-azeem:
two User Auths?
talha-azeem:
one for controller requests and the other one for Socket ones?
talha-azeem:
and can we do nested relationship loaded using
load/3
?
ZachDaniel:
yes, you can
ZachDaniel:
load(foo: [bar: [baz: :buz]])
ZachDaniel:
They are just two different modules for doing two different thigns
talha-azeem:
noted
ZachDaniel:
if you want to combine them you can
talha-azeem:
so just like we do in preload
ZachDaniel:
but you just have to make sure you’re calling the right functions 😆
talha-azeem:
😂
ZachDaniel:
Lets resolve this one since we got through the main issue. Feel free to open more.
talha-azeem:
Sure. Thank you