Skip to content

Commit

Permalink
improvement: add apple strategy (#750)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
miguel-s and jimsynz committed Sep 1, 2024
1 parent 87eff35 commit 4172428
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions lib/ash_authentication.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
29 changes: 29 additions & 0 deletions lib/ash_authentication/strategies/apple.ex
Original file line number Diff line number Diff line change
@@ -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 changes: 95 additions & 0 deletions lib/ash_authentication/strategies/apple/dsl.ex
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions lib/ash_authentication/strategies/apple/verifier.ex
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions lib/ash_authentication/strategies/oauth2.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions lib/ash_authentication/strategies/oauth2/plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

0 comments on commit 4172428

Please sign in to comment.