Hey Elixir folks,
I’m newer to Elixir and building a GraphQL API with Absinthe. Overall it’s been wonderful and I’m really enjoying working with Elixir and Phoenix — this forum has been an especially wonderful resource, so thanks everyone.
I’ve been able to work my way through the docs and different tutorials and get my API up and running. I’m at a point where I need to add tenancy to this app, and ended up following this guide to use foreign keys for this since this is generally the method I’ve used in the past with different applications and it generally seems easier for me to wrap my head around. I’ve updated all of my models and this seems to be scoping correctly and blocking my query if neglect to set my tenant ID, e.g.
import Myapp.Users
Myapp.Users
iex(2)> list_users
** (RuntimeError) expected account_id or skip_account_id to be set
(foundation 0.1.0) lib/myapp/repo.ex:23: Myapp.Repo.prepare_query/3
(ecto 3.5.6) lib/ecto/repo/queryable.ex:211: Ecto.Repo.Queryable.execute/4
(ecto 3.5.6) lib/ecto/repo/queryable.ex:17: Ecto.Repo.Queryable.all/3
iex(2)> Myapp.Repo.put_account_id("account_1613590824385y45fvnpf")
nil
iex(3)> list_users
[debug] QUERY OK...
But I’m really struggling to understand the right way to use this put_org_id
method. I read through
Bullet proof way to enforce tenants with a foreign key? - #27 by baldwindavid where the OP ran into a similar question, but didn’t find a clear answer here (unless I misunderstood) and I’m stuck banging my head.
I’m managing authentication in my app with:
- A set_current_user plug to set current user:
defmodule MyappWeb.Plugs.SetCurrentUser do
@behaviour Plug
import Plug.Conn
def init(opts), do: opts
def call(conn, _) do
context = build_context(conn)
Absinthe.Plug.put_options(conn, context: context)
end
defp build_context(conn) do
with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
{:ok, %{id: id}} <- MyappWeb.AuthToken.verify(token),
%{} = user <- Myapp.Users.get_user!(id) do
%{current_user: user}
else
_ -> %{}
end
end
end
- Generic authentication middleware as outlined in the docs:
# Used to authenticate users
defmodule MyappWeb.Schema.Middleware.Authenticate do
@behaviour Absinthe.Middleware
def call(resolution, _) do
case resolution.context do
%{current_user: _} ->
resolution
_ ->
resolution
|> Absinthe.Resolution.put_result({:error, "You must be authenticated to create this request"})
end
end
end
The problem I’m running into is if I try to authenticate and make a request, I’m (expectedly) told that I haven’t yet set account_id
. I’ve tried a bunch of different things, and I can make it work if I hardcode this in my build_context
method like Myapp.Repo.put_account_id("account_1613590824385y45fvnpf")
and also in my context like so:
def list_users do
Myapp.Repo.put_account_id("account_1613590824385y45fvnpf")
Repo.all(User)
end
but obviously (1) I need to retrieve the account ID for the user, and (2) I’m certain I shouldn’t be using put_account_id
in every single function. I just don’t really know where exactly I should do this or what’s idiomatic. So I have two questions I’m hoping to get some help with:
-
I’m not clear on how to obtain and pass around the
account_id
. I realize this is quite newb, but what’s the best way for me to get the current user’s account_id (stored as a field on the User model) and where should I store it? In a session? A cookie? -
I would think within the
set_current_user
plug or in some other global way I could use theput_account_id
function to set my user’s tenant ID, but this doesn’t work and I’m sure there’s an easier/better way to do this.
Would love any pointers here — thanks so much!