towerops/lib/towerops_web/plugs/api_auth.ex
Graham McIntire 56093bb493
refactor: use API token auth for profile imports instead of session cookies
Changes profile import endpoint to use standard API token authentication:

API Token Changes:
- Add user_id to api_tokens table (tracks who created the token)
- Update ApiTokens.verify_token/1 to return user along with org_id
- Update ApiAuth plug to assign current_user from token

Profile Import Changes:
- Move endpoint from /api/v1/admin/profiles/import to /api/v1/profiles/import
- Check user.is_superuser in controller instead of using RequireSuperuser plug
- Use api_v1 pipeline (Bearer token auth) instead of browser session
- Update documentation to show API token usage

Security:
- Only API tokens created by superusers can import profiles
- Returns 403 Forbidden if token user is not a superuser
- Logs import attempts with user email for audit trail

This provides a consistent API experience using Bearer tokens
instead of requiring browser session cookies.
2026-01-18 09:30:21 -06:00

53 lines
1.4 KiB
Elixir

defmodule ToweropsWeb.Plugs.ApiAuth do
@moduledoc """
Plug for authenticating API requests using Bearer tokens.
Expects the Authorization header to contain a Bearer token:
Authorization: Bearer towerops_xxxxx
Sets the following assigns on successful authentication:
- :current_organization_id - The organization ID the token is scoped to
- :api_authenticated - true
Returns 401 Unauthorized if:
- No Authorization header is present
- Token is invalid or expired
- Token format is incorrect
"""
import Phoenix.Controller, only: [json: 2]
import Plug.Conn
alias Towerops.ApiTokens
def init(opts), do: opts
def call(conn, _opts) do
case get_req_header(conn, "authorization") do
["Bearer " <> token] ->
authenticate_with_token(conn, token)
_ ->
conn
|> put_status(:unauthorized)
|> json(%{error: "Missing or invalid Authorization header"})
|> halt()
end
end
defp authenticate_with_token(conn, token) do
case ApiTokens.verify_token(token) do
{:ok, organization_id, user} ->
conn
|> assign(:current_organization_id, organization_id)
|> assign(:current_user, user)
|> assign(:api_authenticated, true)
{:error, :invalid_token} ->
conn
|> put_status(:unauthorized)
|> json(%{error: "Invalid or expired API token"})
|> halt()
end
end
end