defmodule MobilizonWeb.Email.User do @moduledoc """ Handles emails sent to users. """ use Bamboo.Phoenix, view: MobilizonWeb.EmailView import Bamboo.Phoenix import MobilizonWeb.Gettext alias Mobilizon.{Config, Crypto, Users} alias Mobilizon.Storage.Repo alias Mobilizon.Users.User alias MobilizonWeb.Email require Logger @spec confirmation_email(User.t(), String.t()) :: Bamboo.Email.t() def confirmation_email( %User{email: email, confirmation_token: confirmation_token}, locale \\ "en" ) do MobilizonWeb.Gettext.put_locale(locale) subject = gettext( "Instructions to confirm your Mobilizon account on %{instance}", instance: Config.instance_name() ) Email.base_email(to: email, subject: subject) |> assign(:locale, locale) |> assign(:token, confirmation_token) |> assign(:subject, subject) |> render(:registration_confirmation) end @spec reset_password_email(User.t(), String.t()) :: Bamboo.Email.t() def reset_password_email( %User{email: email, reset_password_token: reset_password_token}, locale \\ "en" ) do MobilizonWeb.Gettext.put_locale(locale) subject = gettext( "Instructions to reset your password on %{instance}", instance: Config.instance_name() ) Email.base_email(to: email, subject: subject) |> assign(:locale, locale) |> assign(:token, reset_password_token) |> assign(:subject, subject) |> render(:password_reset) end def check_confirmation_token(token) when is_binary(token) do with %User{} = user <- Users.get_user_by_activation_token(token), {:ok, %User{} = user} <- Users.update_user(user, %{ "confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second), "confirmation_sent_at" => nil, "confirmation_token" => nil }) do Logger.info("User #{user.email} has been confirmed") {:ok, user} else _err -> {:error, :invalid_token} end end def resend_confirmation_email(%User{} = user, locale \\ "en") do with :ok <- we_can_send_email(user, :confirmation_sent_at), {:ok, user} <- Users.update_user(user, %{ "confirmation_sent_at" => DateTime.utc_now() |> DateTime.truncate(:second) }) do send_confirmation_email(user, locale) Logger.info("Sent confirmation email again to #{user.email}") {:ok, user.email} end end def send_confirmation_email(%User{} = user, locale \\ "en") do user |> Email.User.confirmation_email(locale) |> Email.Mailer.deliver_later() end @doc """ Check that the provided token is correct and update provided password """ @spec check_reset_password_token(String.t(), String.t()) :: tuple def check_reset_password_token(password, token) do with %User{} = user <- Users.get_user_by_reset_password_token(token), {:ok, %User{} = user} <- Repo.update( User.password_reset_changeset(user, %{ "password" => password, "reset_password_sent_at" => nil, "reset_password_token" => nil }) ) do {:ok, user} else {:error, %Ecto.Changeset{errors: [password: {"registration.error.password_too_short", _}]}} -> {:error, "The password you have choosen is too short. Please make sure your password contains at least 6 charaters."} _err -> {:error, "The token you provided is invalid. Make sure that the URL is exactly the one provided inside the email you got."} end end @doc """ Send the email reset password, if it's not too soon since the last send """ @spec send_password_reset_email(User.t(), String.t()) :: tuple def send_password_reset_email(%User{} = user, locale \\ "en") do with :ok <- we_can_send_email(user, :reset_password_sent_at), {:ok, %User{} = user_updated} <- Repo.update( User.send_password_reset_changeset(user, %{ "reset_password_token" => Crypto.random_string(30), "reset_password_sent_at" => DateTime.utc_now() |> DateTime.truncate(:second) }) ) do mail = user_updated |> Email.User.reset_password_email(locale) |> Email.Mailer.deliver_later() {:ok, mail} else {:error, reason} -> {:error, reason} end end @spec we_can_send_email(User.t(), atom) :: :ok | {:error, :email_too_soon} defp we_can_send_email(%User{} = user, key) do case Map.get(user, key) do nil -> :ok _ -> case Timex.before?( Timex.shift(Map.get(user, key), hours: 1), DateTime.utc_now() |> DateTime.truncate(:second) ) do true -> :ok false -> {:error, :email_too_soon} end end end end