-- Migration 058: Create Duo Security tables -- Stores data from both Accounts API (child accounts) and Admin API (users, phones, auth logs, groups, integrations) -- ── duo_accounts ───────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS duo_accounts ( id SERIAL PRIMARY KEY, account_id VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(255), api_hostname VARCHAR(255), autotask_company_id BIGINT REFERENCES companies(id) ON DELETE SET NULL, user_count INTEGER DEFAULT 0, integration_count INTEGER DEFAULT 0, edition VARCHAR(50), is_parent BOOLEAN DEFAULT false, synced_at TIMESTAMP WITHOUT TIME ZONE, created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_duo_accounts_company ON duo_accounts(autotask_company_id); CREATE INDEX IF NOT EXISTS idx_duo_accounts_is_parent ON duo_accounts(is_parent); -- ── duo_users ──────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS duo_users ( id SERIAL PRIMARY KEY, user_id VARCHAR(30) NOT NULL UNIQUE, duo_account_id VARCHAR(20) NOT NULL REFERENCES duo_accounts(account_id) ON DELETE CASCADE, username VARCHAR(255), email VARCHAR(255), realname VARCHAR(255), status VARCHAR(50), is_enrolled BOOLEAN DEFAULT false, last_login TIMESTAMP WITHOUT TIME ZONE, last_directory_sync TIMESTAMP WITHOUT TIME ZONE, created TIMESTAMP WITHOUT TIME ZONE, notes TEXT, phones_count INTEGER DEFAULT 0, groups JSONB, aliases JSONB, enable_auto_prompt BOOLEAN DEFAULT true, synced_at TIMESTAMP WITHOUT TIME ZONE ); CREATE INDEX IF NOT EXISTS idx_duo_users_account ON duo_users(duo_account_id); CREATE INDEX IF NOT EXISTS idx_duo_users_status ON duo_users(status); CREATE INDEX IF NOT EXISTS idx_duo_users_email ON duo_users(email); CREATE INDEX IF NOT EXISTS idx_duo_users_enrolled ON duo_users(is_enrolled); -- ── duo_phones ─────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS duo_phones ( id SERIAL PRIMARY KEY, phone_id VARCHAR(30) NOT NULL UNIQUE, duo_account_id VARCHAR(20) NOT NULL REFERENCES duo_accounts(account_id) ON DELETE CASCADE, name VARCHAR(255), number VARCHAR(50), type VARCHAR(50), platform VARCHAR(50), model VARCHAR(255), os_version VARCHAR(50), app_version VARCHAR(50), activated BOOLEAN DEFAULT false, last_seen TIMESTAMP WITHOUT TIME ZONE, capabilities JSONB, users JSONB, synced_at TIMESTAMP WITHOUT TIME ZONE ); CREATE INDEX IF NOT EXISTS idx_duo_phones_account ON duo_phones(duo_account_id); -- ── duo_auth_logs ──────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS duo_auth_logs ( id SERIAL PRIMARY KEY, txid VARCHAR(50) NOT NULL UNIQUE, duo_account_id VARCHAR(20) NOT NULL REFERENCES duo_accounts(account_id) ON DELETE CASCADE, timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL, user_name VARCHAR(255), user_id VARCHAR(30), factor VARCHAR(50), result VARCHAR(50), reason VARCHAR(255), application_name VARCHAR(255), application_key VARCHAR(50), access_device_ip INET, access_device_location JSONB, auth_device_ip INET, auth_device_name VARCHAR(255), event_type VARCHAR(50), synced_at TIMESTAMP WITHOUT TIME ZONE ); CREATE INDEX IF NOT EXISTS idx_duo_auth_logs_account ON duo_auth_logs(duo_account_id); CREATE INDEX IF NOT EXISTS idx_duo_auth_logs_timestamp ON duo_auth_logs(timestamp DESC); CREATE INDEX IF NOT EXISTS idx_duo_auth_logs_result ON duo_auth_logs(result); CREATE INDEX IF NOT EXISTS idx_duo_auth_logs_user ON duo_auth_logs(user_name); -- ── duo_groups ─────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS duo_groups ( id SERIAL PRIMARY KEY, group_id VARCHAR(30) NOT NULL UNIQUE, duo_account_id VARCHAR(20) NOT NULL REFERENCES duo_accounts(account_id) ON DELETE CASCADE, name VARCHAR(255), description TEXT, member_count INTEGER DEFAULT 0, status VARCHAR(50), synced_at TIMESTAMP WITHOUT TIME ZONE ); CREATE INDEX IF NOT EXISTS idx_duo_groups_account ON duo_groups(duo_account_id); -- ── duo_integrations ───────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS duo_integrations ( id SERIAL PRIMARY KEY, integration_key VARCHAR(50) NOT NULL UNIQUE, duo_account_id VARCHAR(20) NOT NULL REFERENCES duo_accounts(account_id) ON DELETE CASCADE, name VARCHAR(255), type VARCHAR(100), enabled BOOLEAN DEFAULT true, notes TEXT, synced_at TIMESTAMP WITHOUT TIME ZONE ); CREATE INDEX IF NOT EXISTS idx_duo_integrations_account ON duo_integrations(duo_account_id);