From ef3c965e089d8a187e04a7bfcb4076085761c228 Mon Sep 17 00:00:00 2001 From: Nizar Venturini Date: Thu, 7 Apr 2016 01:20:58 +0100 Subject: [PATCH] Add docs to all the things! --- README.md | 5 +-- lib/addict/controller.ex | 45 ++++++++++++++++++- lib/addict/crypto.ex | 10 +++++ lib/addict/helper.ex | 22 ++++----- lib/addict/interactors/create_session.ex | 5 +++ lib/addict/interactors/destroy_session.ex | 5 +++ .../generate_encrypted_password.ex | 5 +++ .../generate_password_reset_link.ex | 5 +++ lib/addict/interactors/get_user_by_email.ex | 4 ++ lib/addict/interactors/get_user_by_id.ex | 4 ++ lib/addict/interactors/inject_hash.ex | 4 ++ lib/addict/interactors/insert_user.ex | 5 +++ lib/addict/interactors/login.ex | 5 +++ lib/addict/interactors/register.ex | 8 +++- lib/addict/interactors/reset_password.ex | 5 +++ .../interactors/send_reset_password_link.ex | 5 +++ .../interactors/update_user_password.ex | 6 +++ lib/addict/interactors/validate_password.ex | 6 +++ .../validate_user_for_registration.ex | 6 +++ lib/addict/interactors/verify_password.ex | 5 +++ lib/addict/mailers/generic.ex | 3 ++ lib/addict/mailers/mail_sender.ex | 3 ++ lib/addict/mailers/mailers.ex | 5 ++- lib/addict/presenter.ex | 6 +++ 24 files changed, 166 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e08ba76..b385207 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/addict/controller.ex b/lib/addict/controller.ex index 01ffb50..6c7bbf0 100644 --- a/lib/addict/controller.ex +++ b/lib/addict/controller.ex @@ -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), @@ -13,6 +23,9 @@ defmodule Addict.AddictController do end end + @doc """ + Renders registration layout + """ def register(%{method: "GET"} = conn, _) do csrf_token = generate_csrf_token conn @@ -20,6 +33,11 @@ defmodule Addict.AddictController do |> 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), @@ -32,6 +50,9 @@ defmodule Addict.AddictController do end end + @doc """ + Renders login layout + """ def login(%{method: "GET"} = conn, _) do csrf_token = generate_csrf_token conn @@ -39,6 +60,12 @@ defmodule Addict.AddictController do |> 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) @@ -46,6 +73,11 @@ defmodule Addict.AddictController do 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"] @@ -55,6 +87,9 @@ defmodule Addict.AddictController do end end + @doc """ + Renders Password Recovery layout + """ def recover_password(%{method: "GET"} = conn, _) do csrf_token = generate_csrf_token conn @@ -62,6 +97,11 @@ defmodule Addict.AddictController do |> 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 @@ -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"] @@ -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) diff --git a/lib/addict/crypto.ex b/lib/addict/crypto.ex index 33e1b12..55d7351 100644 --- a/lib/addict/crypto.ex +++ b/lib/addict/crypto.ex @@ -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) diff --git a/lib/addict/helper.ex b/lib/addict/helper.ex index f48f862..5d5aceb 100644 --- a/lib/addict/helper.ex +++ b/lib/addict/helper.ex @@ -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 diff --git a/lib/addict/interactors/create_session.ex b/lib/addict/interactors/create_session.ex index 2445227..df6c058 100644 --- a/lib/addict/interactors/create_session.ex +++ b/lib/addict/interactors/create_session.ex @@ -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 diff --git a/lib/addict/interactors/destroy_session.ex b/lib/addict/interactors/destroy_session.ex index 25c3065..c7a9468 100644 --- a/lib/addict/interactors/destroy_session.ex +++ b/lib/addict/interactors/destroy_session.ex @@ -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 diff --git a/lib/addict/interactors/generate_encrypted_password.ex b/lib/addict/interactors/generate_encrypted_password.ex index ae94e8d..fb6ec8f 100644 --- a/lib/addict/interactors/generate_encrypted_password.ex +++ b/lib/addict/interactors/generate_encrypted_password.ex @@ -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 diff --git a/lib/addict/interactors/generate_password_reset_link.ex b/lib/addict/interactors/generate_password_reset_link.ex index 55a2fe0..a691974 100644 --- a/lib/addict/interactors/generate_password_reset_link.ex +++ b/lib/addict/interactors/generate_password_reset_link.ex @@ -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}" diff --git a/lib/addict/interactors/get_user_by_email.ex b/lib/addict/interactors/get_user_by_email.ex index 196b2a8..6ba7b3b 100644 --- a/lib/addict/interactors/get_user_by_email.ex +++ b/lib/addict/interactors/get_user_by_email.ex @@ -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 diff --git a/lib/addict/interactors/get_user_by_id.ex b/lib/addict/interactors/get_user_by_id.ex index d26edf8..4bcca18 100644 --- a/lib/addict/interactors/get_user_by_id.ex +++ b/lib/addict/interactors/get_user_by_id.ex @@ -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 diff --git a/lib/addict/interactors/inject_hash.ex b/lib/addict/interactors/inject_hash.ex index 809078a..497f913 100644 --- a/lib/addict/interactors/inject_hash.ex +++ b/lib/addict/interactors/inject_hash.ex @@ -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"])) diff --git a/lib/addict/interactors/insert_user.ex b/lib/addict/interactors/insert_user.ex index 0622190..c84d8c9 100644 --- a/lib/addict/interactors/insert_user.ex +++ b/lib/addict/interactors/insert_user.ex @@ -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) diff --git a/lib/addict/interactors/login.ex b/lib/addict/interactors/login.ex index f41a0da..c27ea5a 100644 --- a/lib/addict/interactors/login.ex +++ b/lib/addict/interactors/login.ex @@ -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), diff --git a/lib/addict/interactors/register.ex b/lib/addict/interactors/register.ex index 84804cb..e6cab5f 100644 --- a/lib/addict/interactors/register.ex +++ b/lib/addict/interactors/register.ex @@ -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 diff --git a/lib/addict/interactors/reset_password.ex b/lib/addict/interactors/reset_password.ex index 90a3dd0..7bf7f61 100644 --- a/lib/addict/interactors/reset_password.ex +++ b/lib/addict/interactors/reset_password.ex @@ -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"] diff --git a/lib/addict/interactors/send_reset_password_link.ex b/lib/addict/interactors/send_reset_password_link.ex index 87bc0e8..985a328 100644 --- a/lib/addict/interactors/send_reset_password_link.ex +++ b/lib/addict/interactors/send_reset_password_link.ex @@ -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) diff --git a/lib/addict/interactors/update_user_password.ex b/lib/addict/interactors/update_user_password.ex index 620172f..e8a14d9 100644 --- a/lib/addict/interactors/update_user_password.ex +++ b/lib/addict/interactors/update_user_password.ex @@ -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)) diff --git a/lib/addict/interactors/validate_password.ex b/lib/addict/interactors/validate_password.ex index cc520c3..e35d783 100644 --- a/lib/addict/interactors/validate_password.ex +++ b/lib/addict/interactors/validate_password.ex @@ -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 diff --git a/lib/addict/interactors/validate_user_for_registration.ex b/lib/addict/interactors/validate_user_for_registration.ex index 805a65c..b79b077 100644 --- a/lib/addict/interactors/validate_user_for_registration.ex +++ b/lib/addict/interactors/validate_user_for_registration.ex @@ -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 diff --git a/lib/addict/interactors/verify_password.ex b/lib/addict/interactors/verify_password.ex index ae9d0b9..fde8c0f 100644 --- a/lib/addict/interactors/verify_password.ex +++ b/lib/addict/interactors/verify_password.ex @@ -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 diff --git a/lib/addict/mailers/generic.ex b/lib/addict/mailers/generic.ex index 1001c96..02ad88c 100644 --- a/lib/addict/mailers/generic.ex +++ b/lib/addict/mailers/generic.ex @@ -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 diff --git a/lib/addict/mailers/mail_sender.ex b/lib/addict/mailers/mail_sender.ex index 35d2ac8..f42f80c 100644 --- a/lib/addict/mailers/mail_sender.ex +++ b/lib/addict/mailers/mail_sender.ex @@ -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 diff --git a/lib/addict/mailers/mailers.ex b/lib/addict/mailers/mailers.ex index f395fe9..d0ca48e 100644 --- a/lib/addict/mailers/mailers.ex +++ b/lib/addict/mailers/mailers.ex @@ -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 @@ -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 diff --git a/lib/addict/presenter.ex b/lib/addict/presenter.ex index 6b5175f..ff7bb52 100644 --- a/lib/addict/presenter.ex +++ b/lib/addict/presenter.ex @@ -1,5 +1,11 @@ defmodule Addict.Presenter do +@moduledoc """ +Normalized structure presentation +""" + @doc """ + Strips `:__struct__`, `:__meta__` and `:encrypted_password` from the structure + """ def strip_all(model) do model |> Map.drop([:__struct__, :__meta__, :encrypted_password]) end