// Invite-only registration. // // registration_enabled is a single on/off switch: open to anyone, or closed to // everyone. This adds the middle setting an operator actually wants — open to // people you invited. A code is single-use, expires, and can be revoked or // deleted without touching the account it created. // // The code is stored hashed. An invite grants account creation, so a leaked // settings dump or database backup should not hand someone a working code, the // same reason password reset tokens are not stored in the clear. exports.up = pgm => { pgm.sql(` CREATE TABLE IF NOT EXISTS registration_invites ( id SERIAL PRIMARY KEY, code_hash TEXT NOT NULL UNIQUE, -- The last few characters, so the list can show which code a row is -- without being able to reconstruct it. code_hint TEXT NOT NULL, note TEXT NOT NULL DEFAULT '', created_by INTEGER REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ NOT NULL, -- Set when used. The row is kept so an admin can see who used which code. used_at TIMESTAMPTZ, used_by INTEGER REFERENCES users(id) ON DELETE SET NULL, -- Set when revoked. Separate from deletion: a revoked code stays visible. revoked_at TIMESTAMPTZ, revoked_by INTEGER REFERENCES users(id) ON DELETE SET NULL ); CREATE INDEX IF NOT EXISTS idx_registration_invites_hash ON registration_invites(code_hash); CREATE INDEX IF NOT EXISTS idx_registration_invites_expires ON registration_invites(expires_at); `); }; exports.down = pgm => { pgm.sql('DROP TABLE IF EXISTS registration_invites;'); };