92 lines
2.3 KiB
Elixir
92 lines
2.3 KiB
Elixir
defmodule Plausible.Auth.EmailVerification do
|
|
@moduledoc """
|
|
API for verifying emails.
|
|
"""
|
|
|
|
import Ecto.Query, only: [from: 2]
|
|
|
|
alias Plausible.Auth
|
|
alias Plausible.Auth.EmailActivationCode
|
|
alias Plausible.Repo
|
|
|
|
require Logger
|
|
|
|
@expiration_hours 4
|
|
|
|
@spec any?(Auth.User.t()) :: boolean()
|
|
def any?(user) do
|
|
Repo.exists?(from(v in EmailActivationCode, where: v.user_id == ^user.id))
|
|
end
|
|
|
|
@spec issue_code(Auth.User.t(), NaiveDateTime.t()) ::
|
|
{:ok, EmailActivationCode.t()} | {:error, :hard_bounce | :unknown_error}
|
|
def issue_code(user, now \\ NaiveDateTime.utc_now()) do
|
|
now = NaiveDateTime.truncate(now, :second)
|
|
|
|
verification =
|
|
user
|
|
|> EmailActivationCode.new(now)
|
|
|> Repo.insert!(
|
|
on_conflict: [
|
|
set: [
|
|
issued_at: now,
|
|
code: EmailActivationCode.generate_code()
|
|
]
|
|
],
|
|
conflict_target: :user_id,
|
|
returning: true
|
|
)
|
|
|
|
email_template = PlausibleWeb.Email.activation_email(user, verification.code)
|
|
|
|
case Plausible.Mailer.send(email_template) do
|
|
:ok ->
|
|
Logger.debug(
|
|
"E-mail verification e-mail sent. In dev environment GET /sent-emails for details."
|
|
)
|
|
|
|
{:ok, verification}
|
|
|
|
error ->
|
|
error
|
|
end
|
|
end
|
|
|
|
@spec verify_code(Auth.User.t(), String.t() | non_neg_integer()) ::
|
|
:ok | {:error, :incorrect | :expired}
|
|
def verify_code(user, code) do
|
|
with {:ok, verification} <- get_verification(user, code) do
|
|
Repo.transaction(fn ->
|
|
user
|
|
|> Ecto.Changeset.change(email_verified: true)
|
|
|> Repo.update!()
|
|
|
|
Repo.delete_all(from(c in EmailActivationCode, where: c.id == ^verification.id))
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
end
|
|
|
|
defp get_verification(user, code) do
|
|
verification = Repo.get_by(EmailActivationCode, user_id: user.id, code: code)
|
|
|
|
cond do
|
|
is_nil(verification) ->
|
|
{:error, :incorrect}
|
|
|
|
expired?(verification) ->
|
|
{:error, :expired}
|
|
|
|
true ->
|
|
{:ok, verification}
|
|
end
|
|
end
|
|
|
|
@spec expired?(EmailActivationCode.t()) :: boolean()
|
|
def expired?(verification) do
|
|
expiration_time = NaiveDateTime.shift(NaiveDateTime.utc_now(), hour: -1 * @expiration_hours)
|
|
NaiveDateTime.before?(verification.issued_at, expiration_time)
|
|
end
|
|
end
|