Search code examples
node.jsfirebaseexpresssession-cookiesfirebase-hosting

Trouble with post requests when i deploy Firebase functions and hosting. When i post request it could not verify the authentication flow


I check authentication flow and all get and post requests on my localhost is perfect. But after i deploy Firebase functions and hosting, only get requests are success post request failed. Any one can help me what is the problem?

Browser result after post request fails

TypeError: Cannot read properties of undefined (reading 'isLoggedIn')
    at C:\Users\helez\appAdmin-Artvinca\functions\app.js:118:34
    at Layer.handle_error (C:\Users\helez\appAdmin-Artvinca\functions\node_modules\express\lib\router\layer.js:71:5)
    at trim_prefix (C:\Users\helez\appAdmin-Artvinca\functions\node_modules\express\lib\router\index.js:326:13)
    at C:\Users\helez\appAdmin-Artvinca\functions\node_modules\express\lib\router\index.js:286:9
    at Function.process_params (C:\Users\helez\appAdmin-Artvinca\functions\node_modules\express\lib\router\index.js:346:12)
    at next (C:\Users\helez\appAdmin-Artvinca\functions\node_modules\express\lib\router\index.js:280:10)
    at Layer.handle_error (C:\Users\helez\appAdmin-Artvinca\functions\node_modules\express\lib\router\layer.js:67:12)
    at trim_prefix (C:\Users\helez\appAdmin-Artvinca\functions\node_modules\express\lib\router\index.js:326:13)
    at C:\Users\helez\appAdmin-Artvinca\functions\node_modules\express\lib\router\index.js:286:9
    at Function.process_params (C:\Users\helez\appAdmin-Artvinca\functions\node_modules\express\lib\router\index.js:346:12)

app.js

require('dotenv').config();

const functions = require("firebase-functions");
const path = require('path');

const express = require('express');
const cors = require('cors');

const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const csrf = require('csurf');
const flash = require('connect-flash');
const multer = require('multer');

const errorController = require('./controllers/error');
const UserAdmin = require('./models/userAdmin');

const MONGODB_URİ = process.env.MONGO_DB

const app = express();
// Automatically allow cross-origin requests
app.use(cors({ origin: true }));

const store = new MongoDBStore({
  uri: MONGODB_URİ,
  collection: 'sessions',
});
const csrfProtection = csrf();

const fileStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'images');
  },
  filename: (req, file, cb) => {
    cb(null, new Date() + '-' + file.originalname);
  }
});

const fileFilter = (req, file, cb) => {
  if (file.mimetype === 'image/png' || file.mimetype === 'image/jpg' || file.mimetype === 'image/jpeg' ) {
    cb(null, true);
  } else {
    cb(null, false);
  };
};

app.set('view engine', 'ejs');
app.set('views', 'views');



const adminRoutes = require('./routes/admin');
const authRoutes = require('./routes/auth');

app.use(bodyParser.urlencoded({ extended: false }));
app.use(multer({storage:fileStorage, fileFilter:fileFilter}).single('image'));
app.use(express.static(path.join(__dirname, 'design'))); 
app.use('/images', express.static(path.join(__dirname, 'images'))); 
app.use(session({
  name: "__session",
  secret: 'my-secret', 
  resave: false, 
  saveUninitialized: false,
  store: store 
}))

app.use(csrfProtection);
app.use(flash());

app.use((req, res, next) => {
  res.locals.isAuthenticated = req.session.isLoggedIn;
  res.locals.csrfToken = req.csrfToken();
  console.log("locals", res.locals)
  next();
});

app.use((req, res, next) => {
  if (!req.session.user) {
    return next();
  }
  UserAdmin.findById(req.session.user._id)
    .then(user => {
      if (!user) {
        return next();
      }
      req.user = user;
      next();
    })
    .catch(err => {
      next(new Error(err));
    });
});

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Method', 'GET, POST, PUT, PATCH, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  next();
});

app.use('/admin', adminRoutes);
app.use(authRoutes);

app.get('/500', errorController.get500);

app.use(errorController.get404);

app.use((error, req, res, next) => {
  res.status(500).render('500', {
    pageTitle: 'Error!',
    path: '/500',
    isAuthenticated: req.session.isLoggedIn
  });
  //console.log("app.js error", error)
});

mongoose
  .connect(
    MONGODB_URİ
  )
  /* .then(result => {
   UserAdmin.findOne().then(user => {
      if (!user) {
        const user = new UserAdmin({
          name: 'Demir',
          email: 'demir@test.com'
        });
        user.save();
      }
    });
    app.listen(5000);
  })*/
  .catch(err => {
    console.log(err);
  });

exports.app = functions.https.onRequest(app);

//controllers

exports.getwords = (req, res, next) => {
  const page = +req.query.page || 1;
  let totalItems;
  Words.find().countDocuments().then(numWords => {
    totalItems = numWords;

    return Words.find()
      .sort({ harf: "asc" }) 
      .skip((page - 1) * ITEM_PER_PAGE)
      .limit(ITEM_PER_PAGE)
    // .select('title price -_id')
    // .populate('userId', 'name')
  })
    .then(words => {
      res.render('admin/words', {
        content: words,
        pageTitle: 'Words',
        path: '/admin/words',
        currentPage: page,
        hasNextPage: ITEM_PER_PAGE * page < totalItems,
        hasPreviousPage: page > 1,
        nextPage: page + 1,
        previousPage: page - 1,
        lastPage: Math.ceil(totalItems / ITEM_PER_PAGE),
      });
    })
    .catch(err => {
      const error = new Error(err);
      error.httpStatusCode = 500;
      return next(error);
    });
};

exports.addWords = (req, res, next) => {
  const harf = req.body.harf;
  const wordsName = req.body.word;
  const description = req.body.description;

  const errors = validationResult(req);

  if (!errors.isEmpty()) {
    console.log(errors.array());
    return res.status(422).render('admin/add-word', {
      pageTitle: 'Add Word',
      path: '/admin/add-word',
      editing: false,
      hasError: true,
      content: {
        harf: harf,
        word: wordName,
        description: description,
      },
      errorMessage: errors.array()[0].msg,
      validationErrors: errors.array()
    });
  }

  const word= new Words({
    harf: harf,
    word: wordName,
    description: description,
  });

  word
    .save()
    .then(result => {
      res.redirect('/admin/words');
    })
    .catch(err => {
      const error = new Error(err);
      error.httpStatusCode = 500;
      return next(error);
    });
}

//routes

router.get('/words', isAuth, adminController.getWords);
router.post('/add-word',
  [
    body('harf')
      .isString()
      .isLength({ max: 1 })
      .trim(),

    body('word')
      .isString()
      .isLength({ min: 2 })
      .trim(),

    body('description')
      .isLength({ min: 5, max: 5000 })
      .trim()
  ], isAuth, adminController.addWord);

// add-word.ejs

<%- include('../includes/head.ejs') %>
<link rel="stylesheet" href="/css/forms.css">
<link rel="stylesheet" href="/css/product.css">
</head>
<body>
  <%- include('../includes/navigation.ejs') %>

    <main>
        
        <% if (errorMessage) { %>
            <div class="user-message user-message--error"><%= errorMessage %></div> 
        <% } %>
        <form class="product-form" action="/admin/add-word" method="POST" enctype="multipart/form-data">
 
                        
              <select id="harf" name="harf" class="form-select" aria-label="Default select example" onchange="myFunction()"> 
                <% function myFunction() { %>
                    var x = document.getElementById("harf").value;
                    document.getElementById("harf").innerHTML = x;
                 <% } %>
                <option selected >Harf Seçiniz</option>
                <% for (let i=0; i < harfler.length; i++) { %> 
                                      
                    <option value="<%= harfler[i] %>" 
                         > <%= harfler[i] %> </option>
                    <% } %>
            
              </select>
 
            <!-- <div class="form-control">
                <label for="harf">Harf</label>
                <input 
                class="<%= validationErrors.find(e => e.param === 'harf') ? 'invalid' : '' %>"
                type="text" 
                name="harf" 
                id="harf" 
                value=""
                 >
            </div> -->
            <div class="form-control">
                <label for="word">New Word</label>
                <input 
                class="<%= validationErrors.find(e => e.param === 'word') ? 'invalid' : '' %>"
                type="text" 
                name="word" 
                id="word" 
                value="">
            </div>
            <div class="form-control">
                <label for="description">Description</label>
                <textarea 
                    class="<%= validationErrors.find(e => e.param === 'description') ? 'invalid' : '' %>"
                    name="description" 
                    id="description" 
                    rows="10"></textarea>
            </div>
            <% if (editing) { %>
                <input type="hidden" value="<%= content._id %>" name="selectedId">
            <% } %>
            <input type="hidden" name="_csrf" value="<%= csrfToken %>">
            <button class="btn btn-outline-primary btn-sm" type="submit">Add Word</button>
            
        </form>
    </main>
    <%- include('../includes/end.ejs') %>

Solution

  • finally I find the problem and solution

    1.) csurf library creates csrfToken() but this blocks nodejs working. Maybe this is because of the csurf deprecated note: in app.js I forgot the csrfToken line below but this is not the issue.

    res.locals.isAuthenticated = await req.session.isLoggedIn;
    res.locals.csrfToken = req.csrfToken();
    

    RESULT: you must stop using csurf

    2)in html pages I use post method with the line below

    <input type="hidden" name="_csrf" value="<%= csrfToken %>">
    

    RESULT: You must stop using the input line

    3)I prefer to use cors() library instead of csurf() for csrf attacks

    4) if you use firebase hosting and functions together. ****a)You should delete index.hmtl file in the public folder. ****b)you should add rewrite to firebase.json. (my functions name admin)

    {"functions": [
    {
      "source": "functions",
      "codebase": "function-test",
      "ignore": [
        "node_modules",
        ".git",
        "firebase-debug.log",
        "firebase-debug.*.log"
      ]
    }],"hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "**",
        "function": "admin",
        "region": "us-central1"
      }
    ],
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]}}
    

    c) finally you need to change all routes without functions name (in my case admin) login example: instead of using test.com/admin/login you must use test.com/login

    5)if you prefer to use sessions with firebase auth you need to specify the name:__session otherwise it will not work because of the google cdn.

    app.use(session({name: "__session",secret: 'my-secret',  resave: false, saveUninitialized: false,store: store }))
    

    6) The last important issue which you can face in html

    enctype="multipart/form-data"
    

    if you use "multipart/form-data" you need to use third party library like multer otherwise it will not work. you can read enter link description here

    Other answers talk about the middleware to use on the server side. My answer attempt to provide developers with a simple playbook to debug the problem themselves.
    

    In this question, the situation is:

    You have a form on the client side The data is sent by clicking on Submit button and you don't use JavaScript to send requests (so no fetch, axios, xhr,... but we can extend the solution for these cases later) You use Express for the server side You cannot access data in your Express code. req.body is undefined There are some actions that you can do to find the solution by yourself:

    Open the Network tab and search for your request. Check the request header Content-Type. In this situation (form submit), the header is likely application/x-www-form-urlencoded or multipart/form-data if you send a file (with enctype="multipart/form-data" in the form tag) Now check your Express code if you use the appropriate middleware to parse incoming requests. If your Content-Type is application/x-www-form-urlencoded then you should have app.use(express.urlencoded({extended: true})) in your code.

    If your Content-Type is multipart/form-data: because Express doesn't have a built-in middleware to parse this kind of request, you should another library for that. multer is a good one.

    If you have done all the steps above, now you can access data in req.body :). If you have problems with the syntax, you should check the Express document page. The syntax could be changed because this question is posted a long time ago. Now, we can extend this simple playbook for other cases that involve JavaScript code. The key is to check the request Content-Type. If you see application/json then you should use app.use(express.json()) in your code.

    In summary, find your request Content-Type, then use the appropriate middleware to parse it.

    Share Edit Follow answered Aug 9, 2022 at 2:31 Đăng Khoa Đinh's user avatar Đăng Khoa Đinh 4,75833 gold badges1414 silver badges32