Managing Gcal Response + express and header issue - api

I'm new to node and banging my head against a wall on what should be a simple node+express+googlecal+pug issue
node/express route accepts requests and calls controller
controller ensures validation of auth and then...
executes a successful gcal function...console.log has the data i need
trying to directly (in controller function) returns "Cannot set headers after they are sent to the client"....why is a call to Gcal API forcing a response back to client?
Trying to make it more micro via individual calls to each function results in same result
What am I missing here?
getcalendars: async function(oAuth2Client, res) {
const calendar = google.calendar({ version: "v3", auth: oAuth2Client });
cal = await calendar.calendarList.list(
{},
(err, result) => {
//console.log("HEADERS SENT1?: "+res.headersSent);
if (err) {
console.log('The API returned an error: ' + err);
return;
}
console.log(JSON.stringify(result));
message2 = JSON.stringify(result)
res.render('schedules', {message2: message2})
return
});
},
EDIT: Calling function
router.route('/dashboard/schedules')
.get(async function(req, res) {
if (req.session.loggedin) {
//x = gcalController.getcalendars(req, res);
token = await gcalController.gettoken(req, res);
isAuth = await gcalController.calauth(token);
listcalendars = await gcalController.getcalendars(isAuth,res);
} else {
res.redirect("/?error=failedAuthentication")
//res.send('Please login to view this page!');
}
});

Can't set headers already sent happens when you're sending a response more than once. Usually you can terminate the function by returning your res.send() call.
It looks like the express middleware that created the res object is sending a response by the time your res.render() gets pulled out of the microtask queue.
Can you show the full code? It seems that this is probably originating in the scope where getcalendars is called.

Related

Post request with useAxios

I am trying to use the composition api on my Vue app, and I need to do a post request to my backend api. I am trying to make use of the "useAxios" utility from vueuse, but I can't figure out how to pass data into a post request. It isn't shown properly in the docs...
I want to convert the following axios request into one that uses "useAxios".
await axios.put(`/blog/posts/${route.params.postID}/`, post.value)
.then(() => notification = "Post Created!")
.catch(() => {
error = "Failed to create post"
});
I tried setting the value of the data field, but that didn't work...
const {data, execute, isFinished} = useAxios(axios)
data.value = post
await execute(`/admin/blog/posts/${route.params.postID}/`, {method: "PUT"})
I also tried passing the post object into the execute method as a parameter, but my ide complained.
Thanks in advance!
Set up your pending request ahead of time:
const { data, execute, isFinished } =
useAxios(`/admin/blog/posts/${route.params.postID}/`,
{ method: "PUT" },
{ immediate:false });
Then in the future you can call it by passing the data as follows:
const requestBody = { /* your data */ };
await execute({ data: requestBody });

Handling an authentication page returned by an axios request in vue

I have a vue app that sits behind a firewall, which controls authentication. When you first access the app you need to authenticate after which you can access the app and all is well until the authentication expires. From the point of view of my app I only know that the user needs to re-authenticate when I use axios to send off an API request and instead of the expected payload I receive a 403 error, which I catch with something like the following:
import axios from 'axios'
var api_url = '...'
export default new class APICall {
constructor() {
this.axios = axios.create({
headers: {
'Accept': 'application/json',
'Cache-Control': 'no-cache',
'Content-Type': 'application/json',
},
withCredentials: true,
baseURL: api_url
});
}
// send a get request to the API with the attached data
GET(command) {
return this.axios.get(command)
.then((response) => {
if (response && response.status === 200) {
return response.data; // all good
} else {
return response; // should never happen
}
}).catch((err) => {
if (err.message
&& err.message=="Request failed with status code 403"
&& err.response && err.response.data) {
// err.response.data now contains HTML for the authentication page
// and successful authentication on this page resends the
// original axios request, which is in err.response.config
}
})
}
}
Inside the catch statement, err.response.data is the HTML for the authentication page and successfully authenticating on this page automatically re-fires the original request but I can't for the life of me see how to use this to return the payload I want to my app.
Although it is not ideal from a security standpoint, I can display the content of err.response.data using a v-html tag when I do this I cannot figure out how to catch the payload that comes back when the original request is fired by the authentication page, so the payload ends up being displayed in the browser. Does anyone know how to do this? I have tried wrapping everything inside promises but I think that the problem is that I have not put a promise around the re-fired request, as I don't have direct control of it.
Do I need to hack the form in err.response.data to control how the data is returned? I get the feeling I should be using an interceptor but am not entirely sure how they work...
EDIT
I have realised that the cleanest approach is to open the form in error.response.data in a new window, so that the user can re-authenticate, using something like:
var login_window = window.open('about:blank', '_blank');
login_window.document.write(error.response.data)
Upon successful re-authentication the login_window now contains the json for the original axios get request. So my problem now becomes how to detect when the authentication fires and login_window contains the json that I want. As noted in Detect form submission on a page, extracting the json from the formatting window is also problematic as when I look at login_window.document.body.innerText "by hand" I see a text string of the form
JSON
Raw Data
Headers
Save
Copy
Collapse All
Expand All
status \"OK\"
message \"\"
user \"andrew\"
but I would be happy if there was a robust way of determining when the user submits the login form on the page login_window, after which I can resend the request.
I would take a different approach, which depends on your control over the API:
Option 1: you can control (or wrap) the API
have the API return 401 (Unauthorized - meaning needs to authenticate) rather than 403 (Forbidden - meaning does not have appropriate access)
create an authentication REST API (e.g. POST https://apiserver/auth) which returns a new authentication token
Use an Axios interceptor:
this.axios.interceptors.response.use(function onResponse(response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// no need to do anything here
return response;
}, async function onResponseError(error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
if ("response" in error && "config" in error) { // this is an axios error
if (error.response.status !== 401) { // can't handle
return error;
}
this.token = await this.axios.post("auth", credentials);
error.config.headers.authorization = `Bearer ${this.token}`;
return this.axios.request(config);
}
return error; // not an axios error, can't handler
});
The result of this is that the user does not experience this at all and everything continues as usual.
Option 2: you cannot control (or wrap) the API
use an interceptor:
this.axios.interceptors.response.use(function onResponse(response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// no need to do anything here
return response;
}, async function onResponseError(error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
if ("response" in error && "config" in error) { // this is an axios error
if (error.response.status !== 403) { // can't handle
return error;
}
if (!verifyLoginHtml(error.response.data)) { // this is not a known login page
return error;
}
const res = await this.axios.post(loginUrl, loginFormData);
return res.data; // this should be the response to the original request (as mentioned above)
}
return error; // not an axios error, can't handler
});
One solution is to override the <form>'s submit-event handler, and then use Axios to submit the form, which gives you access to the form's response data.
Steps:
Query the form's container for the <form> element:
// <div ref="container" v-html="formHtml">
const form = this.$refs.container.querySelector('form')
Add a submit-event handler that calls Event.preventDefault() to stop the submission:
form.addEventListener('submit', e => {
e.preventDefault()
})
Use Axios to send the original request, adding your own response handler to get the resulting data:
form.addEventListener('submit', e => {
e.preventDefault()
axios({
method: form.method,
url: form.action,
data: new FormData(form)
})
.then(response => {
const { data } = response
// data now contains the response of your original request before authentication
})
})
demo

How do I serve a file from S3 through Meteor Iron Router

My question is very similar to this one which describes how to serve a local file using Iron Router. I need to do the same, but instead of reading the file synchronously from disk, I need to get the file from S3 which is an asynchronous call.
The problem appears to be the fact that the action method has returned before the asynchronous s3.getObject completes giving me the following error.
Error: Can't render headers after they are sent to the client.
I'm assuming that Iron Router is generating the response for me when it realizes that I haven't handled the response in my action method, but I'm stumped about how to tell it to wait for my asynchronous call to finish.
Here is my code.
Router.map(function () {
this.route('resumeDownload', {
where: 'server',
path: '/resume/:_id',
action: function () {
var response = this.response;
var candidate = Candidates.findOne(this.params._id);
if (!candidate || !candidate.resumeS3Key) {
// this works fine because the method hasn't returned yet.
response.writeHead(404);
return response.end();
}
var s3 = new AWS.S3();
s3.getObject({Bucket: 'myBucket', Key: candidate.resumeS3Key}, function (err, data) {
if (err) {
// this will cause the error to be displayed
response.writeHead(500);
return response.end();
}
// this will also cause the error to be displayed
response.writeHead(200, {'Content-Type': data.ContentType});
response.end(data.Body);
});
}
});
});
I was able to solve this one myself. I needed to use a future in my action method.
Here is the working code.
Router.map(function () {
this.route('resumeDownload', {
where: 'server',
path: '/resume/:_id',
action: function () {
var response = this.response,
candidate = Candidates.findOne(this.params._id);
if (!candidate || !candidate.resumeS3Key) {
response.writeHead(404);
return response.end();
}
var Future = Npm.require('fibers/future'),
s3 = new AWS.S3(),
futureGetObject = Future.wrap(s3.getObject.bind(s3)),
data = futureGetObject({Bucket: 'myBucket', Key: candidate.resumeS3Key}).wait();
response.writeHead(200, {'Content-Type': data.ContentType});
response.end(data.Body);
}
});
});

How do I design my Node.js API so that I can also consume it from the server side?

I have an API that returns some JSON from mongodb:
// In router.js
var api = require('api')
app.get('/lists', api.lists);
// In api.js
var db = require('db')
exports.lists = function(req, res) {
db.lists.find({}, function(err, lists) {
res.send(lists);
});
};
Is there a way to design the API so that I could also consume it from within my Node.js app? I'm trying to avoid having to duplicate any of the database code outside the API. I basically have a controller that can render the data server-side:
// In controller.js
var api = require('api')
exports.page = function(req, res) {
res.send(api.lists()); // This won't work
};
I found a hacky solution which was to pass a callback function to the api.lists(), but I have a feeling this is the "wrong" way to achieve this:
// In api.js
exports.lists = function(req, res, callback) {
db.lists.find({}, function(err, lists) {
if(callback){
callback(lists);
} else {
res.send(lists);
}
});
};
Any ideas?
I think the problem is that in your current code you are coupling your API to the response object. You can decouple them with something like this:
In router.js instead of using api.lists as the callback, define a function that will call api.lists with a callback that is wired to the response object. In this case api.list DOES NOT need to know about the response object but the function that we are creating does.
// In router.js
var api = require('api');
app.get('/lists', function(req, res) {
api.lists(function(err, lists) {
if(err) {
res.send('error page');
return;
}
res.send(lists);
});
});
In api.js we remove the reference to the response object. Now it will just call whatever callback it received with the appropriate data (err + lists). It's up to the callback to do whatever it pleases with the result.
// In api.js
var db = require('db')
exports.lists = function(callback) {
db.lists.find({}, function(err, lists) {
callback(err, lists);
});
};

Sending 405 from express.js when there is a route match but no HTTP method match

I'm looking for a clean way to have my express app return 405 Method Not Allowed if a client sends a request that matches a mapped url route but does not match the mapped HTTP method.
My current implementation is to have a default "catch-all" handler that tries to match the url against the register routes, ignoring the HTTP method. If there is a match, then we know to return a 405, otherwise we let express do its default 404 behavior.
I'm hoping there is a better way that doesn't involve running all the route matching twice (once by express, once by my handler).
Here is an approach that I have used successfully with multiple Django applications and now with Node and Express. It is also follows RFC 2616 (HTTP/1.1) that says the following about HTTP 405:
The response MUST include an Allow header containing a list of valid
methods for the requested resource.
So, the key point is to route the requests to the same handler without regard to methods.
app.all('/page/:id', page.page);
app.all('/page/:id/comments', page.comments);
app.all('/page/:id/attachments', page.attachments);
...
The next point is to validate the method in the handler function 'comments'. Note that the handler is responsible for handling all the methods. In Django's world this is the only way to go because the framework forces you to separate the routing of the URLs from the actual action about to be performed against the resource the URL represents.
In the handler you could check the method like this...
exports.comments = function (req, res) {
if (req.route.method === 'get') {
res.send(200, 'Hello universe.');
} else {
res.set('Allow', 'GET');
res.send(405, 'Method Not Allowed');
}
}
...but as you can expect the code will quickly become repetitious and not nice to read especially when you have many handler functions and many different sets of allowed methods.
Therefore I prepared a shortcut function named restful for the job. Define the function wherever you want. I personally would place it in helpers.js under the same directory where the handler functions are implemented.
var restful = function (req, res, handlers) {
//
// This shortcut function responses with HTTP 405
// to the requests having a method that does not
// have corresponding request handler. For example
// if a resource allows only GET and POST requests
// then PUT, DELETE, etc requests will be responsed
// with the 405. HTTP 405 is required to have Allow
// header set to a list of allowed methods so in
// this case the response has "Allow: GET, POST" in
// its headers [1].
//
// Example usage
//
// A handler that allows only GET requests and returns
//
// exports.myrestfulhandler = function (req, res) {
// restful(req, res, {
// get: function (req, res) {
// res.send(200, 'Hello restful world.');
// }
// });
// }
//
// References
//
// [1] RFC-2616, 10.4.6 405 Method Not Allowed
// https://www.rfc-editor.org/rfc/rfc2616#page-66
//
// [2] Express.js request method
// http://expressjs.com/api.html#req.route
//
var method = req.route.method; // [2]
if (!(method in handlers)) {
res.set('Allow', Object.keys(handlers).join(', ').toUpperCase());
res.send(405);
} else {
handlers[method](req, res);
}
}
With restful it is now quite painless to handle 405 responses automatically and having proper Allow header being set. Just give a function for each method you allow and restful does the rest.
So lets modify the previous example:
exports.comments = function (req, res) {
restful(req, res, {
get: function (req, res) {
res.send(200, 'Hello restful universe.');
}
});
}
Why the name restful? In RESTful web it is quite essential for the API to obey the conventions like responsing with HTTP 405 to the request having non-supported method. Many of those conventions could be integrated to restful when needed. Therefore the name is restful and not something like auto405 or http405handler.
Hope this helps. Any thoughts?
Method 1: Use .route() and .all()
// Your route handlers
const handlers = require(`./handlers.js`);
// The 405 handler
const methodNotAllowed = (req, res, next) => res.status(405).send();
router
.route(`/products`)
.get(handlers.getProduct)
.put(handlers.addProduct)
.all(methodNotAllowed);
This works because requests are passed to the handlers in the order they are attached to the route (the request "waterfall"). The .get() and .put() handlers will catch GET and PUT requests, and the rest will fall through to the .all() handler.
Method 2: Middleware
Create middleware which checks for allowed methods, and returns a 405 error if the method is not whitelisted. This approach is nice because it allows you to see and set the allowed methods for each route along with the route itself.
Here's the methods.js middleware:
const methods = (methods = ['GET']) => (req, res, next) => {
if (methods.includes(req.method)) return next();
res.error(405, `The ${req.method} method for the "${req.originalUrl}" route is not supported.`);
};
module.exports = methods;
You would then use the methods middleware in your routes like this:
const handlers = require(`./handlers.js`); // route handlers
const methods = require(`./methods.js`); // methods middleware
// allows only GET or PUT requests
router.all(`/products`, methods([`GET`, `PUT`]), handlers.products);
// defaults to allowing GET requests only
router.all(`/products`, methods(), handlers.products);
Due to ambiguity, there really is no other way. Personally, I would do something like this:
var route = '/page/:id/comments'
app.get(route, getComments)
app.all(route, send405)
function send405(req, res, next) {
var err = new Error()
err.status = 405
next(err)
}
Either way, you have to check the routes twice.
Kinda old question but here is what i did. I just put this after all my routes but before my 400 handler
// Handle 405 errors
app.use(function(req, res, next) {
var flag = false;
for (var i = 0; i < req.route.stack.length; i++) {
if (req.method == req.route.stack[i].method) {
flag = true;
}
}
if (!flag) {
err = new Error('Method Not Allowed')
err.status = 405;
return next(err)
}
next();
});
I have been doing it this way:
Say if you have GET and POST method handlers for /. You can wrap the path with app.route or router.route and assign the handlers accordingly.
app.route("/").get((req, res) => {
/* DO SOMETHING*/
}).post((req, res) => {
/* DO SOMETHING*/
}).all((req, res) => {
res.status(405).send();
});
http://expressjs.com/en/4x/api.html#app.route
http://expressjs.com/en/4x/api.html#router.route
A request will get matched to the route and filtered through the handlers. If a handler is present, it will get handled as usual. Else, it will reach the all handler that will set the status code to 405 and ending the request.
I fixed it like this :
/*paths here*/
router.get('/blah/path1', blah.do_something );
router.post('/blah/path2', blah.do_something_else );
/* if we get here we haven't already gone off down another path */
router.all('/*', (req,res) => { res.status(405),
res.json({'status':405,
'message':req.method + ' not allowed on this route'})
});
/* simples */
I thought this was a pretty interesting problem and so I dove deeeeeep down into the depths of the express app function and found a way to dynamically build a 405 error that includes all of the possible routes (without having to manually update anything when you add a new route).
app.use("", (req, _, next) => {
const err = buildError(app, req);
if (!err) return next();
return next(err);
});
For those interested you can find the npm package here https://www.npmjs.com/package/express-ez-405, and below is a quick example of what it looks like to use it.
const express = require("express");
const { buildError } = require("express-ez-405");
const app = express();
app.use(express.json());
const userRouter = require("./routes/user");
const mainRouter = require("./routes/main");
const nestedRouter = require("./routes/nested");
// // Routes
app.use("/main", mainRouter);
app.use("/user", userRouter);
app.use("/nested/route", nestedRouter);
// Routes
// 405 & 404 error catcher
app.use("", (req, _, next) => {
const err = buildError(app, req);
if (!err) return next();
return next(err);
});
// 405 & 404 error catcher
// Error handling
app.use((err, _, res, __) =>
res.status(err.status).json({ message: err.message })
);
// Error handling
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server listening on port: ${PORT}...`);
});
Once this in in there you never have to worry about updating it regardless of whether you add, remove, or change routing.