mirror of
https://github.com/team-alembic/ash_authentication.git
synced 2024-09-19 12:52:55 +12:00
improvement: Only require tokens to be enabled when using a strategy which needs them.
This commit is contained in:
parent
1ee220544b
commit
4e66a402fe
11 changed files with 104 additions and 7 deletions
|
@ -124,7 +124,7 @@ Configure JWT settings for this resource
|
|||
| Name | Type | Default | Docs |
|
||||
|------|------|---------|------|
|
||||
| [`token_resource`](#authentication-tokens-token_resource){: #authentication-tokens-token_resource .spark-required} | `module \| false` | | The resource used to store token information, such as in-flight confirmations, revocations, and if `store_all_tokens?` is enabled, authentication tokens themselves. |
|
||||
| [`enabled?`](#authentication-tokens-enabled?){: #authentication-tokens-enabled? } | `boolean` | `true` | Should JWTs be generated by this resource? |
|
||||
| [`enabled?`](#authentication-tokens-enabled?){: #authentication-tokens-enabled? } | `boolean` | `false` | Should JWTs be generated by this resource? |
|
||||
| [`store_all_tokens?`](#authentication-tokens-store_all_tokens?){: #authentication-tokens-store_all_tokens? } | `boolean` | `false` | Store all tokens in the `token_resource`. See the [tokens guide](/documentation/topics/tokens.md) for more. |
|
||||
| [`require_token_presence_for_authentication?`](#authentication-tokens-require_token_presence_for_authentication?){: #authentication-tokens-require_token_presence_for_authentication? } | `boolean` | `false` | Require a locally-stored token for authentication. See the [tokens guide](/documentation/topics/tokens.md) for more. |
|
||||
| [`signing_algorithm`](#authentication-tokens-signing_algorithm){: #authentication-tokens-signing_algorithm } | `String.t` | `"HS256"` | The algorithm to use for token signing. Available signing algorithms are; EdDSA, Ed448ph, Ed448, Ed25519ph, Ed25519, PS512, PS384, PS256, ES512, ES384, ES256, RS512, RS384, RS256, HS512, HS384 and HS256. |
|
||||
|
|
|
@ -150,7 +150,7 @@ concepts:
|
|||
|
||||
- "phases" - in terms of HTTP, each strategy is likely to have many phases (eg OAuth 2.0's "request" and "callback" phases). Essentially you need one phase for each HTTP endpoint you wish to support with your strategy. In our case we just want one sign in endpoint.
|
||||
- "actions" - actions are exactly as they sound - Resource actions which can be executed by the strategy, whether generated by the strategy (as in the password strategy) or typed in by the user (as in the OAuth 2.0 strategy). The reason that we wrap the strategy's actions this way is that all the built-in strategies (and we hope yours too) allow the user to customise the name of the actions that it uses. At the very least it should probably append the strategy name to the action. Using `Strategy.action/4` allows us to refer these by a more generic name rather than via the user-specified one (eg `:register` vs `:register_with_password`).
|
||||
- "routes" - `AshAuthentication.Plug` (or [`AshAuthentication.Phoenix.Router.html`](`e:ash_authentication_phoenix:AshAuthentication.Phoenix.Router.html`)) will generate routes using `Plug.Router` (or [`Phoenix.Router`](`e:phoenix:Phoenix.Router.html`)) - the `routes/1` callback is used to retrieve this information from the strategy.
|
||||
- "routes" - `AshAuthentication.Plug` (or [`AshAuthentication.Phoenix.Router.html`](https://hexdocs.pm/ash_authentication_phoenix/AshAuthentication.Phoenix.Router.html)) will generate routes using `Plug.Router` (or [`Phoenix.Router`](https://hexdocs.pm/phoenix/Phoenix.Router.html)) - the `routes/1` callback is used to retrieve this information from the strategy.
|
||||
|
||||
Given this information, let's implement the strategy. It's quite long, so I'm
|
||||
going to break it up into smaller chunks.
|
||||
|
@ -210,7 +210,7 @@ straight into `store_authentication_result/2` from
|
|||
end
|
||||
```
|
||||
|
||||
Finally, we implement our sign in action. We use `Ash.Query` to find all
|
||||
Next, we implement our sign in action. We use `Ash.Query` to find all
|
||||
records whose name field matches the input, then constrain it to only records
|
||||
whose name field starts with "Marty". Depending on whether the name field has a
|
||||
unique identity on it we have to deal with it returning zero or more users, or
|
||||
|
@ -267,6 +267,14 @@ the resource.
|
|||
end
|
||||
```
|
||||
|
||||
Lastly, we have to implement the `tokens_required?/1` function. This function
|
||||
indicates Ash Authentication whether your strategy creates or consumes any
|
||||
tokens. Since our strategy does not, we can simply return false:
|
||||
|
||||
```elixir
|
||||
def token_required?(_), do: false
|
||||
```
|
||||
|
||||
## Bonus round - transformers and verifiers
|
||||
|
||||
In some cases it may be required for your strategy to modify it's own
|
||||
|
|
|
@ -84,7 +84,7 @@ defmodule MyApp.NewUserConfirmationSender do
|
|||
end
|
||||
```
|
||||
|
||||
Provided you have your authentication routes hooked up either via `AshAuthentication.Plug` or [`AshAuthentication.Phoenix.Router`](`e:ash_authentication_phoenix:AshAuthentication.Phoenix.Router.html`) then the user will be confirmed when the token is submitted.
|
||||
Provided you have your authentication routes hooked up either via `AshAuthentication.Plug` or [`AshAuthentication.Phoenix.Router`](https://hexdocs.pm/ash_authentication_phoenix/AshAuthentication.Phoenix.Router.html) then the user will be confirmed when the token is submitted.
|
||||
|
||||
## Confirming changes to monitored fields
|
||||
|
||||
|
|
|
@ -51,4 +51,8 @@ defimpl AshAuthentication.Strategy, for: AshAuthentication.AddOn.Confirmation do
|
|||
@spec action(Confirmation.t(), action, map, keyword) :: {:ok, Resource.record()} | {:error, any}
|
||||
def action(strategy, :confirm, params, options),
|
||||
do: Confirmation.Actions.confirm(strategy, params, options)
|
||||
|
||||
@doc false
|
||||
@spec tokens_required?(Confirmation.t()) :: true
|
||||
def tokens_required?(_), do: true
|
||||
end
|
||||
|
|
|
@ -77,7 +77,7 @@ defmodule AshAuthentication.Dsl do
|
|||
doc: """
|
||||
Should JWTs be generated by this resource?
|
||||
""",
|
||||
default: true
|
||||
default: false
|
||||
],
|
||||
store_all_tokens?: [
|
||||
type: :boolean,
|
||||
|
|
|
@ -45,4 +45,8 @@ defimpl AshAuthentication.Strategy, for: AshAuthentication.Strategy.MagicLink do
|
|||
|
||||
def action(strategy, :sign_in, params, options),
|
||||
do: MagicLink.Actions.sign_in(strategy, params, options)
|
||||
|
||||
@doc false
|
||||
@spec tokens_required?(MagicLink.t()) :: true
|
||||
def tokens_required?(_), do: true
|
||||
end
|
||||
|
|
|
@ -66,4 +66,8 @@ defimpl AshAuthentication.Strategy, for: AshAuthentication.Strategy.OAuth2 do
|
|||
|
||||
def action(strategy, :sign_in, params, options),
|
||||
do: OAuth2.Actions.sign_in(strategy, params, options)
|
||||
|
||||
@doc false
|
||||
@spec tokens_required?(OAuth2.t()) :: boolean
|
||||
def tokens_required?(_), do: false
|
||||
end
|
||||
|
|
|
@ -94,4 +94,10 @@ defimpl AshAuthentication.Strategy, for: AshAuthentication.Strategy.Password do
|
|||
|
||||
def action(strategy, :sign_in_with_token, params, options),
|
||||
do: Password.Actions.sign_in_with_token(strategy, params, options)
|
||||
|
||||
@doc false
|
||||
@spec tokens_required?(Password.t()) :: boolean
|
||||
def tokens_required?(strategy) when strategy.sign_in_tokens_enabled?, do: true
|
||||
def tokens_required?(strategy) when is_map(strategy.resettable), do: true
|
||||
def tokens_required?(_), do: false
|
||||
end
|
||||
|
|
|
@ -125,4 +125,10 @@ defprotocol AshAuthentication.Strategy do
|
|||
@spec action(t, action, params :: map, options :: keyword) ::
|
||||
:ok | {:ok, Resource.record()} | {:error, any}
|
||||
def action(strategy, action_name, params, options \\ [])
|
||||
|
||||
@doc """
|
||||
Indicates that the strategy creates or consumes tokens.
|
||||
"""
|
||||
@spec tokens_required?(t) :: boolean
|
||||
def tokens_required?(strategy)
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule AshAuthentication.Verifier do
|
|||
"""
|
||||
|
||||
use Spark.Dsl.Verifier
|
||||
alias AshAuthentication.Info
|
||||
alias AshAuthentication.{Info, Strategy, Strategy.Password}
|
||||
alias Spark.{Dsl.Transformer, Error.DslError}
|
||||
import AshAuthentication.Utils
|
||||
|
||||
|
@ -17,7 +17,8 @@ defmodule AshAuthentication.Verifier do
|
|||
| {:error, term}
|
||||
| {:warn, String.t() | list(String.t())}
|
||||
def verify(dsl_state) do
|
||||
with {:ok, _domain} <- validate_domain_presence(dsl_state) do
|
||||
with {:ok, _domain} <- validate_domain_presence(dsl_state),
|
||||
:ok <- validate_tokens_may_be_required(dsl_state) do
|
||||
validate_token_resource(dsl_state)
|
||||
end
|
||||
end
|
||||
|
@ -46,6 +47,68 @@ defmodule AshAuthentication.Verifier do
|
|||
end
|
||||
end
|
||||
|
||||
defp validate_tokens_may_be_required(dsl_state) do
|
||||
strategies_requiring_tokens =
|
||||
dsl_state
|
||||
|> Info.authentication_strategies()
|
||||
|> Enum.filter(&Strategy.tokens_required?/1)
|
||||
|
||||
tokens_enabled? =
|
||||
dsl_state
|
||||
|> Info.authentication_tokens_enabled?()
|
||||
|
||||
case {strategies_requiring_tokens, tokens_enabled?} do
|
||||
{[], _} ->
|
||||
:ok
|
||||
|
||||
{_, true} ->
|
||||
:ok
|
||||
|
||||
{[password | _], false}
|
||||
when is_struct(password, Password) and is_map(password.resettable) ->
|
||||
{:error,
|
||||
DslError.exception(
|
||||
path: [:authentication, :tokens, :enabled?],
|
||||
message: """
|
||||
The `#{password.name}` password authentication strategy requires tokens be enabled because reset tokens are in use.
|
||||
|
||||
To fix this error you can either:
|
||||
|
||||
1. disable password resets by removing the `resettable` configuration from your password strategy, or
|
||||
2. enable tokens.
|
||||
"""
|
||||
)}
|
||||
|
||||
{[password | _], false}
|
||||
when is_struct(password, Password) and is_map(password.sign_in_tokens_enabled?) ->
|
||||
{:error,
|
||||
DslError.exception(
|
||||
path: [:authentication, :tokens, :enabled?],
|
||||
message: """
|
||||
The `#{password.name}` password authentication strategy requires tokens be enabled because sign-in tokens are in use.
|
||||
|
||||
To fix this error you can either:
|
||||
|
||||
1. disable sign in tokens by setting `sign_in_tokens? false` your password strategy, or
|
||||
2. enable tokens.
|
||||
"""
|
||||
)}
|
||||
|
||||
{[strategy | _], false} ->
|
||||
{:error,
|
||||
DslError.exception(
|
||||
path: [:authentication, :tokens, :enabled?],
|
||||
message: """
|
||||
The `#{inspect(strategy.name)}` authentication strategy requires tokens be enabled.
|
||||
|
||||
To fix this error you can either:
|
||||
1. disable the `#{inspect(strategy.name)}` strategy, or
|
||||
2. enable tokens.
|
||||
"""
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_token_resource(dsl_state) do
|
||||
if_tokens_enabled(dsl_state, fn dsl_state ->
|
||||
with {:ok, resource} when is_truthy(resource) <-
|
||||
|
|
|
@ -118,5 +118,7 @@ defmodule Example.OnlyMartiesAtTheParty do
|
|||
)}
|
||||
end
|
||||
end
|
||||
|
||||
def tokens_required?(_), do: false
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue