Multitenency with generated User actions - fine with web UI but not via the API pipeline

To enable multitenency with Users I’ve modified the default User and Token Resources generated by Ash Authentication to declare context multitenency.

I have a Tenant Plug that adds the Tenant to the conn assigns from the host as well. When I do this the generated web UI works great :+1:

However, when I try the same thing with the API pipeline and calling sign_in_with_password/2 on User from a controller it fails. I’m wondering if this code supports tenants like the UI does or if I need to use a modified version of it.

How does it fail? Are you passing the tenant into the call to the action?

I am, but the query isn’t finding the user:

 %AshAuthentication.Errors.AuthenticationFailed{
      caused_by: %{
        message: "Query returned no users",

I’m not sure where the WHERE (FALSE) is coming from:

SELECT u0."id", u0."email", u0."hashed_password", u0."confirmed_at" FROM "megahotel"."users" AS u0 WHERE (FALSE) []

AH, it’s from here:

   query =
      if is_nil(identity) do
        # This will fail due to the argument being `nil`, so this is just a formality
        Query.filter(query, false)
      else
        Query.filter(query, ^ref(identity_field) == ^identity)
      end

So Query.get_argument(query, identity_field) is returning nil?

I should bring AshAuthentication in locally so I can debug better.

Oh sorry, here’s the full error:

[debug] QUERY OK source=“users” db=0.7ms queue=0.8ms idle=412.6ms
SELECT u0.“id”, u0.“confirmed_at”, u0.“hashed_password”, u0.“email” FROM “megahotel”.“users” AS u0 WHERE (FALSE)
↳ anonymous fn/3 in AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:779
ERROR: %Ash.Error.Forbidden{
bread_crumbs: [“Error returned from: App.Accounts.User.sign_in_with_password”],
query: “#Query<>”,
errors: [
%AshAuthentication.Errors.AuthenticationFailed{
caused_by: %{
message: “Query returned no users”,
module: AshAuthentication.Strategy.Password.SignInPreparation,
strategy: %AshAuthentication.Strategy.Password{
require_confirmed_with: nil,
confirmation_required?: true,
hash_provider: AshAuthentication.BcryptProvider,
hashed_password_field: :hashed_password,
identity_field: :email,
name: :password,
password_confirmation_field: :password_confirmation,
password_field: :password,
provider: :password,
register_action_accept: ,
register_action_name: :register_with_password,
registration_enabled?: true,
resettable: %AshAuthentication.Strategy.Password.Resettable{
token_lifetime: {3, :days},
request_password_reset_action_name: :request_password_reset_with_password,
password_reset_action_name: :password_reset_with_password,
sender: {App.Accounts.User.Senders.SendPasswordResetEmail, }
},
resource: App.Accounts.User,
sign_in_action_name: :sign_in_with_password,
sign_in_enabled?: true,
sign_in_token_lifetime: {60, :seconds},
sign_in_tokens_enabled?: true,
sign_in_with_token_action_name: :sign_in_with_token,
strategy_module: AshAuthentication.Strategy.Password
},
action: :sign_in
},
changeset: nil,
field: nil,
query: ash.Query<
resource: App.Accounts.User,
tenant: “megahotel”,
arguments: %{
password: “redacted”,
email: ash.CiString<“bryan@brycelabs.com”>
},
filter: ash.Filter,
select: [:id, :confirmed_at, :hashed_password, :email]
>,
strategy: %AshAuthentication.Strategy.Password{
require_confirmed_with: nil,
confirmation_required?: true,
hash_provider: AshAuthentication.BcryptProvider,
hashed_password_field: :hashed_password,
identity_field: :email,
name: :password,
password_confirmation_field: :password_confirmation,
password_field: :password,
provider: :password,
register_action_accept: ,
register_action_name: :register_with_password,
registration_enabled?: true,
resettable: %AshAuthentication.Strategy.Password.Resettable{
token_lifetime: {3, :days},
request_password_reset_action_name: :request_password_reset_with_password,
password_reset_action_name: :password_reset_with_password,
sender: {App.Accounts.User.Senders.SendPasswordResetEmail, }
},
resource: App.Accounts.User,
sign_in_action_name: :sign_in_with_password,
sign_in_enabled?: true,
sign_in_token_lifetime: {60, :seconds},
sign_in_tokens_enabled?: true,
sign_in_with_token_action_name: :sign_in_with_token,
strategy_module: AshAuthentication.Strategy.Password
},
splode: Ash.Error,
bread_crumbs: [“Error returned from: App.Accounts.User.sign_in_with_password”],
vars: ,
path: ,
stacktrace: splode.Stacktrace<>,
class: :forbidden
}
]
}

Can you show the code where you are making the call that is failing?

Alright, I figured it out. It was the security/policy change in the newer Ash Authentication version: Policies on Authenticated Resources — ash_authentication v4.3.9

Basically it was failing because the User Resource’s actions are locked down by default to only work with the library code.