alexslade

alexslade

Alternative title: A yearning for mackerel

I have a specific problem (I want to load a :team resource on the current_user that AshAuthentication puts into conn assigns. I could do this as a follow-on query, but I’m trying to do it in one).

But really, I want to learn to fish solve this myself.

I tried adding a preparation, at first to the global preparations, and later to get_by_subject which seems to be used by the password strategy.

Either way, I get errorless redirects back to the sign-in page as if I wasn’t logged in. I can’t see anything with logging set to :debug, I can’t see anything relevant with log_successful_policy_breakdowns or policy_breakdowns turned on.

What are my next steps for finding out why ash_auth is failing here? I’m assuming there must be another way to get to an error code, though I accept it may be something that I simply need to understand and avoid. (I also assume there may be a better way of doing this team load).

Any tips are much appreciated.

Showing Posts 1 to 6

barnabasJ

barnabasJ

Ash Core Team

Not seeing any logs seems a bit strange.

I assume you have some policies for your team, because AshAuth calls the read action with a private context set that uses the bypass policy set up by AshAuth

    bypass AshAuthentication.Checks.AshAuthenticationInteraction do
      authorize_if always()
    end

# in the policy  
def match?(_, %{subject: %{context: %{private: %{ash_authentication?: true}}}}, _), do: true

So, there is not really an actor at that point, which could lead to the load failing. You could use the accessing_from policy to allow a user to read their team if they are loading through a relationship

policy action_type(:read) do
  authorize_if accessing_from(User, :team)
end

And than the LiveUserAuth module would redirect because there is no user.

What did you set log_successful_policy_breakdowns / policy_breakdown to? I think it has to be a log level.

Otherwise, using a preparation for this is a valid approach.

alexslade

alexslade OP

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.

barnabasJ

barnabasJ

Ash Core Team

I think this happens here:

https://github.com/team-alembic/ash_authentication_phoenix/blob/4a43c238c7de711551fc86d0a535c2dbd186cac0/lib/ash_authentication_phoenix/live_session.ex#L119

This calls the action on the user resource internally, and if that returns an error, nil is set as the user. It seems the tenant error is detected before any policies are run, so you don’t get a log for the team.

Not sure about how you could debug this better. But here is the approach I took.

I looked at it more like a regular bug than anything Ash specific. From the little LiveView experience I had, I knew that the user would be most likely set in a live_session. So i looked at the router setup and saw the ash_authentication_live_session setup by the ash_auth igniter and went to it’s definition. There I saw that the Module was added to the list of on_mount hooks. That lead me to look at the on_mount function in the module.
I saw the call to AshAuthentication.subject_to_user and looked at that function. There I saw the Ash.Query being constructed and the read being called.

I saw that there was this special context that would allow to bypass the policies and that there is no real actor yet. This is what I put in my first answer.

barnabasJ

barnabasJ

Ash Core Team

As a next step I would add a debug statement there. To see what is actually returned from the read.

I’m not sure how you would integrate the tenant here, though. Maybe instead of having just a preparation that adds the load, you create your own that looks at the subject to get the tenant and adds the load with an initial query that sets the tenant.

alexslade

alexslade OP

Thanks Barnaby, I may PR a logger statement there or something so that failure errors are at least logged.

No next step needed on the issue itself, I solved the tenancy issue with Ash.PlugHelpers.set_tenant in the router, this is picked up by AshAuth.

Thanks for your help :heart:

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
subsaharancoder
I’ve followed the Phoenix LiveView file upload code here Uploads — Phoenix LiveView v1.0.0-rc.7 and so far everything works just fine wit...
New
mohsen
I’m using an Umbrella project for a Phoenix application, and I want to have one Ecto Repo and one PostgreSQL database shared by all apps....
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews