Created API token system for programmatic access: - API tokens table with organization scoping - ApiTokens context for token management - ApiAuth plug for Bearer token authentication - Tokens shown once on creation, then hashed for security Implemented RESTful API v1 endpoints: - SitesController: CRUD operations for sites - DevicesController: CRUD operations for devices - All operations scoped to authenticated organization - Proper authorization checks via site ownership Technical details: - Tokens prefixed with "towerops_" for easy identification - SHA-256 hashing for token storage - Last used timestamp tracking (async update) - Optional token expiration support - Standard JSON error responses (40x status codes) Routes: - /api/v1/sites (GET, POST, PATCH, DELETE) - /api/v1/devices (GET, POST, PATCH, DELETE) Authentication: - Authorization: Bearer towerops_xxxxx header required - Returns 401 for invalid/expired tokens - Returns 403 for unauthorized resource access
52 lines
1.3 KiB
Elixir
52 lines
1.3 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} ->
|
|
conn
|
|
|> assign(:current_organization_id, organization_id)
|
|
|> assign(:api_authenticated, true)
|
|
|
|
{:error, :invalid_token} ->
|
|
conn
|
|
|> put_status(:unauthorized)
|
|
|> json(%{error: "Invalid or expired API token"})
|
|
|> halt()
|
|
end
|
|
end
|
|
end
|