Getting empty ctx.body on post requests in Koajs - node.js

I'm new to Koa.
I wrote a simple api server with it. I have used "koa-bodyparser" and i have added content-type: application/json to the request header, but i still get empty request body on post requests.
Could anyone please guide me?
this is my server.js
const Koa = require('koa');
const bodyParser = require('koa-bodyparser')();
const compress = require('koa-compress')();
const cors = require('#koa/cors')();
const helmet = require('koa-helmet')();
const logger = require('koa-logger')();
const errorHandler = require('./middleware/error.middleware');
const applyApiMiddleware = require('./api');
const { isDevelopment } = require('./config');
const db = require('./db/db');
const server = new Koa();
db.connectDB();
/**
* Add here only development middlewares
*/
if (isDevelopment) {
server.use(logger);
}
/**
* Pass to our server instance middlewares
*/
server
.use(errorHandler)
.use(helmet)
.use(compress)
.use(cors)
.use(bodyParser);
/**
* Apply to our server the api router
*/
applyApiMiddleware(server);
module.exports = server;
and this is my endpoint:
router.post('/', controller.createOne);
and the createone method:
exports.createOne = async ctx => {
console.log(ctx.body);
ctx.assert(username, 400, 'Username is required');
ctx.assert(password, 400, 'Password is required')
try {
const { name, username, password } = ctx.request.body;
let user = await User.findOne({ username });
if (user){
ctx.status = 400;
ctx.body = { errors: [{ msg: 'User already exists' }] };
}
user = new User({
name,
username,
password
});
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
user.password = undefined;
ctx.status = 201;
ctx.body = user;
} catch (error) {
console.error(error.message);
ctx.status = 500;
ctx.body = { errors: [{ msg: error.message }] }
}
};
and the postman request:

You are confusing ctx.body with ctx.request.body, at least in your log statement (in the destructured assignment it is actually correct).
ctx.body is the same as ctx.response.body, it's the response body which is empty because you didn't set it yet.
ctx.request.body is the request body which you actually want.
Some other issues I noticed:
You use username and password in those ctx.assert lines before they are defined.
In your duplicate user case, you forgot to return from your function, so the rest of the function will still run and even with an existing user you will create a new one.
Since you seem to be working on debugging that error 500: A little tip, error.message is quite useless for debugging as it's missing the stack (most importantly - since this is what shows you where exactly the error came from) and the error class, code and other properties. If you use console.error, always log the whole error object and not just the message: console.error(error). If you want to prepend some text, don't use concatenation, use separate arguments instead, so the object is still formatted: console.error('Error in request:', error).
If you need a string (for example when returning it as response, which you should do only in development mode by the way because you don't want to expose your inner workings to potential attackers), use error.stack and not error.message, because it will contain a lot more information.

Related

Modified Req Url in Express middleware ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client

Hey guys I am facing the error Error "[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" when I am trying to modify the req.url in a express middleware.
My middleware
export function ModifyQueryMiddleware(config, Authconfig, Repo ){
const accessTokenMap = new Map();
return async (request, res, next) => {
const accessToken = request.header('authorization') as string;
if(!accessToken){
throw new HttpException(res, 403)
}
if(!accessTokenMap.get(accessToken)){
const JWKS = jose.createRemoteJWKSet(new URL(config.jkwsUri));
try {
const jwtVerifyResult = await jose.jwtVerify(accessToken.replace('Bearer ', ''), JWKS);
const {payload} = jwtVerifyResult;
accessTokenMap.set(accessToken, payload)
const aumParams = await authentication(payload, authConfig,Repo);
const queryRestrictionStrategy = QueryRestrictionStrategyFactory(aumParams, request)
queryBuilder(queryRestrictionStrategy)
next()
} catch(err){
}
}
const payload = accessTokenMap.get(accessToken);
const aumParams = await authentication(payload, authConfig, repo);
const queryRestrictionStrategy = QueryRestrictionStrategyFactory(aumParams, request)
queryBuilder(queryRestrictionStrategy)
next()
}
}
My queryBuilder:
export function queryBuilder(strategy: QueryRestrictionStrategy){
const {req, id} = strategy
if(req.url === '/someurl'){
req.url = `/someurl?${id}`
}
return
}
I am really confused as I don't modify the header of a response instead I am just modifying the query without the querybuilder the middleware works fine. I already looked at a few questions regarding this error however the res was there always modified.
Any help or tips would be really appreciated !
Your code can call next twice when !accessTokenMap.get(accessToken) is true. You have to return once that part of your code is handled:
if (!accessTokenMap.get(accessToken)) {
const JWKS = jose.createRemoteJWKSet(new URL(config.jkwsUri));
try {
...
next();
} catch(err) {
next(err); // make sure to pass the error along!
}
return;
}

Why this callable cloud function is failing with "app":"MISSING"?

I am calling a cloud function which runs a transaction, however it is returning an error to console which says:
Callable request verification passed {"verifications":{"auth":"VALID","app":"MISSING"}}
Googling it led me to App Check which is a new thing in Firebase. I am using React-Native firebase npm packages and following its documentation about App Check is extremely difficult due to lack of proper explanation and examples.
Below I have the code which I am trying to execute in the function:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const firestore_ = admin.firestore();
// const { CustomProvider } = require("#react-native-firebase/app-check");
const appCheckForDefaultApp = admin.appCheck();
const GeoPoint = admin.firestore.GeoPoint;
const FieldValue = admin.firestore.FieldValue;
const _geofirestore_ = require("geofirestore");
const GeoFirestore = _geofirestore_.initializeApp(firestore_);
exports.createNew = functions.runWith({
allowInvalidAppCheckToken: true // Opt-out: Requests with invalid App
// Check tokens continue to your code.
}).region('europe-west6').https.onCall(async (data, context) => {
try {
//Checking that the user calling the Cloud Function is authenticated
if (!context.auth) {
return "The user is not authenticated: " + context.auth;
// throw new UnauthenticatedError('The user is not authenticated. Only authenticated Admin users can create new users.');
}
const longitude = data.longitude;
const latitude = data.latitude;
const thirty_mins_old = data.thirty_mins_old;
const currenttime = data.currenttime;
const GeoFirestore_ = new _geofirestore_.GeoFirestore(firestore_);
const sfDocRef = GeoFirestore_.collection('mycollection')
.limit(1)
.near({ center: new GeoPoint(latitude, longitude), radius: 0.05 });
GeoFirestore.runTransaction((transaction) => {
const geotransaction = new _geofirestore_.GeoTransaction(transaction, new GeoPoint(latitude, longitude));
return geotransaction.get(sfDocRef._collectionPath).then((sfDoc) => {
...
});
});
} catch (error) {
if (error.type === 'UnauthenticatedError') {
throw new functions.https.HttpsError('unauthenticated', error.message);
} else if (error.type === 'NotAnAdminError' || error.type === 'InvalidRoleError') {
throw new functions.https.HttpsError('failed-precondition', error.message);
} else {
throw new functions.https.HttpsError('internal', error.message);
}
}
});
EDIT:
I am debugging the app so I am not working on production. Does debugging still requires this to be configured?
The log message you are seeing is not an error - it's informational.
On each request, your callable functions will verify any auth or appcheck token included in the request. When these tokens are not present, the execution is passed to your handler - it's your responsibility to handle requests with missing tokens if necessary. It looks like you are already handling the case for missing auth token.
When executing functions in your auth emulator, auth/appcheck tokens are minimally verified - i.e. they should be valid JWT token but we don't actually verify the signature to ensure that it's signed by Firebase Auth/AppCheck backend.
If your function is erroring in your development environment, I suspect that the error is elsewhere.

Error: No responses defined for platform: null when listing users google calendar events

I'm trying to implement a reminders dialogflow agent in node js that reminds the user on his google calendar upcoming events. however I'm getting an No responses defined for platform: null error when calling the intent for listing the upcoming events.
This is my code:
const express = require('express');
const google = require('googleapis').google;
const jwt = require('jsonwebtoken');
const dfff = require('dialogflow-fulfillment')
const {googlec} = require('googleapis');
// Google's OAuth2 client
const OAuth2 = google.auth.OAuth2;
// Including our config file
const CONFIG = require('./config');
// Creating our express application
const app = express();
// Allowing ourselves to use cookies
const cookieParser = require('cookie-parser');
app.use(cookieParser());
// Setting up EJS Views
app.set('view engine', 'ejs');
app.set('views', __dirname);
console.log(dfff)
app.get('/', function (req, res) {
// Create an OAuth2 client object from the credentials in our config file
const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);
// Obtain the google login link to which we'll send our users to give us access
const loginLink = oauth2Client.generateAuthUrl({
access_type: 'offline', // Indicates that we need to be able to access data continously without the user constantly giving us consent
scope: CONFIG.oauth2Credentials.scopes // Using the access scopes from our config file
});
return res.render("index", { loginLink: loginLink });
});
app.get('/auth_callback', function (req, res) {
// Create an OAuth2 client object from the credentials in our config file
const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);
if (req.query.error) {
// The user did not give us permission.
return res.redirect('/error');
} else {
oauth2Client.getToken(req.query.code, function(err, token) {
if (err)
return res.redirect('/');
// Store the credentials given by google into a jsonwebtoken in a cookie called 'jwt'
res.cookie('jwt', jwt.sign(token, CONFIG.JWTsecret));
return res.redirect('/');
});
}
});
app.post('/', express.json(),(req,res)=>{
//if (!req.cookies.jwt) {
// We haven't logged in
//return res.redirect('/');
//}
const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);
const calendar = google.calendar({version: 'v3' , auth:oauth2Client});
const agent = new dfff.WebhookClient({
request : req,
response : res
})
function welcome(agent){
agent.add("Hi")
}
function listEvents(agent){
calendar.events.list({
'calendarId': 'primary',
'auth':oauth2Client,
'timeMin': (new Date()).toISOString(),
'showDeleted': false,
'singleEvents': true,
'maxResults': 10,
'singleEvents': true,
'orderBy': 'startTime'
}).then((err,response)=> {
let events = response.result.items;
if (events.length > 0) {
for (let i = 0; i < events.length; i++) {
var event = events[i];
var when = event.start.dateTime;
if (!when) {
when = event.start.date;
}
return agent.add('Be ready for '+ event.summary + ' event '+ 'at ' + when )
}}
else {
return agent.add('You dont have any upcoming events.');
}
});
}
let intenMap = new Map()
intenMap.set('Default_Welcome_Intent',welcome)
intenMap.set('Remind_me',listEvents)
agent.handleRequest(intenMap)
});
// Listen on the port defined in the config file
app.listen(CONFIG.port, function () {
console.log(`Listening on port ${CONFIG.port}`);
});
Whenever listEvents function is processed I'm getting (Error: No responses defined for platform null) any idea why?
The issue is that listEvents() does an asynchronous operation that returns a Promise (the call to calendar.events.list()), which you handle through the .then() block, but you don't have a way for the agent.handleRequest() function to know this and to wait for the Promise to complete. To do this, you need to return a Promise.
Fortunately, in your case, the solution is straightforward, since the call and then() chain return a Promise, you can just return it. This would be done by adding the return keyword before calendar.events.list(). It might look something like this:
function listEvents(agent){
return calendar.events.list({
// Parameters go here
}).then((err,response)=> {
// Code to handle response and return a message go here
});
}

My Dialogflow Web service isn't detecting followup intents [Nodejs]

I have a default fallback intent that has a followup fallback intent that is attached to it (its a followup and also linked via output and input contexts). When I use the Dialogflow console to test it this feature it works well. However, when I go through my API I don't get the same effect. Can someone tell me where I am going wrong in my code?
"use strict";
const express = require("express");
const bodyParser = require("body-parser");
const app = express().use(bodyParser.json()); // creates http server
const { JWT } = require("google-auth-library");
const dialogflow = require("dialogflow");
const uuid = require("uuid");
const token = "token"; // type here your verification token
const result = "";
app.get("/", (req, res) => {
// check if verification token is correct
if (req.query.token !== token) {
return res.sendStatus(401);
}
// return challenge
return res.end(req.query.challenge);
});
app.post("/", (req, res) => {
// check if verification token is correct
if (req.query.token !== token) {
return res.sendStatus(401);
}
// print request body
console.log(req.body);
//var request = req;
//console.log(req.body.result.parameters.testing);
console.log(req.body.result.resolvedQuery);
runSample();
/**
* Send a query to the dialogflow agent, and return the query result.
* #param {string} projectId The project to be used
*/
async function runSample(projectId = "projectid") {
// A unique identifier for the given session
const sessionId = uuid.v4();
// Create a new session
const sessionClient = new dialogflow.SessionsClient();
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
// The query to send to the dialogflow agent
text: req.body.result.resolvedQuery,
// The language used by the client (en-US)
languageCode: "en-US"
}
}
};
// Send request and log result
const responses = await sessionClient.detectIntent(request);
console.log("Detected intent");
const result = responses[0].queryResult;
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
console.log(` Intent: ${result.intent.displayName}`);
//first not matched user query
if (result.intent.displayName === 'Default Fallback Intent')
{
result.fulfillmentText = 'Sorry, can you rephase your question?';
//result.fulfillmentText = 'Sorry, I am unable to answer your question';
}
if (result.intent.displayName === 'Default Fallback Intent - fallback')
{
//result.fulfillmentText = 'Sorry, can you rephase your question?';
result.fulfillmentText = 'Sorry, I am unable to answer your question';
}
// return a text response
const data = {
responses: [
{
type: "text",
elements: [result.fulfillmentText]
}
]
};
// return a text response
res.json(data);
} else {
console.log(` No intent matched.`);
}
}
});
// listen for requests :)
const listener = app.listen(3000, function() {
console.log("Your app is listening on port " + listener.address().port);
});
It does not look like you are either looking at the Context that is sent back as part of result, nor sending any Context in your call to sessionClient.detectIntent(request);. Dialogflow only maintains state in Contexts, in the same way a web server might maintain state through Cookies, so it does not know that an Output Context was set unless you send it as an Input Context the next round.
Because of this, every Fallback ends up being treated as the "Default Fallback Intent".

"Cannot set headers after they are sent"-error although using standard Express procedure

I have a quite standard line of code to send a status and a json, but Express keeps returning the error Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client, pointing it to this line: res.status(201).json(result)
Claiming the error is resulted by the .json(result) part.
I've checked and eliminated anything looking like another res.send() in the code, so there's no double res:s. Checked the other stackoverflow-questions, all seemed to boil down to there being a double res.send of some sort.
blogRouter.post('/', async (req, res) => {
const decryptToken = jwt.verify(req.token, process.env.SECRET)
const user = await User.findById(decryptToken.id)
const body = req.body
const blog = new Blog({ title: body.title, author: body.author, url: body.url, likes: body.likes, user: user.id })
const result = await blog.save()
user.blogs = user.blogs.concat(result._id)
await user.save()
res.status(201).json(result)
})

Resources