-- The right to join, once. -- -- novox/hq ADR 0004: a token carries the broker's address, the fingerprint to expect, the control -- plane's signing identity, and a one-time secret. Only the last of those is stored here -- the -- other three are facts about the mesh, the same in every token, and belong wherever the mesh's -- own configuration lives rather than copied into each row. create table enrolment_token ( id uuid primary key default gen_random_uuid(), -- A token is issued FOR a node record, and that is where re-enrolment is decided -- (novox/hq 09-the-node-lifecycle). The host presenting it does not need to know whether it -- is joining as a new node or returning as an existing one; what the identity binds to was -- settled when the token was made. -- -- Cascading: a node record removed takes its unused tokens with it. A token outliving the -- record it was issued for is a right to join as nobody. node uuid not null references node(id) on delete cascade, -- The secret is never stored. What is stored is a hash of it, so a copy of this table is not -- a set of working credentials -- the same reason a password is not kept. -- -- Unique because a collision would make two tokens redeem as one, and because it lets the -- lookup at redemption be by hash rather than a scan. secret text not null unique, issued timestamptz not null default now(), -- "Useless once used and useless after it expires" is two conditions, so it is two columns. -- Neither is a status field: a status has to be written by something noticing, and nothing -- notices a token quietly ageing out. Both are read from what is already here. expires timestamptz not null, redeemed timestamptz ); -- Redemption looks a token up by the hash of what was presented, and it is the one query on the -- path where a node is waiting. create index enrolment_token_node on enrolment_token (node);