Firebase cloud function with fetch request with basic auth to external api - javascript

I seem to be having an issue with getting the expected response from a fetch call within a firebase cloud function. I'm sure it's due to my lack of knowledge on how the responses, promises, etc. work.
I'm trying to use atlassian crowd's rest api for SSO. If I use postman, I can get the desired results from the request. So I know that part of it is working.
What led me to using a cloud function is that making the same request using fetch was resulting in CORS issues from localhost. I figured if I can take the browser out of the equation, then the CORS issues would disappear. Which they have, but I'm not getting the desired response.
My cloud function looks like this:
const functions = require('firebase-functions');
const fetch = require('node-fetch');
const btoa = require('btoa');
const cors = require('cors')({origin:true});
const app_name = "app_name";
const app_pass = "app_password";
exports.crowdAuthentication = functions.https.onRequest((request, response)=>
{
cors(request, response, () =>{
let _uri = "https://my.server.uri/crowd/rest/usermanagement/1/session";
let _headers = {
'Content-Type':'application/json',
'Authorization':`Basic ${btoa(`${app_name}:${app_pass}`)}`
}
let _body = {
username: request.body.username,
password: request.body.password
}
const result = fetch(_uri, {
method: 'POST',
headers: _headers,
body: JSON.stringify(_body),
credentials: 'include'
})
response.send(result);
})
})
I'm then making the call in my application using fetch to the firebase endpoint and passing the username/password:
fetch('https://my.firebase.endpoint/functionName',{
method: 'POST',
body: JSON.stringify({username:"myusername",password:"mypassword"}),
headers: {
'Content-Type':'application/json'
}
})
// get the json from the readable stream
.then((res)=>{return res.json();})
// log the response - {size:0, timeout:0}
.then((res)=>
{
console.log('response: ',res)
})
.catch(err=>
{
console.log('error: ',err)
})
Thanks for looking.

Edit of May 2020
Note that request-promise is deprecated and I recommend to use axios.
Update following our discussion in the comments below
It appears that it doesn't work with the node-fetch library and that you should use another library like request-promise.
Therefore you should adapt your code as follows:
//......
var rp = require('request-promise');
exports.crowdAuthentication = functions.https.onRequest((request, response) => {
cors(request, response, () => {
let _uri = "https://my.server.uri/crowd/rest/usermanagement/1/session";
let _headers = {
'Content-Type': 'application/json',
'Authorization': `Basic ${btoa(`${app_name}:${app_pass}`)}`
}
let _body = {
username: request.body.username,
password: request.body.password
}
var options = {
method: 'POST',
uri: _uri,
body: _body,
headers: _headers,
json: true
};
rp(options)
.then(parsedBody => {
response.send(parsedBody);
})
.catch(err => {
response.status(500).send(err)
//.... Please refer to the following official video: https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3
});
});
});
Initial answer with node-fetch
The fetch() method is asynchronous and returns a Promise. You therefore need to wait this Promise resolves before sending back the response, as follows:
exports.crowdAuthentication = functions.https.onRequest((request, response)=>
{
cors(request, response, () =>{
let _uri = "https://my.server.uri/crowd/rest/usermanagement/1/session";
let _headers = {
'Content-Type':'application/json',
'Authorization':`Basic ${btoa(`${app_name}:${app_pass}`)}`
}
let _body = {
username: request.body.username,
password: request.body.password
}
fetch(_uri, {
method: 'POST',
headers: _headers,
body: JSON.stringify(_body),
credentials: 'include'
})
.then(res => {
res.json()
})
.then(json => {
response.send(json);
}
.catch(error => {
//.... Please refer to the following official video: https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3
});
})
})
In addition, note that you need to be on the "Flame" or "Blaze" pricing plan.
As a matter of fact, the free "Spark" plan "allows outbound network requests only to Google-owned services". See https://firebase.google.com/pricing/ (hover your mouse on the question mark situated after the "Cloud Functions" title)

Related

Refactor from fetch to await that can yield same result

So I moved over a non-reusable fetch request code snippet to my API:
let response = await fetch(visitURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + userJWT
},
body: JSON.stringify(endingVisit)
});
if (response.ok) {
let {visitId, createdAt} = await response.json();
const viewVisitDto = new ViewVisitDto(`${visitId}${createdAt}${visitorId}${doctorId}${oldPatientId}`);
return viewVisitDto;
} else {
throw new Error("deactivated!")
}
I was able to get this far:
axios.post(visitURL, {
headers,
body: JSON.stringify(visit)
}).then((response) => {
console.log(response);
}).catch((error) => {
console.log(error);
})
But does not exactly give me the visitId and createdAt from the response and I cannot use a response.ok nor a response.json(). Essentially I need to pull out that visitId and createdAt that should be coming back in the response.
I also tried just using node-fetch library, but although in VS code it seems to accept it, TypeScript is not happy with it even when I do install #types/node-fetch and even when I create a type definition file for it, my API just doesn't like it.
Guessing what you are after is
// don't know axios, but if it returns a promise await it
const dto = await axios.post(visitURL, {
headers,
body: JSON.stringify(visit)
}).then((response) => {
// parse response
return {resonse.visitId, resonse.createdAt}
}).then(({visitId, createdAt}) => {
// form dto (where are other vals)?
return new ViewVisitDto(`${visitId}${createdAt}${visitorId}${doctorId}${oldPatientId}`);
}).catch((error) => {
console.log(error);
})
However - you don't mention where doctorId and oldPatientId come from... You try providing more info, including output of the console.log's and the surrounding code

http 401 error when providing an access token the outlook api

I am trying to create a folder for a user, and I have been unsuccessful with api call attempts. My code is able to receive the correct access token, so I believe the be bug would be in createFolderTestFunction below.
async function redirectToDashboard() {
console.log("redirect to dashboard");
// var response = await requestTokenSilent();
var response;
if (!response || !response.status == 200) {
response = await requestTokenPopup();
}
if (response.accessToken) {
console.log(response);
createFolderTest(response.accessToken);
// location.href = hostname;
} else {
console.log("Unable to acquire token");
}
}
function createFolderTest(accessToken) {
var options = {
method: "POST",
headers: {
Authorization: accessToken,
"Content-Type": "application/json"
},
mode: "cors",
body: JSON.stringify({
displayName: "#COOLMONDAY"
})
};
var graphEndpoint = "https://outlook.office.com/api/v2.0/me/Inbox/";
fetch(graphEndpoint, options)
.then(resp => {
console.log(resp);
})
.catch(err => {
console.log(err);
});
}
A recommendation would be to get this working in Graph Explorer first. As this eliminates any issues with language being used and access token permissions.
https://developer.microsoft.com/en-us/graph/graph-explorer/preview
The Microsoft Graph endpoint is actually https://graph.microsoft.com/ , you can use the outlook url but moving forward Graph is where we invest in documentation, sdks and tooling.
As per the documentation https://learn.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http
You should be using, you're missing 'mailfolders'
POST /me/mailFolders
You could also use our JavaScript SDK which makes mistakes like these a little easier with intellisense and strongly typed objects.
const options = {
authProvider,
};
const client = Client.init(options);
const mailFolder = {
displayName: "displayName-value"
};
let res = await client.api('/me/mailFolders')
.post(mailFolder);
https://learn.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=javascript

How to make a post request with async fetch in mvc and JavaScript?

Im trying to fetch subcategories from my mvc application with reference to the category id with async fetch
I already fetched the categories and its all working
but when i try to fetch the subcategories with a post request it doesn't work!
//SubCategories
const categoriesLiList = document.querySelectorAll('.btn');
const getSubCategories = async () => {
const liBtnClick = list => {
nodeListForEach(list, cur => {
cur.addEventListener('click', () => {
debugger;
let categoryId = cur.value;
console.log(categoryId);
const getSubCategoriesById = async (url = ``, data = {}) => {
const subsResult = await fetch(url, {
method: "POST",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json"
},
redirect: "follow",
referrer: "no-referrer",
body: JSON.stringify(data)
});
const subsData = await subsResult.json();
const populateSubCategories = arr => {
arr.forEach(cur => {
const subCategoriesLi = `
<li>${cur.Name}</li>
`;
document.querySelector('#subcategories').insertAdjacentHTML('beforeend', subCategoriesLi);
});
};
populateSubCategories(subsData);
};
getSubCategoriesById(`/controllername/jsonresult/ID`, { ID: categoryId });
});
});
};
liBtnClick(categoriesLiList);
};
getSubCategories();
The result should be the data from the api but its not reading the ID param.
what should i change in my post request??
EDIT: I am such an idiot lol my api wasn't working correctly, so for future purposes always test your apis with postman :)
also, there's no need for a post request! just a normal fetch get reques:
await fetch(`/controllerName/JsonResult/${categoryId}`);
I am such an idiot lol
my api wasn't working correctly, so for future purposes always test your apis with postman :)
also, there's no need for a post request! just a normal fetch get reques:
await fetch(`/controllerName/JsonResult/${categoryId}`);

Making a x-www-form-urlencoded request with axios

const { user } = require('./config');
const axios = require('axios');
const Querystring = require('querystring');
let body = Querystring['stringify']({
email: 'MY EMAIL#email.com',
password: 'pass'
})
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
axios['post']('https://minecraftservers.org/login', body, config)
['then'](response => console.log(response))
Im trying to login through a website
it doesn't have an api
the headers are correct
if you're wandering how i knew this, i used chrome dev tools
like reverse engineer
content-type: application/x-www-form-urlencoded
that's the header they used when i tried to login to the site
this is what i get when i logged in through the site and not the code, it works there.
You can use URLSearchParams
const params = new URLSearchParams();
params.append('firstName', 'paul');
params.append('lastName', 'fred');
axios.post('/user', params);
It avoids adding another library.
I guess systax is your problem. Do you have any difficulties other than the syntax?
const { user } = require('./config');
const axios = require('axios');
const Querystring = require('querystring');
let body = Querystring['stringify']({
email: 'MY EMAIL#email.com',
password: 'pass'
})
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
axios.post('https://minecraftservers.org/login', body, config)
.then(response => console.log(response))
Try
axios.post('https://minecraftservers.org/login', body, config)
.then(response => console.log(response))

Run Promises in Sequence

My Sequence is:
POST file to dropbox from local server
Delete file from local server (after successful POST)
Create a shareable link for the recently POSTed file
Send User the new shareable link
(Please note I am using the Dropbox API v2)
My Code:
I am using request-promise-native library.
let upload = (req,res) => {
let options = {
method: 'POST',
uri: 'https://content.dropboxapi.com/2/files/upload',
headers: {
'Authorization': 'Bearer Token here',
'Dropbox-API-Arg': "{\"path\": \"/test/"+req.file.originalname+"\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}",
'Content-Type': 'application/octet-stream'
},body: fs.createReadStream(`uploads/${req.file.originalname}`)
};
rp(options)
.then(() => {return _deleteLocalFile(req.file.originalname)})
.then(() => {return _generateShareableLink(req.file.originalname)}) // This makes a POST request to the Dropbox API and should return a link.
.then((shareableLink) => {sendJsonResponse(res, 200, shareableLink)})
.catch(function (err) {sendJsonResponse(res, 500, err)});
};
From my understanding (correct me if Im wrong) promises run in parallel and return a value overtime (whichever promise resolves first). How can I run promises in a specific sequence? Is my implementation of promises within best practices?
Your promises will work in sequence if they are used in chain.
I can suggest you also to use ES8 async/await features to get more beautiful code
let upload = async (req,res) => {
let options = {
method: 'POST',
uri: 'https://content.dropboxapi.com/2/files/upload',
headers: {
'Authorization': 'Bearer Token here',
'Dropbox-API-Arg': "{\"path\": \"/test/"+req.file.originalname+"\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}",
'Content-Type': 'application/octet-stream'
},body: fs.createReadStream(`uploads/${req.file.originalname}`)
};
try {
const request = await rp(options);
const dlf = await _deleteLocalFile(req.file.originalname);
const shareableLink= await _generateShareableLink(req.file.originalname);
const sendedResponse = await sendJsonResponse(res, 200, shareableLink);
} catch(e) {
await sendJsonResponse(res, 500, err);
}
}

Categories