Pixelex.Identity.Salts (Pixelex v0.1.0)

Copy Markdown View Source

Today's hashing salt and yesterday's, cached in ETS.

A visitor is identified by a keyed hash of their user agent and IP. The key is a random salt that changes every UTC day and is deleted shortly after, so the identifier is unlinkable across days and cannot be recomputed later even with the raw inputs. That is what makes the scheme pseudonymous while it lives and anonymous once the salt is gone — and it is why pixelex needs no cookie, and therefore no banner.

Rotation without a scheduler

The salts table is keyed by UTC date. Rotation is INSERT ... ON CONFLICT DO NOTHING for today: the first node to notice the new day writes a salt, every other node reads it. No cron job to forget, no leader election, and no window in which two nodes hash the same visitor differently.

previous is not an optimisation

It is the whole reason this module has state at all. At 00:00 UTC the salt changes, so every visitor's hash changes, so every open session looks like a brand new person. Pixelex.Identity computes the id under both salts and the session lookup tries both — which is the only thing standing between this design and data that quietly corrupts itself one night at a time. Plausible does the same; it is the subtlest part of the scheme and the easiest to omit without noticing, because nothing fails, the numbers just get worse.

Refresh, rotation and cleanup are three different clocks

  • refresh (salt_refresh_ms, 90s) — reload ETS from the store. Only converges nodes after one of them rotated. Does not create anything.
  • rotation (daily, 00:00 UTC) — implied by the date key, above.
  • cleanup (salt_ttl_hours, 48h) — delete salts older than two rotations, so previous is always still there.

:memory mode

With salt_persistence: :memory nothing is written to the database. A backup can then never be replayed to reconstruct browsing history, which is the strictest posture available (Ackee's). The cost is that a restart severs every open session, and separate nodes disagree about who a visitor is.

Summary

Functions

Returns a specification to start this module under a supervisor.

Delete salts older than salt_ttl_hours. Safe to call from a cron.

%{current: binary, previous: binary | nil}.

Force a reload. Tests, and anything that just rotated deliberately.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

cleanup()

@spec cleanup() :: :ok

Delete salts older than salt_ttl_hours. Safe to call from a cron.

get()

@spec get() :: %{current: binary(), previous: binary() | nil}

%{current: binary, previous: binary | nil}.

Reads straight from ETS — no GenServer call, because this runs on the path of every single event and a serialised lookup there would be the bottleneck of the whole system.

refresh()

@spec refresh() :: %{current: binary(), previous: binary() | nil}

Force a reload. Tests, and anything that just rotated deliberately.

start_link(opts)