Current section
Files
Jump to
Current section
Files
README.md
# HaytniHaytni is a configurable authentication system for Phoenix, inspired (and yet the word is weak) by Devise (should be almost compatible with it).Goals:* non-bloatware: + all logics are not located in controllers + minimize changes (upgrade)* easily customisable and extendable: + enable (or disable) any plugin + add your own plugin(s) to the stackPlugins:* authenticable (`Haytni.AuthenticablePlugin`): handles hashing and storing an encrypted password in the database* registerable (`Haytni.RegisterablePlugin`): the elements to create a new account or edit its own account* rememberable (`Haytni.RememberablePlugin`): provides "persistent" authentification (the "remember me" feature)* confirmable (`Haytni.ConfirmablePlugin`): accounts have to be validated by email* recoverable (`Haytni.RecoverablePlugin`): recover for a forgotten password* lockable (`Haytni.LockablePlugin`): automatic lock an account after a number of failed attempts to sign in* ~~trackable (``): register users's connections (IP + when)~~Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) and published on [HexDocs](https://hexdocs.pm). Once published, the docs can be found at [https://hexdocs.pm/haytni](https://hexdocs.pm/haytni).## InstallationIf [available in Hex](https://hex.pm/docs/publish), the package can be installedby adding `haytni` to your list of dependencies in `mix.exs`:```elixirdef deps do [ {:haytni, "~> 0.0.2"}, # ... ]end```Run `mix deps.get`.Configure Haytni *your_app*/config/config.exs```config :haytni, repo: YourApp.Repo, schema: YourApp.User, #mailer: YourApp.Mailer # see below```These are the mandatory options. See options of each plugin for full customizations.Run `mix haytni.install` which has the following options (command arguments):* `--table <table>` (default: `"users"`): the name of your table (used to generate migrations)* `--plugin Module1 --plugin Module2 ... --plugin ModuleN` (default: value of `config :haytni, plugins: [...]`): the names of the (Elixir) modules/plugins to enableChange *your_app*/lib/*your_app*_web/router.ex```elixirdefmodule YourAppWeb.Router do use YourAppWeb, :router require Haytni # <= add this line # ... pipeline :browser do # ... plug Haytni.CurrentUserPlug # <= add this line end scope "/" do # ... Haytni.routes() # <= add this line end # ...end```Change *your_app*/lib/*your_app*/user.ex```elixirdefmodule YouApp.User do require Haytni # <= add this line # ... schema "..." do # ... Haytni.fields() # <= add this line end # ...end```### EmailsFor plugins which send emails (Confirmable, Lockable and Recoverable):Create *your_app*/lib/mailer.ex as follows:```elixirdefmodule YourApp.Mailer do use Bamboo.Mailer, otp_app: :your_app def from, do: {"mydomain.com", "noreply.mydomain.com"}end```Add to *your_app*/lib/*your_app*_web/router.ex```elixir if Mix.env() == :dev do Application.ensure_started(:bamboo) if Version.compare(Application.spec(:bamboo, :vsn) |> to_string, "0.8.0") == :lt do # Bamboo > 0.8 forward "/sent_emails", Bamboo.SentEmailViewerPlug else # Bamboo <= 0.8 forward "/sent_emails", Bamboo.EmailPreviewPlug end end```Configure email sending in *your_app*/config/dev.exs:```elixirconfig :yourapp, YourApp.Mailer, adapter: Bamboo.LocalAdapterconfig :haytni, mailer: YourApp.Mailer # <= add/change this line```For production (*your_app*/config/prod.exs): [see Bamboo's documentation](https://hexdocs.pm/bamboo/readme.html)General configuration:* `layout` (default: `false` for none): the layout to apply to Haytni's templates* `plugins` (default: `[Haytni.AuthenticablePlugin, Haytni.RegisterablePlugin, Haytni.RememberablePlugin, Haytni.ConfirmablePlugin, Haytni.LockablePlugin, Haytni.RecoverablePlugin]`): a list of `Haytni.Plugin` modules to use**Warning:** plugins order matters (in some case). Example: for correct handling of "automatic" authentification (find_user callback), Authenticable must appears before Rememberable in order to give precedence to the current session on the remember me cookie.## Plugins### AuthenticableFields:* email (string)* encrypted_password (string)Configuration:* `authentication_keys` (default: `~W[email]a`): the key(s), in addition to the password, requested to login. You can redefine it to `~W[name]a`, for example, to ask the username instead of its email address.* TODO: hashing algorithm/method (default: `bcrypt`)Routes:* `session_path` (actions: new/create)### RegisterableChange *your_app*/lib/*your_app*/user.ex```elixirdefmodule YourApp.User do # ... @attributes ~W[email password]a # add any field you'll may need # called when a user try to register himself def create_registration_changeset(%__MODULE__{} = struct, params) do struct |> cast(params, @attributes) |> validate_required(@attributes) # add any custom validation here |> Haytni.validate_create_registration() end # called when a user try to edit its own account (logic is completely different from registration) def update_registration_changeset(%__MODULE__{} = struct, params) do struct |> cast(params, ~W[email password current_password]a) # /!\ email and password are not necessarily required here /!\ # add any custom validation here |> Haytni.validate_update_registration() end # ...end```Configuration:* `password_length` (default: `6..128`): define min and max password length as an Elixir Range* `email_regexp` (default: `~R/^[^@\s]+@[^@\s]+$/`): the Regexp that an email at registration or profile edition needs to match* `case_insensitive_keys` (default: `~W[email]a`): list of fields to automatically downcase on registration. May be unneeded depending on your database (eg: *citext* columns for PostgreSQL or columns with a collation suffixed by "\_ci" for MySQL)* `strip_whitespace_keys` (default: `~W[email]a`): list of fields to automatically strip from whitespaces* `email_index_name` (default: `"users_email_index"`): the name of the unique index/constraint on email fieldRoutes:* `registration_path` (actions: new/create, edit/update)### RememberableFields:* remember_token (string, nullable, unique, default: `NULL`): the token to sign in automatically (`NULL` if the account doesn't use this function)* remember_created_at (datetime@utc, nullable, default: `NULL`): when the token was generated (also `NULL` if the account doesn't use this function)Configuration:* `remember_for` (default: `{2, :week}`): the period of validity of the token/which the user won't be asked for credentials* `remember_salt` (default: `""`): the salt to (de)cipher the token stored in the (signed) cookie* `remember_token_length` (default: 16): the length of the token (before being ciphered)* `remember_cookie_name` (default: `"remember_token"`): the name of the cookie holding the token for automatic sign in* `remember_cookie_options` (default: `[http_only: true]`): to set custom options of the cookie (options are: *domain*, *max_age*, *path*, *http_only*, *secure* and *extra*, see documentation of Plug.Conn.put_resp_cookie/4)Routes: none### ConfirmableFields:* confirmed_at (datetime@utc, nullable, default: `NULL`): when the account was confirmed else `NULL`* confirmation_sent_at (datetime@utc): when the confirmation was sent* confirmation_token (string, nullable, unique, default: `NULL`): the token to be confirmed if any pending confirmation (else `NULL`)* unconfirmed_email (string, nullable, default: `NULL`): on email change the new email is stored here until its confirmationConfiguration:* `reconfirmable` (default: `true`): if `true`, on an email change, the user has to confirm its new address* `confirmation_keys` (default: `~W[email]a`): the key(s) to be matched before sending a new confirmation* `confirm_within` (default: `{3, :day}`): delay after which confirmation token is considered as expired (ie the user has to ask for a new one)Routes:* `confirmation_path` (actions: show, new/create)### RecoverableFields:* reset_password_token (string, nullable, unique, default: `NULL`): the unique token to reinitialize the password (`NULL` if none)* reset_password_sent_at (datetime@utc, nullable, default: `NULL`): when the reinitialization token was generated (also `NULL` if there is no pending request)Configuration:* `reset_token_length` (default: `32`): the length of the generated token* `reset_password_within` (default: `{6, :hour}`): the delay before the token expires* `reset_password_keys` (default: `~W[email]a`): the field(s) to be matched to send a reinitialization tokenRoutes:* `password_path` (actions: new/create, edit/update)### LockableFields:* failed_attempts (integer, default: `0`): the current count of successive failures to login* locked_at (datetime@utc, nullable, default: `NULL`): when the account was locked (`NULL` while the account is not locked)* unlock_token (string, nullable, unique, default: `NULL`): the token send to the user to unlock its accountConfiguration:* `maximum_attempts` (default: `20`): the amount of successive attempts to login before locking the corresponding account* `unlock_token_length` (default: `32`): the length of the generated token* `unlock_keys` (default: `~W[email]a`): the field(s) to match to accept the unlock requestRoutes:* `unlock_path` (actions: new/create, show)## Quick recapFunctions you have to implement:* for Registerable: *YourApp.User*.create_registration_changeset/2 and *YourApp.User*.update_registration_changeset/2* for sending emails (plugins Confirmable, Lockable and Recoverable): *YourApp*.Mailer.from/0