defmodule ToweropsWeb.Plugs.GraphQLAuth do @moduledoc """ Plug for authenticating GraphQL requests using either API tokens or mobile session tokens. Accepts two token types via Authorization: Bearer : - API tokens (prefixed with "towerops_") → sets current_organization_id + current_user - Mobile session tokens → sets current_user only (no org_id) Returns 401 Unauthorized if neither token type is valid. """ import Phoenix.Controller, only: [json: 2] import Plug.Conn alias Towerops.Accounts alias Towerops.ApiTokens alias Towerops.MobileSessions def init(opts), do: opts def call(conn, _opts) do case get_req_header(conn, "authorization") do ["Bearer " <> token] -> authenticate(conn, token) _ -> unauthorized(conn, "Missing or invalid Authorization header") end end defp authenticate(conn, "towerops_" <> _ = token) do case ApiTokens.verify_token(token) do {:ok, organization_id, user} -> conn |> assign(:current_organization_id, organization_id) |> assign(:current_user, user) {:error, :invalid_token} -> unauthorized(conn, "Invalid or expired API token") end end defp authenticate(conn, token) do with {:ok, session} <- validate_mobile_session(token), {:ok, user} <- get_user(session.user_id) do _ = Task.start(fn -> MobileSessions.touch_session(session) end) assign(conn, :current_user, user) else {:error, _reason} -> unauthorized(conn, "Invalid or expired authentication token") end end defp validate_mobile_session(token) do case MobileSessions.get_session_by_token(token) do nil -> {:error, :invalid_token} session -> {:ok, session} end end defp get_user(user_id) do case Accounts.get_user(user_id) do nil -> {:error, :user_not_found} user -> {:ok, user} end end defp unauthorized(conn, message) do conn |> put_status(:unauthorized) |> json(%{error: message}) |> halt() end end