GraphQL Relationship Resolved w/o Context

Hi there, 👋 I have a query like
{
alarm(filter: {id: { eq: 4711}}) {
message
events(sort: {field: ID, order: DESC}) {
id
message
}
}
}
{
alarm(filter: {id: { eq: 4711}}) {
message
events(sort: {field: ID, order: DESC}) {
id
message
}
}
}
which I need to filter with a Ash.Query.after_action. The filter is called as expected, but the actor of the request (available in the outer filter with the correct value) is nil for this nested. Do I manually need to forward the context in some way or might that be a bug? This is the stack trace raising an exception from Ash.Query.after_action (no error, but manually raised to get the stack):
(ash_graphql 0.25.5) lib/graphql/resolver.ex:1806: AshGraphql.Graphql.Resolver.to_resolution/3
(absinthe 1.7.1) lib/absinthe/middleware/dataloader.ex:37: Absinthe.Middleware.Dataloader.get_result/2
(absinthe 1.7.1) lib/absinthe/phase/document/execution/resolution.ex:232: Absinthe.Phase.Document.Execution.Resolution.reduce_resolution/1
(absinthe 1.7.1) lib/absinthe/phase/document/execution/resolution.ex:187: Absinthe.Phase.Document.Execution.Resolution.do_resolve_field/3
...
(phoenix 1.6.16) lib/phoenix/router/route.ex:41: Phoenix.Router.Route.call/2
(phoenix 1.6.16) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
(ash_graphql 0.25.5) lib/graphql/resolver.ex:1806: AshGraphql.Graphql.Resolver.to_resolution/3
(absinthe 1.7.1) lib/absinthe/middleware/dataloader.ex:37: Absinthe.Middleware.Dataloader.get_result/2
(absinthe 1.7.1) lib/absinthe/phase/document/execution/resolution.ex:232: Absinthe.Phase.Document.Execution.Resolution.reduce_resolution/1
(absinthe 1.7.1) lib/absinthe/phase/document/execution/resolution.ex:187: Absinthe.Phase.Document.Execution.Resolution.do_resolve_field/3
...
(phoenix 1.6.16) lib/phoenix/router/route.ex:41: Phoenix.Router.Route.call/2
(phoenix 1.6.16) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
12 Replies
Jan Ulbrich
Jan UlbrichOP•2y ago
These are the Ash-related dependencies I’m using:
{:ash, "~> 2.9.19"},
{:ash_authentication, "~> 3.11.3"},
{:ash_graphql, "~> 0.25.5"},
{:ash_json_api, git: "https://github.com/ash-project/ash_json_api.git", build: "main"},
{:ash_postgres, "~> 1.3.28"},
{:ash, "~> 2.9.19"},
{:ash_authentication, "~> 3.11.3"},
{:ash_graphql, "~> 0.25.5"},
{:ash_json_api, git: "https://github.com/ash-project/ash_json_api.git", build: "main"},
{:ash_postgres, "~> 1.3.28"},
ZachDaniel
ZachDaniel•2y ago
Can I see what your after action hook looks like?
Jan Ulbrich
Jan UlbrichOP•2y ago
Sorry, I missed your question earlier! Here you go:
actions do
read :read do
primary?(true)

prepare(fn query, context ->
IO.inspect(context, label: "Context") # <- actor nil
Ash.Query.after_action(query, fn _query, events ->
{:ok, patch_results_applying_actor_filters(events, context)}
end)
end)
end
actions do
read :read do
primary?(true)

prepare(fn query, context ->
IO.inspect(context, label: "Context") # <- actor nil
Ash.Query.after_action(query, fn _query, events ->
{:ok, patch_results_applying_actor_filters(events, context)}
end)
end)
end
In that function:
def patch_results_applying_actor_filters(events, context) do
with actor when actor != nil <- Map.get(context, :actor) do
...
end
end
def patch_results_applying_actor_filters(events, context) do
with actor when actor != nil <- Map.get(context, :actor) do
...
end
end
ZachDaniel
ZachDaniel•2y ago
can you add a change to the root resource and see if the actor is being passed at all? I don't see a reason why the actor wouldn't be available in that context you showed
Jan Ulbrich
Jan UlbrichOP•2y ago
Yes, I’ll add the same debug statement. Gimme a minute. It’s weird: The inner query is kind of executed first
[info] POST /graphql
[debug] Processing with Absinthe.Plug
Parameters: %{"query" => "{\n alarm(filter: { id: {eq: 4711}}) {\n originatedAt\n extid\n longitude\n latitude\n day\n month\n year\n payload\n kind\n subkind\n events(sort: {field: ORIGINATED_AT}) {\n originatedAt\n payload\n }\n }\n}", "variables" => nil}
Pipelines: [:graphql]
[debug] ABSINTHE schema=TickerWeb.Schema variables=%{}
---
{
alarm(filter: { id: {eq: 4711}}) {
originatedAt
extid
longitude
latitude
day
month
year
payload
kind
subkind
events(sort: {field: ORIGINATED_AT}) {
originatedAt
payload
}
}
}
---
[debug] QUERY OK source="alarms" db=9.9ms queue=0.1ms idle=1777.5ms
SELECT a0."id", a0."created_at", a0."updated_at", a0."year", a0."month", a0."day", a0."originated_at", a0."message", a0."extid", a0."operations_center", a0."kind", a0."subkind", a0."longitude", a0."latitude", a0."payload" FROM "alarms" AS a0 WHERE (a0."id" = $1) [4711]
Inner context: %{tracer: nil, actor: nil, authorize?: nil}
Outer context: %{
tracer: nil,
actor: %{
id: "bwb-102290",
[...]
},
authorize?: true
}
[debug] QUERY OK source="alarms" db=3.7ms idle=1788.8ms
SELECT a0."created_at", a0."updated_at", a0."message", a0."operations_center", a0."id" FROM "alarms" AS a0 WHERE (a0."id"::bigint = $1::bigint) [4711]
[debug] QUERY OK source="events" db=201.7ms idle=1793.6ms
SELECT e0."id", e0."originated_at", e0."extid", e0."payload" FROM "events" AS e0 WHERE (e0."id"::bigint = $1::bigint) ORDER BY e0."originated_at" [4711]
[info] POST /graphql
[debug] Processing with Absinthe.Plug
Parameters: %{"query" => "{\n alarm(filter: { id: {eq: 4711}}) {\n originatedAt\n extid\n longitude\n latitude\n day\n month\n year\n payload\n kind\n subkind\n events(sort: {field: ORIGINATED_AT}) {\n originatedAt\n payload\n }\n }\n}", "variables" => nil}
Pipelines: [:graphql]
[debug] ABSINTHE schema=TickerWeb.Schema variables=%{}
---
{
alarm(filter: { id: {eq: 4711}}) {
originatedAt
extid
longitude
latitude
day
month
year
payload
kind
subkind
events(sort: {field: ORIGINATED_AT}) {
originatedAt
payload
}
}
}
---
[debug] QUERY OK source="alarms" db=9.9ms queue=0.1ms idle=1777.5ms
SELECT a0."id", a0."created_at", a0."updated_at", a0."year", a0."month", a0."day", a0."originated_at", a0."message", a0."extid", a0."operations_center", a0."kind", a0."subkind", a0."longitude", a0."latitude", a0."payload" FROM "alarms" AS a0 WHERE (a0."id" = $1) [4711]
Inner context: %{tracer: nil, actor: nil, authorize?: nil}
Outer context: %{
tracer: nil,
actor: %{
id: "bwb-102290",
[...]
},
authorize?: true
}
[debug] QUERY OK source="alarms" db=3.7ms idle=1788.8ms
SELECT a0."created_at", a0."updated_at", a0."message", a0."operations_center", a0."id" FROM "alarms" AS a0 WHERE (a0."id"::bigint = $1::bigint) [4711]
[debug] QUERY OK source="events" db=201.7ms idle=1793.6ms
SELECT e0."id", e0."originated_at", e0."extid", e0."payload" FROM "events" AS e0 WHERE (e0."id"::bigint = $1::bigint) ORDER BY e0."originated_at" [4711]
I thought, that I may have added some query as a precondition, but that’s not the case: If I remove the relation in the query, there’s no query to the events table at all.
[debug] Processing with Absinthe.Plug
Parameters: %{"query" => "{\n alarm(filter: { id: {eq: 4711}}) {\n originatedAt\n extid\n longitude\n latitude\n day\n month\n year\n payload\n kind\n subkind\n }\n}", "variables" => nil}
Pipelines: [:graphql]
[debug] ABSINTHE schema=TickerWeb.Schema variables=%{}
---
{
alarm(filter: { id: {eq: 4711}}) {
originatedAt
extid
longitude
latitude
day
month
year
payload
kind
subkind
}
}
---
[debug] QUERY OK source="alarms" db=9.0ms idle=117.1ms
SELECT a0."id", a0."created_at", a0."updated_at", a0."year", a0."month", a0."day", a0."originated_at", a0."message", a0."extid", a0."operations_center", a0."kind", a0."subkind", a0."longitude", a0."latitude", a0."payload" FROM "alarms" AS a0 WHERE (a0."id" = $1) [4811]
[debug] Processing with Absinthe.Plug
Parameters: %{"query" => "{\n alarm(filter: { id: {eq: 4711}}) {\n originatedAt\n extid\n longitude\n latitude\n day\n month\n year\n payload\n kind\n subkind\n }\n}", "variables" => nil}
Pipelines: [:graphql]
[debug] ABSINTHE schema=TickerWeb.Schema variables=%{}
---
{
alarm(filter: { id: {eq: 4711}}) {
originatedAt
extid
longitude
latitude
day
month
year
payload
kind
subkind
}
}
---
[debug] QUERY OK source="alarms" db=9.0ms idle=117.1ms
SELECT a0."id", a0."created_at", a0."updated_at", a0."year", a0."month", a0."day", a0."originated_at", a0."message", a0."extid", a0."operations_center", a0."kind", a0."subkind", a0."longitude", a0."latitude", a0."payload" FROM "alarms" AS a0 WHERE (a0."id" = $1) [4811]
ZachDaniel
ZachDaniel•2y ago
it has to do with the after action hooks i.e the inner query's "after action" happens before the outer query's "after action" and you're on the latest ash_graphql? I figured it out
Jan Ulbrich
Jan UlbrichOP•2y ago
Yes, it’s 0.25.5 and installed as that version and not directly from master.
ZachDaniel
ZachDaniel•2y ago
releasing a fix now. I was a bit worried as this sounded like something that could affect authorization like the builtin policy authorization but its not really, only if you have query preparations that reference the actor on read I guess in some designs it could have been a security issue but not for people using the policy authorizer at least basically the case is when ash_graphql loaded related things, query preparations would get actor: nil but its highly unlikely that that would cause things to be less secure more like more secure (well, accidentally secure, preventing users who should be able to do things from doing them)
Jan Ulbrich
Jan UlbrichOP•2y ago
Which is at least conservative and wouldn’t compromise date.
ZachDaniel
ZachDaniel•2y ago
Yeah anyway 0.25.6 is out w/ the fix 🙂
Jan Ulbrich
Jan UlbrichOP•2y ago
Wow, you’re wild! 😀 Yes, that works: I can confirm that I now get the same context and my filters show the right data. Pew… 😌 As always: Thanks again for your fast help! 🚀
ZachDaniel
ZachDaniel•2y ago
My pleasure 🙂

Did you find this page helpful?