Any pointers on testing authenticated LV's with AshAuthentication Magic Link?

I'm attempting to reproduce a similar workflow to the email/password instructions in the docs but am running into issues around confirmation add ons when combined with magic link authentication. My current setup looks like this, but is erroring with a Fails with AshAuthentication.Errors.CannotConfirmUnconfirmedUser:
def register_and_log_in_user(%{conn: conn} = context) do
user = Ash.Seed.seed!(MyApp.Accounts.User, %{email: "test@gmail.com"})

strategy = AshAuthentication.Info.strategy!(MyApp.Accounts.User, :magic_link)

AshAuthentication.Strategy.action(strategy, :request, %{email: user.email})

{:ok, token} = AshAuthentication.Strategy.MagicLink.request_token_for(strategy, user)
{:ok, authenticated } = AshAuthentication.Strategy.action(strategy, :sign_in, %{token: token})

new_conn =
conn
|> Phoenix.ConnTest.init_test_session(%{})
|> AshAuthentication.Plug.Helpers.store_in_session(authenticated)

%{context | conn: new_conn}
end
def register_and_log_in_user(%{conn: conn} = context) do
user = Ash.Seed.seed!(MyApp.Accounts.User, %{email: "test@gmail.com"})

strategy = AshAuthentication.Info.strategy!(MyApp.Accounts.User, :magic_link)

AshAuthentication.Strategy.action(strategy, :request, %{email: user.email})

{:ok, token} = AshAuthentication.Strategy.MagicLink.request_token_for(strategy, user)
{:ok, authenticated } = AshAuthentication.Strategy.action(strategy, :sign_in, %{token: token})

new_conn =
conn
|> Phoenix.ConnTest.init_test_session(%{})
|> AshAuthentication.Plug.Helpers.store_in_session(authenticated)

%{context | conn: new_conn}
end
Any guidance would be much appreciated!
GitHub
ash_authentication/documentation/topics/testing.md at main · team-...
The Ash Authentication framework. Contribute to team-alembic/ash_authentication development by creating an account on GitHub.
7 Replies
barnabasj
barnabasj4w ago
Is this happening in a specific test? Do you have a stack trace?
Arvind S.
Arvind S.OP4w ago
This is being set up in conn_case.ex so the suite that pulls in register_and_log_in_user inside the setup block fails before getting to any assertions Stack trace fyr:
** (MatchError) no match of right hand side value: {:error, %AshAuthentication.Errors.AuthenticationFailed{caused_by: %Ash.Error.Forbidden{bread_crumbs: ["Error returned from: MyApp.Accounts.User.sign_in_with_magic_link"], changeset: "#Changeset<>", errors: [%AshAuthentication.Errors.CannotConfirmUnconfirmedUser{resource: "Elixir.MyApp.Accounts.User", confirmation_strategy: "confirm_change", splode: Ash.Error, bread_crumbs: ["Error returned from: MyApp.Accounts.User.sign_in_with_magic_link"], vars: [], path: [], stacktrace: #Splode.Stacktrace<>, class: :forbidden}]}, changeset: nil, field: nil, query: nil, strategy: %AshAuthentication.Strategy.MagicLink{identity_field: :email, lookup_action_name: :get_by_email, name: :magic_link, prevent_hijacking?: false, registration_enabled?: true, request_action_name: :request_magic_link, require_interaction?: true, resource: MyApp.Accounts.User, sender: {MyApp.Senders.Accounts.SendMagicLinkEmail, []}, sign_in_action_name: :sign_in_with_magic_link, single_use_token?: true, token_lifetime: {10, :minutes}, token_param_name: :token}, splode: nil, bread_crumbs: [], vars: [], path: [], stacktrace: #Splode.Stacktrace<>, class: :forbidden}}
** (MatchError) no match of right hand side value: {:error, %AshAuthentication.Errors.AuthenticationFailed{caused_by: %Ash.Error.Forbidden{bread_crumbs: ["Error returned from: MyApp.Accounts.User.sign_in_with_magic_link"], changeset: "#Changeset<>", errors: [%AshAuthentication.Errors.CannotConfirmUnconfirmedUser{resource: "Elixir.MyApp.Accounts.User", confirmation_strategy: "confirm_change", splode: Ash.Error, bread_crumbs: ["Error returned from: MyApp.Accounts.User.sign_in_with_magic_link"], vars: [], path: [], stacktrace: #Splode.Stacktrace<>, class: :forbidden}]}, changeset: nil, field: nil, query: nil, strategy: %AshAuthentication.Strategy.MagicLink{identity_field: :email, lookup_action_name: :get_by_email, name: :magic_link, prevent_hijacking?: false, registration_enabled?: true, request_action_name: :request_magic_link, require_interaction?: true, resource: MyApp.Accounts.User, sender: {MyApp.Senders.Accounts.SendMagicLinkEmail, []}, sign_in_action_name: :sign_in_with_magic_link, single_use_token?: true, token_lifetime: {10, :minutes}, token_param_name: :token}, splode: nil, bread_crumbs: [], vars: [], path: [], stacktrace: #Splode.Stacktrace<>, class: :forbidden}}
Ok I think I have a work around, if I set the confirmed_at manually to DateTime.utc_now() or a valid timestamp the errors went away
barnabasj
barnabasj4w ago
From the error message I assume you have configured the confirmation addon and you are hitting this line https://github.com/team-alembic/ash_authentication/blob/e8dcae5d182b21b3ac8ff11b3af3495e69d34bd0/lib/ash_authentication/add_ons/confirmation/confirmation_hook_change.ex#L191 You can probably add a date in the seed to make it pass, but it might make sense to take a step back and look at what exactly you have configured. Are you only using magic link sign in?
GitHub
ash_authentication/lib/ash_authentication/add_ons/confirmation/conf...
The Ash Authentication framework. Contribute to team-alembic/ash_authentication development by creating an account on GitHub.
Arvind S.
Arvind S.OP4w ago
Right here's my config:
authentication do
add_ons do
log_out_everywhere do
apply_on_password_change? true
end

confirmation :confirm_new_user do
monitor_fields [:email]
confirm_on_create? true
confirm_on_update? false
confirm_action_name :confirm_new_user
require_interaction? true
auto_confirm_actions [:sign_in_with_magic_link]
prevent_hijacking? false

sender SendNewUserConfirmationEmail
end

confirmation :confirm_change do
monitor_fields [:email]
confirm_on_create? false
confirm_on_update? true
confirm_action_name :confirm_change
require_interaction? true

sender SendEmailChangeConfirmationEmail
end
end

tokens do
enabled? true
token_resource MyApp.Accounts.Token
signing_secret MyApp.Secrets
store_all_tokens? true
require_token_presence_for_authentication? true
end

strategies do
magic_link do
identity_field :email
registration_enabled? true
require_interaction? true
prevent_hijacking? false

sender SendMagicLinkEmail
end
end
end
authentication do
add_ons do
log_out_everywhere do
apply_on_password_change? true
end

confirmation :confirm_new_user do
monitor_fields [:email]
confirm_on_create? true
confirm_on_update? false
confirm_action_name :confirm_new_user
require_interaction? true
auto_confirm_actions [:sign_in_with_magic_link]
prevent_hijacking? false

sender SendNewUserConfirmationEmail
end

confirmation :confirm_change do
monitor_fields [:email]
confirm_on_create? false
confirm_on_update? true
confirm_action_name :confirm_change
require_interaction? true

sender SendEmailChangeConfirmationEmail
end
end

tokens do
enabled? true
token_resource MyApp.Accounts.Token
signing_secret MyApp.Secrets
store_all_tokens? true
require_token_presence_for_authentication? true
end

strategies do
magic_link do
identity_field :email
registration_enabled? true
require_interaction? true
prevent_hijacking? false

sender SendMagicLinkEmail
end
end
end
I only have magic link at the moment, but will be adding Google-based OAuth hence the need for the confirmation strategy
barnabasj
barnabasj4w ago
I'll look at recreating this tomorrow, I feel like the auto_confirm_actions should handle this, but I haven't used the confirm addon myself yet. I'll report back with my findings, until then you can use the workaround with setting the date manually
Arvind S.
Arvind S.OP4w ago
Awesome, thanks!
barnabasj
barnabasj4w ago
I only had time for some surface level investigating, but from what I gathered so far, because you actually seed the user first the create action for sign in is doing an upsert which is detected and treated as an update. So it hits your confirmed change confirmation addon config which doesn't have the auto_confirm_actions and prevent_hijacking options set like your confirm_new_user config. I'm still curious about how this actually plays all together as it doesn't feel very intuitive. I will write more once I gather some more information

Did you find this page helpful?