Packages

phoenix_kit

1.7.5
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit_web plugs db_sync_socket_plug.ex
Raw

lib/phoenix_kit_web/plugs/db_sync_socket_plug.ex

defmodule PhoenixKitWeb.Plugs.DBSyncSocketPlug do
@moduledoc """
Plug for handling DB Sync WebSocket connections.
This plug handles the HTTP upgrade to WebSocket and validates
the connection code before handing off to DBSyncWebsock.
## Usage
In your endpoint:
plug PhoenixKitWeb.Plugs.DBSyncSocketPlug
Or mount at a specific path in router (done automatically by phoenix_kit_socket macro).
"""
@behaviour Plug
require Logger
alias PhoenixKit.DBSync
@impl Plug
def init(opts), do: opts
@impl Plug
def call(%{request_path: "/db-sync/websocket"} = conn, _opts) do
handle_websocket_request(conn)
end
def call(conn, _opts) do
conn
end
defp handle_websocket_request(conn) do
# Check if this is a WebSocket upgrade request
if websocket_request?(conn) do
code = get_code_from_params(conn)
if DBSync.enabled?() do
case validate_and_upgrade(conn, code) do
{:ok, conn} -> conn
{:error, conn} -> conn
end
else
Logger.warning("DBSync: Connection attempt but module is disabled")
send_forbidden(conn, "Module disabled")
end
else
send_bad_request(conn, "Expected WebSocket upgrade")
end
end
defp websocket_request?(conn) do
upgrade_header =
Plug.Conn.get_req_header(conn, "upgrade")
|> List.first()
|> Kernel.||("")
|> String.downcase()
upgrade_header == "websocket"
end
defp get_code_from_params(conn) do
conn = Plug.Conn.fetch_query_params(conn)
conn.query_params["code"]
end
defp validate_and_upgrade(conn, nil) do
Logger.warning("DBSync: Connection attempt without code")
{:error, send_forbidden(conn, "Missing code")}
end
defp validate_and_upgrade(conn, code) do
case DBSync.validate_code(code) do
{:ok, session} ->
Logger.info("DBSync: Sender connecting with code #{code}")
# Capture connection metadata
connection_info = extract_connection_info(conn)
conn =
WebSockAdapter.upgrade(
conn,
PhoenixKitWeb.DBSyncWebsock,
[code: code, session: session, connection_info: connection_info],
timeout: 60_000
)
{:ok, Plug.Conn.halt(conn)}
{:error, :invalid_code} ->
Logger.warning("DBSync: Invalid code attempt: #{code}")
{:error, send_forbidden(conn, "Invalid code")}
{:error, :already_used} ->
Logger.warning("DBSync: Code already used: #{code}")
{:error, send_forbidden(conn, "Code already used")}
end
end
defp extract_connection_info(conn) do
# Get remote IP - check for forwarded headers first (for proxies)
remote_ip = get_remote_ip(conn)
# Get user agent
user_agent =
Plug.Conn.get_req_header(conn, "user-agent")
|> List.first()
# Get origin/referer
origin =
Plug.Conn.get_req_header(conn, "origin")
|> List.first()
referer =
Plug.Conn.get_req_header(conn, "referer")
|> List.first()
# Get host info
host = conn.host
port = conn.port
scheme = if conn.scheme == :https, do: "https", else: "http"
# Get WebSocket protocol version
ws_version =
Plug.Conn.get_req_header(conn, "sec-websocket-version")
|> List.first()
# Get accept-language for locale info
accept_language =
Plug.Conn.get_req_header(conn, "accept-language")
|> List.first()
%{
remote_ip: remote_ip,
user_agent: user_agent,
origin: origin,
referer: referer,
host: host,
port: port,
scheme: scheme,
request_path: conn.request_path,
query_string: conn.query_string,
websocket_version: ws_version,
accept_language: accept_language,
connected_at: DateTime.utc_now()
}
end
defp get_remote_ip(conn) do
# Check X-Forwarded-For first (for load balancers/proxies)
forwarded_for =
Plug.Conn.get_req_header(conn, "x-forwarded-for")
|> List.first()
if forwarded_for do
# Take the first IP in the chain (original client)
forwarded_for
|> String.split(",")
|> List.first()
|> String.trim()
else
# Fall back to direct connection IP
conn.remote_ip
|> :inet.ntoa()
|> to_string()
end
end
defp send_forbidden(conn, message) do
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(403, message)
|> Plug.Conn.halt()
end
defp send_bad_request(conn, message) do
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(400, message)
|> Plug.Conn.halt()
end
end