这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions lib/authority_ecto/changeset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,38 @@ defmodule Authority.Ecto.Changeset do
end
end

@doc """
Hashes the value stored in the `source` field, and puts the resulting
hash in the `destination` field. The `source` field will be removed
from the changeset.

By default, the password will be hashed using `Comeonin.Bcrypt`. See
`put_encrypted_password/4` to use a different algorithm. Valid options
are `:bcrypt`, `:argon2`, or `:pbkdf2`.

## Examples

iex> put_encrypted_password(changeset, :password, :encrypted_password)
%Ecto.Changeset{}

iex> put_encrypted_password(changeset, :password, :encrypted_password, :argon2)
%Ecto.Changeset{}

"""
def put_encrypted_password(changeset, source, destination, algorithm \\ :bcrypt) do
password = get_change(changeset, source)
confirmation = String.to_existing_atom(Atom.to_string(source) <> "_confirmation")

if password do
changeset
|> put_change(destination, hash_password(algorithm, password))
|> delete_change(source)
|> delete_change(confirmation)
else
changeset
end
end

defp parse_timespec(nil), do: nil

defp parse_timespec({n, :hours}) do
Expand All @@ -45,4 +77,23 @@ defmodule Authority.Ecto.Changeset do
|> Kernel.+(n)
|> DateTime.from_unix!()
end

Enum.each(
[
bcrypt: Comeonin.Bcrypt,
argon2: Comeonin.Argon2,
pbkdf2: Comeonin.Pbkdf2
],
fn {name, mod} ->
if Code.ensure_compiled?(mod) do
defp hash_password(unquote(name), value) do
unquote(mod).hashpwsalt(value)
end
end
end
)

defp hash_password(name, _value) do
raise "Invalid algorithm: #{name}. Did you forget to install #{name}_elixir?"
end
end
6 changes: 5 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ defmodule Authority.Ecto.MixProject do
[
{:authority, ">= 0.0.0"},
{:ecto, ">= 0.0.0"},
{:ex_doc, ">= 0.0.0", only: [:dev, :test]}
{:comeonin, ">= 0.0.0"},
{:ex_doc, ">= 0.0.0", only: [:dev, :test]},
{:bcrypt_elixir, ">= 0.0.0", only: [:dev, :test]},
{:argon2_elixir, ">= 0.0.0", only: [:dev, :test]},
{:pbkdf2_elixir, ">= 0.0.0", only: [:dev, :test]}
]
end
end
9 changes: 6 additions & 3 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
%{
%{"argon2_elixir": {:hex, :argon2_elixir, "1.2.14", "0fc4bfbc1b7e459954987d3d2f3836befd72d63f3a355e3978f5005dd6e80816", [], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"authority": {:hex, :authority, "0.2.0", "f19cc180120a55be5563d54653357c3a2d2785fb22098ae8ef40571040162d46", [], [], "hexpm"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "1.0.5", "cca70b5c8d9a98a0151c2d2796c728719c9c4b3f8bd2de015758ef577ee5141e", [], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"comeonin": {:hex, :comeonin, "4.0.3", "4e257dcb748ed1ca2651b7ba24fdbd1bd24efd12482accf8079141e3fda23a10", [], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [], [], "hexpm"},
"ecto": {:hex, :ecto, "2.2.8", "a4463c0928b970f2cee722cd29aaac154e866a15882c5737e0038bbfcf03ec2c", [], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.4.0", "992f38fabe705bb45821a728f20914c554b276838433349d4f2341f7a687cddf", [], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [], [], "hexpm"},
}
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [], [], "hexpm"}}
39 changes: 39 additions & 0 deletions test/authority_ecto/changeset_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,50 @@ defmodule Authority.Ecto.ChangesetTest do

alias Authority.Ecto.Changeset
alias Authority.Ecto.Test.Token
alias Authority.Ecto.Test.User

@expirations %{
"72_hours" => {72, :hours}
}

@valid_attrs %{
email: "user@example.com",
password: "testing123",
password_confirmation: "testing123"
}

defp test_encrypted_password(name, mod) do
changeset =
%User{}
|> User.changeset(@valid_attrs)
|> Changeset.put_encrypted_password(:password, :encrypted_password, name)

assert hash = get_change(changeset, :encrypted_password)
assert mod.checkpw(@valid_attrs.password, hash)
end

test "put_encrypted_password/3 deletes virtual attributes" do
changeset =
%User{}
|> User.changeset(@valid_attrs)
|> Changeset.put_encrypted_password(:password, :encrypted_password)

refute get_change(changeset, :password)
refute get_change(changeset, :password_confirmation)
end

test "put_encrypted_password/4 (bcrypt)" do
test_encrypted_password(:bcrypt, Comeonin.Bcrypt)
end

test "put_encrypted_password/4 (argon2)" do
test_encrypted_password(:argon2, Comeonin.Argon2)
end

test "put_encrypted_password/4 (pbkdf2)" do
test_encrypted_password(:pbkdf2, Comeonin.Pbkdf2)
end

test "put_token_expiration/3 sets the expires_at" do
changeset =
%Token{}
Expand Down
19 changes: 19 additions & 0 deletions test/support/user.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule Authority.Ecto.Test.User do
use Ecto.Schema
import Ecto.Changeset

@attrs [:email, :password, :password_confirmation]

schema "users" do
field(:email, :string)
field(:encrypted_password, :string)
field(:password, :string, virtual: true)
field(:password_confirmation, :string, virtual: true)
timestamps()
end

def changeset(user, attrs \\ %{}) do
user
|> cast(attrs, @attrs)
end
end