Current section
Files
Jump to
Current section
Files
lib/authsense.ex
defmodule Authsense do
@moduledoc """
Sensible authentication helpers for Phoenix/Ecto.
### Basic use
Create your own module and use Authsense.
defmodule Myapp.Auth do
use Authsense,
repo: Myapp.Auth,
model: Myapp.User
end
## Authentication
`authenticate/1` will validate a login.
Auth.authenticate(changeset) #=> {:ok, user} or {:error, changeset_with_errors}
Auth.authenticate({ "userid", "password" }) #=> %User{} | nil
## Logging in/out
`put_current_user/2` will set session variables for logging in or out.
conn |> Auth.put_current_user(user) # login
conn |> Auth.put_current_user(nil) # logout
## Get current user
`fetch_current_user/2` - to get authentication data, use `Auth` as a plug:
# controller
import Auth
plug :fetch_current_user
When using this plug, you can then get the current user:
conn.assigns.current_user #=> %User{} | nil
## Usage in models
`generate_hashed_password/1` will update `:hashed_password` in a user changeset.
User.changeset(...)
|> Auth.generate_hashed_password()
## Configuration
Set configuration using `use Authsense, repo: Myapp.Repo, ...`. These keys are available:
- `repo` (required) - the Ecto repo to connect to.
- `model` (required) - the user model to use.
- `crypto` - the crypto module to use. (default: `Comeonin.Pbkdf2`)
- `identity_field` - field that identifies the user. (default: `:email`)
- `password_field` - virtual field that has the plaintext password. (default: `:password`)
- `hashed_password_field` - field where the password is stored. (default: `:hashed_password`)
- `login_error` - the error to add to the changeset on `Auth.authenticate/1`. (default: "Invalid credentials.")
## Recipes
For information on how to build login pages, secure your website, and other
things: see [Recipes](recipes.html).
"""
@doc false
def defaults do
%{
crypto: Comeonin.Pbkdf2,
identity_field: :email,
password_field: :password,
hashed_password_field: :hashed_password,
login_error: "Invalid credentials.",
repo: nil,
model: nil
}
end
@doc """
Updates an `Ecto.Changeset` to generate a hashed password.
If the changeset has `:password` in it, it will be hashed and stored as
`:hashed_password`. (Fields can be configured in `Authsense`.)
changeset
|> Auth.generate_hashed_password()
It's typically used in a model's `changeset/2` function.
defmodule Example.User do
use Example.Web, :model
def changeset(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
|> Auth.generate_hashed_password()
|> validate_confirmation(:password, message: "password confirmation doesn't match")
|> unique_constraint(:email)
end
end
Also see `Authsense.Service.generate_hashed_password/2` for the underlying
implementation.
"""
@callback generate_hashed_password(Ecto.Changeset.t) :: Ecto.Changeset.t
@doc """
Checks if someone can authenticate with a given username/password pair.
Works on both Ecto changesets or tuples.
%User{}
|> change(%{ email: "rico@gmail.com", password: "password" })
|> Auth.authenticate
Auth.authenticate({ "rico@gmail.com", "password" })
Returns `{:ok, user}` on success, or `{:error, changeset}` on failure. If
used as a tuple, it returns `{:error, nil}` on failure.
Typically used within a login action.
def login_create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
case Auth.authenticate(changeset) do
{:ok, user} ->
conn
|> Auth.put_current_user(user)
|> put_flash(:info, "Welcome.")
|> redirect(to: "/")
{:error, changeset} ->
render(conn, "login.html", changeset: changeset)
end
end
"""
@callback authenticate(Ecto.Changeset.t | { String.t, String.t }) ::
{:error, Ecto.Changeset.t} |
{:error, nil} |
{:ok, Ecto.Schema.t}
@doc """
Loads a user by a given identity field value. Returns a nil on failure.
get_user("rico@gmail.com") #=> %User{...}
"""
@callback get_user(String.t) ::
Ecto.Schema.t | nil
@doc """
Sets the current user for the session.
conn
|> Auth.put_current_user(user)
|> put_flash(:info, "Welcome.")
|> redirect(to: "/")
To logout, set it to nil.
conn
|> Auth.put_current_user(nil)
|> put_flash(:info, "You've been logged out.")
|> redirect(to: "/")
This sets the `:current_user_id` in the Session store. To access the User
model, use `Auth` as a plug (see `Authsense.Plug`).
"""
@callback put_current_user(Plug.Conn.t, Ecto.Schema.t | nil) ::
Plug.Conn.t
@doc """
Sets the `:current_user` assigns variable based on session.
defmodule Auth do
use Authsense, # ...
end
# in your controller or pipeline:
import Auth
plug :fetch_current_user
By doing so, you'll get access to the `:current_user` assigns. It will be set
to the User model if logged in, or to `nil` if logged out.
conn.assigns.current_user
<%= if @current_user %>
Hello, <%= @current_user.name %>
<% else %>
You are not logged in.
<% end %>
"""
@callback fetch_current_user(Plug.Conn.t, []) :: Plug.Conn.t
defmacro __using__(opts \\ []) do
quote do
@behaviour Authsense
@auth_options Map.merge(Authsense.defaults, Enum.into(unquote(opts), %{}))
def generate_hashed_password(changeset), do:
Authsense.Service.generate_hashed_password(changeset, @auth_options)
def authenticate(credentials), do:
Authsense.Service.authenticate(credentials, @auth_options)
def get_user(email), do:
Authsense.Service.get_user(email, @auth_options)
def put_current_user(conn, user), do:
Authsense.Plug.put_current_user(conn, user)
def fetch_current_user(conn, _opts \\ []), do:
Authsense.Plug.fetch_current_user(conn, @auth_options)
end
end
end