File size: 3,346 Bytes
936b397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
defmodule PlausibleWeb.HelpScoutController do
  use PlausibleWeb, :controller

  alias Plausible.HelpScout

  @conversation_token_seconds 8 * 60 * 60

  plug :make_iframe_friendly

  def callback(conn, %{"customer-id" => customer_id, "conversation-id" => conversation_id}) do
    assigns = %{conversation_id: conversation_id, customer_id: customer_id, token: ""}

    with :ok <- HelpScout.validate_signature(conn),
         {:ok, details} <- HelpScout.get_details_for_customer(customer_id, conversation_id) do
      assigns = Map.put(assigns, :token, sign_token(conversation_id))

      conn
      |> render("callback.html", Map.merge(assigns, details))
    else
      {:error, {:user_not_found, [email | _]}} ->
        conn
        |> render("callback.html", Map.merge(assigns, %{error: ":user_not_found", email: email}))

      {:error, error} ->
        conn
        |> render("callback.html", Map.put(assigns, :error, inspect(error)))
    end
  end

  def callback(conn, _) do
    render(conn, "bad_request.html")
  end

  def show(
        conn,
        %{
          "email" => email,
          "token" => token,
          "conversation_id" => conversation_id,
          "customer_id" => customer_id
        } =
          params
      ) do
    assigns = %{
      xhr?: params["xhr"] == "true",
      conversation_id: conversation_id,
      customer_id: customer_id,
      token: token
    }

    with :ok <- match_conversation(token, conversation_id),
         {:ok, details} <-
           HelpScout.get_details_for_emails(
             [email],
             customer_id,
             conversation_id,
             params["team_identifier"]
           ) do
      render(conn, "callback.html", Map.merge(assigns, details))
    else
      {:error, error} ->
        render(conn, "callback.html", Map.put(assigns, :error, inspect(error)))
    end
  end

  def search(conn, %{
        "term" => term,
        "token" => token,
        "conversation_id" => conversation_id,
        "customer_id" => customer_id
      }) do
    assigns = %{
      conversation_id: conversation_id,
      customer_id: customer_id,
      token: token
    }

    case match_conversation(token, conversation_id) do
      :ok ->
        users = HelpScout.search_users(term, customer_id, conversation_id)
        render(conn, "search.html", Map.merge(assigns, %{users: users, term: term}))

      {:error, error} ->
        render(conn, "search.html", Map.put(assigns, :error, inspect(error)))
    end
  end

  defp match_conversation(token, conversation_id) do
    case verify_token(token) do
      {:ok, token_data} ->
        if token_data.conversation_id == conversation_id do
          :ok
        else
          {:error, :invalid_conversation}
        end

      {:error, _error} ->
        {:error, :invalid_token}
    end
  end

  # Exposed for testing
  @doc false
  def sign_token(conversation_id) do
    Phoenix.Token.sign(PlausibleWeb.Endpoint, "hs-conversation", %{
      conversation_id: conversation_id
    })
  end

  defp verify_token(token) do
    Phoenix.Token.verify(PlausibleWeb.Endpoint, "hs-conversation", token,
      max_age: @conversation_token_seconds
    )
  end

  defp make_iframe_friendly(conn, _opts) do
    conn
    |> delete_resp_header("x-frame-options")
    |> put_root_layout(html: {PlausibleWeb.HelpScoutView, :layout})
  end
end