Alan Heywood
Alan Heywood
AEAsh Elixir
Created by Alan Heywood on 9/12/2023 in #support
Implementing token inactivity timeout with automatic expiry extend
Hi folks, I am using the ash_authentication password strategy with my resource configured to store all tokens, and require token presence. The project has a VueJS frontend that communicates with the backend via GQL. When a user logs in, the frontend receives the token and includes it in the header as a bearer token. This is all working well. I have set a short-lived token_lifetime, and would now like to add a way to log the user out after a period of inactivity, and also extend their session expiry whenever they make a request to keep them logged in beyond the initial expiry time. I have realised that this may be at odds with using JWTs as the token, as they are effectively tamper proof. Any thoughts on how to implement this? I think what I actually want is closer to a session based login strategy, that doesn't use JWTs at all, and simply generates a random token to give to the frontend.
26 replies
AEAsh Elixir
Created by Alan Heywood on 8/16/2023 in #support
Nested aggregates and calculations
I have a calculation that looks like this conceptually:
some_documentation_created (calculation) refers to:
count_of_documented_skills (aggregate) filters by:
status_calculated (calculation) refers to:
latest_documentation_status (aggregate) sorts by:
timestamp (calculation)
all_documentation_approved (calculation) refers to:
count_of_skills (aggregate)
count_of_approved_skills (aggregate) filters by:
status_calculated (calculation) refers to:
latest_documentation_status (aggregate) sorts by:
timestamp (calculation)
some_documentation_created (calculation) refers to:
count_of_documented_skills (aggregate) filters by:
status_calculated (calculation) refers to:
latest_documentation_status (aggregate) sorts by:
timestamp (calculation)
all_documentation_approved (calculation) refers to:
count_of_skills (aggregate)
count_of_approved_skills (aggregate) filters by:
status_calculated (calculation) refers to:
latest_documentation_status (aggregate) sorts by:
timestamp (calculation)
Can a calculation refer to an aggregate that filters by a calculation (that refers to an aggregate)? If it should be possible, the issue is illustrated here: https://gist.github.com/ahey/2c3fcb0dd215f3a82620563e96344949
13 replies
AEAsh Elixir
Created by Alan Heywood on 6/8/2023 in #support
Metaprogramming with expressions
I am generating a module at compile time, and my Ash.Query.filter doesn't work as intended. Please see comment below
defp generate_oban_worker_module(module, cache) do
contents =
quote do
require Ash.Query
use Oban.Worker, ...

@impl Oban.Worker
def perform(%Oban.Job{args: %{"id" => id}}) do
record =
unquote(cache.module)
# The following line doesn't work.
# It is evaluating to true and returning all records
|> Ash.Query.filter(id == ^id)
|> unquote(cache.api).read_one!(authorize?: false)
...
end

Module.create(
worker_name(module, cache),
contents,
Macro.Env.location(__ENV__)
)
end
defp generate_oban_worker_module(module, cache) do
contents =
quote do
require Ash.Query
use Oban.Worker, ...

@impl Oban.Worker
def perform(%Oban.Job{args: %{"id" => id}}) do
record =
unquote(cache.module)
# The following line doesn't work.
# It is evaluating to true and returning all records
|> Ash.Query.filter(id == ^id)
|> unquote(cache.api).read_one!(authorize?: false)
...
end

Module.create(
worker_name(module, cache),
contents,
Macro.Env.location(__ENV__)
)
end
3 replies
AEAsh Elixir
Created by Alan Heywood on 6/5/2023 in #support
Possible to add notifier to a resource from an extension on another resource?
Is it possible to dynamically (at compile time) add a notifier to a resource, from within the transformer of an extension on another resource?
17 replies
AEAsh Elixir
Created by Alan Heywood on 5/4/2023 in #support
Adding a Notifier breaks elixir_sense autocompletions
I have recently added an ash Notifier to my project, and it has caused the elixir_sense plugin to throw an error when trying to show documentation. It is attempting to call .sections() on my notifier, but there is no spark DSL for notifiers.
[Error - 11:47:38] Request textDocument/completion failed.
Message: an exception was raised:
** (UndefinedFunctionError) function Ht.SectionAuthorisedRole.Notifier.sections/0 is undefined or private
(heretask 0.1.0) Ht.SectionAuthorisedRole.Notifier.sections()
(elixir 1.14.4) lib/enum.ex:4249: Enum.flat_map_list/2
(elixir 1.14.4) lib/enum.ex:4250: Enum.flat_map_list/2
(spark 1.1.8) lib/spark/elixir_sense/plugin.ex:538: Spark.ElixirSense.Plugin.get_constructors/4
(spark 1.1.8) lib/spark/elixir_sense/plugin.ex:106: Spark.ElixirSense.Plugin.get_suggestions/4
(elixir_sense 2.0.0) lib/elixir_sense/providers/suggestion/generic_reducer.ex:48: ElixirSense.Providers.Suggestion.GenericReducer.reduce/6
(elixir 1.14.4) lib/enum.ex:4751: Enumerable.List.reduce/3
(elixir 1.14.4) lib/enum.ex:2514: Enum.reduce_while/3
Code: -32000
[Error - 11:47:38] Request textDocument/completion failed.
Message: an exception was raised:
** (UndefinedFunctionError) function Ht.SectionAuthorisedRole.Notifier.sections/0 is undefined or private
(heretask 0.1.0) Ht.SectionAuthorisedRole.Notifier.sections()
(elixir 1.14.4) lib/enum.ex:4249: Enum.flat_map_list/2
(elixir 1.14.4) lib/enum.ex:4250: Enum.flat_map_list/2
(spark 1.1.8) lib/spark/elixir_sense/plugin.ex:538: Spark.ElixirSense.Plugin.get_constructors/4
(spark 1.1.8) lib/spark/elixir_sense/plugin.ex:106: Spark.ElixirSense.Plugin.get_suggestions/4
(elixir_sense 2.0.0) lib/elixir_sense/providers/suggestion/generic_reducer.ex:48: ElixirSense.Providers.Suggestion.GenericReducer.reduce/6
(elixir 1.14.4) lib/enum.ex:4751: Enumerable.List.reduce/3
(elixir 1.14.4) lib/enum.ex:2514: Enum.reduce_while/3
Code: -32000
10 replies
AEAsh Elixir
Created by Alan Heywood on 4/20/2023 in #support
Multiple filters giving unexpected result
Say I have a data model similar to this:
defmodule Ht.Org do
has_many :accounts, Ht.Account
end

defmodule Ht.User do
has_many :accounts, Ht.Account
end

defmodule Ht.Account do
belongs_to :org, Ht.Org
belongs_to :user, Ht.User
end
defmodule Ht.Org do
has_many :accounts, Ht.Account
end

defmodule Ht.User do
has_many :accounts, Ht.Account
end

defmodule Ht.Account do
belongs_to :org, Ht.Org
belongs_to :user, Ht.User
end
I create some records and perform this query (note that create! is my own simple factory helper)
org = create!(:org)

user_1 = create!(:user)
user_1_account = create!(:account, user_id: user_1.id, org_id: org.id)

user_2 = create!(:user)
user_2_account = create!(:account, user_id: user_2.id, org_id: org.id)

Ht.User
|> Ash.Query.filter(id == ^user_1.id)
|> Ash.Query.filter(id == ^user_2.id or accounts.org.id == ^org.id)
|> Api.read(authorize?: false)
|> IO.inspect()
org = create!(:org)

user_1 = create!(:user)
user_1_account = create!(:account, user_id: user_1.id, org_id: org.id)

user_2 = create!(:user)
user_2_account = create!(:account, user_id: user_2.id, org_id: org.id)

Ht.User
|> Ash.Query.filter(id == ^user_1.id)
|> Ash.Query.filter(id == ^user_2.id or accounts.org.id == ^org.id)
|> Api.read(authorize?: false)
|> IO.inspect()
This is returning both user records. My understanding is that multiple Ash.Query.filter calls are combined with and, so the first filter should constrain the results to 1 record at most. Is there anything I am doing wrong in the way my filters are defined?
12 replies
AEAsh Elixir
Created by Alan Heywood on 3/29/2023 in #support
relates_to_actor_attribute_via
I have a resource called Actor that is not backed by the database, it looks something like this:
defmodule Ht.Actor do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

relationships do
belongs_to :user, Ht.User
belongs_to :role, Ht.Role
end
end
defmodule Ht.Actor do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

relationships do
belongs_to :user, Ht.User
belongs_to :role, Ht.Role
end
end
The user will always be set, and the role will sometimes be set. I want to be able to write policies for both cases. What I have done up until now is pass either the user or the role to Ash.PlugHelpers.set_actor(), which lets me write policies like this:
policies do
bypass Ht.Policy.Checks.actor_is(Ht.User) do
authorize_if action_type(:read) && relates_to_actor_via([:accounts, :user])
end

bypass Ht.Policy.Checks.actor_is(Ht.Role) do
authorize_if action_type(:read) && relates_to_actor_via([:accounts, :roles])
end

policy always() do
forbid_if always()
end
end
policies do
bypass Ht.Policy.Checks.actor_is(Ht.User) do
authorize_if action_type(:read) && relates_to_actor_via([:accounts, :user])
end

bypass Ht.Policy.Checks.actor_is(Ht.Role) do
authorize_if action_type(:read) && relates_to_actor_via([:accounts, :roles])
end

policy always() do
forbid_if always()
end
end
My preferred choice has always been to pass the actual actor record to Ash.PlugHelpers.set_actor() instead of user or role, however I wasn't able to get this to work with relates_to_actor_via. I now need to add more data to my Actor resource, and have it available in a custom read, so I am looking for a way to make it work. First I looked for a way to set some context at the same place I call set_actor however it looks like that context is not passed into a custom read. I am considering a new check relates_to_actor_attribute_via. That way I can pass my full actor record to set_actor and write my policies on the necessary actor attribute. The other possible solution is having a way to specify arbitrary context on the connection that is passed to read actions, etc in the same way actor is. Any thoughts on these approaches?
9 replies
AEAsh Elixir
Created by Alan Heywood on 3/24/2023 in #support
Improve compile times
I have noticed that my compile times are growing. For example, if I change one resource file and compile, it takes about 8 seconds for the compilation to complete. I started out with a single Ash API, and I have now split this into two. However, compiling with the --verbose flag shows that all files in the the api of the modified resource are compiled (expected) however a number of resources from the other API are also recompiled. 23 files are recompiled in total. So, is 8 seconds to recompile 23 files on an M1 Macbook pro normal? Could the fact that I have cross-api relationships be contributing to this? 8 seconds doesn't sound bad, however as the application grows it could become a barrier to doing test driven development.
51 replies
AEAsh Elixir
Created by Alan Heywood on 2/23/2023 in #support
Building can? function that supports filter checks
I am looking to build out a custom GQL query that allows my front-end to ask the backend whether it has permissions to do an action on a resource instance. I have already built this out to a resource level to the extent that the front-end can ask: "Can I perform this action on this resource type". Next up, I want to support asking "can I perform this action on this particular resource with ID = x". @frankdugan3 already added some canfunctions, which I am using in the above scenario. However I will need extra functionality so that in addition, the filter checks are taken into account. The reason is that some of my action policies use relates_to_actor_via. For the reads I suppose I could actually perform the read and use the result. However for :update there are also filter checks and I can't use that strategy. Any thoughts on how best to approach this? I have started looking into it however quickly got lost in the engine / request modules.
20 replies
AEAsh Elixir
Created by Alan Heywood on 2/14/2023 in #support
Alternative approach for aggregate of aggregate with group by
Assuming three resources: A has_many B has_many C, and a requirement to calculate, for each record A, the sum of distinct C.status values, grouped by C.status. I tried implementing this via the ash DSL, however I think I would need a few features not yet implemented such as "aggregate of aggregate" and "group by aggregate". Would a custom calculation and falling back to ecto be the best way forward?
4 replies
AEAsh Elixir
Created by Alan Heywood on 1/30/2023 in #support
Ecto.Repo.insert! compatibility issue as of ash v2.5.10
I noticed some of my tests failing after upgrading to the latest ash, relating to the work recently done to add relationships to the underlying ecto schemas. Please see https://github.com/ahey/ash_postgres/commit/acc947292d2aa1e4a9ffccaf4d63069cf6c82a90 for details and a test case.
16 replies
AEAsh Elixir
Created by Alan Heywood on 1/29/2023 in #support
Error loading aggregate when resource has a relates_to_actor_via policy
Hi Folks, I have encountered an issue when trying to load a count aggregate. The conditions to reproduce seem to be: - DataLayer is Postgres - Resource has a relates_to_actor_via policy on read - The relates_to_actor_via path includes a has_many relationship - An aggregate is loaded I tried to debug this down in the authorizer / check layers however was not able to figure it out so far. I've created a failing test on ash_postgres https://github.com/ahey/ash_postgres/commit/f290ae47b83aa2c1148978327d970fe38ec5000b
18 replies