Skip to content

Commit

Permalink
Add docs to all the things!
Browse files Browse the repository at this point in the history
  • Loading branch information
trenpixster committed Apr 7, 2016
1 parent 517837c commit ef3c965
Show file tree
Hide file tree
Showing 24 changed files with 166 additions and 16 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,13 @@ If the user is not logged in and requests for the above action, he will be redir
## Adding Custom Mailer

For adding a custom Mailer just follow the conventions:
- Module must be `Addict.Mailers.Theemailprovider`
- Module must be `Addict.Mailers.TheEmailProvider`
- Add the Mailer file in `/lib/addict/mailers`
- Make sure the mailer implements the behaviour defined [here](https://github.com/trenpixster/addict/blob/master/lib/addict/mailers/generic.ex)

Once that is done, just set `mail_service` configuration to `:theemailprovider`.
Once that is done, just set `mail_service` configuration to `:the_email_provider`.

## TODO
Add missing docs
Check the [issues](https://github.com/trenpixster/addict/issues) on this repository to check or track the ongoing improvements and new features.

## Contributing
Expand Down
45 changes: 44 additions & 1 deletion lib/addict/controller.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
defmodule Addict.AddictController do
@moduledoc """
Controller for addict
Responsible for handling requests for serving templates (GETs) and managing users (POSTs)
"""
use Phoenix.Controller

@doc """
Registers a user. Invokes `Addict.Configs.post_register/3` afterwards.
Requires to have at least `"email"` and "`password`" on `user_params`
"""
def register(%{method: "POST"} = conn, user_params) do
user_params = parse(user_params)
result = with {:ok, user} <- Addict.Interactors.Register.call(user_params),
Expand All @@ -13,13 +23,21 @@ defmodule Addict.AddictController do
end
end

@doc """
Renders registration layout
"""
def register(%{method: "GET"} = conn, _) do
csrf_token = generate_csrf_token
conn
|> put_addict_layout
|> render("register.html", csrf_token: csrf_token)
end

@doc """
Logs in a user. Invokes `Addict.Configs.post_login/3` afterwards.
Requires to have at least `"email"` and "`password`" on `auth_params`
"""
def login(%{method: "POST"} = conn, auth_params) do
auth_params = parse(auth_params)
result = with {:ok, user} <- Addict.Interactors.Login.call(auth_params),
Expand All @@ -32,20 +50,34 @@ defmodule Addict.AddictController do
end
end

@doc """
Renders login layout
"""
def login(%{method: "GET"} = conn, _) do
csrf_token = generate_csrf_token
conn
|> put_addict_layout
|> render("login.html", csrf_token: csrf_token)
end

@doc """
Logs out the user. Invokes `Addict.Configs.post_logout/3` afterwards.
No required params, it removes the session from the user.
"""

def logout(%{method: "POST"} = conn, _) do
case Addict.Interactors.DestroySession.call(conn) do
{:ok, conn} -> return_success(conn, %{}, Addict.Configs.post_logout)
{:error, errors} -> return_error(conn, errors, Addict.Configs.post_logout)
end
end

@doc """
Recover user password. Sends an e-mail with a reset password link. Invokes `Addict.Configs.post_recover_password/3` afterwards.
Requires to have `"email"` on `user_params`
"""
def recover_password(%{method: "POST"} = conn, user_params) do
user_params = parse(user_params)
email = user_params["email"]
Expand All @@ -55,13 +87,21 @@ defmodule Addict.AddictController do
end
end

@doc """
Renders Password Recovery layout
"""
def recover_password(%{method: "GET"} = conn, _) do
csrf_token = generate_csrf_token
conn
|> put_addict_layout
|> render("recover_password.html", csrf_token: csrf_token)
end

@doc """
Resets the user password. Invokes `Addict.Configs.post_reset_password/3` afterwards.
Requires to have `"token"`, `"signature"` and "`password`" on `params`
"""
def reset_password(%{method: "POST"} = conn, params) do
params = parse(params)
case Addict.Interactors.ResetPassword.call(params) do
Expand All @@ -70,6 +110,9 @@ defmodule Addict.AddictController do
end
end

@doc """
Renders Password Reset layout
"""
def reset_password(%{method: "GET"} = conn, params) do
csrf_token = generate_csrf_token
token = params["token"]
Expand All @@ -91,7 +134,7 @@ defmodule Addict.AddictController do
defp return_error(conn, errors, custom_fn) do
if custom_fn == nil, do: custom_fn = fn (a,_,_) -> a end
errors = errors |> Enum.map(fn {key, value} ->
%{message: "#{String.capitalize(Atom.to_string(key))}: #{value}"}
%{message: "#{Mix.Utils.camelize(Atom.to_string(key))}: #{value}"}
end)
conn
|> custom_fn.(:error, errors)
Expand Down
10 changes: 10 additions & 0 deletions lib/addict/crypto.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
defmodule Addict.Crypto do
@moduledoc """
Signs and verifies text
"""

@doc """
Sign `plaintext` with a `key`
"""
def sign(plaintext, key \\ Addict.Configs.secret_key) do
:crypto.hmac(:sha512, key, plaintext) |> Base.encode16
end

@doc """
Verify `plaintext` is signed with a `key`
"""
def verify(plaintext, signature, key \\ Addict.Configs.secret_key) do
base_signature = sign(plaintext, key)
do_verify(base_signature == signature)
Expand Down
22 changes: 12 additions & 10 deletions lib/addict/helper.ex
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
defmodule Addict.Helper do
@moduledoc """
Addict Helper functions
"""

@doc """
Returns the current user in session in a Hash
"""
def current_user(conn) do
conn |> Plug.Conn.fetch_session |> Plug.Conn.get_session(:current_user)
end

@doc """
Verifies if user is logged in
Returns a boolean
"""
def is_logged_in(conn) do
current_user(conn) != "null"
end

def router_helper do
Addict.Configs.repo
|> to_string
|> String.split(".")
|> Enum.at(0)
|> String.to_char_list
|> :string.concat('.Router.Helpers')
|> to_string
end
end
5 changes: 5 additions & 0 deletions lib/addict/interactors/create_session.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
defmodule Addict.Interactors.CreateSession do
import Plug.Conn

@doc """
Adds `user` as `:current_user` to the session in `conn`
Returns `{:ok, conn}`
"""
def call(conn, user) do
conn = conn
|> fetch_session
Expand Down
5 changes: 5 additions & 0 deletions lib/addict/interactors/destroy_session.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
defmodule Addict.Interactors.DestroySession do
import Plug.Conn
@doc """
Removes `:current_user` from the session in `conn`
Returns `{:ok, conn}`
"""

def call(conn) do
conn = conn
Expand Down
5 changes: 5 additions & 0 deletions lib/addict/interactors/generate_encrypted_password.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
defmodule Addict.Interactors.GenerateEncryptedPassword do
@doc """
Securely hashes `password`
Returns the hash as a String
"""
def call(password) do
Comeonin.Pbkdf2.hashpwsalt password
end
Expand Down
5 changes: 5 additions & 0 deletions lib/addict/interactors/generate_password_reset_link.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
defmodule Addict.Interactors.GeneratePasswordResetLink do
@doc """
Generates a ready to use password reset path. The generated token is timestamped.
Returns the password reset path with a token and it's respective signature.
"""
def call(user_id, secret \\ Addict.Configs.secret_key, reset_path \\ Addict.Configs.reset_password_path) do
current_time = to_string(:erlang.system_time(:seconds))
reset_string = Base.encode16 "#{current_time},#{user_id}"
Expand Down
4 changes: 4 additions & 0 deletions lib/addict/interactors/get_user_by_email.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
defmodule Addict.Interactors.GetUserByEmail do
@doc """
Gets user by e-mail.
Returns `{:ok, user}` or `{:error, [authentication: "Incorrect e-mail/password"]}`
"""
def call(email, schema \\ Addict.Configs.user_schema, repo \\ Addict.Configs.repo) do
repo.get_by(schema, email: email) |> process_response
end
Expand Down
4 changes: 4 additions & 0 deletions lib/addict/interactors/get_user_by_id.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
defmodule Addict.Interactors.GetUserById do
@doc """
Gets user by e-mail.
Returns `{:ok, user}` or `{:error, [user_id: "Unable to find user"]}`
"""
def call(id, schema \\ Addict.Configs.user_schema, repo \\ Addict.Configs.repo) do
repo.get_by(schema, id: id) |> process_response
end
Expand Down
4 changes: 4 additions & 0 deletions lib/addict/interactors/inject_hash.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
defmodule Addict.Interactors.InjectHash do
alias Addict.Interactors.GenerateEncryptedPassword
@doc """
Adds `"encrypted_password"` and drops `"password"` from provided hash.
Returns the new hash with `"encrypted_password"` and without `"password"`.
"""
def call(user_params) do
user_params
|> Map.put("encrypted_password", GenerateEncryptedPassword.call(user_params["password"]))
Expand Down
5 changes: 5 additions & 0 deletions lib/addict/interactors/insert_user.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
defmodule Addict.Interactors.InsertUser do
@doc """
Inserts the `schema` populated with `user_params` to the `repo`.
Returns `{:ok, user}` or `{:error, error_message}`
"""
def call(schema, user_params, repo) do
user_params = for {key, val} <- user_params, into: %{}, do: {String.to_atom(key), val}
repo.insert struct(schema, user_params)
Expand Down
5 changes: 5 additions & 0 deletions lib/addict/interactors/login.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
defmodule Addict.Interactors.Login do
alias Addict.Interactors.{GetUserByEmail, VerifyPassword}

@doc """
Verifies if the `password` is correct for the provided `email`
Returns `{:ok, user}` or `{:error, [errors]}`
"""
def call(%{"email" => email, "password" => password}) do
with {:ok, user} <- GetUserByEmail.call(email),
{:ok} <- VerifyPassword.call(user, password),
Expand Down
8 changes: 7 additions & 1 deletion lib/addict/interactors/register.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
defmodule Addict.Interactors.Register do
alias Addict.Interactors.{ValidateUserForRegistration, InsertUser, InjectHash}

@doc """
Executes the user registration flow: parameters validation, password hash generation, user insertion and e-mail sending.
Also applies custom defined `Addict.Configs.extra_validation/2`.
Returns `{:ok, user}` or `{:error, [errors]}`
"""
def call(user_params, configs \\ Addict.Configs) do
extra_validation = configs.extra_validation || fn (a,b) -> a end
extra_validation = configs.extra_validation || fn (a,_) -> a end

{valid, errors} = ValidateUserForRegistration.call(user_params)
user_params = InjectHash.call user_params
Expand Down
5 changes: 5 additions & 0 deletions lib/addict/interactors/reset_password.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ defmodule Addict.Interactors.ResetPassword do
alias Addict.Interactors.{GetUserById, UpdateUserPassword, ValidatePassword}
require Logger

@doc """
Executes the password reset flow: parameters validation, password hash generation, user updating.
Returns `{:ok, user}` or `{:error, [errors]}`
"""
def call(params) do
token = params["token"]
password = params["password"]
Expand Down
5 changes: 5 additions & 0 deletions lib/addict/interactors/send_reset_password_link.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ defmodule Addict.Interactors.SendResetPasswordEmail do
alias Addict.Interactors.{GetUserByEmail, GeneratePasswordResetLink}
require Logger

@doc """
Executes the password recovery flow: verifies if the user exists and sends the e-mail with the reset link
Either returns `{:ok, user}` or `{:ok, nil}`. `{:ok, nil}` is returned when e-mail is not found to avoid user enumeration.
"""
def call(email, configs \\ Addict.Configs) do
{result, user} = GetUserByEmail.call(email)

Expand Down
6 changes: 6 additions & 0 deletions lib/addict/interactors/update_user_password.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
defmodule Addict.Interactors.UpdateUserPassword do
alias Addict.Interactors.GenerateEncryptedPassword
@doc """
Updates the user `encrypted_password`
Returns `{:ok, user}` or `{:error, [errors]}`
"""

def call(user, password, repo \\ Addict.Configs.repo) do
user
|> Ecto.Changeset.change(encrypted_password: GenerateEncryptedPassword.call(password))
Expand Down
6 changes: 6 additions & 0 deletions lib/addict/interactors/validate_password.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
defmodule Addict.Interactors.ValidatePassword do

@doc """
Validates a password according to the defined strategies.
For now, only the `:default` strategy exists: password must be at least 6 chars long.
Returns `{:ok, []}` or `{:error, [errors]}`
"""
def call(changeset, nil) do
call(changeset, [])
end
Expand Down
6 changes: 6 additions & 0 deletions lib/addict/interactors/validate_user_for_registration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ defmodule Addict.PasswordUser do
end
end

@doc """
Validates if the user is valid for insertion.
Checks if `password` is valid and if `email` is well formatted and unique.
Returns `{:ok, []}` or `{:error, [errors]}`
"""
defmodule Addict.Interactors.ValidateUserForRegistration do
import Ecto.Changeset
alias Addict.Interactors.ValidatePassword
Expand Down
5 changes: 5 additions & 0 deletions lib/addict/interactors/verify_password.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
defmodule Addict.Interactors.VerifyPassword do
import Ecto.Query

@doc """
Verifies if the password for the user is valid
Returns `{:ok}` or `{:error, [authentication: "Incorrect e-mail/password"]}`
"""
def call(user, password) do
Comeonin.Pbkdf2.checkpw(password, user.encrypted_password) |> process_response
end
Expand Down
3 changes: 3 additions & 0 deletions lib/addict/mailers/generic.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@moduledoc """
Defines the required behaviour for e-mail providers
"""
defmodule Addict.Mailers.Generic do
@callback send_email(String.t, String.t, String.t, String.t) :: any
end
3 changes: 3 additions & 0 deletions lib/addict/mailers/mail_sender.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
defmodule Addict.Mailers.MailSender do
@moduledoc """
Sends register and reset token e-mails
"""
require Logger

def send_register(user_params) do
Expand Down
5 changes: 4 additions & 1 deletion lib/addict/mailers/mailers.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
defmodule Addict.Mailers do
@moduledoc """
Sends e-mail using the configured mail service on `Addict.Configs.mail_service`
"""
require Logger

def send_email(to, from, subject, html_body, mail_service \\ Addict.Configs.mail_service) do
Expand All @@ -11,7 +14,7 @@ defmodule Addict.Mailers do
end

defp do_send_email(to, from, subject, html_body, mail_service) do
mail_service = to_string(mail_service) |> String.capitalize
mail_service = to_string(mail_service) |> Mix.Utils.camelize
mailer = Module.concat Addict.Mailers, mail_service
mailer.send_email(to, from, subject, html_body)
end
Expand Down
Loading

0 comments on commit ef3c965

Please sign in to comment.