Skip to content

Instantly share code, notes, and snippets.

@ahmadshah
Created October 10, 2016 13:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ahmadshah/c1209d2e09d6796178717b2a6ec4e2dd to your computer and use it in GitHub Desktop.
Save ahmadshah/c1209d2e09d6796178717b2a6ec4e2dd to your computer and use it in GitHub Desktop.
defmodule Reactor.Service.Authenticator do
@behaviour Reactor.Contract.Service.Authenticator
@moduledoc """
User authentication service.
"""
use Reactor.Resolver, repositories: [:user]
alias Comeonin.Bcrypt, as: Comeonin
alias Reactor.Entity.User, as: UserEntity
@doc """
Authenticate user via email and password.
"""
def authenticate(email, password) do
with %UserEntity{}=user <- @user_repo.find_by_auth_key(email),
:ok <- validate_hash(password, user.password),
:ok <- validate_state(user.state),
do: {:ok, user}
end
@doc """
Autheticate user via primary key.
"""
def authenticate_with_id(id) do
with %UserEntity{}=user <- @user_repo.find_by_id(id),
:ok <- validate_state(user.state),
do: {:ok, user}
end
@doc false
defp validate_hash(input, hash_value) do
case Comeonin.checkpw(input, hash_value) do
false -> {:error, :invalid_password}
true -> :ok
end
end
@doc false
defp validate_state(state) when state == "suspended",
do: {:error, :user_is_suspended}
defp validate_state(_),
do: :ok
end
defmodule Reactor.Service.AuthenticatorTest do
use ExUnit.Case, async: true
alias Reactor.Service.Authenticator, as: AuthenticatorService
test "authenticate user with email and password" do
{:ok, user} = AuthenticatorService.authenticate("foo@bar.com", "123456")
assert user.email == "foo@bar.com"
end
test "authenticate user with id" do
{:ok, user} = AuthenticatorService.authenticate_with_id("1")
assert user.email == "foo@bar.com"
end
test "authenticate non-existing user with email and password" do
assert {:error, :user_not_found} == AuthenticatorService.authenticate("trigger_error_response", "123456")
end
test "authenticate user with email and wrong password" do
assert {:error, :invalid_password} == AuthenticatorService.authenticate("foo@bar.com", "1234567")
end
test "authenticate suspended user with email and password" do
assert {:error, :user_is_suspended} = AuthenticatorService.authenticate("trigger_suspended_error_response", "123456")
end
test "authenticate non-existing user with id" do
assert {:error, :user_not_found} == AuthenticatorService.authenticate_with_id("trigger_error_response")
end
test "authenticate suspended user with id" do
assert {:error, :user_is_suspended} = AuthenticatorService.authenticate_with_id("trigger_suspended_error_response")
end
end
use Mix.Config
config :reactor, :repositories,
user: Reactor.Repo.Postgre.User
defmodule Reactor.Mock.Repo.User do
@moduledoc """
User repository.
"""
@behaviour Reactor.Contract.Repo.User
alias Ecto.UUID
alias Comeonin.Bcrypt, as: Comeonin
alias Reactor.Entity.User, as: UserEntity
alias Henchman.Randomizer, as: Henchman
def find_by_id("trigger_error_response", opts \\ []),
do: {:error, :user_not_found}
def find_by_id("trigger_suspended_error_response", opts),
do: Map.put(user_struct, :state, "suspended")
def find_by_id(id, opts),
do: user_struct
def find_by_auth_key("trigger_error_response", opts \\ []),
do: {:error, :user_not_found}
def find_by_auth_key("trigger_suspended_error_response", opts),
do: Map.put(user_struct, :state, "suspended")
def find_by_auth_key(email, opts),
do: user_struct
def find_by_uuid(uuid, opts \\ []),
do: user_struct
def find_by_column(columns, opts),
do: user_struct
defp user_struct do
%UserEntity{
id: "1",
uuid: UUID.generate,
email: "foo@bar.com",
password: Comeonin.hashpwsalt("123456"),
first_name: "Foo",
last_name: "Bar",
state: "active",
account_confirmation_key: Henchman.generate(20),
}
end
end
defmodule Reactor.Repo.Postgre.User do
@moduledoc """
User repository.
"""
@behaviour Reactor.Contract.Repo.User
use Reactor.Repository, :postgre_repo
alias Reactor.Repo
alias Reactor.Repo.Postgre.Model.User, as: UserModel
def find_by_id(id, opts \\ []) do
case Repo.get(UserModel, id) do
nil -> user_not_found_error
user -> to_struct(user, opts)
end
end
def find_by_auth_key(email, opts \\ []),
do: find_by_column([email: email], opts)
def find_by_uuid(uuid, opts \\ []),
do: find_by_column([uuid: uuid], opts)
def find_by_column(columns, opts) do
case Repo.get_by(UserModel, columns) do
nil -> user_not_found_error
user -> to_struct(user, opts)
end
end
defp user_not_found_error,
do: {:error, :user_not_found}
end
defmodule Reactor.Resolver do
@moduledoc false
defmacro __using__(opts) do
quote do
repositories = Keyword.get(unquote(opts), :repositories)
if !is_nil(repositories) do
Enum.map(repositories, fn (repo) ->
repo_module = Application.get_env(:reactor, :repositories)[repo]
if is_nil(repo_module), do: raise RuntimeError, message: "There is no available module for #{repo}"
attribute_name = String.to_atom(to_string(repo) <> "_repo")
Module.put_attribute(__MODULE__, attribute_name, repo_module)
end)
end
end
end
end
use Mix.Config
config :reactor, :repositories,
user: Reactor.Mock.Repo.User
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment