Remotely identify GF status

Okay, nobody has a solution, so I built a solution. This is a PHP script. (Shuddup – I like php!) It emulates the login to the GlowForge cloud web interface and then retrieves the machine information. If it works, you’ll see a bunch of json output.

You only need to set your login email address, login password, and the path for storing the cookie file.

Warning: Since this script requires your username and password for getting into your glowforge account (and this forum), be very careful to not put it on a public-facing web server!

<?php
/**********************************************
 Script to check GlowForge status
 Created by Neal Krawetz, 2023-12-28
 License: Public domain, not responsible for how you use it or any damage it may cause.
 Use at your own risk!
 **********************************************/

global $CookieJar;

// BEGIN CUSTOMIZATION

$USER='YOUR_EMAIL_ADDRESS'; // this is your GF login
$PASS='YOUR_PASSWORD'; // this is your GF password
$CookieJar='/dev/shm/gf-cookies.txt'; // path to cookie storage
$Verbose=false; // enable for debugging

// END CUSTOMIZATION

function GetURL($method,$url,$parameters=false,$verbose=false)
  {
  global $CookieJar;
  $uas = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0';
  $options = array(
        CURLOPT_URL            => $url,     // set the URL
        CURLOPT_USERAGENT      => $uas,     // set the user-agent string
        CURLOPT_RETURNTRANSFER => true,     // return web page
        CURLOPT_HEADER         => false,    // DON'T return headers in addition to content
        CURLINFO_HEADER_OUT    => true,     // Show me the posted request
        CURLOPT_FOLLOWLOCATION => true,     // follow redirects
        CURLOPT_ENCODING       => "",       // handle all encodings
        CURLOPT_AUTOREFERER    => true,     // set referer on redirect
        CURLOPT_CONNECTTIMEOUT => 120,      // timeout on connect
        CURLOPT_TIMEOUT        => 120,      // timeout on response
        CURLOPT_MAXREDIRS      => 10,       // stop after 10 redirects
        CURLOPT_SSL_VERIFYPEER => true,     // Validate SSL Certificates
        CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST  => strtoupper($method), // request method
        CURLOPT_COOKIESESSION  => true,     // store session cookies
        CURLOPT_COOKIEJAR      => $CookieJar, // where to store cookies
        CURLOPT_COOKIEFILE     => $CookieJar, // where to read cookies
        );

  $out=array();
  if ($parameters !== false)
    {
    $options[CURLOPT_HTTPHEADER] = array('Content-Type: application/x-www-form-urlencoded');
    $options[CURLOPT_POST] = true;
    $options[CURLOPT_POSTFIELDS] = http_build_query($parameters);
    $out['post_parameters'] = $options[CURLOPT_POSTFIELDS];
    }

  $ch =  curl_init(); // curl handle
  curl_setopt_array($ch, $options);
  $out['body'] = curl_exec($ch);
  $out['err'] = curl_errno($ch);
  $out['errmsg'] = curl_error($ch);
  $out['header'] = curl_getinfo($ch);
  curl_close($ch);
  if ($verbose) { print_r($out); }

  return($out);
  }

/******************************************* 
 Check if it needs to get cookies
 *******************************************/
$NeedCookies=true; // assume it needs cookies
if (is_file($CookieJar))
  {
  // Check if cookies exist and have not expired
  $fp=fopen($CookieJar,"r");
  if ($fp)
    {
    // There are 3 necessary cookies:
    // #HttpOnly_.glowforge.com
    // #HttpOnly_.accounts.glowforge.com
    // .glowforge.com
    $Now=time();
    $Cookie=array();
    while($line=fgets($fp))
      {
      $line=explode("\t",$line);
      if (!isset($line[6])) { continue; } // bad line
      if (!is_numeric($line[4])) { continue; } // bad line
      if (intval($line[4])+30 <= $Now) { continue; } // needs to be valid for at least 30 seconds
      $Cookie[$line[0]]=1; // looks valid, so store it
      }
    fclose($fp);

    // Are all of the essential cookies valid?
    if (isset($Cookie['#HttpOnly_.glowforge.com']) &&
        isset($Cookie['#HttpOnly_.accounts.glowforge.com']) &&
        isset($Cookie['.glowforge.com']))
        {
        $NeedCookies=false; // already got good cookies!
        }
    }
  }

/******************************************* 
 Get cookies as needed.
 *******************************************/
if ($NeedCookies)
  {
  // Get initial Google cookies.
  if ($Verbose) { echo "Get cookies!\n"; }
  if (is_file($CookieJar)) { unlink($CookieJar); } // toss any old cookies
  $out=GetURL("GET","https://app.glowforge.com/");
  if (!isset($out['header']['http_code']) || (intval($out['header']['http_code'])!==200))
    {
    echo "Error: Bad return code.\n";
    print_r($out);
    exit;
    }
  // Grab the authenticity token!
  // E.g.: <input type="hidden" name="authenticity_token" value="BlaBlaBla" />
  // This token is supposed to deter bots and scripts, uh, like this script.
  $p=strpos($out['body'],'name="authenticity_token"'); // find the field
  $Tok=substr($out['body'],$p);
  $p=strpos($Tok,'value="'); // find the value
  $Tok=substr($Tok,$p+7);
  $p=strpos($Tok,'"'); // find the end of the value
  $Tok=substr($Tok,0,$p);
  #echo "Tok=$Tok\n";

  // Login to get session cookies.
  if ($Verbose) { echo "Post login\n"; }
  $Post=array();
  $Post['authenticity_token'] = $Tok;
  $Post['user[email]'] = $USER;
  $Post['user[password]'] = $PASS;
  $Post['user[remember_me]'] = 0;
  $Post['commit'] = 'Sign In';
  $out=GetURL("POST","https://accounts.glowforge.com/users/sign_in",$Post);
  if (!isset($out['header']['http_code']) || (intval($out['header']['http_code'])!==200))
    {
    echo "Error: Bad login.\n";
    print_r($out);
    exit;
    }
  } // if $NeedCookies

/******************************************* 
 Get machines! (Requires valid login and session cookies.)
 *******************************************/
$out=GetURL("GET","https://api.glowforge.com/gfcore/users/machines");
if (!isset($out['header']['http_code']) || (intval($out['header']['http_code'])!==200))
  {
  echo "Error: Bad get machines.\n";
  print_r($out);
  exit;
  }
echo $out['body']; // output the json
?>

Since it requires PHP, you can run it from the command-line, like:

php ./gf-getmachine.php > machines.json
cat machines.json | json_pp | less

If it needs to get the login cookies, then this may take 2-3 seconds to complete.
If it already has valid login cookies (from a previous run), then it takes less than a second to complete.

I don’t know if there’s a login limit or how often you can call it. I was running it every few minutes while trying to build and debug it, and I didn’t get locked out.

I strongly suggest NOT trying to abuse it by calling it ever few seconds (or more often). Otherwise, GF might notice and disable this capability or start locking accounts for abusive behavior. (I know I would if I were them.)

15 Likes