File size: 5,413 Bytes
8da2481
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
defmodule PlausibleWeb.Site.MembershipController do
  @moduledoc """
    This controller deals with user management via the UI in Site Settings -> People. It's important to enforce permissions in this controller.

    Owner and Admin - Can manage users, can trigger a 'transfer ownership' request
    Editor and Viewer - Can not access user management settings
    Anyone - Can accept invitations

    Everything else should be explicitly disallowed.
  """

  use PlausibleWeb, :controller
  use Plausible.Repo
  use Plausible

  alias Plausible.Teams

  plug PlausibleWeb.RequireAccountPlug

  plug PlausibleWeb.Plugs.AuthorizeSiteAccess, [:owner, :admin]

  def invite_member_form(conn, _params) do
    site =
      conn.assigns.current_user
      |> Plausible.Sites.get_for_user!(conn.assigns.site.domain)
      |> Plausible.Repo.preload(:owners)

    limit = Teams.Billing.team_member_limit(site.team)
    usage = Teams.Billing.team_member_usage(site.team)
    below_limit? = Plausible.Billing.Quota.below_limit?(usage, limit)

    render(
      conn,
      "invite_member_form.html",
      site: site,
      team_member_limit: limit,
      is_at_limit: not below_limit?,
      skip_plausible_tracking: true
    )
  end

  def invite_member(conn, %{"email" => email, "role" => role}) do
    site_domain = conn.assigns.site.domain

    site =
      Plausible.Sites.get_for_user!(conn.assigns.current_user, site_domain)
      |> Plausible.Repo.preload(:owners)

    case Teams.Invitations.InviteToSite.invite(
           site,
           conn.assigns.current_user,
           email,
           role
         ) do
      {:ok, invitation} ->
        conn
        |> put_flash(
          :success,
          "#{email} has been invited to #{site_domain} as #{PlausibleWeb.SiteView.with_indefinite_article("#{invitation.role}")}"
        )
        |> redirect(to: Routes.site_path(conn, :settings_people, site.domain))

      {:error, :already_a_member} ->
        render(conn, "invite_member_form.html",
          error: "Cannot send invite because #{email} is already a member of #{site.domain}",
          site: site,
          skip_plausible_tracking: true
        )

      {:error, {:over_limit, limit}} ->
        render(conn, "invite_member_form.html",
          error:
            "Your account is limited to #{limit} team members. You can upgrade your plan to increase this limit.",
          site: site,
          skip_plausible_tracking: true,
          is_at_limit: true,
          team_member_limit: limit
        )

      {:error, %Ecto.Changeset{} = changeset} ->
        error_msg =
          case changeset.errors[:invitation] do
            {"already sent", _} ->
              "This invitation has been already sent. To send again, remove it from pending invitations first."

            _ ->
              "Something went wrong."
          end

        conn
        |> put_flash(:error, error_msg)
        |> redirect(to: Routes.site_path(conn, :settings_people, site.domain))
    end
  end

  @doc """
    Updates the role of a user. The user being updated could be the same or different from the user taking
    the action. When updating the role, it's important to enforce permissions:

    Owner - Can update anyone's role except for themselves. If they want to change their own role, they have to use the 'transfer ownership' feature.
    Admin - Can update anyone's role except for owners. Can downgrade their own access to 'viewer'. Can promote a viewer to admin.
  """
  def update_role_by_user(conn, %{"id" => user_id, "new_role" => new_role_str}) do
    %{site: site, current_user: current_user, site_role: site_role} = conn.assigns

    case Teams.Memberships.update_role(
           site,
           user_id,
           new_role_str,
           current_user,
           site_role
         ) do
      {:ok, guest_membership} ->
        redirect_target =
          if guest_membership.team_membership.user_id == current_user.id and
               guest_membership.role == :viewer do
            Routes.stats_path(conn, :stats, site.domain, [])
          else
            Routes.site_path(conn, :settings_people, site.domain)
          end

        conn
        |> put_flash(
          :success,
          "#{guest_membership.team_membership.user.name} is now #{PlausibleWeb.SiteView.with_indefinite_article(to_string(guest_membership.role))}"
        )
        |> redirect(to: redirect_target)

      {:error, _} ->
        conn
        |> put_flash(:error, "You are not allowed to grant the #{new_role_str} role")
        |> redirect(to: Routes.site_path(conn, :settings_people, site.domain))
    end
  end

  def remove_member_by_user(conn, %{"id" => user_id} = _params) do
    site = conn.assigns.site

    if user = Repo.get(Plausible.Auth.User, user_id) do
      Teams.Memberships.remove(site, user)

      redirect_target =
        if user_id == conn.assigns[:current_user].id do
          Routes.site_path(conn, :index)
        else
          Routes.site_path(conn, :settings_people, site.domain)
        end

      conn
      |> put_flash(
        :success,
        "#{user.name} has been removed from #{site.domain}"
      )
      |> redirect(to: redirect_target)
    else
      conn
      |> put_flash(
        :success,
        "User has been removed from #{site.domain}"
      )
      |> redirect(to: Routes.site_path(conn, :settings_people, site.domain))
    end
  end
end