-- Authentication: users, API tokens, sessions, audit log, and user tracking columns -- Migration: 009_auth -- Date: 2026-01 BEGIN; -------------------------------------------------------------------------------- -- Users -------------------------------------------------------------------------------- CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), username TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL DEFAULT '', email TEXT, password_hash TEXT, -- NULL for LDAP/OIDC-only users auth_source TEXT NOT NULL DEFAULT 'local', -- 'local', 'ldap', 'oidc' oidc_subject TEXT, -- Stable OIDC sub claim role TEXT NOT NULL DEFAULT 'viewer', -- 'admin', 'editor', 'viewer' is_active BOOLEAN NOT NULL DEFAULT true, last_login_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE UNIQUE INDEX idx_users_oidc_subject ON users(oidc_subject) WHERE oidc_subject IS NOT NULL; CREATE INDEX idx_users_username ON users(username); CREATE INDEX idx_users_auth_source ON users(auth_source); CREATE INDEX idx_users_role ON users(role); -------------------------------------------------------------------------------- -- API Tokens -------------------------------------------------------------------------------- CREATE TABLE api_tokens ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, -- Human-readable label token_hash TEXT UNIQUE NOT NULL, -- SHA-256 of raw token token_prefix TEXT NOT NULL, -- First 13 chars for display (silo_ + 8 hex) scopes TEXT[] NOT NULL DEFAULT '{}', -- Reserved for future fine-grained permissions last_used_at TIMESTAMPTZ, expires_at TIMESTAMPTZ, -- NULL = never expires created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_api_tokens_user ON api_tokens(user_id); CREATE INDEX idx_api_tokens_hash ON api_tokens(token_hash); -------------------------------------------------------------------------------- -- Sessions (schema required by alexedwards/scs pgxstore) -------------------------------------------------------------------------------- CREATE TABLE sessions ( token TEXT PRIMARY KEY, data BYTEA NOT NULL, expiry TIMESTAMPTZ NOT NULL ); CREATE INDEX idx_sessions_expiry ON sessions(expiry); -------------------------------------------------------------------------------- -- Audit Log -------------------------------------------------------------------------------- CREATE TABLE audit_log ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), timestamp TIMESTAMPTZ NOT NULL DEFAULT now(), user_id UUID REFERENCES users(id) ON DELETE SET NULL, username TEXT NOT NULL, action TEXT NOT NULL, -- 'create', 'update', 'delete', 'login', etc. resource_type TEXT NOT NULL, -- 'item', 'revision', 'project', 'relationship' resource_id TEXT NOT NULL, details JSONB, ip_address TEXT ); CREATE INDEX idx_audit_log_timestamp ON audit_log(timestamp DESC); CREATE INDEX idx_audit_log_user ON audit_log(username); CREATE INDEX idx_audit_log_resource ON audit_log(resource_type, resource_id); -------------------------------------------------------------------------------- -- Add user tracking columns to existing tables -------------------------------------------------------------------------------- -- Items: track who created and last updated ALTER TABLE items ADD COLUMN created_by TEXT; ALTER TABLE items ADD COLUMN updated_by TEXT; -- Relationships/BOM: track who created and last updated ALTER TABLE relationships ADD COLUMN created_by TEXT; ALTER TABLE relationships ADD COLUMN updated_by TEXT; -- Projects: track who created ALTER TABLE projects ADD COLUMN created_by TEXT; -- Sync log: track who triggered the sync ALTER TABLE sync_log ADD COLUMN triggered_by TEXT; COMMIT;