Packages

EndPointBlank Elixir client library: authorization plus request/response/error/log ingestion (with masking, batching, timeouts, and a bounded queue) — also used for self-monitoring.

Current section

Files

Jump to
end_point_blank_elixir lib end_point_blank auth_cache.ex
Raw

lib/end_point_blank/auth_cache.ex

defmodule EndPointBlank.AuthCache do
@moduledoc """
ETS-backed authorization result cache with TTL expiry and a size cap.
Concurrent reads go directly to ETS (no GenServer round-trip).
Mutations are serialized through the GenServer to make eviction safe.
Cache key: `"epb_auth:{client_auth}:{path}:{method}:{app_name}"`
Value stored: the `source_application_environment_id` from the 201 response.
"""
use GenServer
require Logger
@table :epb_auth_cache
@max_size 1000
@default_ttl_ms 300_000
# ── Public API ──────────────────────────────────────────────────────────────
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Looks up *key* in the cache.
Returns `{:hit, source_env_id}` if the entry exists and has not expired,
or `:miss` otherwise.
"""
def get(key) do
now = System.monotonic_time(:millisecond)
case :ets.lookup(@table, key) do
[{^key, source_env_id, expires_at}] when expires_at > now ->
{:hit, source_env_id}
_ ->
:miss
end
end
@doc "Stores a successful auth result (source_env_id may be nil) under *key*."
def put(key, source_env_id) do
GenServer.cast(__MODULE__, {:put, key, source_env_id})
end
# ── GenServer callbacks ──────────────────────────────────────────────────────
@impl true
def init(_opts) do
:ets.new(@table, [:named_table, :public, :set, read_concurrency: true])
{:ok, %{}}
end
@impl true
def handle_cast({:put, key, source_env_id}, state) do
now = System.monotonic_time(:millisecond)
expires_at = now + ttl_ms()
# Evict expired entries
:ets.select_delete(@table, [{{:_, :_, :"$1"}, [{:<, :"$1", now}], [true]}])
# Enforce size cap: remove the entry expiring soonest
if :ets.info(@table, :size) >= @max_size do
oldest =
:ets.foldl(
fn {k, _, exp}, acc ->
case acc do
nil -> {k, exp}
{_, min_exp} when exp < min_exp -> {k, exp}
acc -> acc
end
end,
nil,
@table
)
if oldest, do: :ets.delete(@table, elem(oldest, 0))
end
:ets.insert(@table, {key, source_env_id, expires_at})
{:noreply, state}
end
defp ttl_ms do
try do
EndPointBlank.Config.get().cache_ttl * 1_000
rescue
_ -> @default_ttl_ms
end
end
end