mirror of
https://github.com/team-alembic/ash_authentication.git
synced 2024-09-21 05:43:05 +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_reset_action_name: 1,
|
||||
private_key: 1,
|
||||
private_key_id: 1,
|
||||
private_key_path: 1,
|
||||
read_action_name: 1,
|
||||
read_expired_action_name: 1,
|
||||
redirect_uri: 1,
|
||||
|
@ -92,6 +94,7 @@ spark_locals_without_parens = [
|
|||
store_token_action_name: 1,
|
||||
strategy_attribute_name: 1,
|
||||
subject_name: 1,
|
||||
team_id: 1,
|
||||
token_lifetime: 1,
|
||||
token_param_name: 1,
|
||||
token_resource: 1,
|
||||
|
|
|
@ -115,6 +115,7 @@ defmodule AshAuthentication do
|
|||
AshAuthentication.Strategy.Auth0,
|
||||
AshAuthentication.Strategy.Github,
|
||||
AshAuthentication.Strategy.Google,
|
||||
AshAuthentication.Strategy.Apple,
|
||||
AshAuthentication.Strategy.MagicLink,
|
||||
AshAuthentication.Strategy.OAuth2,
|
||||
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: nil,
|
||||
private_key: nil,
|
||||
private_key_id: nil,
|
||||
private_key_path: nil,
|
||||
provider: :oauth2,
|
||||
redirect_uri: nil,
|
||||
register_action_name: nil,
|
||||
|
@ -240,6 +242,7 @@ defmodule AshAuthentication.Strategy.OAuth2 do
|
|||
sign_in_action_name: nil,
|
||||
site: nil,
|
||||
strategy_module: __MODULE__,
|
||||
team_id: nil,
|
||||
token_url: nil,
|
||||
trusted_audiences: nil,
|
||||
user_url: nil
|
||||
|
@ -280,6 +283,8 @@ defmodule AshAuthentication.Strategy.OAuth2 do
|
|||
openid_configuration_uri: nil | binary,
|
||||
openid_configuration: nil | map,
|
||||
private_key: secret,
|
||||
private_key_id: secret,
|
||||
private_key_path: secret,
|
||||
provider: atom,
|
||||
redirect_uri: secret,
|
||||
register_action_name: atom,
|
||||
|
@ -288,6 +293,7 @@ defmodule AshAuthentication.Strategy.OAuth2 do
|
|||
sign_in_action_name: atom,
|
||||
site: secret,
|
||||
strategy_module: module,
|
||||
team_id: secret,
|
||||
token_url: secret,
|
||||
trusted_audiences: secret_list,
|
||||
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_secret, !!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} <-
|
||||
add_secret_value(config, strategy, :trusted_audiences, true),
|
||||
{:ok, config} <- add_http_adapter(config),
|
||||
|
|
Loading…
Reference in a new issue