Search code examples
javascriptnode.jshttpcookiesnode-fetch

Session cookie from node-fetch is invalid?


I am writing a javascript program (for a github action) right now but ran into a problem.
I was trying to log into www.overleaf.com and access the page https://www.overleaf.com/project after generating a session cookie by sending a POST request to https://www.overleaf.com/login with my credentials and the csrf token.
The response contained the requested token in the set-cookie header as expected, however, when I tried to access https://www.overleaf.com/project via GET, I get redirected back to https://www.overleaf.com/login
When copying a session cookie saved in my browser, the request works just fine as expected.

I tried doing the same thing in the command line with cURL and it worked there.

I am fairly certain my authentication request is accepted by Overleaf's server, because I have tried intentionally incorrectly sending the password or the csrf token and in both cases, the response does not give me a new session cookie but sends the old one.

If anyone has any clue what is going wrong, I'd be very thankful for your input.

This is what worked in the terminal, which I'm trying to replicate in javascript with node-fetch:
curl -v --header "Content-Type: application/json" --cookie "GCLB=someothercookie;overleaf_session2=firstsessioncookie" --data '{"_csrf":"the_csrf_token", "email": "MYEMAIL", "password":"MYPASSWORD"}' https://www.overleaf.com/login
to get the cookie and csrf token and
curl -v https://www.overleaf.com/project --cookie "overleaf_session2=returnedsessioncookie; GCLB=someothercookie" as the request that returns the html page of my projects.

This is my javascript code, I have double, triple, quadruple checked it but I think I'm missing something.

const fetch = require("node-fetch");
const parser = require("node-html-parser");
const scparser = require("set-cookie-parser");
 
async function run() {
 
  const email = process.env.EMAIL;
  const password = process.env.PASSWORD;
 
  var cookies = await login(email, password);
 
  console.log(await all_projects(cookies));
 
}
 
async function login(email, password) {
  const login_get = await fetch("https://www.overleaf.com/login");
 
  const get_cookies = login_get.headers.raw()["set-cookie"];
  const parsed_get_cookies = scparser.parse(get_cookies, {
    decodeValues: false
  });
  const overleaf_session2_get = parsed_get_cookies.find(
    (element) => element.name == "overleaf_session2"
  ).value;
  const gclb = parsed_get_cookies.find(
    (element) => element.name == "GCLB"
  ).value;
  console.log("overleaf_session2_get:", overleaf_session2_get, "gclb:", gclb);
 
  const get_responsetext = await login_get.text();
  const _csrf = parser
    .parse(get_responsetext)
    .querySelector("input[name=_csrf]")
    .getAttribute("value");
 
  login_json = { _csrf: _csrf, email: email, password: password };
  console.log(login_json);
  const login_post = await fetch("https://www.overleaf.com/login", {
    method: "post",
    body: JSON.stringify(login_json),
    headers: {
      "Content-Type": "application/json",
      "Cookie": "GCLB=" + gclb + ";overleaf_session2=" + overleaf_session2_get
    }
  });
 
  const post_cookies = login_post.headers.raw()["set-cookie"];
  const parsed_post_cookies = scparser.parse(post_cookies, {
    decodeValues: false
  });
  const overleaf_session2_post = parsed_post_cookies.find(
    (element) => element.name == "overleaf_session2"
  ).value;
 
  console.log(
    "successful:",
    overleaf_session2_get != overleaf_session2_post ? "true" : "false"
  );
  console.log(await fetch("https://www.overleaf.com/project", {
    headers: {
      "Cookie": "overleaf_session2=" + overleaf_session2_post
    }
  }))
  return "overleaf_session2=" + overleaf_session2_post;
}
 
async function all_projects(cookies) {
  const res = await fetch("https://www.overleaf.com/project", {
    headers: {
      Cookie: cookies
    }
  });
  return res;
}
 
run();

Solution

  • I fixed my issue by not using node-fetch and switching to https.

    Here is what worked:

    async function login(email, password) {
      //GET login page
      const get = await get_login();
      //get necessary info from response
      const csrf = parser
        .parse(get.html)
        .querySelector(`meta[name="ol-csrfToken"]`)
        .getAttribute("content");
      const session1 = scparser
        .parse(get.headers["set-cookie"], { decodeValues: false })
        .find((cookie) => cookie.name == "overleaf_session2").value;
      const gclb = scparser
        .parse(get.headers["set-cookie"], { decodeValues: false })
        .find((cookie) => cookie.name == "GCLB").value;
    
      //POST login data
      const post = await post_login(csrf, email, password, session1, gclb);
      //get necessary data from response
      const session2 = scparser
        .parse(post["set-cookie"], { decodeValues: false })
        .find((cookie) => cookie.name == "overleaf_session2").value;
    
      //GET new csrf token from project page
      const projects = await get_projects(session2, gclb);
      const csrf2 = parser
        .parse(projects.html)
        .querySelector(`meta[name="ol-csrfToken"]`)
        .getAttribute("content");
    
      //return data
      return {
        session: session2,
        gclb: gclb,
        csrf: csrf2,
        projects: projects.html
      };
    }
    
    async function get_login() {
      const url = "https://www.overleaf.com/login";
      return new Promise((resolve) => {
        https.get(url, (res) => {
          var data;
          res.on("data", (chunk) => {
            data += chunk;
          });
          res.on("end", () => {
            resolve({ html: data, headers: res.headers });
          });
        });
      });
    }
    
    async function get_projects(session2, gclb) {
      const url = "https://www.overleaf.com/project";
      return new Promise((resolve) => {
        https.get(
          url,
          { headers: { Cookie: `GCLB=${gclb};overleaf_session2=${session2}` } },
          (res) => {
            var data;
            res.on("data", (chunk) => {
              data += chunk;
            });
            res.on("end", () => {
              resolve({ html: data, headers: res.headers });
            });
          }
        );
      });
    }
    
    async function post_login(_csrf, email, password, session1, gclb) {
      const url = "https://www.overleaf.com/login";
      const options = {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Cookie: `GCLB=${gclb};overleaf_session2=${session1}`
        }
      };
      const postData = {
        _csrf: _csrf,
        email: email,
        password: password
      };
      return new Promise((resolve) => {
        var req = https.request(url, options, (res) => {
          resolve(res.headers);
        });
    
        req.on("error", (e) => {
          console.error(e);
        });
    
        req.write(JSON.stringify(postData));
        req.end();
      });
    }