user_email.ex 5.5 KB
Newer Older
1
# Pleroma: A lightweight social networking server
kaniini's avatar
kaniini committed
2
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3
4
# SPDX-License-Identifier: AGPL-3.0-only

5
defmodule Pleroma.Emails.UserEmail do
6
7
  @moduledoc "User emails"

Roman Chvanikov's avatar
Roman Chvanikov committed
8
  use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
9

10
11
  alias Pleroma.Config
  alias Pleroma.User
Haelwenn's avatar
Haelwenn committed
12
13
  alias Pleroma.Web.Endpoint
  alias Pleroma.Web.Router
14

15
  defp instance_name, do: Config.get([:instance, :name])
16

17
  defp sender do
18
    email = Config.get([:instance, :notify_email]) || Config.get([:instance, :email])
19
    {instance_name(), email}
20
21
  end

22
23
  defp recipient(email, nil), do: email
  defp recipient(email, name), do: {name, email}
24
  defp recipient(%User{} = user), do: recipient(user.email, user.name)
25

26
27
  def password_reset_email(user, token) when is_binary(token) do
    password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
28
29
30
31
32
33
34
35
36

    html_body = """
    <h3>Reset your password at #{instance_name()}</h3>
    <p>Someone has requested password change for your account at #{instance_name()}.</p>
    <p>If it was you, visit the following link to proceed: <a href="#{password_reset_url}">reset password</a>.</p>
    <p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
    """

    new()
37
    |> to(recipient(user))
38
    |> from(sender())
39
40
41
    |> subject("Password reset")
    |> html_body(html_body)
  end
42

Ivan Tashkinov's avatar
Ivan Tashkinov committed
43
44
45
46
47
48
  def user_invitation_email(
        user,
        %Pleroma.UserInviteToken{} = user_invite_token,
        to_email,
        to_name \\ nil
      ) do
49
50
51
52
    registration_url =
      Router.Helpers.redirect_url(
        Endpoint,
        :registration_page,
53
        user_invite_token.token
54
55
56
57
58
59
60
61
62
63
64
65
66
67
      )

    html_body = """
    <h3>You are invited to #{instance_name()}</h3>
    <p>#{user.name} invites you to join #{instance_name()}, an instance of Pleroma federated social networking platform.</p>
    <p>Click the following link to register: <a href="#{registration_url}">accept invitation</a>.</p>
    """

    new()
    |> to(recipient(to_email, to_name))
    |> from(sender())
    |> subject("Invitation to #{instance_name()}")
    |> html_body(html_body)
  end
68
69
70
71
72
73

  def account_confirmation_email(user) do
    confirmation_url =
      Router.Helpers.confirm_email_url(
        Endpoint,
        :confirm_email,
74
        user.id,
75
        to_string(user.confirmation_token)
76
77
78
79
80
81
82
83
84
85
86
87
88
89
      )

    html_body = """
    <h3>Welcome to #{instance_name()}!</h3>
    <p>Email confirmation is required to activate the account.</p>
    <p>Click the following link to proceed: <a href="#{confirmation_url}">activate your account</a>.</p>
    """

    new()
    |> to(recipient(user))
    |> from(sender())
    |> subject("#{instance_name()} account confirmation")
    |> html_body(html_body)
  end
Roman Chvanikov's avatar
Roman Chvanikov committed
90
91
92
93
94
95

  @doc """
  Email used in digest email notifications
  Includes Mentions and New Followers data
  If there are no mentions (even when new followers exist), the function will return nil
  """
96
  @spec digest_email(User.t()) :: Swoosh.Email.t() | nil
Roman Chvanikov's avatar
Roman Chvanikov committed
97
  def digest_email(user) do
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)

    mentions =
      notifications
      |> Enum.filter(&(&1.activity.data["type"] == "Create"))
      |> Enum.map(fn notification ->
        object = Pleroma.Object.normalize(notification.activity)
        object = update_in(object.data["content"], &format_links/1)

        %{
          data: notification,
          object: object,
          from: User.get_by_ap_id(notification.activity.actor)
        }
      end)

    followers =
      notifications
      |> Enum.filter(&(&1.activity.data["type"] == "Follow"))
      |> Enum.map(fn notification ->
        %{
          data: notification,
          object: Pleroma.Object.normalize(notification.activity),
          from: User.get_by_ap_id(notification.activity.actor)
        }
Roman Chvanikov's avatar
Roman Chvanikov committed
123
124
      end)

125
126
127
    unless Enum.empty?(mentions) do
      styling = Config.get([__MODULE__, :styling])
      logo = Config.get([__MODULE__, :logo])
minibikini's avatar
minibikini committed
128

Roman Chvanikov's avatar
Roman Chvanikov committed
129
130
131
132
      html_data = %{
        instance: instance_name(),
        user: user,
        mentions: mentions,
133
134
135
        followers: followers,
        unsubscribe_link: unsubscribe_url(user, "digest"),
        styling: styling
Roman Chvanikov's avatar
Roman Chvanikov committed
136
137
      }

138
139
140
141
142
143
      logo_path =
        if is_nil(logo) do
          Path.join(:code.priv_dir(:pleroma), "static/static/logo.png")
        else
          Path.join(Config.get([:instance, :static_dir]), logo)
        end
minibikini's avatar
minibikini committed
144

Roman Chvanikov's avatar
Roman Chvanikov committed
145
146
147
148
      new()
      |> to(recipient(user))
      |> from(sender())
      |> subject("Your digest from #{instance_name()}")
minibikini's avatar
minibikini committed
149
      |> put_layout(false)
Roman Chvanikov's avatar
Roman Chvanikov committed
150
      |> render_body("digest.html", html_data)
minibikini's avatar
minibikini committed
151
      |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.png", type: :inline))
Roman Chvanikov's avatar
Roman Chvanikov committed
152
153
154
    end
  end

minibikini's avatar
minibikini committed
155
156
  defp format_links(str) do
    re = ~r/<a.+href=['"].*>/iU
157
    %{link_color: color} = Config.get([__MODULE__, :styling])
minibikini's avatar
minibikini committed
158

minibikini's avatar
minibikini committed
159
    Regex.replace(re, str, fn link ->
160
      String.replace(link, "<a", "<a style=\"color: #{color};text-decoration: none;\"")
minibikini's avatar
minibikini committed
161
162
163
    end)
  end

Roman Chvanikov's avatar
Roman Chvanikov committed
164
165
166
167
168
  @doc """
  Generate unsubscribe link for given user and notifications type.
  The link contains JWT token with the data, and subscription can be modified without
  authorization.
  """
169
  @spec unsubscribe_url(User.t(), String.t()) :: String.t()
Roman Chvanikov's avatar
Roman Chvanikov committed
170
171
172
173
174
175
  def unsubscribe_url(user, notifications_type) do
    token =
      %{"sub" => user.id, "act" => %{"unsubscribe" => notifications_type}, "exp" => false}
      |> Pleroma.JWT.generate_and_sign!()
      |> Base.encode64()

176
    Router.Helpers.subscription_url(Endpoint, :unsubscribe, token)
Roman Chvanikov's avatar
Roman Chvanikov committed
177
  end
178
end