Packages
ash_authentication
5.0.0-rc.4
5.0.0-rc.12
5.0.0-rc.11
5.0.0-rc.10
5.0.0-rc.9
5.0.0-rc.8
5.0.0-rc.7
5.0.0-rc.6
5.0.0-rc.5
5.0.0-rc.4
5.0.0-rc.3
5.0.0-rc.2
5.0.0-rc.1
5.0.0-rc.0
4.14.1
4.14.0
4.13.7
4.13.6
4.13.5
4.13.4
4.13.3
4.13.2
retired
4.13.1
retired
4.13.0
4.12.0
4.11.0
4.10.0
4.9.9
4.9.8
4.9.7
4.9.6
4.9.5
4.9.4
4.9.3
4.9.2
4.9.1
4.9.0
4.8.7
4.8.6
4.8.5
4.8.3
4.8.2
4.8.1
4.8.0
4.7.6
4.7.5
4.7.4
4.7.3
4.7.2
4.7.1
4.7.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.6
4.5.5
4.5.4
4.5.3
4.5.2
4.5.1
4.5.0
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.12
4.3.11
4.3.10
4.3.9
4.3.8
4.3.7
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
4.0.0-rc.6
4.0.0-rc.5
4.0.0-rc.3
4.0.0-rc.2
4.0.0-rc.1
4.0.0-rc.0
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.16
3.11.15
3.11.14
3.11.13
3.11.12
3.11.11
3.11.10
3.11.9
3.11.8
3.11.7
3.11.6
3.11.5
3.11.4
3.11.3
3.11.2
3.11.1
3.11.0
3.10.8
3.10.7
3.10.6
3.10.5
3.10.4
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.0
3.7.9
3.7.8
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
3.6.1
3.6.0
3.5.3
3.5.2
3.5.1
3.5.0
3.3.1
3.3.0
3.2.2
3.2.1
3.2.0
3.1.0
3.0.3
Authentication extension for the Ash Framework.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/ash_authentication.add_strategy.magic_link.ex
# SPDX-FileCopyrightText: 2022 Alembic Pty Ltd
#
# SPDX-License-Identifier: MIT
# credo:disable-for-this-file Credo.Check.Design.AliasUsage
if Code.ensure_loaded?(Igniter) do
defmodule Mix.Tasks.AshAuthentication.AddStrategy.MagicLink do
use Igniter.Mix.Task
@example "mix ash_authentication.add_strategy.magic_link"
@shortdoc "Adds magic link authentication to your user resource"
@moduledoc """
#{@shortdoc}
## Example
```bash
#{@example}
```
## Options
* `--user`, `-u` - The user resource. Defaults to `YourApp.Accounts.User`
* `--identity-field`, `-i` - The field on the user resource that will be used to identify
the user. Defaults to `email`
"""
def info(_argv, _composing_task) do
%Igniter.Mix.Task.Info{
group: :ash,
example: @example,
extra_args?: false,
only: nil,
positional: [],
schema: [
accounts: :string,
user: :string,
identity_field: :string
],
aliases: [
a: :accounts,
u: :user,
i: :identity_field
],
defaults: [
identity_field: "email"
]
}
end
def igniter(igniter) do
options = parse_options(igniter)
if options[:identity_field] != :email do
Mix.shell().error("""
Could not add magic link strategy with identity field #{inspect(options[:identity_field])}.
Please run `mix ash_authentication.add_strategy magic_link` without specifying a default
""")
exit({:shutdown, 1})
end
case Igniter.Project.Module.module_exists(igniter, options[:user]) do
{true, igniter} ->
igniter
|> magic_link(options)
|> AshAuthentication.Igniter.add_remember_me_strategy(options[:user])
|> AshAuthentication.Igniter.codegen_for_strategy(:magic_link)
{false, igniter} ->
Igniter.add_issue(igniter, """
User module #{inspect(options[:user])} was not found.
Perhaps you have not yet installed ash_authentication?
""")
end
end
defp parse_options(igniter) do
options =
igniter.args.options
|> Keyword.put_new_lazy(:accounts, fn ->
Igniter.Project.Module.module_name(igniter, "Accounts")
end)
options
|> Keyword.put_new_lazy(:user, fn ->
Module.concat(options[:accounts], User)
end)
|> Keyword.update(:identity_field, :email, &String.to_atom/1)
|> Keyword.update!(:accounts, &AshAuthentication.Igniter.maybe_parse_module/1)
|> Keyword.update!(:user, &AshAuthentication.Igniter.maybe_parse_module/1)
end
defp magic_link(igniter, options) do
sender = Module.concat(options[:user], Senders.SendMagicLinkEmail)
igniter
|> Ash.Resource.Igniter.add_new_attribute(options[:user], options[:identity_field], """
attribute #{inspect(options[:identity_field])}, :ci_string do
allow_nil? false
public? true
end
""")
|> make_hashed_password_optional(options)
|> AshAuthentication.Igniter.ensure_identity(options[:user], options[:identity_field])
|> AshAuthentication.Igniter.ensure_get_by_action(options[:user], options[:identity_field])
|> Ash.Resource.Igniter.add_new_action(options[:user], :sign_in_with_magic_link, """
create :sign_in_with_magic_link do
description "Sign in or register a user with magic link."
argument :token, :string do
description "The token from the magic link that was sent to the user"
allow_nil? false
end
argument :remember_me, :boolean do
description "Whether to generate a remember me token"
allow_nil? true
end
upsert? true
upsert_identity #{inspect(:"unique_#{options[:identity_field]}")}
upsert_fields [#{inspect(options[:identity_field])}]
# Uses the information from the token to create or sign in the user
change AshAuthentication.Strategy.MagicLink.SignInChange
change {AshAuthentication.Strategy.RememberMe.MaybeGenerateTokenChange,
strategy_name: :remember_me}
metadata :token, :string do
allow_nil? false
end
end
""")
|> Ash.Resource.Igniter.add_new_action(options[:user], :request_magic_link, """
action :request_magic_link do
argument #{inspect(options[:identity_field])}, :ci_string do
allow_nil? false
end
run AshAuthentication.Strategy.MagicLink.Request
end
""")
|> AshAuthentication.Igniter.add_new_strategy(options[:user], :magic_link, :magic_link, """
magic_link do
identity_field #{inspect(options[:identity_field])}
registration_enabled? true
require_interaction? true
sender #{inspect(sender)}
end
""")
|> create_new_magic_link_sender(sender, options)
|> add_magic_link_config()
end
defp add_magic_link_config(igniter) do
otp_app = Igniter.Project.Application.app_name(igniter)
igniter
|> Igniter.Project.Config.configure_new(
"config.exs",
otp_app,
[:ash_authentication, :return_error_on_invalid_magic_link_token?],
true
)
end
defp make_hashed_password_optional(igniter, options) do
Igniter.Project.Module.find_and_update_module!(igniter, options[:user], fn zipper ->
with {:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:attributes,
1
),
{:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper),
{:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:attribute,
[1, 2, 3],
&Igniter.Code.Function.argument_equals?(&1, 0, :hashed_password)
),
{:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper),
{:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:allow_nil?,
1,
&Igniter.Code.Function.argument_equals?(&1, 0, false)
) do
{:ok, Sourceror.Zipper.remove(zipper)}
else
_ ->
{:ok, zipper}
end
end)
end
defp create_new_magic_link_sender(igniter, sender, _options) do
Igniter.Project.Module.create_module(
igniter,
sender,
~s'''
@moduledoc """
Sends a magic link email
"""
use AshAuthentication.Sender
@impl true
def send(user_or_email, token, _) do
# if you get a user, its for a user that already exists.
# if you get an email, then the user does not yet exist.
email =
case user_or_email do
%{email: email} -> email
email -> email
end
IO.puts("""
Hello, \#{email}! Click this link to sign in:
/auth/user/magic_link/?token=\#{token}
""")
end
''',
on_exists: :warning
)
end
end
else
defmodule Mix.Tasks.AshAuthentication.AddStrategy.MagicLink do
@shortdoc "Adds magic link authentication to your user resource"
@moduledoc @shortdoc
use Mix.Task
def run(_argv) do
Mix.shell().error("""
The task 'ash_authentication.add_strategy.magic_link' requires igniter to be run.
Please install igniter and try again.
For more information, see: https://hexdocs.pm/igniter
""")
exit({:shutdown, 1})
end
end
end