defmodule LSGWeb.IrcAuthSseController do
use LSGWeb, :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(IRC.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 = LSG.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