I have read through several posts regarding this same issue but I cannot figure out where I am initially sending the headers. Here is the stack trace:
Also seems weird that I am getting a 204 as it adds to the db, and then it spits out a 404. Clearly something is wrong, but i'm simply not seeing it.
I've tried adding returns to every res.json()
statement.
OPTIONS /api/users/favorites 204 1.822 ms - 0
PATCH /api/users/favorites 404 19.769 ms - 160
Unhandled rejection Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:455:11)
at ServerResponse.header (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/response.js:771:10)
at ServerResponse.send (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/response.js:267:15)
at /Users/beers/projects/node/cryptopal-api/src/users/usersRouter.js:30:38
From previous event:
at Builder.Target.then (/Users/beers/projects/node/cryptopal-api/node_modules/knex/lib/interface.js:27:24)
at /Users/beers/projects/node/cryptopal-api/src/users/usersRouter.js:19:8
at Layer.handle [as handle_request] (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/layer.js:95:5)
at next (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/route.js:137:13)
at requireAuth (/Users/beers/projects/node/cryptopal-api/src/middleware/jwt-auth.js:31:5)
at Layer.handle [as handle_request] (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/layer.js:95:5)
at next (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/layer.js:95:5)
at /Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/index.js:281:22
at Function.process_params (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/index.js:335:12)
at next (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/index.js:275:10)
at Function.handle (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/index.js:174:3)
at router (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/index.js:47:12)
at Layer.handle [as handle_request] (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/index.js:317:13)
at /Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/index.js:284:7
at Function.process_params (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/index.js:335:12)
at next (/Users/beers/projects/node/cryptopal-api/node_modules/express/lib/router/index.js:275:10)
at /Users/beers/projects/node/cryptopal-api/node_modules/body-parser/lib/read.js:130:5
at invokeCallback (/Users/beers/projects/node/cryptopal-api/node_modules/raw-body/index.js:224:16)
at done (/Users/beers/projects/node/cryptopal-api/node_modules/raw-body/index.js:213:7)
at IncomingMessage.onEnd (/Users/beers/projects/node/cryptopal-api/node_modules/raw-body/index.js:273:7)
at IncomingMessage.emit (events.js:205:15)
at endReadableNT (_stream_readable.js:1154:12)
here is my usersRouter.js
require('dotenv').config();
const express = require('express');
// const rp = require('request-promise');
const usersRouter = express.Router()
const jsonParser = express.json()
const UsersService = require('./usersService.js')
const { requireAuth } = require('../middleware/jwt-auth.js')
usersRouter
.patch('/favorites', requireAuth, (req,res,next) => {
const db = req.app.get('db');
const { coinID } = req.body;
const { user_id } = req;
console.log(res.headersSent) // EQUALS FALSE
// get current favorites for user to see if it already exists in db
UsersService.getUserFavorites(db, user_id)
.then( response => {
console.log(res.headersSent) // EQUALS TRUE
let favExists = false;
response.favorites.forEach( fav => {
if(fav == coinID)
favExists = true;
})
if(favExists){
return res.status(401).json({ error: "Coin already exists in favorites" })
}else{
UsersService.addToUserFavorites(db, user_id, coinID)
.then( response => {
return res.status(204).json({ response })
})
}
})
next()
});
module.exports = usersRouter;
As you can see, the patch
route calls a middleware function requireAuth
to authenticate a user before it will add a favorite.
Here is that file jwt-auth.js
const AuthService = require('../auth/authService.js')
function requireAuth(req, res, next) {
const authToken = req.get('Authorization') || ''
let bearerToken
if (!authToken.toLowerCase().startsWith('bearer ')) {
return res.status(401).json({ error: 'Missing bearer token' })
} else {
bearerToken = authToken.slice(7, authToken.length)
}
try {
const payload = AuthService.verifyJwt(bearerToken);
AuthService.getUserByEmail(
req.app.get('db'),
payload.sub,
)
.then(user => {
if (!user){
return res.status(401).json({ error: 'Unauthorized request' })
}
next();
})
.catch(err => {
console.error(err)
next(err)
})
req.user_id = payload.user_id;
next()
} catch(error) {
return res.status(401).json({ error: 'Unauthorized request' })
}
}
module.exports = {
requireAuth,
}
I will include the usersService.js
and authService.js
files as well because a couple functions are called within them, but I don't believe thats where the error lies.
usersService.js
:
const xss = require('xss');
const config = require('../config.js');
const UsersService = {
getUserByID(db,id){
return db('cryptopal_users')
.where({ id })
.first()
},
getUserFavorites(db,id){
return db('cryptopal_users')
.where('id', id)
.first()
},
addToUserFavorites(db,id,favorites){
return db('cryptopal_users')
.where('id', id)
.update({
favorites: db.raw('array_append(favorites, ?)', [favorites])
})
},
}
module.exports = UsersService;
authService.js
const xss = require('xss');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const config = require('../config.js');
const AuthService = {
validatePassword(password){
if(password.length < 6){
return "Password must be at least 6 characters"
}
},
hashPassword(password){
return bcrypt.hash(password,12);
},
comparePasswords(password,hash){
return bcrypt.compare(password,hash);
},
createJwt(subject, payload) {
return jwt.sign(payload, config.JWT_SECRET, {
subject,
expiresIn: config.JWT_EXPIRY,
algorithm: 'HS256',
})
},
checkEmailUnique(db,email){
return db('cryptopal_users')
.where({ email })
.first()
.then(user => !!user)
},
insertUser(db,user){
return db
.insert(user)
.into('cryptopal_users')
.returning('*')
.then( ([user]) => user )
},
serializeUser(user){
return {
id: user.id,
name: xss(user.name),
email: xss(user.email),
date_created: new Date(user.date_created),
}
},
getUserByEmail(db,email){
return db('cryptopal_users')
.where({ email })
.first()
},
verifyJwt(token) {
return jwt.verify(token, config.JWT_SECRET, {
algorithms: ['HS256'],
})
},
}
module.exports = AuthService;
I believe the issue lies somewhere within the jwt-auth.js
file, but not 100% sure. The code does get all the way through to the end and it inserts the favorite into the database after authenticating the users, but then throws an error about the headers.