Multitenancy and different Liveviews
So this might not be directly related to AshAuthentication, but somebody could help me figure out what's going.
I'm still super new to Elixir and Phoenix so bear with me haha
Continuing from the discussion above with multitenancy
I have a live_view being rendered normally from the router, but this one also has a child live_view rendered with live_render like so:
When I log in, I automatically gets logged out.
When I inspect the socket and session of the messaging live_view, on the second render, there aren't any users or tenant.
In messaging.ex, I have the following:
on_mount {MyAppWeb.UserAuth, :current_user}
which points to the following:
Is there anything I'm missing?
If I comment out the multitenancy part out, I don't encounter the isssue, same thing if I comment out the messaging live view
57 Replies
The second time that mount is called for the
messaging
live view, I don't have any error log, it simply goes through the else block here: (it goes through the happy path correctly the first time around)
get_tenant
is the one from Ash.PlugHelpers
why would you assign the tenant to the current user?
Sorry, I've just tried so many things to get it working lol. I changed it from
:current_user
to :tenant:
but I get the same result🤔
get_tenant
is meant to be called with a conn
Try just not altering the current user assign?
I'll have to look into it tomorrow if that doesn't workWhat do you mean? Simply not calling that
on_mount(:current_user)
?
Doesn't change anything 😦Yeah, so I think the problem is that you may need to somehow thread the
current_user
assign through to the child liveview?
Since it sounds like you're nesting liveviews which is not something I've done before.
Looking a bit into live_render
it appears that all of the session information from the parent live view should be available. However, it looks like the basic problem may be what ash_authentication_live_session
does for you is not extended to child rendered liveviews
So the basic issue is that ash_authentication_live_session
takes the information from the session
and ensures that a current_user
assign exists. I think this is likely a question for the phoenix team, specifically "when you have a live_session with an on_mount
responsible for setting current_user
, how do you get that user into the child liveview", to which I think the answer is probably "figure it out". So in your child liveview you'll have the session, and you may need to add your custom on_mount
hook to fetch the user again from the db.
My read on this is to avoid nesting liveviews like that if you can 🙂LiveBeats is a demo app that uses a sticky live view to maintain an audio player while you navigate from page to page. You can just add the current user
on_mount
hook on the LV to pull it in automatically. 🙂
https://github.com/fly-apps/live_beats/blob/712204ca172ed6d36456c8ed4fd6aaba50099447/lib/live_beats_web/live/player_live.exGitHub
live_beats/player_live.ex at 712204ca172ed6d36456c8ed4fd6aaba500994...
Contribute to fly-apps/live_beats development by creating an account on GitHub.
This is how the "sticky" LV is called inside the main page template: https://github.com/fly-apps/live_beats/blob/712204ca172ed6d36456c8ed4fd6aaba50099447/lib/live_beats_web/components/layouts/live.html.heex#L225
GitHub
live_beats/live.html.heex at 712204ca172ed6d36456c8ed4fd6aaba500994...
Contribute to fly-apps/live_beats development by creating an account on GitHub.
I think you may need to add the ash authentication hook that way too.
Can you add multiple on mount hooks? Try
@on_mount AshAuthentication.Phoenix.LiveSession
I've managed to make it work by simply passing the current user to the session of the child Liveview
I think they advise against that
They say to only put the user_id in the session
Did you try this?
@on_mount AshAuthentication.Phoenix.LiveSession
in MyAppWeb.Messaging
?
Also given the example @frankdugan3 showed I get why you'd nest liveviews, its not an organizational thing, its the only way to actually do certain things
Just not something I've done beforeYes I just tried it but it didn't work. I know the amount of data being serialized is pretty big when I inspect the element in browser haha.
I mean, I guess could pass the ID only and fetch it again on_mount or something
what is in the
session
?
like before you add current_user
there?
Ideally I want to have a story for other users doing this with ash_authentication
Based on the fact that they should be extending the parent session into the child rendered liveview, there should be a "current_user"
session with the user's id
if there is, then @on_mount AshAuthentication.Phoenix.LiveSession
ought to pick that up
I wonder...could it just be an order of operations thing between your on_mount and the on_mount I just mentioned?This is what I get, so the user id is actually there it looks like under
user
This is just before being redirected to the login page and being signed outoh how strange
ah, okay. I see
Is there a way to add
session
hooks in a liveview like @on_mount Module
?
i.e @session Session
?
Anyway: there is a workaround here
that coupled with the on_mount
hook I gave you should do it
okay sorry I might be being dumbDidn't even need the
on_mount
hook, it works just by passing the id actuallyThis is very strange 😄
and what is the
current_user
assign in your LV? The actual user?I'm digging to see if I used one somewhere before going to bed last night that I don't remember 😂
that wouldn't really make sense
So this is our
on_mount
hook, the one I'm having you add
It goes through the resources and ought to turn just the "user"
session into :current_user
My suspicion here is that you don't need the session: %{...}
option at all, just that you need to reorder your on_mount
hooks
i.e
I'm not actually calling any of those
on_mount
hooks, they are commented out right now, which is what I find weird:thinkies:
are you actually using the
current_user
anywhere in your child liveview?Ok so basically, as long as I have something in my
current_user
, it doesn't kick me out. I guess this is gonna be on me to fetch and validate what I need with on_mount AshAuthentication.Phoenix.Livesession
or any other hook I might need to
I just tried passing it a random string and it didn't kick me out. So I'll try to see what I need to do to get the info loaded into that child live_view and report back later today I guess, I don't wanna take up any more of your timeno worries. With any luck, that live session hook is what you need 😄
I'm sure it is being called, but
on_mount AshAuthentication.Phoenix.LiveSession
never changes the session nor the socket/assigns when I inspect them, I really need to pass "something" to session: %{"current_user" => ....}
so I don't get kicked out.
Whatever I pass to the session: %{"current_user" => ...}
ends up in the assigns of the socket under that same key, but it doesn't get resolved to anything, it's just "there.
Reading the docs for that on_mount hook, this part doesn't seem to be working in my case:
I'll try to give as many details as I can about my use case.
Here's what my session and socket for the messaging looks like:
This is basically a live_view that will always be present, living on the side of the screen besides the main app and its live_views
and here are the session and socket of my "main" live view
And finally, in my layout:
So lets try somthing here
can you define a new module with the following contents:
And use that instead of the live session I gave you
Thats just a copy of the imports/aliases and the body of the
on_mount
hook that we are using
Then you can poke around and see where things are going wrong (IO.inspect
everything 😆 )Sure thing, I'll investigate 🥸 😂
🙂 Thanks for being such a good sport 😆
looks like
returns an error
so the with statement fails
In the session of my "main" liveview above, the
tenant
key was an empty array, could it be related?Yeah, that sounds like it may be the primary issue.
what happens if you hard code a good tenant instead of
session["tenant"]
?
Actually, you should probably be getting a different error there? Remind me: your user resource is multitenant, right?yes
what is
value
there?Okay, try replacing
session["tenant"]
with "a_literal_tenant_string"
(the one this user is in)
I'm not sure how tenant is ending up as an empty list though, that sounds pretty strange
Is there anything you're doing to set the tenant in the session that way?
We do this for the session:
So we just pull the tenant off of what is set for Ash.PlugHelpers.set_tenant
I am using
plug(:set_tenant)
in my browser pipeline before plug(:load_from_session)
Thats your own plug though right? Can I see it?
No, that's from
Ash.PlugHelpers
, from this discussion here: https://discord.com/channels/711271361523351632/711271361523351636/1083898958566395986
But reading that again, I think I might have misread what you and jart meant, I was pretty tired 😅Ah, okay interesting. Not sure exactly what it would do if used that way 😆
Actually I can see exactly what it would do haha
it would set the tenant to
[]
Yeah, it does that quite well 😄
How are you doing multitenancy? subdomains?
paths?
No not even that, the way I saw it, the user would be registered by either me or an admin of their org, and when logged in, their airline_id on their profile would serve as differentiator
🤔 interesting
So does your user resource have
global? true
in its multitenancy config?No, does it need it in that case?
well, how can we know what tenant to find the user in if the user itself is the arbiter of what the tenant is?
its a chicken and egg issue
ah I see
I guess I could have them enter their org's identifier while logging in
and you're using attribute multitenancy?
yes
You don't necessarily need to have them enter it, but it sounds like what you actually want is something slightly different
1. if you add
global? true
to your resource, you will be allowed to query it with or without a tenant
So then you don't need to give ash_authentication
a tenant
2. then, add a plug after AshAuthentication's load_resource
plug, that does something like Ash.PlugHelpers.set_tenant(conn, user.airline_id)
Yeah that makes sense
Then you can get the tenant out of the session in your liveviews and use that when making queries
And this one resource would be the only one that needs to be global, since I'll have my tenant when making subsequent queries
exactly
Alright, sorry for all that trouble when it was a code 18 on my part 😄
haha, not at all
its really important for the core team to get hands on experience with the things that give users trouble
and there wasn't really any guide to help you figure this kind of thing out
(sounds like an excellent blog post/docs contribution.... 😆 )
I saw the
global?
attribute for multitenancy but I didn't make the link at all in my mind that I would require that for my use case
Thanks again for your help, I've learned quite a bit digging around and talking to you 🙂Happy to help! Let us know if you have any other questions 😄
I don't mean to bump this post, but this is semi-related:
Am I wrong by saying that
set_tenant
and get_tenant
only work on conns
and not on sockets
? Meaning I won't be able to access the tenant and actors while manipulating the socket? (I'm assuming the same is true for get_actor
and set_actor
)Correct, it only works for the conn. However,
ash_authentication_live_session
sets the tenant to the session as well
Something you could also do manually, i.e with an on_mount
hook looking for the tenant
assign and setting it into the session