towerops/lib/towerops_web/graphql_socket.ex
graham c5481c2cd3 add mobile token auth to GraphQL endpoint (#48)
Extend the GraphQL API and socket to accept mobile session tokens
alongside existing API tokens. Add OrganizationScope middleware
that extracts organization_id from query args for mobile users.
Add my_organizations query for mobile app org listing.

Reviewed-on: graham/towerops-web#48
2026-03-16 15:23:07 -05:00

57 lines
1.7 KiB
Elixir

defmodule ToweropsWeb.GraphQLSocket do
@moduledoc """
WebSocket endpoint for GraphQL subscriptions.
Authenticates via either:
- API token (prefixed with "towerops_") → sets organization_id + user in context
- Mobile session token → sets user only (no organization_id)
"""
use Phoenix.Socket
use Absinthe.Phoenix.Socket, schema: ToweropsWeb.GraphQL.Schema
alias Absinthe.Phoenix.Socket
alias Towerops.Accounts
alias Towerops.ApiTokens
alias Towerops.MobileSessions
@impl true
def connect(%{"token" => "towerops_" <> _ = token}, socket, _connect_info) do
case ApiTokens.verify_token(token) do
{:ok, org_id, user} ->
socket =
socket
|> assign(:organization_id, org_id)
|> assign(:user, user)
|> Socket.put_options(context: %{organization_id: org_id, user: user})
{:ok, socket}
{:error, :invalid_token} ->
:error
end
end
def connect(%{"token" => token}, socket, _connect_info) when is_binary(token) do
with session when not is_nil(session) <- MobileSessions.get_session_by_token(token),
user when not is_nil(user) <- Accounts.get_user(session.user_id) do
_ = Task.start(fn -> MobileSessions.touch_session(session) end)
socket =
socket
|> assign(:user, user)
|> Socket.put_options(context: %{user: user})
{:ok, socket}
else
_ -> :error
end
end
def connect(_params, _socket, _connect_info), do: :error
@impl true
def id(%{assigns: %{organization_id: org_id}}), do: "graphql_socket:#{org_id}"
def id(%{assigns: %{user: user}}), do: "graphql_socket:user:#{user.id}"
def id(_socket), do: nil
end