mirror of
https://github.com/team-alembic/ash_authentication.git
synced 2024-09-20 05:13:10 +12:00
feat!: Configure accepted fields on register (#219)
This commit is contained in:
parent
668538be59
commit
7f1c9678e4
11 changed files with 243 additions and 2 deletions
|
@ -1,4 +1,6 @@
|
||||||
spark_locals_without_parens = [
|
spark_locals_without_parens = [
|
||||||
|
access_token_attribute_name: 1,
|
||||||
|
access_token_expires_at_attribute_name: 1,
|
||||||
api: 1,
|
api: 1,
|
||||||
auth0: 0,
|
auth0: 0,
|
||||||
auth0: 1,
|
auth0: 1,
|
||||||
|
@ -16,12 +18,16 @@ spark_locals_without_parens = [
|
||||||
confirmation: 2,
|
confirmation: 2,
|
||||||
confirmation_required?: 1,
|
confirmation_required?: 1,
|
||||||
confirmed_at_field: 1,
|
confirmed_at_field: 1,
|
||||||
|
destroy_action_name: 1,
|
||||||
enabled?: 1,
|
enabled?: 1,
|
||||||
expunge_expired_action_name: 1,
|
expunge_expired_action_name: 1,
|
||||||
expunge_interval: 1,
|
expunge_interval: 1,
|
||||||
get_by_subject_action_name: 1,
|
get_by_subject_action_name: 1,
|
||||||
get_changes_action_name: 1,
|
get_changes_action_name: 1,
|
||||||
get_token_action_name: 1,
|
get_token_action_name: 1,
|
||||||
|
github: 0,
|
||||||
|
github: 1,
|
||||||
|
github: 2,
|
||||||
hash_provider: 1,
|
hash_provider: 1,
|
||||||
hashed_password_field: 1,
|
hashed_password_field: 1,
|
||||||
identity_field: 1,
|
identity_field: 1,
|
||||||
|
@ -30,6 +36,9 @@ spark_locals_without_parens = [
|
||||||
identity_resource: 1,
|
identity_resource: 1,
|
||||||
inhibit_updates?: 1,
|
inhibit_updates?: 1,
|
||||||
is_revoked_action_name: 1,
|
is_revoked_action_name: 1,
|
||||||
|
magic_link: 0,
|
||||||
|
magic_link: 1,
|
||||||
|
magic_link: 2,
|
||||||
monitor_fields: 1,
|
monitor_fields: 1,
|
||||||
oauth2: 0,
|
oauth2: 0,
|
||||||
oauth2: 1,
|
oauth2: 1,
|
||||||
|
@ -41,27 +50,41 @@ 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,
|
||||||
|
read_action_name: 1,
|
||||||
read_expired_action_name: 1,
|
read_expired_action_name: 1,
|
||||||
redirect_uri: 1,
|
redirect_uri: 1,
|
||||||
|
refresh_token_attribute_name: 1,
|
||||||
|
register_action_accept: 1,
|
||||||
register_action_name: 1,
|
register_action_name: 1,
|
||||||
registration_enabled?: 1,
|
registration_enabled?: 1,
|
||||||
|
request_action_name: 1,
|
||||||
request_password_reset_action_name: 1,
|
request_password_reset_action_name: 1,
|
||||||
require_token_presence_for_authentication?: 1,
|
require_token_presence_for_authentication?: 1,
|
||||||
resettable: 0,
|
resettable: 0,
|
||||||
resettable: 1,
|
resettable: 1,
|
||||||
revoke_token_action_name: 1,
|
revoke_token_action_name: 1,
|
||||||
|
select_for_senders: 1,
|
||||||
sender: 1,
|
sender: 1,
|
||||||
sign_in_action_name: 1,
|
sign_in_action_name: 1,
|
||||||
|
sign_in_enabled?: 1,
|
||||||
signing_algorithm: 1,
|
signing_algorithm: 1,
|
||||||
signing_secret: 1,
|
signing_secret: 1,
|
||||||
|
single_use_token?: 1,
|
||||||
site: 1,
|
site: 1,
|
||||||
store_all_tokens?: 1,
|
store_all_tokens?: 1,
|
||||||
store_changes_action_name: 1,
|
store_changes_action_name: 1,
|
||||||
store_token_action_name: 1,
|
store_token_action_name: 1,
|
||||||
|
strategy_attribute_name: 1,
|
||||||
subject_name: 1,
|
subject_name: 1,
|
||||||
token_lifetime: 1,
|
token_lifetime: 1,
|
||||||
token_url: 1,
|
token_param_name: 1,
|
||||||
token_resource: 1,
|
token_resource: 1,
|
||||||
|
token_url: 1,
|
||||||
|
uid_attribute_name: 1,
|
||||||
|
upsert_action_name: 1,
|
||||||
|
user_id_attribute_name: 1,
|
||||||
|
user_relationship_name: 1,
|
||||||
|
user_resource: 1,
|
||||||
user_url: 1
|
user_url: 1
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
12
.github/workflows/elixir_lib.yml
vendored
12
.github/workflows/elixir_lib.yml
vendored
|
@ -40,6 +40,17 @@ jobs:
|
||||||
with:
|
with:
|
||||||
mix-env: test
|
mix-env: test
|
||||||
|
|
||||||
|
spark-formatter:
|
||||||
|
name: mix spark.formatter --check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-test
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: team-alembic/staple-actions/actions/mix-task@main
|
||||||
|
with:
|
||||||
|
mix-env: test
|
||||||
|
task: spark.formatter --check
|
||||||
|
|
||||||
credo:
|
credo:
|
||||||
name: mix credo --strict
|
name: mix credo --strict
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -113,6 +124,7 @@ jobs:
|
||||||
- credo
|
- credo
|
||||||
- doctor
|
- doctor
|
||||||
- formatter
|
- formatter
|
||||||
|
- spark-formatter
|
||||||
- auditor
|
- auditor
|
||||||
- test
|
- test
|
||||||
- dialyzer
|
- dialyzer
|
||||||
|
|
|
@ -106,6 +106,7 @@ defmodule AshAuthentication.Strategy.Password do
|
||||||
registration_enabled?: true,
|
registration_enabled?: true,
|
||||||
sign_in_enabled?: true,
|
sign_in_enabled?: true,
|
||||||
resettable: [],
|
resettable: [],
|
||||||
|
register_action_accept: [],
|
||||||
name: nil,
|
name: nil,
|
||||||
provider: :password,
|
provider: :password,
|
||||||
resource: nil
|
resource: nil
|
||||||
|
@ -130,6 +131,7 @@ defmodule AshAuthentication.Strategy.Password do
|
||||||
confirmation_required?: boolean,
|
confirmation_required?: boolean,
|
||||||
password_field: atom,
|
password_field: atom,
|
||||||
password_confirmation_field: atom,
|
password_confirmation_field: atom,
|
||||||
|
register_action_accept: [atom],
|
||||||
register_action_name: atom,
|
register_action_name: atom,
|
||||||
sign_in_action_name: atom,
|
sign_in_action_name: atom,
|
||||||
registration_enabled?: boolean,
|
registration_enabled?: boolean,
|
||||||
|
|
|
@ -72,6 +72,11 @@ defmodule AshAuthentication.Strategy.Password.Dsl do
|
||||||
""",
|
""",
|
||||||
default: true
|
default: true
|
||||||
],
|
],
|
||||||
|
register_action_accept: [
|
||||||
|
type: {:list, :atom},
|
||||||
|
default: [],
|
||||||
|
doc: "A list of additional fields to be accepted in the register action."
|
||||||
|
],
|
||||||
password_field: [
|
password_field: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: """
|
doc: """
|
||||||
|
|
|
@ -144,8 +144,14 @@ defmodule AshAuthentication.Strategy.Password.Transformer do
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
accept =
|
||||||
|
[strategy.identity_field]
|
||||||
|
|> Enum.concat(List.wrap(strategy.register_action_accept))
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
Transformer.build_entity(Resource.Dsl, [:actions], :create,
|
Transformer.build_entity(Resource.Dsl, [:actions], :create,
|
||||||
name: strategy.register_action_name,
|
name: strategy.register_action_name,
|
||||||
|
accept: accept,
|
||||||
arguments: arguments,
|
arguments: arguments,
|
||||||
changes: changes,
|
changes: changes,
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
|
3
mix.exs
3
mix.exs
|
@ -179,6 +179,9 @@ defmodule AshAuthentication.MixProject do
|
||||||
"hex.audit",
|
"hex.audit",
|
||||||
"test"
|
"test"
|
||||||
],
|
],
|
||||||
|
"spark.formatter": [
|
||||||
|
"spark.formatter --extensions AshAuthentication,AshAuthentication.TokenResource,AshAuthentication.UserIdentity,AshAuthentication.Strategy.MagicLink,AshAuthentication.AddOn.Confirmation,AshAuthentication.Strategy.Auth0,AshAuthentication.Strategy.Github,AshAuthentication.Strategy.OAuth2,AshAuthentication.Strategy.Password"
|
||||||
|
],
|
||||||
docs: ["docs", "ash.replace_doc_links"],
|
docs: ["docs", "ash.replace_doc_links"],
|
||||||
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
|
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
|
||||||
]
|
]
|
||||||
|
|
23
priv/repo/migrations/20230302020116_migrate_resources2.exs
Normal file
23
priv/repo/migrations/20230302020116_migrate_resources2.exs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
defmodule Example.Repo.Migrations.MigrateResources2 do
|
||||||
|
@moduledoc """
|
||||||
|
Updates resources based on their most recent snapshots.
|
||||||
|
|
||||||
|
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:user) do
|
||||||
|
add :extra_stuff, :text
|
||||||
|
add :not_accepted_extra_stuff, :text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:user) do
|
||||||
|
remove :not_accepted_extra_stuff
|
||||||
|
remove :extra_stuff
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
108
priv/resource_snapshots/repo/user/20230302020116.json
Normal file
108
priv/resource_snapshots/repo/user/20230302020116.json
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
{
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"allow_nil?": true,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"primary_key?": false,
|
||||||
|
"references": null,
|
||||||
|
"size": null,
|
||||||
|
"source": "confirmed_at",
|
||||||
|
"type": "utc_datetime_usec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "fragment(\"uuid_generate_v4()\")",
|
||||||
|
"generated?": false,
|
||||||
|
"primary_key?": true,
|
||||||
|
"references": null,
|
||||||
|
"size": null,
|
||||||
|
"source": "id",
|
||||||
|
"type": "uuid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"primary_key?": false,
|
||||||
|
"references": null,
|
||||||
|
"size": null,
|
||||||
|
"source": "username",
|
||||||
|
"type": "citext"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": true,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"primary_key?": false,
|
||||||
|
"references": null,
|
||||||
|
"size": null,
|
||||||
|
"source": "extra_stuff",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": true,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"primary_key?": false,
|
||||||
|
"references": null,
|
||||||
|
"size": null,
|
||||||
|
"source": "not_accepted_extra_stuff",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": true,
|
||||||
|
"default": "nil",
|
||||||
|
"generated?": false,
|
||||||
|
"primary_key?": false,
|
||||||
|
"references": null,
|
||||||
|
"size": null,
|
||||||
|
"source": "hashed_password",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "fragment(\"now()\")",
|
||||||
|
"generated?": false,
|
||||||
|
"primary_key?": false,
|
||||||
|
"references": null,
|
||||||
|
"size": null,
|
||||||
|
"source": "created_at",
|
||||||
|
"type": "utc_datetime_usec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_nil?": false,
|
||||||
|
"default": "fragment(\"now()\")",
|
||||||
|
"generated?": false,
|
||||||
|
"primary_key?": false,
|
||||||
|
"references": null,
|
||||||
|
"size": null,
|
||||||
|
"source": "updated_at",
|
||||||
|
"type": "utc_datetime_usec"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"base_filter": null,
|
||||||
|
"check_constraints": [],
|
||||||
|
"custom_indexes": [],
|
||||||
|
"custom_statements": [],
|
||||||
|
"has_create_action": true,
|
||||||
|
"hash": "D59416503FF032F48041F4E1793CD42EE01FF6AB3D5C4FF4A9D9CABA836E96C1",
|
||||||
|
"identities": [
|
||||||
|
{
|
||||||
|
"base_filter": null,
|
||||||
|
"index_name": "user_username_index",
|
||||||
|
"keys": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"name": "username"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"multitenancy": {
|
||||||
|
"attribute": null,
|
||||||
|
"global": null,
|
||||||
|
"strategy": null
|
||||||
|
},
|
||||||
|
"repo": "Elixir.Example.Repo",
|
||||||
|
"schema": null,
|
||||||
|
"table": "user"
|
||||||
|
}
|
|
@ -78,6 +78,59 @@ defmodule AshAuthentication.Strategy.Password.ActionsTest do
|
||||||
assert claims["sub"] =~ "user?id=#{user.id}"
|
assert claims["sub"] =~ "user?id=#{user.id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it can register a new user with additional accepted fields" do
|
||||||
|
{:ok, strategy} = Info.strategy(Example.User, :password)
|
||||||
|
|
||||||
|
username = username()
|
||||||
|
password = password()
|
||||||
|
|
||||||
|
assert {:ok, user} =
|
||||||
|
Actions.register(
|
||||||
|
strategy,
|
||||||
|
%{
|
||||||
|
"username" => username,
|
||||||
|
"password" => password,
|
||||||
|
"password_confirmation" => password,
|
||||||
|
"extra_stuff" => "Extra"
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert user.extra_stuff == "Extra"
|
||||||
|
|
||||||
|
assert strategy.hash_provider.valid?(password, user.hashed_password)
|
||||||
|
|
||||||
|
assert {:ok, claims} = Jwt.peek(user.__metadata__.token)
|
||||||
|
assert claims["sub"] =~ "user?id=#{user.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it cant set unaccepted fields" do
|
||||||
|
{:ok, strategy} = Info.strategy(Example.User, :password)
|
||||||
|
|
||||||
|
username = username()
|
||||||
|
password = password()
|
||||||
|
|
||||||
|
assert {:error,
|
||||||
|
%Ash.Error.Invalid{
|
||||||
|
errors: [
|
||||||
|
%Ash.Error.Changes.InvalidAttribute{
|
||||||
|
message: "cannot be changed",
|
||||||
|
field: :not_accepted_extra_stuff
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}} =
|
||||||
|
Actions.register(
|
||||||
|
strategy,
|
||||||
|
%{
|
||||||
|
"username" => username,
|
||||||
|
"password" => password,
|
||||||
|
"password_confirmation" => password,
|
||||||
|
"not_accepted_extra_stuff" => "Extra"
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
test "it returns an error if the user already exists" do
|
test "it returns an error if the user already exists" do
|
||||||
user = build_user()
|
user = build_user()
|
||||||
{:ok, strategy} = Info.strategy(Example.User, :password)
|
{:ok, strategy} = Info.strategy(Example.User, :password)
|
||||||
|
|
|
@ -72,17 +72,19 @@ defmodule DataCase do
|
||||||
def build_user(attrs \\ []) do
|
def build_user(attrs \\ []) do
|
||||||
password = password()
|
password = password()
|
||||||
|
|
||||||
attrs =
|
{force_change_attrs, attrs} =
|
||||||
attrs
|
attrs
|
||||||
|> Map.new()
|
|> Map.new()
|
||||||
|> Map.put_new(:username, username())
|
|> Map.put_new(:username, username())
|
||||||
|> Map.put_new(:password, password)
|
|> Map.put_new(:password, password)
|
||||||
|> Map.put_new(:password_confirmation, password)
|
|> Map.put_new(:password_confirmation, password)
|
||||||
|
|> Map.split([:id])
|
||||||
|
|
||||||
user =
|
user =
|
||||||
Example.User
|
Example.User
|
||||||
|> Ash.Changeset.new()
|
|> Ash.Changeset.new()
|
||||||
|> Ash.Changeset.for_create(:register_with_password, attrs)
|
|> Ash.Changeset.for_create(:register_with_password, attrs)
|
||||||
|
|> Ash.Changeset.force_change_attributes(force_change_attrs)
|
||||||
|> Example.create!()
|
|> Example.create!()
|
||||||
|
|
||||||
attrs
|
attrs
|
||||||
|
|
|
@ -23,6 +23,8 @@ defmodule Example.User do
|
||||||
uuid_primary_key :id, writable?: true
|
uuid_primary_key :id, writable?: true
|
||||||
|
|
||||||
attribute :username, :ci_string, allow_nil?: false
|
attribute :username, :ci_string, allow_nil?: false
|
||||||
|
attribute :extra_stuff, :string
|
||||||
|
attribute :not_accepted_extra_stuff, :string
|
||||||
attribute :hashed_password, :string, allow_nil?: true, sensitive?: true, private?: true
|
attribute :hashed_password, :string, allow_nil?: true, sensitive?: true, private?: true
|
||||||
|
|
||||||
create_timestamp :created_at
|
create_timestamp :created_at
|
||||||
|
@ -165,6 +167,8 @@ defmodule Example.User do
|
||||||
|
|
||||||
strategies do
|
strategies do
|
||||||
password do
|
password do
|
||||||
|
register_action_accept [:extra_stuff]
|
||||||
|
|
||||||
resettable do
|
resettable do
|
||||||
sender fn user, token, _opts ->
|
sender fn user, token, _opts ->
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
|
|
Loading…
Reference in a new issue