summaryrefslogtreecommitdiff
path: root/lib/web/controllers/irc_auth_sse_controller.ex
blob: f67a77f5886abbbc6a781958cb5a6a099ed6a2f2 (plain) (blame)
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
defmodule NolaWeb.IrcAuthSseController do
  use NolaWeb, :controller
  require Logger

  @ping_interval 20_000
  @expire_delay :timer.minutes(3)

  def sse(conn, params) do
    perks =
      if uri = Map.get(params, "redirect_to") do
        {:redirect, uri}
      else
        nil
      end

    token = String.downcase(EntropyString.random_string(65))

    conn
    |> assign(:token, token)
    |> assign(:perks, perks)
    |> put_resp_header("X-Accel-Buffering", "no")
    |> put_resp_header("content-type", "text/event-stream")
    |> send_chunked(200)
    |> subscribe()
    |> send_sse_message("token", token)
    |> sse_loop
  end

  def subscribe(conn) do
    :timer.send_interval(@ping_interval, {:event, :ping})
    :timer.send_after(@expire_delay, {:event, :expire})
    {:ok, _} = Registry.register(Nola.PubSub, "messages:private", [])
    conn
  end

  def sse_loop(conn) do
    {type, event, exit} =
      receive do
        {:event, :ping} ->
          {"ping", "ping", false}

        {:event, :expire} ->
          {"expire", "expire", true}

        {:irc, :text, %{account: account, text: token} = m} ->
          if String.downcase(String.trim(token)) == conn.assigns.token do
            path = Nola.AuthToken.new_path(account.id, conn.assigns.perks)
            m.replyfun.("ok!")
            {"authenticated", path, true}
          else
            {nil, nil, false}
          end

        _ ->
          {nil, nil, false}
      end

    conn =
      if type do
        send_sse_message(conn, type, event)
      else
        conn
      end

    if exit do
      conn
    else
      sse_loop(conn)
    end
  end

  defp send_sse_message(conn, type, data) do
    {:ok, conn} = chunk(conn, "event: #{type}\ndata: #{data}\n\n")
    conn
  end
end