-- ============================================================================= -- MICHANGARRITO - 04 AUTH -- ============================================================================= -- Autenticación y usuarios -- ============================================================================= -- Usuarios CREATE TABLE IF NOT EXISTS auth.users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE, -- Identificación phone VARCHAR(20) NOT NULL, email VARCHAR(100), name VARCHAR(100) NOT NULL, -- Autenticación pin_hash VARCHAR(255), biometric_enabled BOOLEAN DEFAULT false, biometric_key TEXT, -- Rol role VARCHAR(20) NOT NULL DEFAULT 'owner', permissions JSONB DEFAULT '{}', -- Estado status VARCHAR(20) DEFAULT 'active', last_login_at TIMESTAMPTZ, failed_attempts INTEGER DEFAULT 0, locked_until TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(tenant_id, phone) ); CREATE INDEX idx_users_tenant ON auth.users(tenant_id); CREATE INDEX idx_users_phone ON auth.users(phone); CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON auth.users FOR EACH ROW EXECUTE FUNCTION update_updated_at(); -- Sesiones CREATE TABLE IF NOT EXISTS auth.sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, token_hash VARCHAR(255) NOT NULL, refresh_token_hash VARCHAR(255), device_type VARCHAR(20), device_info JSONB, ip_address VARCHAR(45), expires_at TIMESTAMPTZ NOT NULL, refresh_expires_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), last_activity_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_sessions_user ON auth.sessions(user_id); CREATE INDEX idx_sessions_token ON auth.sessions(token_hash); -- Códigos OTP CREATE TABLE IF NOT EXISTS auth.otp_codes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), phone VARCHAR(20) NOT NULL, code VARCHAR(6) NOT NULL, purpose VARCHAR(20) NOT NULL, attempts INTEGER DEFAULT 0, max_attempts INTEGER DEFAULT 3, expires_at TIMESTAMPTZ NOT NULL, used_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_otp_phone ON auth.otp_codes(phone, purpose); -- ============================================================================= -- OAUTH CONNECTIONS (MCH-030: Auth Social) -- ============================================================================= -- Conexiones OAuth para login con Google/Apple CREATE TABLE IF NOT EXISTS auth.oauth_connections ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, -- Proveedor OAuth provider VARCHAR(20) NOT NULL, -- 'google', 'apple' provider_user_id VARCHAR(255) NOT NULL, -- Tokens access_token TEXT, refresh_token TEXT, token_expires_at TIMESTAMPTZ, -- Datos del perfil email VARCHAR(255), name VARCHAR(255), avatar_url TEXT, -- Datos crudos del proveedor raw_data JSONB DEFAULT '{}', -- Timestamps created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), -- Constraints UNIQUE(provider, provider_user_id), UNIQUE(user_id, provider) ); CREATE INDEX idx_oauth_connections_user ON auth.oauth_connections(user_id); CREATE INDEX idx_oauth_connections_provider ON auth.oauth_connections(provider, provider_user_id); CREATE TRIGGER update_oauth_connections_updated_at BEFORE UPDATE ON auth.oauth_connections FOR EACH ROW EXECUTE FUNCTION update_updated_at(); COMMENT ON TABLE auth.oauth_connections IS 'Conexiones OAuth para login social (Google, Apple)'; COMMENT ON COLUMN auth.oauth_connections.provider IS 'Proveedor OAuth: google, apple'; COMMENT ON COLUMN auth.oauth_connections.raw_data IS 'Datos JSON completos retornados por el proveedor';