-- Migration: 000002_webauthn.up.sql -- WebAuthn-based Authentication for Zero-Knowledge Admin Access -- Communities table (represents each gaming community using the platform) CREATE TABLE IF NOT EXISTS communities ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, display_name TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, master_key_salt BYTEA NOT NULL, -- Used for key wrapping/unwrapping storage_node_id TEXT, -- Which storage node handles this community's data retention_days INTEGER DEFAULT 30 -- Auto-deletion policy (DSGVO) ); -- Admin users (co-owners of a community) CREATE TABLE IF NOT EXISTS admin_users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), community_id UUID NOT NULL REFERENCES communities(id) ON DELETE CASCADE, username TEXT NOT NULL, email TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, is_primary_owner BOOLEAN DEFAULT false, UNIQUE(community_id, username) ); -- WebAuthn credentials (hardware-bound authentication) CREATE TABLE IF NOT EXISTS webauthn_credentials ( id BYTEA PRIMARY KEY, -- Credential ID from WebAuthn admin_user_id UUID NOT NULL REFERENCES admin_users(id) ON DELETE CASCADE, public_key BYTEA NOT NULL, sign_count BIGINT NOT NULL DEFAULT 0, aaguid BYTEA, -- Authenticator AAGUID created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, last_used_at TIMESTAMP WITH TIME ZONE, device_name TEXT -- e.g., "YubiKey 5C", "Windows Hello" ); -- Wrapped master keys (encrypted with WebAuthn public key) CREATE TABLE IF NOT EXISTS wrapped_master_keys ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), admin_user_id UUID NOT NULL REFERENCES admin_users(id) ON DELETE CASCADE, community_id UUID NOT NULL REFERENCES communities(id) ON DELETE CASCADE, wrapped_key_data BYTEA NOT NULL, -- Master key encrypted for this admin created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, UNIQUE(admin_user_id, community_id) ); -- Managed Trust Vault (for Discord Bot & external API integrations) CREATE TABLE IF NOT EXISTS managed_trust_vault ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), community_id UUID NOT NULL REFERENCES communities(id) ON DELETE CASCADE, service_name TEXT NOT NULL, -- e.g., "discord_bot", "external_api" encrypted_master_key BYTEA NOT NULL, -- Encrypted with provider's key granted_by UUID NOT NULL REFERENCES admin_users(id), expires_at TIMESTAMP WITH TIME ZONE, -- NULL = permanent, else temporary created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, UNIQUE(community_id, service_name) ); -- Player roster for fast blind-index searching CREATE TABLE IF NOT EXISTS player_roster ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), community_id UUID NOT NULL REFERENCES communities(id) ON DELETE CASCADE, player_name_hash TEXT NOT NULL, -- HMAC hash for blind searching encrypted_player_data BYTEA NOT NULL, -- Contains name, Steam ID, etc. first_seen TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, last_seen TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, UNIQUE(community_id, player_name_hash) ); -- Indexes for performance CREATE INDEX IF NOT EXISTS idx_admin_users_community ON admin_users(community_id); CREATE INDEX IF NOT EXISTS idx_webauthn_admin_user ON webauthn_credentials(admin_user_id); CREATE INDEX IF NOT EXISTS idx_wrapped_keys_admin ON wrapped_master_keys(admin_user_id); CREATE INDEX IF NOT EXISTS idx_managed_trust_community ON managed_trust_vault(community_id); CREATE INDEX IF NOT EXISTS idx_player_roster_community ON player_roster(community_id); CREATE INDEX IF NOT EXISTS idx_player_roster_hash ON player_roster(player_name_hash);