PRAGMA foreign_keys = ON; BEGIN; CREATE TABLE login ( id INTEGER PRIMARY KEY, -- internal use only -- case insensitive match for e.g. emails user TEXT COLLATE NOCASE UNIQUE NOT NULL, pass TEXT NOT NULL CHECK ( -- bcrypt -- $2$[cost]$[22 character salt][31 character hash] length(pass) >= 58 AND -- corrected OpenBSD algorithm (from February 2014) pass LIKE '$2b$%' ) ) STRICT; CREATE TABLE token ( login_id INTEGER NOT NULL, hash BLOB NOT NULL CHECK(length(hash) == 32), -- sha256 issued_at REAL NOT NULL DEFAULT (unixepoch('now')), expires_at REAL NOT NULL DEFAULT (unixepoch('now', '+2 hours')), redeemed INTEGER NOT NULL DEFAULT (FALSE) CHECK (redeemed = FALSE OR redeemed = TRUE), -- boolean CHECK (expires_at > issued_at), FOREIGN KEY(login_id) REFERENCES login(id) ON DELETE CASCADE ON UPDATE CASCADE ) STRICT; CREATE INDEX token_idx ON token (login_id, hash); -- human-readable view of the tokens CREATE VIEW token_active AS SELECT user, hex(hash) AS hash, strftime('%FT%TZ', datetime(issued_at, 'unixepoch')) as issued_at, strftime('%FT%TZ', datetime(expires_at, 'unixepoch')) as expires_at FROM token INNER JOIN login ON login.id = token.login_id WHERE unixepoch('now') < token.expires_at AND NOT redeemed; COMMIT;