After going through the source a bit more and experimenting, I found the issue. Our Team
resource has multi-tenancy turned on. I’ve fixed the issue now (setting tenant in our router) but I’m still keen to learn how I could have debugged this better.
As it stands, I got zero warnings about the multitenancy issue (even with all debug stuff on), so maybe ash_auth is incorrectly swallowing a warning somewhere? (If I can work out where, I’m happy to PR something)
I’ll paste the configs and logs to demonstrate.
I have a user (99% setup by the ash_auth igniter)
# user.ex
...
policies do
bypass AshAuthentication.Checks.AshAuthenticationInteraction do
authorize_if always()
end
# I have temporarily set this policy to always allow to rule it out of debugging
policy always() do
authorize_if always()
end
end
...
actions do
...
read :get_by_subject do
description "Get a user by the subject claim in a JWT"
argument :subject, :string, allow_nil?: false
get? true
prepare AshAuthentication.Preparations.FilterBySubject
prepare build(load: :team) # This is the problematic line
end
...
# dev.exs
...
config :logger, level: :debug
config :ash, :policies, log_policy_breakdowns: :debug
config :ash, :policies, log_successful_policy_breakdowns: :debug
config :ash, :policies, show_policy_breakdowns?: true
...
Here are the full logs if I don’t have the prepare statement active.
[info] GET /
[debug] Processing with WinkWeb.PageController.home/2
Parameters: %{}
Pipelines: [:browser, :require_session, :require_team]
[debug] Successful authorization: Wink.Accounts.User.get_by_subject
Policy Breakdown
unknown actor
Bypass: Policy | 🌟:
condition: AshAuthentication is performing this interaction
authorize if: always true | ✓ | 🌟
Policy | 🌟:
condition: always true
authorize if: always true | ✓ | 🌟
[debug] QUERY OK source="users" db=0.3ms idle=1002.1ms
SELECT u0."id", u0."role", u0."email", u0."team_id", u0."hashed_password" FROM "users" AS u0 WHERE (u0."id"::uuid = $1::uuid) ["8e59877b-82d6-4379-ac11-deeb88c8f99a"]
↳ anonymous fn/3 in AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:767
[info] Sent 200 in 6ms
Here are the logs if I do have the prepare statement active
[info] GET /
[debug] Processing with WinkWeb.PageController.home/2
Parameters: %{}
Pipelines: [:browser, :require_session, :require_team]
[debug] Successful authorization: Wink.Accounts.User.get_by_subject
Policy Breakdown
unknown actor
Bypass: Policy | 🌟:
condition: AshAuthentication is performing this interaction
authorize if: always true | ✓ | 🌟
Policy | 🌟:
condition: always true
authorize if: always true | ✓ | 🌟
[debug] QUERY OK source="users" db=0.2ms idle=1292.0ms
SELECT u0."id", u0."role", u0."email", u0."team_id", u0."hashed_password" FROM "users" AS u0 WHERE (u0."id"::uuid = $1::uuid) ["8e59877b-82d6-4379-ac11-deeb88c8f99a"]
↳ anonymous fn/3 in AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:767
[info] Sent 302 in 2ms
[debug] Phoenix.Router halted in :require_session/2
# At this point, the conn has no `current_user` and so gets bounced by our checks for current user
What I can see is that the same SQL is run, the same policy is run (and passes), but I mysteriously don’t have a current_user being set on the conn. As I said above, I assume an Ash.Error.Invalid.TenantRequired
is being thrown somewhere but caught by AshAuth.