Skip to content

Commit

Permalink
Support configurable password hashing algorithms.
Browse files Browse the repository at this point in the history
* update comeonin to verson 4.1
* add installer option to set the password hashing algorithm
* remove pre 20.0 erlang support (required for comeonin 4)
* closes #299
* resolves #354
  • Loading branch information
smpallen99 committed Oct 13, 2018
1 parent 8e9cd03 commit dde36de
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 71 deletions.
8 changes: 2 additions & 6 deletions .travis.yml
Expand Up @@ -4,17 +4,13 @@ before_script:
- psql -c 'create database coherence_test;' -U postgres
language: elixir
elixir:
- 1.6
- 1.5
- 1.4
otp_release:
- 20.0
- 19.3
- 18.3
matrix:
exclude:
- elixir: 1.3
otp_release: 20.0
sudo: false
notification:
recipients:
- smpallen99@yahoo.com
- smpallen99@gmail.com
76 changes: 75 additions & 1 deletion README.md
Expand Up @@ -238,7 +238,8 @@ end
{:unlock_token_expire_minutes, 5},
{:rememberable_cookie_expire_hours, 2*24},
{:forwarded_invitation_fields, [:email, :name]}
{:allow_silent_password_recovery_for_unknown_user, false}
{:allow_silent_password_recovery_for_unknown_user, false},
{:password_hashing_alg, Comeonin.Bcrypt}
```

You can override this default configs. For example: you can add the following codes inside `config/config.exs`
Expand Down Expand Up @@ -707,6 +708,79 @@ The list of controller actions are:
* :session
* :unlock

## Customizing Password Hashing Algorithm

Coherence uses the `Bcrypt` algorithm by default for hashing passwords. However, with the update to Comeonin 4.0, you can now change the hashing algorithm.

Comeonin currently supports the following 3 algorithms:

* [Argon2](https://github.com/riverrun/argon2_elixir)
* [Bcrypt](https://github.com/riverrun/bcrypt_elixir)
* [Pbkdf2](https://github.com/riverrun/pbkdf2_elixir)

### Change the Hashing Algorithm in an Existing Project

To change the default in an existing project (to Argon2 for example), make the following 2 changes:

* Edit your `config/config.exs` file add/change the following line:

```elixir
# config/config.exs
config :coherence,
# ...
password_hashing_alg: Comeonin.Argon2,
# ...
```

* add the dependency to `mix.exs`

```elixir
# mix.exs
defp deps do
[
# ...
{:argon2_elixir, "~> 1.3"}
]
end
```

### Change the Hashing Algorithm in an Existing Project

To install Coherence in a new project with the `Pbkdf2` hashing algorithm (with the --full option for example):

```bash
# mix coh.install --full --password-hashing-alg=Comeonin.Argon2
```

and add the dependency

```elixir
# mix.exs
defp deps do
[
# ...
{:pbkdf2_elixir, "~> 0.12"}
]
end
```

### Speed up Tests and Database Seeding of Users

The default hashing algorithms are setup for production use. They are very slow by design which can cause very slow tests and database seeding in the dev and test environments. To speed this up, you can add the following to you `config/dev.exs` and/or `config/test.exs` configuration.

However, *DON'T USE THESE SETTINGS IN PRODUCTION*

```elixir
# config/test.exs
config :argon2_elixir,
t_cost: 1,
m_cost: 8
config :bcrypt_elixir, log_rounds: 4
config :pbkdf2_elixir, rounds: 1
```

Note: Only configure the algorithm you have configured!

## Accessing the Currently Logged In User

During login, a current version of the user model is cashed in the credential store. During each authentication request, the user model is fetched from the credential store and placed in conn.assigns[:current_user] to avoid a database fetch on each request.
Expand Down
1 change: 1 addition & 0 deletions config/test.exs
Expand Up @@ -21,6 +21,7 @@ config :coherence, TestCoherence.Repo,

config :coherence,
user_schema: TestCoherence.User,
password_hashing_alg: Comeonin.Bcrypt,
repo: TestCoherence.Repo,
router: TestCoherenceWeb.Router,
module: TestCoherence,
Expand Down
2 changes: 2 additions & 0 deletions lib/coherence/config.ex
Expand Up @@ -40,6 +40,7 @@ defmodule Coherence.Config do
* :module - the name of project module (`module: MyProject`)
* :opts ([])
* :password_hash_field (:password_hash) - The field used to save the hashed password
* :password_hashing_alg (Comeonin.Bcrypt) - Password hashing algorithm to use.
* :password_reset_permitted_attributes - List of allowed password reset atributes as stings,
* :registration_permitted_attributes - List of allowed registration parameter attributes as strings
* :repo: the module name of your Repo (`repo: MyProject.Repo`)
Expand Down Expand Up @@ -114,6 +115,7 @@ defmodule Coherence.Config do
:module,
{:opts, []},
{:password_hash_field, :password_hash},
{:password_hashing_alg, Comeonin.Bcrypt},
:password_reset_permitted_attributes,
:registration_permitted_attributes,
{:rememberable_cookie_expire_hours, 48},
Expand Down
6 changes: 3 additions & 3 deletions lib/coherence/schema.ex
Expand Up @@ -30,7 +30,7 @@ defmodule Coherence.Schema do
The following functions are available when `authenticatable?/0` returns true:
* `checkpw/2` - Validate password.
* `encrypt_password/1` - encrypted a password using `Comeonin.Bcrypt.hashpwsalt`
* `encrypt_password/1` - encrypted a password using `<password_hashing_alg>.hashpwsalt`
* `validate_coherence/2` - run the coherence password validations.
* `validate_password/2` - Used by `validate_coherence for password validation`
Expand Down Expand Up @@ -292,7 +292,7 @@ defmodule Coherence.Schema do
Keyword.get(unquote(opts), :authenticatable, true) do
def checkpw(password, encrypted) do
try do
Comeonin.Bcrypt.checkpw(password, encrypted)
apply(Config.password_hashing_alg(), :checkpw, [password, encrypted])
rescue
_ -> false
end
Expand All @@ -301,7 +301,7 @@ defmodule Coherence.Schema do
defoverridable checkpw: 2

def encrypt_password(password) do
Comeonin.Bcrypt.hashpwsalt(password)
apply(Config.password_hashing_alg(), :hashpwsalt, [password])
end

def validate_coherence(changeset, params) do
Expand Down
60 changes: 10 additions & 50 deletions lib/mix/tasks/coh.install.ex
@@ -1,14 +1,4 @@
defmodule Mix.Tasks.Coh.Install do
use Mix.Task

import Macro, only: [camelize: 1, underscore: 1]
import Mix.Generator
import Mix.Ecto
# import Coherence.Config, only: [use_binary_id?: 0]
import Coherence.Mix.Utils

@shortdoc "Configure the Coherence Package"

@moduledoc """
Configure the Coherence User Model for your Phoenix application. Coherence
is composed of a number of modules that can be enabled with this installer.
Expand Down Expand Up @@ -109,6 +99,8 @@ defmodule Mix.Tasks.Coh.Install do
A `--user-active-field` (false) add active field to user schema and disable logins when set to false.
A `--password-hashing-alg (Comeonin.Bcrypt) add a different password hashing algorithm
## Disable Options
* `--no-config` -- Don't append to your `config/config.exs` file.
Expand Down Expand Up @@ -175,14 +167,16 @@ defmodule Mix.Tasks.Coh.Install do
web_module: :string,
binary_id: :boolean,
layout: :boolean,
user_active_field: :boolean
user_active_field: :boolean,
password_hashing_alg: :string
] ++ Enum.map(@boolean_options, &{String.to_atom(&1), :boolean})

@switch_names Enum.map(@switches, &elem(&1, 0))

@new_user_migration_fields ["add :name, :string", "add :email, :string"]
@new_user_constraints ["create unique_index(:users, [:email])"]

@spec run(command_line_args :: [binary]) :: any
def run(args) do
{opts, parsed, unknown} = OptionParser.parse(args, switches: @switches)

Expand Down Expand Up @@ -292,6 +286,7 @@ defmodule Mix.Tasks.Coh.Install do
module: #{config[:base]},
web_module: #{config[:web_base]},
router: #{config[:router]},
password_hashing_alg: #{config[:password_hashing_alg]},
messages_backend: #{config[:web_base]}.Coherence.Messages,#{layout_field(config)}
logged_out_url: "/",#{user_active_field(config)}
registration_permitted_attributes: ["email","name","password","current_password","password_confirmation"],
Expand Down Expand Up @@ -1184,6 +1179,7 @@ defmodule Mix.Tasks.Coh.Install do
router = opts[:router] || "#{web_base}.Router"
web_path = opts[:web_path] || web_path()
web_module = web_base <> ".Coherence"
password_hashing_alg = opts[:password_hashing_alg] || "Comeonin.Bcrypt"

binding =
binding
Expand Down Expand Up @@ -1227,7 +1223,8 @@ defmodule Mix.Tasks.Coh.Install do
web_module: web_module,
use_binary_id?: binding[:use_binary_id?],
layout: opts[:layout] || false,
user_active_field?: binding[:user_active_field?]
user_active_field?: binding[:user_active_field?],
password_hashing_alg: password_hashing_alg
]
|> Enum.into(opts_map)
|> do_default_config(opts)
Expand Down Expand Up @@ -1295,25 +1292,9 @@ defmodule Mix.Tasks.Coh.Install do

defp parse_options(opts) do
{opts_bin, opts} = Enum.reduce(opts, {[], []}, &option_reduce(&1, &2))
# {:default, true}, {acc_bin, acc} ->
# {list_to_atoms(@default_options) ++ acc_bin, acc}
# {:full, true}, {acc_bin, acc} ->
# {list_to_atoms(@full_options) ++ acc_bin, acc}
# {:full_confirmable, true}, {acc_bin, acc} ->
# {list_to_atoms(@full_confirmable) ++ acc_bin, acc}
# {:full_invitable, true}, {acc_bin, acc} ->
# {list_to_atoms(@full_invitable) ++ acc_bin, acc}
# {:trackable_table, true}, {acc_bin, acc} ->
# {[:trackable_table | acc_bin] -- [:trackable], acc}
# {name, true}, {acc_bin, acc} when name in @all_options_atoms ->
# {[name | acc_bin], acc}
# {name, false}, {acc_bin, acc} when name in @all_options_atoms ->
# {acc_bin -- [name], acc}
# opt, {acc_bin, acc} ->
# {acc_bin, [opt | acc]}
# end

opts_bin = Enum.uniq(opts_bin)

opts_names = Enum.map(opts, &elem(&1, 0))

with [] <- Enum.filter(opts_bin, &(not (&1 in @switch_names))),
Expand All @@ -1326,27 +1307,6 @@ defmodule Mix.Tasks.Coh.Install do

def all_options, do: @all_options_atoms

# def get_layout(opts) do
# get_layout_template opts[:layout] || false
# end

# defp get_layout_template(true), do: {true, nil}
# defp get_layout_template(_) do
# case Path.wildcard web_path("templates/layout/app.html.*") do
# [] -> {true, nil}
# [first | _] -> get_layout_view(first)
# end
# end

# defp get_layout_template(templ) do
# with {:ok, file} <- File.read(web_path("views/layout_view.ex")),
# [_, module] <- Regex.run(~r//, file) do
# {false, {module, Path.rootname(templ)}}
# else
# {true, nil}
# end
# end

def print_installed_options(_config) do
["mix coh.install"]
|> list_config_options(Application.get_env(:coherence, :opts, []))
Expand Down
10 changes: 1 addition & 9 deletions lib/mix/tasks/coherence.install.ex
@@ -1,13 +1,4 @@
defmodule Mix.Tasks.Coherence.Install do
use Mix.Task

import Macro, only: [camelize: 1, underscore: 1]
import Mix.Generator
import Mix.Ecto
import Coherence.Mix.Utils

@shortdoc "Configure the Coherence Package"

@moduledoc """
Configure the Coherence User Model for your Phoenix application. Coherence
is composed of a number of modules that can be enabled with this installer.
Expand Down Expand Up @@ -176,6 +167,7 @@ defmodule Mix.Tasks.Coherence.Install do
@new_user_migration_fields ["add :name, :string", "add :email, :string"]
@new_user_constraints ["create unique_index(:users, [:email])"]

@spec run(command_line_args :: [binary]) :: any
def run(args) do
{opts, parsed, unknown} = OptionParser.parse(args, switches: @switches)

Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Expand Up @@ -48,7 +48,8 @@ defmodule Coherence.Mixfile do
defp deps do
[
{:ecto, "~> 2.0"},
{:comeonin, "~> 3.0"},
{:comeonin, "~> 4.0"},
{:bcrypt_elixir, "~> 1.1"},
{:phoenix, "~> 1.3"},
{:phoenix_html, "~> 2.10"},
{:gettext, "~> 0.14"},
Expand Down
3 changes: 2 additions & 1 deletion mix.lock
@@ -1,8 +1,9 @@
%{
"bcrypt_elixir": {:hex, :bcrypt_elixir, "1.1.1", "6b5560e47a02196ce5f0ab3f1d8265db79a23868c137e973b27afef928ed8006", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"comeonin": {:hex, :comeonin, "3.2.0", "cb10995a22aed6812667efb3856f548818c85d85394d8132bc116fbd6995c1ef", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{: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"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"credo": {:hex, :credo, "0.10.0", "66234a95effaf9067edb19fc5d0cd5c6b461ad841baac42467afed96c78e5e9e", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
Expand Down

0 comments on commit dde36de

Please sign in to comment.