mirror of
https://github.com/team-alembic/ash_authentication.git
synced 2024-09-19 12:52:55 +12:00
improvement: add apple strategy (#750)
* improvement: add apple strategy * fix: update types and formatter * fix: add secret values to config * fix: sort new fields * fix: sort new types * fix: properly set allow_nil for apple secrets * fix: credo and sobelow warnings --------- Co-authored-by: James Harton <james@harton.dev>
This commit is contained in:
parent
87eff35ac5
commit
41724288d3
7 changed files with 174 additions and 0 deletions
|
@ -64,6 +64,8 @@ spark_locals_without_parens = [
|
||||||
password_field: 1,
|
password_field: 1,
|
||||||
password_reset_action_name: 1,
|
password_reset_action_name: 1,
|
||||||
private_key: 1,
|
private_key: 1,
|
||||||
|
private_key_id: 1,
|
||||||
|
private_key_path: 1,
|
||||||
read_action_name: 1,
|
read_action_name: 1,
|
||||||
read_expired_action_name: 1,
|
read_expired_action_name: 1,
|
||||||
redirect_uri: 1,
|
redirect_uri: 1,
|
||||||
|
@ -92,6 +94,7 @@ spark_locals_without_parens = [
|
||||||
store_token_action_name: 1,
|
store_token_action_name: 1,
|
||||||
strategy_attribute_name: 1,
|
strategy_attribute_name: 1,
|
||||||
subject_name: 1,
|
subject_name: 1,
|
||||||
|
team_id: 1,
|
||||||
token_lifetime: 1,
|
token_lifetime: 1,
|
||||||
token_param_name: 1,
|
token_param_name: 1,
|
||||||
token_resource: 1,
|
token_resource: 1,
|
||||||
|
|
|
@ -115,6 +115,7 @@ defmodule AshAuthentication do
|
||||||
AshAuthentication.Strategy.Auth0,
|
AshAuthentication.Strategy.Auth0,
|
||||||
AshAuthentication.Strategy.Github,
|
AshAuthentication.Strategy.Github,
|
||||||
AshAuthentication.Strategy.Google,
|
AshAuthentication.Strategy.Google,
|
||||||
|
AshAuthentication.Strategy.Apple,
|
||||||
AshAuthentication.Strategy.MagicLink,
|
AshAuthentication.Strategy.MagicLink,
|
||||||
AshAuthentication.Strategy.OAuth2,
|
AshAuthentication.Strategy.OAuth2,
|
||||||
AshAuthentication.Strategy.Oidc,
|
AshAuthentication.Strategy.Oidc,
|
||||||
|
|
29
lib/ash_authentication/strategies/apple.ex
Normal file
29
lib/ash_authentication/strategies/apple.ex
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
defmodule AshAuthentication.Strategy.Apple do
|
||||||
|
alias __MODULE__.{Dsl, Verifier}
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Strategy for authenticating using [Apple Sign In](https://developer.apple.com/sign-in-with-apple/)
|
||||||
|
|
||||||
|
This strategy builds on-top of `AshAuthentication.Strategy.Oidc` and
|
||||||
|
[`assent`](https://hex.pm/packages/assent).
|
||||||
|
|
||||||
|
In order to use Apple Sign In you need to provide the following minimum configuration:
|
||||||
|
|
||||||
|
- `client_id`
|
||||||
|
- `team_id`
|
||||||
|
- `private_key_id`
|
||||||
|
- `private_key_path`
|
||||||
|
- `redirect_uri`
|
||||||
|
|
||||||
|
## More documentation:
|
||||||
|
- The [Apple Sign In Documentation](https://developer.apple.com/documentation/sign_in_with_apple).
|
||||||
|
- The [OIDC documentation](`AshAuthentication.Strategy.Oidc`)
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias AshAuthentication.Strategy.{Custom, Oidc}
|
||||||
|
|
||||||
|
use Custom, entity: Dsl.dsl()
|
||||||
|
|
||||||
|
defdelegate transform(strategy, dsl_state), to: Oidc
|
||||||
|
defdelegate verify(strategy, dsl_state), to: Verifier
|
||||||
|
end
|
95
lib/ash_authentication/strategies/apple/dsl.ex
Normal file
95
lib/ash_authentication/strategies/apple/dsl.ex
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
defmodule AshAuthentication.Strategy.Apple.Dsl do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
alias AshAuthentication.Strategy.{Custom, Oidc}
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec dsl :: Custom.entity()
|
||||||
|
def dsl do
|
||||||
|
secret_type = AshAuthentication.Dsl.secret_type()
|
||||||
|
|
||||||
|
Oidc.Dsl.dsl()
|
||||||
|
|> Map.merge(%{
|
||||||
|
name: :apple,
|
||||||
|
args: [{:optional, :name, :apple}],
|
||||||
|
describe: """
|
||||||
|
Provides a pre-configured authentication strategy for [Apple Sign In](https://developer.apple.com/sign-in-with-apple/).
|
||||||
|
|
||||||
|
This strategy is built using the `:oidc` strategy, and thus provides all the same
|
||||||
|
configuration options should you need them.
|
||||||
|
|
||||||
|
## More documentation:
|
||||||
|
- The [Apple Sign In Documentation](https://developer.apple.com/documentation/sign_in_with_apple).
|
||||||
|
- The [OIDC documentation](`AshAuthentication.Strategy.Oidc`)
|
||||||
|
|
||||||
|
#### Strategy defaults:
|
||||||
|
|
||||||
|
#{strategy_override_docs(Assent.Strategy.Apple)}
|
||||||
|
""",
|
||||||
|
auto_set_fields: strategy_fields(Assent.Strategy.Apple, icon: :apple),
|
||||||
|
schema: patch_schema(secret_type)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp patch_schema(secret_type) do
|
||||||
|
Oidc.Dsl.dsl()
|
||||||
|
|> Map.get(:schema, [])
|
||||||
|
|> Keyword.merge(
|
||||||
|
team_id: [
|
||||||
|
type: secret_type,
|
||||||
|
doc: "The Apple team ID associated with the application.",
|
||||||
|
required: true
|
||||||
|
],
|
||||||
|
private_key_id: [
|
||||||
|
type: secret_type,
|
||||||
|
doc: "The private key ID used for signing the JWT token.",
|
||||||
|
required: true
|
||||||
|
],
|
||||||
|
private_key_path: [
|
||||||
|
type: secret_type,
|
||||||
|
doc: "The path to the private key file used for signing the JWT token.",
|
||||||
|
required: true
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strategy_fields(strategy, params) do
|
||||||
|
strategy.default_config([])
|
||||||
|
|> Enum.map(fn
|
||||||
|
{:client_authentication_method, method} ->
|
||||||
|
{:client_authentication_method, String.to_existing_atom(method)}
|
||||||
|
|
||||||
|
{:openid_configuration, config} ->
|
||||||
|
{:openid_configuration, atomize_keys(config)}
|
||||||
|
|
||||||
|
{key, value} ->
|
||||||
|
{key, value}
|
||||||
|
end)
|
||||||
|
|> Keyword.put(:assent_strategy, strategy)
|
||||||
|
|> Keyword.merge(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
# sobelow_skip ["DOS.StringToAtom"]
|
||||||
|
defp atomize_keys(map) do
|
||||||
|
map
|
||||||
|
|> Enum.map(fn {key, value} -> {String.to_atom(key), value} end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strategy_override_docs(strategy) do
|
||||||
|
defaults =
|
||||||
|
strategy.default_config([])
|
||||||
|
|> Enum.map_join(
|
||||||
|
".\n",
|
||||||
|
fn {key, value} ->
|
||||||
|
" * `#{inspect(key)}` is set to `#{inspect(value)}`"
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
The following defaults are applied:
|
||||||
|
|
||||||
|
#{defaults}.
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
19
lib/ash_authentication/strategies/apple/verifier.ex
Normal file
19
lib/ash_authentication/strategies/apple/verifier.ex
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule AshAuthentication.Strategy.Apple.Verifier do
|
||||||
|
@moduledoc """
|
||||||
|
DSL verifier for Apple strategy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias AshAuthentication.Strategy.OAuth2
|
||||||
|
import AshAuthentication.Validations
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec verify(OAuth2.t(), map) :: :ok | {:error, Exception.t()}
|
||||||
|
def verify(strategy, _dsl_state) do
|
||||||
|
with :ok <- validate_secret(strategy, :client_id),
|
||||||
|
:ok <- validate_secret(strategy, :team_id),
|
||||||
|
:ok <- validate_secret(strategy, :private_key_id),
|
||||||
|
:ok <- validate_secret(strategy, :private_key_path) do
|
||||||
|
validate_secret(strategy, :redirect_uri)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -232,6 +232,8 @@ defmodule AshAuthentication.Strategy.OAuth2 do
|
||||||
openid_configuration_uri: nil,
|
openid_configuration_uri: nil,
|
||||||
openid_configuration: nil,
|
openid_configuration: nil,
|
||||||
private_key: nil,
|
private_key: nil,
|
||||||
|
private_key_id: nil,
|
||||||
|
private_key_path: nil,
|
||||||
provider: :oauth2,
|
provider: :oauth2,
|
||||||
redirect_uri: nil,
|
redirect_uri: nil,
|
||||||
register_action_name: nil,
|
register_action_name: nil,
|
||||||
|
@ -240,6 +242,7 @@ defmodule AshAuthentication.Strategy.OAuth2 do
|
||||||
sign_in_action_name: nil,
|
sign_in_action_name: nil,
|
||||||
site: nil,
|
site: nil,
|
||||||
strategy_module: __MODULE__,
|
strategy_module: __MODULE__,
|
||||||
|
team_id: nil,
|
||||||
token_url: nil,
|
token_url: nil,
|
||||||
trusted_audiences: nil,
|
trusted_audiences: nil,
|
||||||
user_url: nil
|
user_url: nil
|
||||||
|
@ -280,6 +283,8 @@ defmodule AshAuthentication.Strategy.OAuth2 do
|
||||||
openid_configuration_uri: nil | binary,
|
openid_configuration_uri: nil | binary,
|
||||||
openid_configuration: nil | map,
|
openid_configuration: nil | map,
|
||||||
private_key: secret,
|
private_key: secret,
|
||||||
|
private_key_id: secret,
|
||||||
|
private_key_path: secret,
|
||||||
provider: atom,
|
provider: atom,
|
||||||
redirect_uri: secret,
|
redirect_uri: secret,
|
||||||
register_action_name: atom,
|
register_action_name: atom,
|
||||||
|
@ -288,6 +293,7 @@ defmodule AshAuthentication.Strategy.OAuth2 do
|
||||||
sign_in_action_name: atom,
|
sign_in_action_name: atom,
|
||||||
site: secret,
|
site: secret,
|
||||||
strategy_module: module,
|
strategy_module: module,
|
||||||
|
team_id: secret,
|
||||||
token_url: secret,
|
token_url: secret,
|
||||||
trusted_audiences: secret_list,
|
trusted_audiences: secret_list,
|
||||||
user_url: secret
|
user_url: secret
|
||||||
|
|
|
@ -88,6 +88,27 @@ defmodule AshAuthentication.Strategy.OAuth2.Plug do
|
||||||
{:ok, config} <- add_secret_value(config, strategy, :client_id, !!strategy.base_url),
|
{:ok, config} <- add_secret_value(config, strategy, :client_id, !!strategy.base_url),
|
||||||
{:ok, config} <- add_secret_value(config, strategy, :client_secret, !!strategy.base_url),
|
{:ok, config} <- add_secret_value(config, strategy, :client_secret, !!strategy.base_url),
|
||||||
{:ok, config} <- add_secret_value(config, strategy, :token_url, !!strategy.base_url),
|
{:ok, config} <- add_secret_value(config, strategy, :token_url, !!strategy.base_url),
|
||||||
|
{:ok, config} <-
|
||||||
|
add_secret_value(
|
||||||
|
config,
|
||||||
|
strategy,
|
||||||
|
:team_id,
|
||||||
|
strategy.assent_strategy != Assent.Strategy.Apple
|
||||||
|
),
|
||||||
|
{:ok, config} <-
|
||||||
|
add_secret_value(
|
||||||
|
config,
|
||||||
|
strategy,
|
||||||
|
:private_key_id,
|
||||||
|
strategy.assent_strategy != Assent.Strategy.Apple
|
||||||
|
),
|
||||||
|
{:ok, config} <-
|
||||||
|
add_secret_value(
|
||||||
|
config,
|
||||||
|
strategy,
|
||||||
|
:private_key_path,
|
||||||
|
strategy.assent_strategy != Assent.Strategy.Apple
|
||||||
|
),
|
||||||
{:ok, config} <-
|
{:ok, config} <-
|
||||||
add_secret_value(config, strategy, :trusted_audiences, true),
|
add_secret_value(config, strategy, :trusted_audiences, true),
|
||||||
{:ok, config} <- add_http_adapter(config),
|
{:ok, config} <- add_http_adapter(config),
|
||||||
|
|
Loading…
Reference in a new issue