CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, token_hash TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, last_seen_at TEXT, revoked_at TEXT, ip_address TEXT, user_agent TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(token_hash); CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id); CREATE TABLE IF NOT EXISTS webauthn_credentials ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, credential_id BLOB NOT NULL UNIQUE, credential_json TEXT NOT NULL, created_at TEXT NOT NULL, last_used_at TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_webauthn_credentials_user ON webauthn_credentials(user_id); CREATE TABLE IF NOT EXISTS auth_challenges ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, type TEXT NOT NULL CHECK(type IN ('webauthn_registration', 'webauthn_login')), session_json TEXT NOT NULL, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, used_at TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_auth_challenges_user_type ON auth_challenges(user_id, type); CREATE TABLE IF NOT EXISTS device_codes ( id TEXT PRIMARY KEY, user_id TEXT, device_code_hash TEXT NOT NULL UNIQUE, user_code_hash TEXT NOT NULL UNIQUE, user_code_display TEXT NOT NULL, name TEXT NOT NULL, scopes TEXT NOT NULL, status TEXT NOT NULL CHECK(status IN ('pending', 'approved', 'denied', 'expired')), created_at TEXT NOT NULL, expires_at TEXT NOT NULL, approved_at TEXT, last_polled_at TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE SET NULL ); CREATE INDEX IF NOT EXISTS idx_device_codes_user_code_hash ON device_codes(user_code_hash); CREATE INDEX IF NOT EXISTS idx_device_codes_device_code_hash ON device_codes(device_code_hash); CREATE TABLE IF NOT EXISTS permissions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, resource_type TEXT NOT NULL CHECK(resource_type IN ('global', 'collection', 'document')), resource_id TEXT, permission TEXT NOT NULL CHECK(permission IN ('read', 'write', 'admin')), granted_by TEXT, created_at TEXT NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(granted_by) REFERENCES users(id) ON DELETE SET NULL ); CREATE INDEX IF NOT EXISTS idx_permissions_user ON permissions(user_id); CREATE TABLE IF NOT EXISTS audit_log ( id TEXT PRIMARY KEY, actor_user_id TEXT, event_type TEXT NOT NULL, resource_type TEXT, resource_id TEXT, ip_address TEXT, user_agent TEXT, created_at TEXT NOT NULL, metadata_json TEXT NOT NULL DEFAULT '{}', FOREIGN KEY(actor_user_id) REFERENCES users(id) ON DELETE SET NULL ); CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log(created_at); ALTER TABLE api_keys ADD COLUMN revoked_at TEXT;