Skip to content

Commit 6f80c37

Browse files
authored
Merge pull request #716 from pow-auth/accept-mfas
Accept MFAs along with anonymous functions
2 parents 6cc5911 + 2fd1d9c commit 6f80c37

File tree

3 files changed

+77
-20
lines changed

3 files changed

+77
-20
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## v1.0.35 (TBA)
44

5+
### Enhancements
6+
7+
* [`Pow.Ecto.Schema.Changeset`] Now handles MFA for `:password_hash_verify`
8+
* [`Pow.Ecto.Schema.Changeset`] Now handles MFA for `:email_validator`
9+
510
### Deprecations
611

712
* [`Pow.Ecto.Schema.Changeset`] Deprecated `:password_hash_methods` in favor of `:password_hash_verify`

lib/pow/ecto/schema/changeset.ex

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ defmodule Pow.Ecto.Schema.Changeset do
1212
1313
* `:password_min_length` - minimum password length, defaults to 8
1414
* `:password_max_length` - maximum password length, defaults to 4096
15-
* `:password_hash_verify` - the password hash and verify functions to use,
16-
defaults to:
15+
* `:password_hash_verify` - the password hash and verify anonymous
16+
functions or MFAs, defaults to:
1717
1818
{&Pow.Ecto.Schema.Password.pbkdf2_hash/1,
1919
&Pow.Ecto.Schema.Password.pbkdf2_verify/2}
20-
* `:email_validator` - the email validation function, defaults to:
20+
21+
It may be anonymous functions of MFAs.
22+
* `:email_validator` - the email validation anonymous function or
23+
MFA, defaults to:
2124
2225
2326
&Pow.Ecto.Schema.Changeset.validate_email/1
@@ -169,7 +172,7 @@ defmodule Pow.Ecto.Schema.Changeset do
169172
validator = get_email_validator(config)
170173

171174
Changeset.validate_change(changeset, :email, {:email_format, validator}, fn :email, email ->
172-
case validator.(email) do
175+
case apply_function_or_mfa(validator, [email]) do
173176
:ok -> []
174177
:error -> [email: {"has invalid format", validation: :email_format}]
175178
{:error, reason} -> [email: {"has invalid format", validation: :email_format, reason: reason}]
@@ -215,16 +218,12 @@ defmodule Pow.Ecto.Schema.Changeset do
215218
"""
216219
@spec verify_password(Ecto.Schema.t(), binary(), Config.t()) :: boolean()
217220
def verify_password(%{password_hash: nil}, _password, config) do
218-
config
219-
|> get_password_hash_function()
220-
|> apply([""])
221+
apply_password_hash_function(config, [""])
221222

222223
false
223224
end
224225
def verify_password(%{password_hash: password_hash}, password, config) do
225-
config
226-
|> get_password_verify_function()
227-
|> apply([password, password_hash])
226+
apply_password_verify_function(config, [password, password_hash])
228227
end
229228

230229
defp maybe_require_password(%{data: %{password_hash: nil}} = changeset) do
@@ -259,21 +258,26 @@ defmodule Pow.Ecto.Schema.Changeset do
259258
defp maybe_validate_password_hash(changeset), do: changeset
260259

261260
defp hash_password(password, config) do
262-
config
263-
|> get_password_hash_function()
264-
|> apply([password])
261+
apply_password_hash_function(config, [password])
265262
end
266263

267-
defp get_password_hash_function(config) do
264+
defp apply_password_hash_function(config, args) do
268265
{password_hash_function, _} = get_password_hash_functions(config)
269266

270-
password_hash_function
267+
apply_function_or_mfa(password_hash_function, args)
268+
end
269+
270+
defp apply_function_or_mfa(fun_or_mfa, apply_args) do
271+
case fun_or_mfa do
272+
fun when is_function(fun) -> apply(fun, apply_args)
273+
{mod, fun, args} when is_list(args) -> apply(mod, fun, args ++ apply_args)
274+
end
271275
end
272276

273-
defp get_password_verify_function(config) do
277+
defp apply_password_verify_function(config, args) do
274278
{_, password_verify_function} = get_password_hash_functions(config)
275279

276-
password_verify_function
280+
apply_function_or_mfa(password_verify_function, args)
277281
end
278282

279283
defp get_password_hash_functions(config) do

test/pow/ecto/schema/changeset_test.exs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ defmodule Pow.Ecto.Schema.ChangesetTest do
4848
assert changeset.valid?
4949
end
5050

51-
test "can validate with custom e-mail validator" do
51+
test "can validate with custom anonymous function e-mail validator" do
5252
config = [email_validator: &{:error, "custom message #{&1}"}]
5353
changeset = Changeset.user_id_field_changeset(%User{}, @valid_params, config)
5454

@@ -62,6 +62,31 @@ defmodule Pow.Ecto.Schema.ChangesetTest do
6262
assert changeset.valid?
6363
end
6464

65+
defmodule CustomEmailValidator do
66+
def email_validate("[email protected]") do
67+
:ok
68+
end
69+
70+
def email_validate(email) do
71+
{:error, "custom message #{email}"}
72+
end
73+
end
74+
75+
test "can validate with custom MFA function e-mail validator" do
76+
config = [email_validator: {CustomEmailValidator, :email_validate, []}]
77+
changeset = Changeset.user_id_field_changeset(%User{}, @valid_params, config)
78+
79+
refute changeset.valid?
80+
assert changeset.errors[:email] == {"has invalid format", [validation: :email_format, reason: "custom message [email protected]"]}
81+
assert changeset.validations[:email] == {:email_format, config[:email_validator]}
82+
83+
config = [email_validator: fn _email -> :ok end]
84+
params = Map.put(@valid_params, "email", "[email protected]")
85+
changeset = Changeset.user_id_field_changeset(%User{}, params, config)
86+
87+
assert changeset.valid?
88+
end
89+
6590
test "uses case insensitive value for user id" do
6691
changeset = User.changeset(%User{}, Map.put(@valid_params, "email", "[email protected]"))
6792
assert changeset.valid?
@@ -210,12 +235,35 @@ defmodule Pow.Ecto.Schema.ChangesetTest do
210235
end) =~ "passing `confirm_password` value to `Pow.Ecto.Schema.Changeset.confirm_password_changeset/3` has been deprecated, please use `password_confirmation` instead"
211236
end
212237

213-
test "can use custom password hash functions" do
238+
test "can use custom anonymous password hash functions" do
214239
password_hash = &(&1 <> "123")
215240
password_verify = &(&1 == &2 <> "123")
216241
config = [password_hash_verify: {password_hash, password_verify}]
242+
params = Map.put(@valid_params, "current_password", "secret")
217243

218-
changeset = Changeset.password_changeset(%User{}, @valid_params, config)
244+
changeset = Changeset.password_changeset(%User{password: "secret123"}, params, config)
245+
246+
assert changeset.valid?
247+
assert changeset.changes[:password_hash] == "secret1234123"
248+
end
249+
250+
defmodule CustomPasswordHash do
251+
def hash(password), do: password <> "123"
252+
def verify(_password, hash), do: hash == "hashed"
253+
end
254+
255+
test "can use custom MFA password hash functions" do
256+
config =
257+
[
258+
password_hash_verify: {
259+
{CustomPasswordHash, :hash, []},
260+
{CustomPasswordHash, :verify, []}
261+
}
262+
]
263+
264+
params = Map.put(@valid_params, "current_password", "secret")
265+
266+
changeset = Changeset.password_changeset(%User{password_hash: "secret123"}, params, config)
219267

220268
assert changeset.valid?
221269
assert changeset.changes[:password_hash] == "secret1234123"

0 commit comments

Comments
 (0)