0

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.

Nate Beers
  • 1,355
  • 2
  • 13
  • 22
  • have you tried inspecting the headers your api is sending? Are you getting the same message when the user is authenticated vs. unauthenticated? – Peter Aug 02 '19 at 18:00
  • [This post](https://stackoverflow.com/a/48123354/6129793) has tons of useful info that will help you debug this. – Peter Aug 02 '19 at 18:01
  • @Peter, i have figured out that the headers are not sent until after ```UsersService.getUserFavorites``` is called by logging ```res.headersSent``` inside the ```.then()```, but nothing inside getUserFavorites should be sending headers. – Nate Beers Aug 02 '19 at 18:21

1 Answers1

0

the problem was that at the very end of the patch route, I had a next(). Once i removed that, it worked fine.

Nate Beers
  • 1,355
  • 2
  • 13
  • 22