app.post works, but router.post does not? - javascript

I'm logging in on the front end with React. It goes to my node/Express/Passport Backend. Im able to log in successfully when it goes to the backend. but then im unable to communicate that back to the front end? I keep getting 404's and whatnot on the front end. I've tried everything, importing everything on the app.js (app.use) to the index.js (router.use) file. the only way i can get it to work is moving the router.post"login" to the app.js file and using it as a app.post, and then it only works when i put the route under the "app.use(routes)". My register post route works fine too, though. So im lost. I've delved into the cors and body parser stuff and the configs() and man, everything. its a deep deep rabbit hole. I need som e guidance here and explanation if thats ok. I've been working really hard on this and really need some mentor help here. I can include the passport middleware thats in another file, but i dont think thats the issue since im able to console.log ity logging me in at the end of the middleware, then console.logging in the last req res function. Thank you so much for helping!
p.s: Really the issue is "headers already sent", because when i try to sendStatus(200) in every which way possible, its not working (when the post route is "router.post". app.post works perfectly fine)
ps.s.s: youll probably see a lot of repeated and/or unneeded importing, but i dont want to fix that at the moment, that was just me trying every which way to get it to work. I can fix that later.
app.js
const express = require("express");
const app = express();
const mongoose = require("mongoose");
const session = require("express-session");
const passport = require("passport");
const crypto = require("crypto");
const routes = require("./routes");
const connection = require("./config/database");
const cors = require("cors");
app.use(cors({ origin: "http://localhost:3001", credentials: true }));
const User = mongoose.models.User;
const bodyParser = require("body-parser");
const MongoStore = require("connect-mongo")(session);
require("dotenv").config();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const sessionStore = new MongoStore({
mongooseConnection: mongoose.connection,
collection: "sessions",
});
app.use(
session({
secret: "zdfbdaf",
resave: true,
saveUninitialized: true,
store: sessionStore,
cookie: {
cookie: { secure: false },
maxAge: 1000 * 60 * 60 * 24,
},
})
);
require("./config/passport");
app.use(passport.initialize());
app.use(passport.session());
app.use(routes);
app.post(
"/login",
passport.authenticate("local"),
(req, res) => {
console.log("working");
res.status(200).send();
}
);
app.listen(3000);
index.js
const router = require("express").Router();
const passport = require("passport");
const bodyParser = require("body-parser");
const genPassword = require("../lib/passwordUtils").genPassword;
const connection = require("../config/database");
const mongoose = require("mongoose");
const User = mongoose.models.User;
const isAuth = require("./authMiddleware").isAuth;
// cors is needed with router.use else you have to put routes on the app.js
// const cors = require("cors");
// router.use(cors({ origin: "http://localhost:3001", credentials: true }));
// const isAdmin = require("./authMiddleware").isAdmin;
router.use(bodyParser.urlencoded({ extended: false }));
/**
* -------------- Post ROUTES ----------------
*
*/
router.post(
"/login",
passport.authenticate("local"),
(req, res) => {
console.log("working");
res.sendStatus(200);
}
);
router.post("/register", (req, res) => {
const saltHash = genPassword(req.body.repeatPassword);
const salt = saltHash.salt;
const hash = saltHash.hash;
const newUser = new User({
username: req.body.email,
hash: hash,
salt: salt,
});
newUser.save().then((user) => {});
res.sendStatus(200);
});
module.exports = router;

Try to remove the app.post method call, and use this to define sub-router routes.
app.use('/', routes);
Currently, you have two handlers for the same route, that can cause bugs.

Related

How to solve: "ForbiddenError: invalid csrf token"

I have problems with setting up csrf. I hope that someone can point me in the right direction.
I'm using next.js with express.js.
When I refresh the page following happens:
I get a _csurf cookie (dev tools > application > cookies)
a csrf token is logged in my console (-> see last code snipped from context)
when I make a POST request (-> see routes/index.js f.ex. "/aignupQ"), I get the error "Invalid csurf token"; in the request header I can see the _csrf cookie
when I refresh the page and make the POST request again everything works.
I'm really confused by the error and really don't understand what is wrong. Here is some relevant code:
server.js:
require("dotenv").config();
const express = require("express");
const next = require("next");
const bodyParser = require("body-parser");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const routes = require('./routes');
const csrf = require("csurf");
const csrfProtection = csrf({
cookie: true,
});
//next.js configuration
const dev = process.env.NODE_DEV !== "production";
const nextApp = next({ dev });
const port = 3000;
const handle = nextApp.getRequestHandler();
nextApp.prepare().then(() => {
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.use(cookieParser());
app.use((err, req, res, next) => {
res.status(500).send("Something went wrong!");
});
app.use(csrfProtection);
app.use('/api', routes);
app.get("*", (req, res) => {
return handle(req, res);
});
//start server
app.listen(port, (err) => {
if (err) throw err;
console.log(`listening on port ${port}`);
});
});
routes/index.js:
const express = require('express')
const router = express.Router()
const getCsrfToken = require('../controllers/csrf')
const postSignupQ = require("../controllers/postSignupQ");
const attachUser = require("../middleware/attachUser");
router.get("/csrfToken", getCsrfToken.getCsrfToken);
router.use(attachUser);
router.post("/signupQ", postSignupQ.postSignupQ);
module.exports = router
controllers/csrf.js
const getCsrfToken = (req, res) => {
res.json({ csrfToken: req.csrfToken() });
};
module.exports = { getCsrfToken };
context - here I console.log(csrf):
useEffect(() => {
const getCsrfToken = async() => {
const { data } = await axios.get('api/csrfToken');
console.log("csurf", data);
axios.defaults.headers['X-CSRF-Token'] = data.csrfToken;
};
getCsrfToken();
}, []);
I don't understand why I get the error message, when I make a POST request for the first time and when I refresh the page everything works. What's the problem and how can I solve this?
EDIT
Thanks to Jack Yu the code snippets above are working. Maybe it can help someone else..
EDIT
I also found your api path might be wrong. In your axios is await axios.get('csrfToken'). But, I saw /api/csrfToken in your router. Change it to await axios.get('/api/csrfToken')
Original Answer
In csurf package, when you use csurf({cookie: true}) with cookie mode in middleware at multiple times, it'll break the csrf token in response header with first time post. You could take a look for more detail in CSRF doesn't work on the first post attempt, I've explain the reason in that post. So, there are two solutions you could use.
Solution 1
According to the comments, you use app.use(csruf({cookie: true})) in server.js and router/index.js. Remove the following line in your router/index.js. When you setup csruf in server.js, you could use req.csrfToken() in controllers/csrf.js without setting up csruf again.
const csrf = require("csurf");
const csrfProtection = csrf({
cookie: true,
});
router.use(csrfProtection);
Solution 2
You'll need to use express-session package. Add following code before the csurf. If you have .use(csrf({cookie: true})) in your routes/index.js, remove it.
const session = require('express-session')
// mark1: change it to false
const csrfProtection = csrf({
cookie: false,
});
// blablabla ... other code
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.use(cookieParser());
// mark2: put here
app.use(session({
name: "test",
secret: "test",
cookie: { maxAge: 3 * 60 * 60 * 1000 },
resave: false,
saveUninitialized: false
}))
// put here
app.use((err, req, res, next) => {
res.status(500).send("Something went wrong!");
});
app.use(csrfProtection);
app.use('/api', routes);
Then change {cookie: true} to {cookie: false} in all csurf setting. When you use session mode, you could use csruf({cookie: false}) many times in middleware.
We need to pass a cookie in header like below:
headerMaps.put("cookie", "_csrf=value; connect.sid=value; csrfToken=value")

How to set different bodyparser on specific express route

I am using express 4.
in my server.js I have express.json() middleware
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const errorHandler = require('./_helpers/error-handler');
const app = express();
const port = process.env.PORT || 5000;
app.use(cors());
app.use(express.json()); /////////////////////////////////////////
const uri = process.env.ATLAS_URI;
mongoose.connect(uri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, useFindAndModify: true});
const connection = mongoose.connection;
connection.once('open', () => {
console.log("MongoDB database connection established successfully");
});
// routers
app.use('/api/users', require('./routes/api/users/users.controller'));
app.use('/api/orders', require('./routes/api/orders/orders.controller'));
app.use('/shopify/app', require('./routes/shopify/app/shopify.controller'));
app.use('/shopify/app/webhooks', require('./routes/shopify/app/webhooks/webhooks.controller')); ///////////////
app.use(errorHandler);
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
but for '/shopify/app/webhooks' route I need to get raw body so I can create hash
but so far I am receiving Object because I have express.json() middleware.
this is my webhooks.controller.js file
const express = require('express');
const router = express.Router();
const crypto = require('crypto')
const SHOPIFY_API_SECRET_KEY = process.env.SHOPIFY_API_SECRET_KEY;
// router.use(express.raw({ type: "application/json"}));
// routes goes here
router.post('/app/uninstalled', express.raw({ type: 'application/json' }), async (req, res, next) => {
const hmac = req.get('X-Shopify-Hmac-Sha256')
console.log(req.body);
// create a hash using the body and our key
const hash = crypto
.createHmac('sha256', SHOPIFY_API_SECRET_KEY)
.update(req.body, 'utf8', 'hex')
.digest('base64')
// Compare our hash to Shopify's hash
if (hash === hmac) {
// It's a match! All good
console.log('Phew, it came from Shopifify!')
res.sendStatus(200)
} else {
// No match! This request didn't originate from Shopify
console.log('Danger! Not from Shopify!')
res.sendStatus(403)
}
})
what I have tried is in webhooks.controller.js router.use(express.raw({type: "application/json"}))
i thought since I am receiving json object I can use express.raw() middleware that accepts json
but it's still not working.
You have to place this route BEFORE your app.use(express.json()) middleware and then you can apply the raw middleware directly to that route:
app.use('/shopify/app/webhooks', express.raw({/* put your options here */}), require('./routes/shopify/app/webhooks/webhooks.controller'));
Keep in mind that this line of code must go physically before your express.json() middleware.
We can get useful info for specific routes before applying body parser.
So, if you want to get raw body for stripe webhooks.
We can do like this.
app.use(bodyParser.json({
extended: true,
verify: function (req, res, buf) {
if (req.originalUrl.endsWith('/stripe/webhooks')) {
req.rawBody = buf
}
}
}))

node-postgres queries not returning anything in Express routes async routes

I'm trying to run a simple query from an express route:
var router = require('express-promise-router')()
const { Pool } = require('pg')
const pool = new Pool({
user: 'user',
password: 'password',
host: 'host',
port: 1234,
database: 'db'
})
router.get('/', async (req, res) => {
console.log('OK')
try {
const { rows } = await pool.query('Select VERSION()')
console.log(rows)
}
catch(e) {
console.log(e)
}
console.log('DONE')
})
module.exports = router
'OK' Prints after sending the request but rows, e, or 'DONE' never print. I'm following the async/await method directly from https://node-postgres.com/guides/async-express.
I've also came across a thread for koa-router where people were having issues with async await calls because of some middle-ware they added that wasn't synchronous
https://github.com/ZijianHe/koa-router/issues/358.
I'm not sure what middle-ware would cause this but here's my app.js that initializes all middle-ware:
var createError = require('http-errors');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var cors = require("cors");
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var dataRouter = require("./routes/data");
var uploadRouter = require("./routes/upload")
var fundingRouter = require('./routes/chartData/fundingOverview')
var testRouter = require('./routes/test')
var authRouter = require('./routes/auth')
var session = require('express-session')
var MongoStore = require('connect-mongo')(session)
var passport = require('passport')
const config = require('config')
const mongo = config.get('mongo')
const mongoose = require('mongoose')
mongoose.connect(mongo, {
useUnifiedTopology: true,
useNewUrlParser: true,
useFindAndModify: false
}).then(res => {
console.log('connected')
}).catch(err => {
console.log(err)
})
var express = require('express');
const mountRoutes = require('./routes')
var app = express();
const bodyParser = require('body-parser')
app.use(bodyParser.json())
mountRoutes(app)
app.use(cors())
var sessionMiddleWare = session({
secret: 'top session secret',
store: new MongoStore({ mongooseConnection: mongoose.connection }),
resave: true,
saveUninitialized: true,
unset: 'destroy',
cookie: {
httpOnly: false,
maxAge: 1000 * 3600 * 24,
secure: false, // this need to be false if https is not used. Otherwise, cookie will not be sent.
}
})
app.use(sessionMiddleWare)
// Run production React server on Node server
if(process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'))
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'))
})
}
// End Run production React Server on Node Server
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({
extended: false
}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// app.use('/upload', uploadRouter)
// app.use('/', indexRouter);
// app.use('/users', usersRouter);
// app.use('/data', dataRouter)
// app.use('/funding', fundingRouter)
// app.use('/login', usersRouter)
// app.use('/auth', authRouter)
// catch 404 and forward to error handler
// app.use(function(req, res, next) {
// next(createError(404));
// });
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
I'm mounting the routes directly after body parser. That's the only middle-ware that's called before the routes and is required in order for me to get data into the back end.
I'm able to execute that simple query by putting it into a script file and running 'node test.js' (I.E without the router) and it works fine so I know it's not a problem with node-postgre.
I know this is a problem with the call stack not being totally synchronous but I'm confused as to what's not at this point. I even made the axios call on the front-end async/await with no luck (I don't think it was necessary though).
Any guidance would be help a lot.
EDIT:
I created a fresh express skeleton and hooked my front-end to make a call to a route on the new express server with the same code, it worked. It led me to discover the call wasn't being completed because I was running the server with Nodemon. When I start the server using 'yarn start' the async calls get processed correctly. The question now is what in nodemon makes async router calls not work?
You need to finish the request/response cycle in your middleware.
So in your code:
router.get('/', async (req, res) => {
console.log('OK')
try {
const { rows } = await pool.query('Select VERSION()')
console.log(rows)
res.status(200).json(rows)
}
catch(e) {
console.log(e)
res.status(500).json(e)
}
console.log('DONE')
})

Failed to connect to localhost 5000

I am trying to connect my node app with MongoDB. The code seems to execute as I get the output
Server is running on port 5000
MongoDB database connection established successfully
on the terminal
But when I try to post get from insomnia it takes about two minutes before I get the error
Error: Server returned nothing (no headers, no data)
const express = require('express');
//const bodyParser = require('body-parser');
const cors = require('cors');
const mongoose = require('mongoose');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
app.use(cors());
app.use(express.json());
const uri = process.env.ATLAS_URI;
mongoose.connect( uri, {useUnifiedTopology: true, useNewUrlParser: true}, () => { console.log("MongoDB database conection established successfully")}).catch(err => console.log(err));
const exercisesRouter = require('./routes/exercises');
const usersRouter = require('./routes/users');
app.use('/exercises', exercisesRouter);
app.use('/users', usersRouter);
//emitter.setMaxListeners();
app.listen(port, () => {
console.log('Server is running on port : ' + port);
});
I am following a tutorial and these are the other files I have
exercise.model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const exerciseSchema = new Schema({
username: {type: String,required: true},
description: {type: String,required: true},
duration: {type: Number,required: true},
date: {type: Date,required: true},
},
{
timestamps: true,
});
const Exercise = mongoose.model('Exercise', exerciseSchema);
module.exports = Exercise;
exercises.js
const router = require('express').Router();
let Exercise = require('../models/exercise.model');
router.route('/').get((req, res) => {
Exercise.find()
.then(exercises => res.json(exercises))
.catch(err => res.status(400).json('Error: ' + err));
});
router.route('/add').post((req, res) => {
const username = req.body.username;
const description = req.body.description;
const duration = Number(req.body.duration);
const date = Date.parse(req.body.date);
const newExercise = new Exercise({
username,
description,
duration,
date,
});
newExercise.save()
.then(() => res.json('Exercise added!'))
.catch(err => res.status(400).json('Error: ' + err));
});
module.exports = router;
users.js
const router = require('express').Router();
let User = require('../models/user.model');
router.route('/').get((req,res) => {
User.find()
.then(users => res.json(users))
.catch(err => res.status(400).json('Error: ' + err));
});
router.route('/add').post((req,res) => {
const username = req.body.username;
const newUser = new User({username});
newUser.save()
.then(() => res.join('User added!'))
.catch(err => res.status(400).json('Error: ' + err));
})
module.exports = router;
user.model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlenght: 3
},
},{
timestamps: true,
});
const User = mongoose.model('User', userSchema);
module.exports = User;
I also get Cannot GET / when I go to http://localhost:5000 on my browser and in the inspector errors it says Refused to load the image 'http://localhost:5000/favicon.ico' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback. This may be a lot but I am trying to learn how to connect backend with front end and would appreciate it if someone can guide me. Thank you in advanced.
The problem seems to be CORS related. This means that the express API you have created, will not accept calls from other domains and expects the interaction to come from the same application. As you are are using insomnia, which is a separate desktop app or something, express will block access to routes. This is the default behavior.
You need to grab the CORS middleware and set up your routes as needed. Just follow the docs.
A quick test to allow all access:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.get('/users', (req, res) => {...
...
Ther is an other method to add CORS
app.use(function (req, res, next) {
//Enabling CORS
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type,
Accept, x-client-key, x-client-token, x-client-secret, Authorization");
next();
});
I'm also going through the same tutorial: https://www.youtube.com/watch?v=7CqJlxBYj-M
The problem is with app.use(express.json());
Comment that out and it'll work fine. But then you won't be able to parse JSON data when you're doing post requests. I'm not sure why express.json() doesn't work.
But either way you can use Body-Parser to solve that problem. First user npm i body-parser to install it to your package manager in your backend folder. Then copy the below code to your index.js
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())
add this code and you'll be fine. This answer gives a detailed explanation of express.json & body-parser.
express.json() is a method inbuilt in express to recognize the incoming Request Object as a JSON Object.
I recommend using body-parser (it is an NPM package) to do the same thing. It is developed by the same peeps who built express and is designed to work with express. body-parser used to be part of express.

How to use Express middleware functions correctly in router?

In this example below, you can see that the csrfProtection and parseForm functions are passed as parameters/callbacks in the GET and POST requests...
var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
// setup route middlewares
var csrfProtection = csrf({ cookie: true })
var parseForm = bodyParser.urlencoded({ extended: false })
// create express app
var app = express()
// parse cookies
// we need this because "cookie" is true in csrfProtection
app.use(cookieParser())
app.get('/form', csrfProtection, function(req, res) { // HERE
// pass the csrfToken to the view
res.render('send', { csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function(req, res) { // AND HERE
res.send('data is being processed')
})
However, if you are using a router, like I am, how can use these same functions? I am aware that by "using" them in app.js, they are made available on the req object but in the example given above, they are required as the 2nd and 2nd & 3rd arguments of the GET and POST routes, but req isn't made available until you're inside the final callback?!
So I know you can't do the below (just as an example)... so how should you use them? Would I have to re-declare them in every routes file?
Separate routes file: routes/someroute.js
...
router
.post('/', req.body, req.csrfProtection, (req, res) => {
})
...
Thanks in advance :)
Reference: https://www.npmjs.com/package/csurf
UPDATE
Following comments below, I have made the following changes to my app.js file.
app.js
...
global.bodyParser = require('body-parser').urlencoded({extended: false});
app.use(global.bodyParser);
global.csrfProtection = csrf({ cookie: false });
...
routes/myroute.js
router
.post('/', global.bodyParser, global.csrfProtection, (req, res) => {})
However, when I restart the server I am seeing this error, which suggests that that the global function is not defined... what am I missing here? :-/
Error: Route.post() requires a callback function but got a [object Undefined]
I think you ask about sharing middlewares across all API/routes files
You can do it like this :
First in your main file lets call it server.js we use you're code
var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
// create express app
var app = express()
// setup route middlewares
app.use(bodyParser.urlencoded({ extended: false }))
// parse cookies
app.use(cookieParser())
//enable your JS API/route script.
const awesomeAPI = require('./awesomeApi.js');
app.use('/awesome', awesomeAPI );
app.listen(3000);
Now you have file let's calle it awesomeApi.js
const express = require('express');
const awesomeApi = express.Router();
awesomeApi.route('/')
.post(req,res => {
//req.body present here. And body parser middle ware works.
})
module.exports = awesomeApi;
Hope this helps.
Some links:
https://expressjs.com/en/guide/using-middleware.html
https://expressjs.com/en/4x/api.html#express

Categories