Packages
ash_authentication
5.0.0-rc.5
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.oauth2.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.Oauth2 do
use Igniter.Mix.Task
@example "mix ash_authentication.add_strategy oauth2 my_provider"
@shortdoc "Adds a generic OAuth2 authentication strategy to your user resource"
@moduledoc """
#{@shortdoc}
Unlike OIDC, generic OAuth2 does not auto-discover provider endpoints.
You can provide the URLs via CLI flags (set as literals in the DSL) or
leave them to be configured via environment variables.
## Example
```bash
#{@example}
```
## Options
* `--user`, `-u` - The user resource. Defaults to `YourApp.Accounts.User`
* `--accounts`, `-a` - The accounts domain. Defaults to `YourApp.Accounts`
* `--identity-field`, `-i` - The field used to identify the user. Defaults to `email`
* `--base-url` - Base URL of the OAuth2 provider
* `--authorize-url` - Authorization endpoint URL (relative to base_url or absolute)
* `--token-url` - Token endpoint URL (relative to base_url or absolute)
* `--user-url` - User info endpoint URL (relative to base_url or absolute)
"""
def info(_argv, _composing_task) do
%Igniter.Mix.Task.Info{
group: :ash,
example: @example,
extra_args?: false,
only: nil,
positional: [{:name, optional: true}],
composes: [],
schema: [
accounts: :string,
user: :string,
identity_field: :string,
name: :string,
base_url: :string,
authorize_url: :string,
token_url: :string,
user_url: :string
],
aliases: [a: :accounts, u: :user, i: :identity_field],
defaults: [identity_field: "email"]
}
end
# sobelow_skip ["DOS.BinToAtom"]
def igniter(igniter) do
options = parse_options(igniter)
name_string = igniter.args.positional[:name] || options[:name]
unless name_string do
raise ArgumentError, """
A provider name is required for the generic OAuth2 strategy.
Via positional argument:
mix ash_authentication.add_strategy.oauth2 my_provider
Via flag:
mix ash_authentication.add_strategy oauth2 --name my_provider
"""
end
name = String.to_atom(name_string)
env_prefix = name |> to_string() |> String.upcase()
case Igniter.Project.Module.module_exists(igniter, options[:user]) do
{true, igniter} ->
identity_resource =
Module.concat(AshAuthentication.Igniter.parent_module(options[:user]), UserIdentity)
secrets_module = Igniter.Project.Module.module_name(igniter, "Secrets")
secret_pairs = [
{:client_id, "#{env_prefix}_CLIENT_ID"},
{:client_secret, "#{env_prefix}_CLIENT_SECRET"},
{:redirect_uri, "#{env_prefix}_REDIRECT_URI"}
]
{url_lines, secret_pairs} =
build_url_config(options, secrets_module, env_prefix, secret_pairs)
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
""")
|> AshAuthentication.Igniter.ensure_identity(options[:user], options[:identity_field])
|> AshAuthentication.Igniter.ensure_user_identity_resource(
options[:user],
identity_resource
)
|> AshAuthentication.Igniter.add_oauth_register_action(
options[:user],
name,
identity_field: options[:identity_field],
identity_resource: identity_resource
)
|> AshAuthentication.Igniter.add_oauth_secrets(
secrets_module,
options[:user],
name,
secret_pairs
)
|> AshAuthentication.Igniter.add_new_strategy(options[:user], :oauth2, name, """
oauth2 :#{name} do
client_id #{inspect(secrets_module)}
client_secret #{inspect(secrets_module)}
redirect_uri #{inspect(secrets_module)}
#{url_lines}
identity_resource #{inspect(identity_resource)}
end
""")
|> AshAuthentication.Igniter.codegen_for_strategy(name)
|> Igniter.add_notice("""
OAuth2 strategy "#{name}" setup:
Set these environment variables:
#{env_prefix}_CLIENT_ID=<your_client_id>
#{env_prefix}_CLIENT_SECRET=<your_client_secret>
#{env_prefix}_REDIRECT_URI=http://localhost:4000/auth
#{url_env_notice(options, env_prefix)}
The provider's callback URL should be set to:
http://localhost:4000/auth/#{name}/callback
""")
{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 build_url_config(options, secrets_module, env_prefix, secret_pairs) do
url_fields = [
{:base_url, "BASE_URL"},
{:authorize_url, "AUTHORIZE_URL"},
{:token_url, "TOKEN_URL"},
{:user_url, "USER_URL"}
]
Enum.reduce(url_fields, {"", secret_pairs}, fn {field, env_suffix}, {lines, pairs} ->
case Keyword.get(options, field) do
nil ->
env_var = "#{env_prefix}_#{env_suffix}"
line = "#{field} #{inspect(secrets_module)}"
{"#{lines}#{line}\n ", pairs ++ [{field, env_var}]}
value ->
line = "#{field} \"#{value}\""
{"#{lines}#{line}\n ", pairs}
end
end)
end
defp url_env_notice(options, env_prefix) do
[
{:base_url, "BASE_URL", "<provider_base_url>"},
{:authorize_url, "AUTHORIZE_URL", "<authorize_endpoint>"},
{:token_url, "TOKEN_URL", "<token_endpoint>"},
{:user_url, "USER_URL", "<user_info_endpoint>"}
]
|> Enum.reject(fn {field, _, _} -> Keyword.has_key?(options, field) end)
|> Enum.map_join("\n", fn {_, suffix, placeholder} ->
" #{env_prefix}_#{suffix}=#{placeholder}"
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
end
else
defmodule Mix.Tasks.AshAuthentication.AddStrategy.Oauth2 do
@shortdoc "Adds a generic OAuth2 authentication strategy to your user resource"
@moduledoc @shortdoc
use Mix.Task
def run(_argv) do
Mix.shell().error("The task 'ash_authentication.add_strategy.oauth2' requires igniter.")
exit({:shutdown, 1})
end
end
end