126 lines
3.2 KiB
Elixir
126 lines
3.2 KiB
Elixir
defmodule Forum.Router do
|
|
use Plug.Router
|
|
use Joken.Config
|
|
require Logger
|
|
|
|
plug Plug.Logger
|
|
plug :log_request
|
|
plug :fetch_cookies
|
|
plug :verify_session_host
|
|
plug :match
|
|
plug :dispatch
|
|
|
|
defp verify_session_host(conn, _opts) do
|
|
if conn.host == "session.frm.so" do
|
|
case authenticate(conn) do
|
|
{:ok, claims} ->
|
|
conn
|
|
|> Plug.Conn.assign(:claims, claims)
|
|
|
|
:error ->
|
|
conn |> send_resp(401, "unauthorized") |> halt()
|
|
end
|
|
else
|
|
conn
|
|
end
|
|
end
|
|
|
|
defp authenticate(conn) do
|
|
with token when is_binary(token) <- conn.cookies["auth_token"], {:ok, claims} <- verify_token(token) do
|
|
{:ok, claims}
|
|
else
|
|
_ -> :error
|
|
end
|
|
end
|
|
|
|
defp verify_token(token) do
|
|
secret = Application.fetch_env!(:forum, :auth)[:jwt_secret]
|
|
signer = Joken.Signer.create("HS256", secret)
|
|
|
|
case Joken.verify_and_validate(%{}, token, signer) do
|
|
{:ok, claims} -> {:ok, claims}
|
|
{:error, _reason} = err -> err
|
|
end
|
|
end
|
|
|
|
get "/admin", host: "session.frm.so", do: handle_admin(conn)
|
|
get "/admin/graphyellow.svg", host: "session.frm.so", do: admin_asset(conn, "ui/graphyellow.svg", "image/svg+xml")
|
|
get "/admin/*path", host: "session.frm.so", do: handle_admin(conn)
|
|
|
|
get "/", host: "session.frm.so" do
|
|
user_id = conn.assigns.claims["id"]
|
|
email = conn.assigns.claims["email"]
|
|
conn
|
|
|> WebSockAdapter.upgrade(Forum.WsHandler, %{user_id: user_id, email: email}, timeout: :infinity)
|
|
|> halt()
|
|
end
|
|
|
|
match _ do
|
|
send_resp(conn, 404, "not found")
|
|
end
|
|
|
|
defp handle_admin(conn) do
|
|
if websocket_upgrade?(conn) do
|
|
conn
|
|
|> WebSockAdapter.upgrade(Forum.WsHandler, %{}, timeout: :infinity)
|
|
|> halt()
|
|
else
|
|
admin_page(conn)
|
|
end
|
|
end
|
|
|
|
defp websocket_upgrade?(conn) do
|
|
connection = get_req_header(conn, "connection") |> Enum.join(",") |> String.downcase()
|
|
upgrade = get_req_header(conn, "upgrade") |> List.first() |> to_string() |> String.downcase()
|
|
|
|
String.contains?(connection, "upgrade") and upgrade == "websocket"
|
|
end
|
|
|
|
defp admin_page(conn) do
|
|
html = File.read!(Forum.Assets.path("ui/admin.html"))
|
|
|
|
conn
|
|
|> put_resp_content_type("text/html")
|
|
|> send_resp(200, html)
|
|
end
|
|
|
|
defp admin_asset(conn, path, content_type) do
|
|
conn
|
|
|> put_resp_content_type(content_type)
|
|
|> send_file(200, Forum.Assets.path(path))
|
|
end
|
|
|
|
defp log_request(conn, _opts) do
|
|
started_at = System.monotonic_time(:microsecond)
|
|
|
|
Plug.Conn.register_before_send(conn, fn conn ->
|
|
duration_us = System.monotonic_time(:microsecond) - started_at
|
|
|
|
Forum.LogStore.add(%{
|
|
"source_ip" => remote_ip(conn),
|
|
"host" => conn.host || "",
|
|
"method" => conn.method,
|
|
"path" => conn.request_path,
|
|
"query_string" => conn.query_string,
|
|
"status" => conn.status || 0,
|
|
"duration_ms" => Float.round(duration_us / 1_000, 2)
|
|
})
|
|
|
|
conn
|
|
end)
|
|
end
|
|
|
|
defp remote_ip(conn) do
|
|
case Plug.Conn.get_req_header(conn, "x-real-ip") do
|
|
[ip | _] -> ip
|
|
[] -> fallback_ip(conn)
|
|
end
|
|
end
|
|
|
|
defp fallback_ip(%{remote_ip: remote_ip}) when is_tuple(remote_ip) do
|
|
remote_ip |> :inet.ntoa() |> to_string()
|
|
end
|
|
|
|
defp fallback_ip(_), do: ""
|
|
end
|