This commit is contained in:
Jonas Bengtsson
2026-09-05 14:22:29 +02:00
parent f9d10364f8
commit 2117f96ae7
20 changed files with 1721 additions and 112 deletions
+35 -1
View File
@@ -105,4 +105,38 @@ CREATE TABLE IF NOT EXISTS imei_attributes (
COMMENT ON TABLE imei_attributes IS
'Time-varying device attributes (e.g. offsets) as an append-only timeline; the value valid at T is the row with the greatest effective_from <= T.';
COMMENT ON COLUMN imei_attributes.effective_from IS
'Time from which the value applies; backdating allowed. Resolved against the device timestamp (payload_time, received_at fallback) in GET /messages.';
'Time from which the value applies; backdating allowed. Resolved against the device timestamp (payload_time, received_at fallback) in GET /messages.';
-- Interactive users for the management UI: username/password login with a
-- role. 'admin' (level 2) manages everything incl. users; 'superuser'
-- (level 1) configures devices/configs/tokens and manages day-to-day
-- 'user' accounts; 'user' (level 0) is a read-only viewer. Passwords are
-- stored as scrypt hashes, never plaintext. The first admin is seeded
-- lazily from BOOTSTRAP_ADMIN_USER/BOOTSTRAP_ADMIN_PASSWORD when the table
-- is empty (same bootstrap pattern as app_tokens).
CREATE TABLE IF NOT EXISTS users (
username TEXT PRIMARY KEY CHECK (username <> ''),
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user'
CHECK (role IN ('admin', 'superuser', 'user')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Login sessions: the browser keeps the opaque bearer token; only its
-- SHA-256 digest is persisted (same pattern as app_tokens). Sessions
-- expire server-side; the role is NOT cached here (joined fresh from
-- users on every request), so role changes apply immediately. Deleting a
-- user cascades to their sessions.
CREATE TABLE IF NOT EXISTS sessions (
token_hash TEXT PRIMARY KEY CHECK (token_hash ~ '^[0-9a-f]{64}$'),
username TEXT NOT NULL REFERENCES users (username) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_sessions_expiry ON sessions (expires_at);
COMMENT ON TABLE users IS
'Interactive UI users (scrypt password hashes); admin = full access, superuser = configure + manage user accounts, user = read-only. First admin seeded from BOOTSTRAP_ADMIN_* when empty.';
COMMENT ON TABLE sessions IS
'Login sessions (SHA-256 digests of opaque bearer tokens); role resolved fresh from users on each request.';