Session cookie from node-fetch is invalid? - javascript

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();

Yes your authentication request is probably valid however this is likely to be a security issue which browsers do not allow you to do such thing and freely access another website's cookie.
Browsers do not allow you to access other domain's cookies, If they did then web would be an unsafe place because for example Stackoverflow could access my Facebook account cookie and extract my personal information.

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();
});
}

Related

How to improve sequential promises execution and force fulfillment

This code is being used in a Sveltekit web application.
In the first step I get a user jwt token from an api like : dashboard.example.com/auth/local
and in the second step I'm using the response of the first api call to get full information from an api endpoint like this : example.com/api/users/token
This is an endpoint in an Sveltekit application:
import { json as json$1, error } from '#sveltejs/kit';
import axios from 'axios';
import md5 from 'md5';
import { SITE_ADDRESS } from '$lib/Env';
let userToken;
/** #type {import('#sveltejs/kit').RequestHandler} */
export async function POST({ request }) {
const bodyData = await request.json();
let identifier = bodyData.data.identifier;
let password = bodyData.data.password;
let loginToken = bodyData.data.loginToken;
let newLoginToken = md5(identifier + password + process.env.SECURE_HASH_TOKEN);
let dataResult = await axios
.post(`${import.meta.env.VITE_SITE_API}/auth/local`, {
identifier: identifier,
password: password
})
.then((response) => {
return response.data;
})
.then((response) => {
let userSummaryData = response;
userToken = md5(
userSummaryData.user.username + userSummaryData.user.id + process.env.SECURE_HASH_TOKEN
);
let userCompleteData = axios
.post(`${SITE_ADDRESS}/api/users/${userToken}`, {
data: {
userID: userSummaryData.user.id,
username: userSummaryData.user.username
}
})
.then((response) => {
return {
userJWT: userSummaryData.jwt,
userSummary: userSummaryData.user,
userFullSummary: response.data.userFullSummary
};
});
return userCompleteData;
})
.catch((error) => {
// console.log(' ---- Err ----');
});
if (dataResult && newLoginToken == loginToken) {
return json$1(
{
userJWT: dataResult.userJWT,
userSummary: dataResult.userSummary,
userFullSummary: dataResult.userFullSummary
},
{
headers: {
'cache-control': 'private, max-age=0, no-store'
}
}
);
} else if (dataResult && newLoginToken != loginToken) {
throw error(400, 'Something wrong happened');
}
throw error(401, 'Something wrong happened');
}
This code is work perfectly in localhost. But when I test it on host I get error 401.
and the question is :
Why this works on localhost but doesn't work on the server?
How can I improve this kind of promises (I'd like to use the response of the first api call in the second api call and return both
as a result)

PUT 404 (NOT FOUND) and SyntaxError: Unexpected token < in JSON at position 0

[PUT 404 (NotFound) ][1]
Client-side code
const confirmDeliver = (event) => {
const newQuantity = inventory.quantity - 1;
const updateQuantity = { newQuantity };
const url = `http://localhost:5000/inventory/${inventoryId}`;
fetch(url, {
method: "PUT",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(updateQuantity),
})
.then((response) => response.json())
.then((data) => console.log(data)); };
Server-side code
app.put("inventory/:id", async (req, res) => {
const id = req.params.id;
const updatedQuantity = req.body;
const filter = { _id: ObjectId(id) };
const options = { upsert: true };
const updatedDoc = {
$set: {
quantity: updatedQuantity.quantity,
},
};
const result = await inventoryCollection.updateOne(
filter,
options,
updatedDoc
);
res.send(result);
});
Can anyone tell me why I am getting this error? How can I solve this?
‘Unexpected token <‘ means the response returned is not a valid json. This error may be from a 404 error html page responded from backend.
The request method PUT is different from request methods GET, POST. Make sure the backend has a PUT request method with an appropriate endpoint http://localhost:5000/inventory/your-inventory-id.
To fast test a SUCCESS connection to backend, simply code in backend to return an empty {} json response.

How do I declare the body of a POST with the fetch API

I am building a comments section onto a Node/Express app for family reunions. I first wrote it all on the server side, but then ran into the issue where I was unable to update the DOM after posting the comment without refreshing the page.
My research yielded that I could use AJAX or the fetch API to do this, client-side.
I'm using some client-side JavaScript to post comments. I have a route for the POST request:
router.post('/:reunionId', isAuth, reunionController.postComment);
The controller code is:
exports.postComment = (req, res, next) => {
const commentText = req.body.newComment;
const reunionId = req.body.reunionId;
const foundReunion = Reunion.findById(reunionId)
.populate({
path: 'comments',
options: { sort: { createdAt: -1 } },
})
.then((reunion) => {
console.log(reunion);
const comment = new Comment({
_id: new mongoose.Types.ObjectId(),
text: commentText,
reunionId: new mongoose.Types.ObjectId(reunionId),
userId: req.user._id,
});
foundReunion.comments.push(comment);
comment.save();
foundReunion.save();
console.log('Operation completed successfully');
return foundReunion;
})
.catch((error) => {
const newError = new Error(error);
newError.httpStatusCode = 500;
return next(newError);
});
};
And the client-side code:
const commentForm = document.getElementById('comment-form');
const commentInput = document.getElementById('newComment');
const commentsContainer = document.getElementById('allComments');
let commentText = document.getElementById('newComment').value;
const reunionId = document.getElementById('reunionId').value;
const csrfToken = document.getElementById('csrf').value;
commentForm.addEventListener('submit', handleCommentSubmit, false);
commentInput.addEventListener('change', (event) => {
commentText = event.target.value;
});
async function handleCommentSubmit(event) {
event.preventDefault();
console.log('Someone clicked the comment submit button...');
console.log(csrfToken); // This works.
console.log(reunionId); // This works.
console.log(commentText); // This works.
const url = `http://localhost:3006/reunions/${reunionId}`;
fetch(url, {
method: 'POST',
credentials: 'include',
headers: {
'X-CSRF-Token': csrfToken,
},
body: { // This is not working.
reunionId,
commentText,
},
})
.then((response) => {
const d = response.comment.createdAt.getDate();
const m = monthNames[response.comment.createdAt.getMonth()];
const y = response.comment.createdAt.getFullYear();
const commentDiv = document.createElement('div');
commentDiv.classList.add('comments-container');
const commentP = doucment.createElement('p');
commentP.classList.add('comment-header-text');
const email = response.comment.userId.email;
const hr = document.createElement('hr');
commentP.textContent = `On ${m}+ ' ' +${d}+ ', ' +${y}, ${email} wrote:`;
commentDiv.appendChild(commentP);
commentDiv.appendChild(commentText);
commentDiv.appendChild(hr);
commentsContainer.appendChild(commentDiv);
})
.catch((error) => console.log(error));
The client makes the POST request, properly passes the csrf token, but the server cannot read the reunionId or commentText from the body of the request. I get Reunion.findOne({ null }) in the server logs.
I am simply not sure what Content-Type to declare, whether I need to at all, or how to pass the two pieces of data I need in the body of the call to fetch.
Thanks very much in advance.
The body of a post must always be a string. What you are missing is you need to JSON.strigify your object and them make add the content-type header to specify that the body is application/json:
fetch(url, {
method: 'POST',
credentials: 'include',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
reunionId,
commentText,
}),
})

is there anyway i can refresh spotify token in react?

I create a function that I can log in to my Spotify and get the access token and I create a function to refresh my token but it does not work properly when I pass it to the request function with Axios and it returns 400 or 404.
what should I do ?
here is my code :
const AUTH_URL =
" https://accounts.spotify.com/authorize?client_id=MY_ID&response_type=token&redirect_uri=http://localhost:3000/&scope=user-read-playback-state";
let Login = () => {
const spotifyHandle = (params) => {
const afterHashtag = params.substring(1);
const param = afterHashtag.split("&");
const paramsSplit = param.reduce((Para, currentPara) => {
const [key, value] = currentPara.split("=");
Para[key] = value;
return Para;
}, {});
return paramsSplit;
};
useEffect(() => {
if (window.location.hash) {
const { access_token, expires_in } = spotifyHandle(window.location.hash);
localStorage.clear();
localStorage.setItem("accessToken", access_token);
localStorage.setItem("expiresIn", expires_in);
}
});
return (
<div>
<a href={AUTH_URL}>
<button>Login</button>
</a>
</div>
);
};
here the refresh function:
let refresh = async () => {
const clientId = "id";
const clientSecret = "secret";
const headers = {
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
auth: {
username: clientId,
password: clientSecret,
},
};
const data = {
grant_type: "client_credentials",
};
try {
const response = await axios.post(
"https://accounts.spotify.com/api/token",
qs.stringify(data),
headers
);
console.log(response.data.access_token);
return response.data.access_token;
} catch (error) {
console.log(error);
}
};
The Spotify API follows the OAuth 2.0 specs and it requires (as presented at this Spotify's documentation section):
grant_type to be equal to authorization_code
code to be equal to the authorization code returned from the initial request to the Account /authorize endpoint
redirect_uri This parameter is used for validation only (there is no actual redirection). The value of this parameter must exactly match the value of redirect_uri supplied when requesting the authorization code.
And a Authorization is also required at the request header, as stated at the docs:
Base 64 encoded string that contains the client ID and client secret key. The field must have the format: Authorization: Basic *<base64 encoded client_id:client_secret>*

Using result of one function as a variable in another - node.js

I'm writing a node.js script to generate a GitHub installation access token. Here's what I've got:
const axios = require("axios");
var fs = require('fs');
var jwt = require("jsonwebtoken");
var gitInstallationAccessToken = {
genJWTToken: function(callback) {
var private_key = fs.readFileSync("/path/to/my/pemfile.pem");
const now = Math.round(Date.now() / 1000);
const payload = {
iat : now,
exp : now + (10 * 60),
iss : 7233
};
const token = jwt.sign(payload, private_key, { algorithm: 'RS256' })
callback(token);
},
genInstallationAccessToken: function(token, callback) {
var jwt = gitInstallationAccessToken.genJWTToken(function(token) {
return token;
});
console.log("JWT: ", jwt)
var instance = axios({
method: "post",
url: "https://api.github.com/installations/:installation_id/access_tokens",
headers: {
"Accept" : "application/vnd.github.machine-man-preview+json",
"Authorization" : `Bearer ${jwt}`
}
})
.then(function(response) {
console.log("Response: ",response.data);
callback(response);
})
.catch(function(error) {
console.warn("Unable to authenticate");
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (error.response) {
console.warn(`Status ${error.response.status}`);
console.warn(`${error.response.data.message}`);
}
});
}
}
module.exports = gitInstallationAccessToken;
gitInstallationAccessToken.genInstallationAccessToken(function(response) {
console.log("response: ", response)
});
My JWT token is getting generated by genJWTToken. I can see that if I add a console.log("Token: ", token) before the callback in genJWTToken.
I now need to use that token in genInstallationAccessToken but I'm clearly calling it wrong. As the following returns undefined:
var jwt = gitInstallationAccessToken.genJWTToken(function(token) {
return token;
});
console.log("JWT: ", jwt)
How do I fix this?
I think you should consider refactoring this and use chained promises it will be easier to understand and control..
Something like this:
function getToken() {
return new Promise(function(resolve, reject) {
resolve('token')
})
}
function chainPromise() {
var token
getToken().then((response) => {
token = response
console.log(token)
}).then(() => {
console.log('I am here and also see: ', token)
})
}
chainPromise()
You should then be able to track down the path of your token quite easily

Categories

Resources