lib/coherence/services/confirmable_service.ex
defmodule Coherence.ConfirmableService do
@moduledoc """
Confirmable allows users to confirm a new account.
When enabled, newly created accounts are emailed send a confirmation
email with a confirmation link. Clicking on the confirmation link enables
the account.
Access to the account is disabled until the account is enabled, unless the
the `allow_unconfirmed_access_for` option is configured. If the account is
not confirmed before the `confirmation_token_expire_days` configuration days
expires, a new confirmation must be sent.
Confirmable adds the following database columns to the user model:
* :confirmation_token - a unique token required to confirm the account
* :confirmed_at - time and date the account was confirmed
* :confirmation_sent_at - the time and date the confirmation token was created
The following configuration is used to customize confirmable behavior:
* :confirmation_token_expire_days - number days to allow confirmation. default 5 days
* :allow_unconfirmed_access_for - number of days to allow login access to the account before confirmation.
default 0 (disabled)
"""
use Coherence.Config
use CoherenceWeb, :service
import Coherence.Controller
alias Coherence.{Messages, Schemas}
defmacro __using__(opts \\ []) do
quote do
# import unquote(__MODULE__)
use Coherence.Config
alias Coherence.Schemas
def confirmable? do
Config.has_option(:confirmable) and Keyword.get(unquote(opts), :confirmable, true)
end
if Config.has_option(:confirmable) and Keyword.get(unquote(opts), :confirmable, true) do
@doc """
Checks if the user has been confirmed.
Returns true if confirmed, false otherwise
"""
def confirmed?(user) do
!!user.confirmed_at
end
@doc """
Confirm a user account.
Adds the `:confirmed_at` datetime field on the user model.
Returns a changeset ready for Repo.update
"""
def confirm(user) do
Schemas.change_user(user, %{
confirmed_at: NaiveDateTime.utc_now(),
confirmation_token: nil
})
end
@doc """
Confirm a user account.
Adds the `:confirmed_at` datetime field on the user model.
deprecated! Please use Coherence.ControllerHelpers.unlock!/1.
"""
def confirm!(user) do
IO.warn(
"#{inspect(Config.user_schema())}.confirm!/1 has been deprecated. Please use Coherence.ControllerHelpers.confirm!/1 instead."
)
changeset =
Schemas.change_user(user, %{
confirmed_at: NaiveDateTime.utc_now(),
confirmation_token: nil
})
if confirmed?(user) do
changeset =
Ecto.Changeset.add_error(
changeset,
:confirmed_at,
Messages.backend().already_confirmed()
)
{:error, changeset}
else
Config.repo().update(changeset)
end
defoverridable(confirm!: 1, confirm: 1, confirmed?: 1)
end
defoverridable(confirmable?: 0)
end
end
end
@doc """
Confirm a user account.
Adds the `:confirmed_at` datetime field on the user model.
deprecated! Please use Coherence.ControllerHelpers.unlock!/1.
"""
@spec confirm(Ecto.Schema.t()) :: Ecto.Changeset.t()
def confirm(user) do
Schemas.change_user(user, %{confirmed_at: NaiveDateTime.utc_now(), confirmation_token: nil})
end
@doc """
Confirm a user account.
Adds the `:confirmed_at` datetime field on the user model.
deprecated! Please use Coherence.ControllerHelpers.unlock!/1.
"""
@spec confirm!(Ecto.Schema.t()) :: Ecto.Changeset.t() | {:error, Ecto.Changeset.t()}
def confirm!(user) do
changeset =
Schemas.change_user(user, %{confirmed_at: NaiveDateTime.utc_now(), confirmation_token: nil})
if confirmed?(user) do
changeset =
Ecto.Changeset.add_error(changeset, :confirmed_at, Messages.backend().already_confirmed())
{:error, changeset}
else
Schemas.update(changeset)
end
end
@doc """
Checks if the user has been confirmed.
Returns true if confirmed, false otherwise
"""
@spec confirmed?(Ecto.Schema.t()) :: boolean
def confirmed?(user) do
for_option(true, fn ->
!!user.confirmed_at
end)
end
@doc """
Checks if the confirmation token has expired.
Returns true when the confirmation has expired.
"""
@spec expired?(Ecto.Schema.t()) :: boolean
def expired?(user) do
for_option(fn ->
expired?(user.confirmation_sent_at, days: Config.confirmation_token_expire_days())
end)
end
@doc """
Checks if the user can access the account before confirmation.
Returns true if the unconfirmed access has not expired.
"""
@spec unconfirmed_access?(Ecto.Schema.t()) :: boolean
def unconfirmed_access?(user) do
for_option(fn ->
case Config.allow_unconfirmed_access_for() do
0 -> false
days -> not expired?(user.confirmation_sent_at, days: days)
end
end)
end
defp for_option(other \\ false, fun) do
if Config.has_option(:confirmable) do
fun.()
else
other
end
end
end