Yep sure!
In my User Resource the strategy is defined
strategies do
magic_link do
identity_field :email
registration_enabled? false
sender WedEvents.Accounts.User.Senders.SendMagicLinkEmail
end
and the sign action is specified. This is the same action that is create via codegen.
` create :sign_in_with_magic_link do
description "Sign in a user with magic link if they exist."
argument :token, :string do
description "The token from the magic link that was sent to the user"
allow_nil? false
end
upsert? true
upsert_identity :unique_email
upsert_fields [:email]
change AshAuthentication.Strategy.MagicLink.SignInChange
metadata :token, :string do
allow_nil? false
end
end
Version is 4.3.3 as can be seen in the stacktrace below
ArgumentError at GET /auth/user/magic_link/
Exception:
** (ArgumentError) Found an action of type create while looking for an action of type read
Perhaps you've passed a changeset with the incorrect action type?
(ash 3.4.43) lib/ash/resource/info.ex:614: Ash.Resource.Info.action/3
(ash 3.4.43) lib/ash/query/query.ex:531: Ash.Query.for_read/4
(ash_authentication 4.3.3) lib/ash_authentication/strategies/magic_link/actions.ex:82: AshAuthentication.Strategy.MagicLink.Actions.sign_in/3
(ash_authentication 4.3.3) lib/ash_authentication/strategies/magic_link/plug.ex:44: AshAuthentication.Strategy.MagicLink.Plug.sign_in/2
(ash_authentication 4.3.3) lib/ash_authentication/plug/dispatcher.ex:29: AshAuthentication.Plug.Dispatcher.call/2
(phoenix 1.7.14) lib/phoenix/router/route.ex:42: Phoenix.Router.Route.call/2
(phoenix 1.7.14) lib/phoenix/router.ex:484: Phoenix.Router.__call__/5
(wed_events 0.1.0) lib/wed_events_web/endpoint.ex:1: WedEventsWeb.Endpoint.plug_builder_call/2
(wed_events 0.1.0) deps/plug/lib/plug/debugger.ex:136: WedEventsWeb.Endpoint."call (overridable 3)"/2
(wed_events 0.1.0) lib/wed_events_web/endpoint.ex:1: WedEventsWeb.Endpoint.call/2
(phoenix 1.7.14) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(bandit 1.6.0) lib/bandit/pipeline.ex:127: Bandit.Pipeline.call_plug!/2
(bandit 1.6.0) lib/bandit/pipeline.ex:36: Bandit.Pipeline.run/4
(bandit 1.6.0) lib/bandit/http1/handler.ex:12: Bandit.HTTP1.Handler.handle_data/3
(bandit 1.6.0) lib/bandit/delegating_handler.ex:18: Bandit.DelegatingHandler.handle_data/3
(bandit 1.6.0) /home/deepc/Repos/wed_events/deps/thousand_island/lib/thousand_island/handler.ex:385: Bandit.DelegatingHandler.handle_info/2
(stdlib 5.2.3) gen_server.erl:1095: :gen_server.try_handle_info/3
(stdlib 5.2.3) gen_server.erl:1183: :gen_server.handle_msg/6
(stdlib 5.2.3) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
Code:
lib/ash/resource/info.ex
609
610 %{type: ^type} = action ->
611 action
612
613 %{type: found_type} ->
614> raise ArgumentError, """
615 Found an action of type #{found_type} while looking for an action of type #{type}
616
617 Perhaps you've passed a changeset with the incorrect action type?
618 """
619 end
lib/ash/query/query.ex
526 query =
527 query
528 |> Map.put(:params, Map.merge(query.params, Map.new(args)))
529 |> set_context(Keyword.get(opts, :context, %{}))
530
531> action = Ash.Resource.Info.action(query.resource, action_name, :read)
532
533 if action do
534 name = fn ->
535 "query:" <> Ash.Resource.Info.trace_name(query.resource) <> ":#{action_name}"
536 end
lib/ash_authentication/strategies/magic_link/actions.ex
77 |> Keyword.put_new_lazy(:domain, fn -> Info.domain!(strategy.resource) end)
78
79 strategy.resource
80 |> Query.new()
81 |> Query.set_context(%{private: %{ash_authentication?: true}})
82> |> Query.for_read(strategy.sign_in_action_name, params, options)
83 |> Ash.read()
84 |> case do
85 {:ok, [user]} ->
86 {:ok, user}
87
lib/ash_authentication/strategies/magic_link/plug.ex
39 params =
40 conn.params
41 |> Map.take([to_string(strategy.token_param_name)])
42
43 opts = opts(conn)
44> result = Strategy.action(strategy, :sign_in, params, opts)
45 store_authentication_result(conn, result)
46 end
47
48 defp subject_params(conn, strategy) do
49 subject_name =
lib/ash_authentication/plug/dispatcher.ex
24 @spec call(Conn.t(), config | any) :: Conn.t()
25 def call(conn, {phase, strategy, return_to}) do
26 activity = {Strategy.name(strategy), phase}
27
28 strategy
29> |> Strategy.plug(phase, conn)
30 |> get_authentication_result()
31 |> case do
32 {conn, _} when conn.state not in @unsent ->
33 conn
34
lib/phoenix/router/route.ex
37 @doc "Used as a plug on forwarding"
38 def call(%{path_info: path, script_name: script} = conn, {fwd_segments, plug, opts}) do
39 new_path = path -- fwd_segments
40 {base, ^new_path} = Enum.split(path, length(path) - length(new_path))
41 conn = %{conn | path_info: new_path, script_name: script ++ base}
42> conn = plug.call(conn, plug.init(opts))
43 %{conn | path_info: path, script_name: script}
44 end
45
46 @doc """
47 Receives the verb, path, plug, options and helper
lib/phoenix/router.ex
479 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
480 halted_conn
481
482 %Plug.Conn{} = piped_conn ->
483 try do
484> plug.call(piped_conn, plug.init(opts))
485 else
486 conn ->
487 measurements = %{duration: System.monotonic_time() - start}
488 metadata = %{metadata | conn: conn}
489 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
lib/wed_events_web/endpoint.ex
1> defmodule WedEventsWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :wed_events
3
4 # The session will be stored in the cookie and signed,
5 # this means its contents can be read but not tampered with.
6 # Set :encryption_salt if you would also like to encrypt it.
deps/plug/lib/plug/debugger.ex
131 case conn do
132 %Plug.Conn{path_info: ["__plug__", "debugger", "action"], method: "POST"} ->
133 Plug.Debugger.run_action(conn)
134
135 %Plug.Conn{} ->
136> super(conn, opts)
137 end
138 rescue
139 e in Plug.Conn.WrapperError ->
140 %{conn: conn, kind: kind, reason: reason, stack: stack} = e
141 Plug.Debugger.__catch__(conn, kind, reason, stack, @plug_debugger)
lib/wed_events_web/endpoint.ex
1> defmodule WedEventsWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :wed_events
3
4 # The session will be stored in the cookie and signed,
5 # this means its contents can be read but not tampered with.
6 # Set :encryption_salt if you would also like to encrypt it.
lib/phoenix/endpoint/sync_code_reload_plug.ex
17
18 def call(conn, {endpoint, opts}), do: do_call(conn, endpoint, opts, true)
19
20 defp do_call(conn, endpoint, opts, retry?) do
21 try do
22> endpoint.call(conn, opts)
23 rescue
24 exception in [UndefinedFunctionError] ->
25 case exception do
26 %UndefinedFunctionError{module: ^endpoint} when retry? ->
27 # Sync with the code reloader and retry once
lib/bandit/pipeline.ex
122 end
123 end
124
125 @spec call_plug!(Plug.Conn.t(), plug_def()) :: Plug.Conn.t() | no_return()
126 defp call_plug!(%Plug.Conn{} = conn, {plug, plug_opts}) when is_atom(plug) do
127> case plug.call(conn, plug_opts) do
128 %Plug.Conn{} = conn -> conn
129 other -> raise("Expected #{plug}.call/2 to return %Plug.Conn{} but got: #{inspect(other)}")
130 end
131 end
132
lib/bandit/pipeline.ex
31 conn = build_conn!(transport, method, request_target, headers, opts)
32 span = Bandit.Telemetry.start_span(:request, measurements, Map.put(metadata, :conn, conn))
33
34 try do
35 conn
36> |> call_plug!(plug)
37 |> maybe_upgrade!()
38 |> case do
39 {:no_upgrade, conn} ->
40 %Plug.Conn{adapter: {_mod, adapter}} = conn = commit_response!(conn)
41 Bandit.Telemetry.stop_span(span, adapter.metrics, %{conn: conn})
lib/bandit/http1/handler.ex
7 @impl ThousandIsland.Handler
8 def handle_data(data, socket, state) do
9 transport = %Bandit.HTTP1.Socket{socket: socket, buffer: data, opts: state.opts}
10 connection_span = ThousandIsland.Socket.telemetry_span(socket)
11
12> case Bandit.Pipeline.run(transport, state.plug, connection_span, state.opts) do
13 {:ok, transport} -> maybe_keepalive(transport, state)
14 {:error, _reason} -> {:close, state}
15 {:upgrade, _transport, :websocket, opts} -> do_websocket_upgrade(opts, state)
16 end
17 end
lib/bandit/delegating_handler.ex
13 |> handle_bandit_continuation(socket)
14 end
15
16 @impl ThousandIsland.Handler
17 def handle_data(data, socket, %{handler_module: handler_module} = state) do
18> handler_module.handle_data(data, socket, state)
19 |> handle_bandit_continuation(socket)
20 end
21
22 @impl ThousandIsland.Handler
23 def handle_shutdown(socket, %{handler_module: handler_module} = state) do
/home/deepc/Repos/wed_events/deps/thousand_island/lib/thousand_island/handler.ex
380 {%ThousandIsland.Socket{socket: raw_socket} = socket, state}
381 )
382 when msg in [:tcp, :ssl] do
383 ThousandIsland.Telemetry.untimed_span_event(socket.span, :async_recv, %{data: data})
384
385> __MODULE__.handle_data(data, socket, state)
386 |> handle_continuation(socket)
387 end
388
389 def handle_info(
390 {msg, raw_socket},
gen_server.erl
No code available.
gen_server.erl
No code available.
proc_lib.erl
No code available.
Connection details
Params
%{"token" => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3QiOiJzaWduX2luX3dpdGhfbWFnaWNfbGluayIsImF1ZCI6In4-IDQuMyIsImV4cCI6MTczMjE5NjQwMywiaWF0IjoxNzMyMTk1ODAzLCJpZGVudGl0eSI6ImFkbWluQGV4YW1wbGUuY29tIiwiaXNzIjoiQXNoQXV0aGVudGljYXRpb24gdjQuMy4zIiwianRpIjoiMzA0dnRwdjI5NnYxYnEwbjBrMDAyaGM5IiwibmJmIjoxNzMyMTk1ODAzLCJzdWIiOiJ1c2VyIn0.eZ2sVQlRMKTxrmHaAzdrDahoLcU08T-Ko89qNR2r0So"}
Request info
- URI: http://127.0.0.1:4000/auth/user/magic_link/
- Query string: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3QiOiJzaWduX2luX3dpdGhfbWFnaWNfbGluayIsImF1ZCI6In4-IDQuMyIsImV4cCI6MTczMjE5NjQwMywiaWF0IjoxNzMyMTk1ODAzLCJpZGVudGl0eSI6ImFkbWluQGV4YW1wbGUuY29tIiwiaXNzIjoiQXNoQXV0aGVudGljYXRpb24gdjQuMy4zIiwianRpIjoiMzA0dnRwdjI5NnYxYnEwbjBrMDAyaGM5IiwibmJmIjoxNzMyMTk1ODAzLCJzdWIiOiJ1c2VyIn0.eZ2sVQlRMKTxrmHaAzdrDahoLcU08T-Ko89qNR2r0So
Headers
- accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7
- accept-encoding: gzip, deflate, br, zstd
- accept-language: en-US,en;q=0.9
- connection: keep-alive
- cookie: _wed_events_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYUXRkcTlhUXE3VTN6aWtrUmNkUGdYQjBJ.HXqgXcm-2bHHXpqazHqG0FX_i6qJuYv2oTOY4jhJVxk
- host: 127.0.0.1:4000
- sec-ch-ua: “Microsoft Edge”;v=“131”, “Chromium”;v=“131”, “Not_A Brand”;v=“24”
- sec-ch-ua-mobile: ?0
- sec-ch-ua-platform: “Windows”
- sec-fetch-dest: document
- sec-fetch-mode: navigate
- sec-fetch-site: none
- sec-fetch-user: ?1
- upgrade-insecure-requests: 1
- user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0
Session
%{"_csrf_token" => "Qtdq9aQq7U3zikkRcdPgXB0I"}