I'm working on a small petition project, I would like to set a cookie when a user signs it, so if he tries to access the page again, it should redirect him already to a "thanks page". If the user didn't, then he can proceed to sign it.
I'm getting the Cannot set headers after they are sent to the client error, maybe someone understands what I'm doing wrong. Down below the js code.
const express = require("express");
const { getSignatures, addSignature } = require("./db.js");
const app = express();
const cookieParser = require("cookie-parser");
app.use(express.urlencoded({ extended: false }));
app.use(express.static("./public"));
const hb = require("express-handlebars");
app.engine("handlebars", hb());
app.set("view engine", "handlebars");
app.use(cookieParser());
app.get("/", (req, res) => {
res.redirect("/petition");
});
app.get("/petition", (req, res) => {
let hasSigned = req.cookies.petition_signed;
if (!hasSigned) {
res.render("petition", {
layout: "main",
});
} else {
res.redirect("/thanks");
}
});
app.post("/petition", (req, res) => {
const { firstName, lastName, signature } = req.body;
//console.log("req.body: ", req.body);
if (firstName === "" || lastName === "" || signature === "") {
res.render("petitionerror", {
error: "Please fill out all the elements before submitting",
});
} else {
addSignature(firstName, lastName, signature).then((data) => {
res.cookie("petition_signed", "yes");
console.log(data);
});
res.redirect("/thanks");
}
});
app.get("/thanks", (req, res) => {
res.render("thankyou", {
layout: "main",
});
});
app.get("/signers", (req, res) => {
getSignatures().then((data) => {
res.render("signers", { success: true, rows: data.rows });
});
});
app.listen(8080, () => console.log("listening on 8080"));
The way you have it structured, it is trying to do res.cookie after res.redirect because addSignature is an asynchronous function.
addSignature(firstName, lastName, signature).then((data) => {
res.cookie("petition_signed", "yes");
console.log(data);
res.redirect("/thanks");
});
You need to ensure res.cookie is called prior to redirect (which returns headers to the client)
Related
I'm trying to create an application which uses JWKS with RSA to sign cookies. I have some issues with the passport strategy I defined, when I try using it, the following error appears:
done(null, jwks.keys[0])
^
TypeError: Cannot read properties of undefined (reading '0')
My code for the passport Strategy I created is the following:
passport.use('jwt-jku', new JwtStrategy({
jwtFromRequest: req => { return (req && req.cookies) ? req.cookies.jwtCookie : null },
secretOrKeyProvider: (req, rawJwtToken, done) => {
const decoded = jwt.decode(rawJwtToken, { complete: true }) // Tiene .header y .payload
if (decoded.header.jku /* && decoded.payload.role === 'admin' */) {
axios({
method: 'get',
url: decoded.header.jku,
responseType: 'json'
}).then(function (jwks) {
console.log(jwks.toString())
done(null, jwks.keys[0])
})
} /* else if(decoded.payload.role !== 'admin') { ESTO NO VA porq no hay 'res'
req.send("You can't access the fortune teller endpoint! Only admin has access to it!")
} */
}
}, (jwtPayload, done) => { return done(null, jwtPayload ?? false) }
))
I use a file called jwks.json that contains:
{"keys":[{"kty":"RSA","kid":"tbrTvJIqhJ7jnYPdQL-LpkPUwqFltyQnWC5QGGUY8JM","use":"sig","alg":"RS256","e":"AQAB","n":"sFTW93A8v-oDFIS7oB2kzZq0RG15Oim9-FyeiTg6QBHcRb4kbTfXf0bQ3dTe-y0mVKUdZUtFyk71DNmI1INpO9y-QU2b-03KIW56As-kR6cmZeDIFwqDjceoUJreLEdEnxkObxuD5d5Qb7Zt8TS1BiRkX5vpiV32VTWpPZs0TglBv-Jdm54r1bvmrwaVUUMMi0DUptKIKLbqysl1j4OIXQSUMjRJF7OFIDfaZqvhPBNXTW7dbvxbfOq46Gpz_EqD2VBlYXOOgfXh8OhhhPkgZnbkq2HSUO1TZoiGFlq6M-Rah64D_8ESsDG2Bvk1Tx7hUCUmiba4AG2am2Gr8qcMmQ","d":"N0NHogHzkCQrtfUJhptzyJpZL-7tZOyrJJkGeLP-EZFnaxY1_WLYb4PGtxddkPPsBPBAn7vxEgAf7diJeSz8QK4klHvZzG9rT4H7zEC-WFSLBLfnjc0WQMuXtTdsNQ1a9M_aBblmY5cCt5hBqaxepZ-nM42-KfagWsE4Gt43DNN0vO7ztotY5TzKGv2R8TIz8BJIYVLVlUwvFheTDNJ97kz6EMR7keTzRcpg6pj-21l0NCLIySkRv4ZEQirW12kek7hgtI0A-mCiU61-GzX0S_MFEGi16Clk0kIQ6Ld2stT26xT01x0-htubgp-KEgW64GXKetOCzXrjZVivSHywPw","p":"vHSS2gMw56CgPyQmrLQNwR4cwL6QC77Zk5IE2QZlRWBDKUwOYioruqoc8t3Nop5xMcFjbNrtEb2RlMDB-lfyfJtcIWo9xh67AMcZ5Zq-FGnJBTjCry8Vf08r9jAfXzvr5dGbFnp4xV2gpuATQ3apBLy9eCkQKOVshdTbqtbKnCM","q":"74fZ4QSOQKqroCqqGnJ-MyyzTL2mSnthDzo7AmwSIZlzkgaqXO4o27uwRJVW2dxEK0XmDKNd94XNflADv1SDhu3mWcDVjkOjnc-zrVgEZjUWG3sABtwUWlSijOkmZG5jD9KPvb1ksvN0K3Xn0wcu8z1XJ9xSm4gw3NXHM2eBEhM","dp":"Zp64BL1uGQNPn3JpGOwm_T0PfSmHuM2hHwmJWEByTvffOpKu1meOTmQpxVpgiqfqjaR4JqOEKn_m0XCxcEgJxC65WXzHzKWZQRAqGuxLi0QbcZt57_kcpKmRD8semjY_-3JLGE9yik60l4o3Eyp_WRQJvG_n280qbin9wCacR68","dq":"skdAqTQE1KiyuLFIjgqtV1Vr1jBJdqJB6V2D0hexnwRhekwvvJ8eFf2qAkHMtLY2nqaVxM5LABG8ksjnIR68Dk8-5Yqa6Bn7y1dt-hTKoW_dlnKD4JcpJtpbadmWU6W9YWdsyOIP8wk0a6yD8RMlpLEBD4_yhARnqdYLP31IGbU","qi":"iIjOxBfcAwzikLRjMFQRfoEcW2RXDzi0xs2c9keut4j1DVgacVlS5J7sDHcJDsJJToHmaasix7-nl7nA5IYn6UiayzU7BqOc0D-orn8HecWWeYQ6hMZ81U80jRHmSN2cp-g4X2NtduxlGw8yoSlAKxQyEpgb5SqXYcDr5hQNgy4"}]}
I don't know what else to do, following I leave the whole code, maybe its easier to understand or something... Thank you!
const express = require('express')
const logger = require('morgan')
const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy
const JwtStrategy = require('passport-jwt').Strategy
const fortune = require('fortune-teller')
const axios = require('axios')
const cookieParser = require('cookie-parser')
const jwt = require('jsonwebtoken')
const fs = require('fs')
const path = require('path')
const jose = require('node-jose') // probar jose
const keyStore = jose.JWK.createKeyStore()
keyStore.generate('RSA', 2048, { alg: 'RS256', use: 'sig' })
.then(result => {
fs.writeFileSync(
path.join(__dirname, 'jwks.json'),
JSON.stringify(keyStore.toJSON(true))
)
// console.log(`Exported JWKSet: ${JSON.stringify(keyStore.toJSON(true), undefined, 2)}`)
})
const port = 3000
const app = express()
app.use(logger('dev'))
app.use(cookieParser())
/*
Configure the local strategy for using it in Passport.
The local strategy requires a `verify` function which receives the credentials
(`username` and `password`) submitted by the user. The function must verify
that the username and password are correct and then invoke `done` with a user
object, which will be set at `req.user` in route handlers after authentication.
*/
passport.use('local', new LocalStrategy(
{
usernameField: 'username', // it MUST match the name of the input field for the username in the login HTML formulary
passwordField: 'password', // it MUST match the name of the input field for the password in the login HTML formulary
session: false // we will store a JWT in the cookie with all the required session data. Our server does not need to keep a session, it's going to be stateless
},
function (username, password, done) {
if (username === 'walrus' && password === 'walrus') {
const user = {
username: 'walrus',
description: 'the only user that deserves to contact the fortune teller'
}
return done(null, user) // the first argument for done is the error, if any. In our case there is no error, and so we pass null. The object user will be added by the passport middleware to req.user and thus will be available there for the next middleware and/or the route handler
}
return done(null, false) // in passport returning false as the user object means that the authentication process failed.
}
))
passport.use('jwt-jku', new JwtStrategy({
jwtFromRequest: req => { return (req && req.cookies) ? req.cookies.jwtCookie : null },
secretOrKeyProvider: (req, rawJwtToken, done) => {
const decoded = jwt.decode(rawJwtToken, { complete: true }) // Tiene .header y .payload
if (decoded.header.jku /* && decoded.payload.role === 'admin' */) {
axios({
method: 'get',
url: decoded.header.jku,
responseType: 'json'
}).then(function (jwks) {
console.log(jwks.toString())
done(null, jwks.keys[0])
})
} /* else if(decoded.payload.role !== 'admin') { ESTO NO VA porq no hay 'res'
req.send("You can't access the fortune teller endpoint! Only admin has access to it!")
} */
}
}, (jwtPayload, done) => { return done(null, jwtPayload ?? false) }
))
app.use(express.urlencoded({ extended: true })) // needed to retrieve html form fields (it's a requirement of the local strategy)
app.use(passport.initialize()) // we load the passport auth middleware to our express application. It should be loaded before any route.
app.get('/', passport.authenticate('jwt-jku', { session: false, failureRedirect: '/login' }),
(req, res) => {
res.send(
"<a href='/'>Refresh</a> / <a href='/logout'>Logout</a><br><br>User: " +
req.user.sub + '<br><br>' + fortune.fortune()
)
}
)
app.get('/login',
(req, res) => {
res.sendFile('login.html', { root: __dirname })
}
)
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login', session: false }),
async (req, res) => {
const [key] = keyStore.all({ use: 'sig' })
const opt = {
compact: true,
jwk: key,
fields: {
typ: 'jwt',
jku: 'http://localhost:3000/jwks.json' // IP server
}
}
const payload = JSON.stringify({
exp: Math.floor(Date.now() / 1000) + 604800, // 1 week (7×24×60×60=604800s) from now
iat: Math.floor(Date.now() / 1000),
role: 'user'
})
const token = await jose.JWS.createSign(opt, key).update(payload).final()
// console.log(`Token sent. Debug at https://jwt.io/?value=${token}`)
res.cookie('jwtCookie', token, { httpOnly: true, secure: false }) // Store the token into the cookie
res.redirect('/')
// res.send(token)
}
)
/* app.get('/user', (req, res) => {
const user = { // Create the json object user with a name and a description
name: 'walrus',
description: 'it is what it is'
}
res.json(user) // Send the json object 'user'
}) */
app.get('/jwks.json', async (req, res) => {
const ks = fs.readFileSync('./jwks.json')
const keyStore = await jose.JWK.asKeyStore(ks.toString())
res.send(keyStore.toJSON())
})
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
I'm using NodeJS w/ Express to create a web app that records your audio using the VMSG library and posts the BLOB audio to my file system using HTTP Requests and multer. It also adds that instance of a recording into a MongoDB database.
I'm having an issue with the fetch command. It's not working unless I put an alert right after the fetch. The way I have it set up is that I have my main express app (index.js), and a router to the /recordingsDirectory (recordings.js) which is the endpoint for processing the posts. My main index HTML page uses Handlebars and uses a separate JS script (recorder.js) to 1) use the VMSG library and 2) fetch a POST to the /recordingsDirectory once someone submits the audio file w/ the name and the AudioBlob present. This is where I'm stuck. I can fetch in recorder.js with an alert line after the fetch, but I can't have the fetch on the end of the else if block by itself. I'd like to do it without this since the alert is ugly. A solution I've tried is that I tried to make the onsubmit function async and await fetch since I thought maybe it's waiting for a promise but that didn't work.
Here are the files. I commented CRITICAL and SUPER CRITICAL to the lines of code that you should check out and I think where the issues lie:
index.js
const express = require('express')
const handlebars = require('express-handlebars')
const path = require('path')
const XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest
const xhr = new XMLHttpRequest()
const db = require('./db')
const app = express()
const PORT = process.env.PORT || 8000
app.set('view engine', 'hbs')
app.engine('hbs', handlebars({
layoutsDir: path.join(__dirname, 'views', 'layouts'),
extname: 'hbs',
defaultLayout: 'index',
partialsDir: path.join(__dirname, 'views', 'partials'),
}))
app.use(express.json())
app.use(express.urlencoded({extended: false}))
app.use((err, req, res, next) => {
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
return res.status(400).send({ status: 404, message: err.message })
}
next()
})
app.get('/', (req, res) => {
res.render('main', {
title: 'Main Page'
})
})
app.get('/recordings', (req, res) => {
var database = db.get().db('AudioJungle')
database.collection('recordings').find().sort({ "date": -1 }).toArray(function(err, docs) {
res.render('recordings', {
title: 'Recordings',
recordings: docs
})
})
})
// CRITICAL
app.use('/recordingsDirectory', require('./recordings/recordings'))
app.use(express.static('public'))
app.use('/scripts', express.static(path.join(__dirname, 'node_modules', 'vmsg')))
db.connect(function(err) {
if (err) {
console.log('Unable to connect to Mongo.')
process.exit(1)
} else {
app.listen(PORT, () => console.log(`Listening on Port: ${PORT}`))
}
})
process.on('SIGINT', function() {
db.close(function () {
console.log('Disconnected on app termination');
process.exit(0);
});
});
app.use((req, res, next) => {
res.status(404).send({
status: 404,
error: 'Not found'
})
})
recordings.js (Aka the /recordingsDirectory endpoint for a fetch POST)
const express = require('express')
const router = express.Router()
const multer = require('multer')
const fs = require('fs-extra')
const db = require('../db')
const { ObjectId } = require('bson')
const moment = require('moment')
const upload = multer({
storage: multer.diskStorage({
destination: (req, file, callback) => {
let path = './public/uploads'
fs.mkdirsSync(path)
callback(null, path)
},
filename: (req, file, callback) => {
createRecording(req).then((id) => {
var file_name = id + '.mp3'
callback(null, file_name)
})
}
})
})
var type = upload.single('audio-file')
// CRITICAL
router.post('/', type, (req, res) => {
console.log('made it')
res.status(200)
res.send('OK')
})
router.delete('/delete', (req, res) => {
deleteRecording(req.body._id).then((dbResponse) => {
if (dbResponse == null || dbResponse == undefined) {
res.status(400).json({ msg: 'ID already deleted' })
} else {
res.status(200)
}
})
})
router.get('/', (req, res) => {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
recordings.findOne({"_id": ObjectId(req.query.id)}, function(err, result) {
if (err) throw err
if (result == null || result == undefined) {
return res.status(400).json({
status: 404,
error: 'Recording no longer in the database'
})
}
res.status(200)
res.json({
name: result.name,
date: result.date
})
})
})
async function createRecording(req) {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
var audioObject = {
name: req.body.name,
date: moment().format('MMMM Do YYYY, h:mm:ss a')
}
var dbResponse = await recordings.insertOne(audioObject)
return dbResponse.insertedId
}
async function deleteRecording(id) {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
var audioToDelete = {
_id: ObjectId(id)
}
var deleteResult = await recordings.deleteOne(audioToDelete)
return deleteResult
}
module.exports = router
And below is the Script the audio and name and tries to Fetch (where I need the alert for it to actually process into the /recordingsdirectory)
recorder.js
import { record } from "/scripts/vmsg.js";
let recordButton = document.getElementById("record");
var blobObj = null
recordButton.onclick = function() {
record({wasmURL: "/scripts/vmsg.wasm"}).then(blob => {
blobObj = blob
var tag = document.createElement("p")
tag.id="finishedRecording"
var text = document.createTextNode("Audio File Recorded")
tag.appendChild(text)
var element = document.getElementById("box")
element.appendChild(tag)
document.getElementById('box').appendChild(a)
})
}
let form = document.getElementById('mp3Form');
form.addEventListener("submit", submitAudio)
function submitAudio() {
var fileName = form.elements[0].value
if (fileName == "") {
alert('Please enter a name for your file')
} else if (blobObj != null) {
// CRITICAL
// SUPER CRITICAL WHERE FETCH DOESN'T WORK UNLESS I PUT AN ALERT AT THE END
const formData = new FormData()
formData.append('name', fileName)
formData.append('audio-file', blobObj)
const options = {
method: 'POST',
body: formData
}
fetch('/recordingsDirectory', options);
// If I comment an alert here, /recordingsDirectory will process the post since it console.logs 'made it'
} else {
alert('Record some Audio to upload')
}
}
Here's my file system.
Also, I'd like to mention that the fetch works properly on my Windows PC without having to add the alert, but it doesn't work without the alert on my macbook. If any one figures out a fix or an error in how I'm doing things to allow this please let me know. I've been stuck on this problem for a day now. Thanks a bunch!
I have a list of articles that have a property views and I want to increment that property in the database each time a user clicks on an article title. Currently nothing happens when I do it. Why isn't it working and how can I increment that property each time on click? Here is my React part:
const incrementViews = (id) => {
var item = posts.find(x => x._id === id);
item.views += 1;
}
<div className="post-title">
<Link to={`/post/${post._id}`}>
<h2><a href="#" onClick={() => incrementViews(post._id)}>{post.title}</a>
</h2>
</Link>
</div>
and my server.js:
// Requiring the dependencies
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
require('dotenv').config();
const mongoose = require('mongoose');
const PORT = process.env.PORT || 3001;
const BASE_URL = process.env.REACT_APP_BASE_URL;
console.log(BASE_URL)
const itemRoutes = express.Router();
let Comment = require('./comment.model');
app.use(cors());
app.use(bodyParser.json());
mongoose.connect(BASE_URL, { useNewUrlParser: true })
const connection = mongoose.connection;
connection.once('open', function () {
console.log('Connection to MongoDB established succesfully!');
});
let collection = connection.collection("posts_with_tags_test");
collection.createIndex(
{
postContent: 'text',
title: 'text'
}
);
// Serve static assets
if (process.env.NODE_ENV === 'production') {
app.use(express.static('build'));
}
itemRoutes.route('/').get(async (req, res) => {
let collection = connection.collection("posts_with_tags_test");
let response = await collection.find({})
.toArray();
res.send(response);
});
itemRoutes.route('/search').post(async (req, res) => {
let result = await connection.collection("posts_with_tags_test").find({
$text: {
$search: req.body.searchString
}
}).toArray();
res.send(result);
});
itemRoutes.route("increment/:id"").post(async (req, res) => {
const { id } = req.params;
collection.updateOne({ _id: id }, { $inc: { views: 1 } });
return res.status(200).json({ msg: "OK" });
});
itemRoutes.route('/comments').get(async (req, res) => {
let collection = connection.collection("comments");
let response = await collection.find({})
.toArray();
res.send(response);
});
itemRoutes.route('/comments')
.post((req, res) => {
res.setHeader('Content-Type', 'application/json');
let comment = new Comment(req.body);
comment.save()
.then(comment => {
res.status(200).json({ comment })
})
.catch(err => {
res.status(400).send('failed')
})
});
app.use('/', itemRoutes);
app.use('/comments', itemRoutes);
app.use('/search', itemRoutes);
app.use('/increment', itemRoutes);
app.listen(PORT, function () {
console.log('Server is running on' + ' ' + PORT);
})
I think there are two problems in frontend and backend respectively.
Front-end
You should use post variable as a state variable so as to re-render then component when changes are made on post.
Back-end
There is no issue with increasing view in your code.
Here, you need to return success status.
The function incrementViews only increments views on the frontend and never sends any data to the API. One way you can make it work is as follows:
server.js
itemRoutes.route("/increment/:id").post(async (req, res) => {
const { id } = req.params;
collection.updateOne({ _id: id }, { $inc: { views: 1 } });
return res.status(200).json({ msg: "OK" });
});
React
const incrementViews = (id) => {
// Assuming your API server is running on port 5000.
fetch(`http://localhost:5000/increment/${id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((res) => res.json())
.then(console.log)
.catch(console.error);
};
Update
The reason you're getting 404 is a missing colon : in the route parameters.
// Notice the :id, colon is important.
itemRoutes.route("/increment/:id").post(async (req, res) => {
const { id } = req.params;
// ...
});
Here is a demo reproduced on Glitch. Removed database logic and just added a response messages.
I tested the demo using Postman and it works fine.
On a POST request to https://adaptive-sassy-legal.glitch.me/increment/123, should return a response as shown below.
{
msg: "itemsRoute increment.",
id: "123"
}
Update 2
Another thing which I forgot to mention in the previous update was to update the middleware.
// Use only `/increment` instead of `increment/:id`.
app.use("/increment", itemRoutes);
Here is an updated demo.
I'm using Node.js, express.js, mongoose, and pug.
I'm trying to create a registration/signup/login system. The name and email are being stored to mongoose database with schema specified for the name, email and date. The name and email value come from the pug page which sends a post request. I will add other registration details later on in the code, but I'm unable to think about a way to implement email checking.
This is my /routes/index.js
const express = require('express');
const {
check,
validationResult
} = require('express-validator');
const router = express.Router();
//Mongoose
const mongoose = require('mongoose');
var registration = require('../models/registrations') //Important
//Routes
//------------------------------------------------------------------------------------REGISTER/SIGN UP
router.get('/', (req, res) => {
res.render('./home.pug')
})
router.get('/register', (req, res) => {
res.render('register', {
title: 'Register'
});
});
router.post('/register', [
check('name')
.isLength({
min: 5
})
.withMessage('Please enter a name'),
check('email')
.isLength({
min: 14
})
.withMessage('Please enter an email'),
], (req, res) => {
const errors = validationResult(req);
if (errors.isEmpty()) {
const regis = new registration(req.body);
regis.save()
.then(() => {
res.send('Thank you for your registration!');
})
.catch((err) => {
console.log(err);
res.send('Sorry! Something went wrong.');
})
} else {
res.status(422).render('register', {
title: 'Registration form',
errors: errors.array(),
data: req.body,
});
}
console.log(req.body)
})
//Register-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x
//-------------------------------------------------------------------------------------------LOGIN
router.get('/login', (req, res) => {
res.render('login', {
title: 'Login'
});
});
//Login-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x
router.get('/about', (req, res) => {
res.render('about', {
title: 'About'
});
});
module.exports = router;
I have tried using this:
var emailcheck = registration.findOne({email: req.body.email});
if (emailcheck){
//Give error
}else{
//Run this
}
However, it didn't work like I thought it would. Sometimes it gave me an error, sometimes it would run even when the email is already used.
It seems that registration.findOne function returns Promise and you don't handle it properly. Try await it
var emailcheck = await registration.findOne({email: req.body.email});
Or do it in closures manner
registration.findOne({email: req.body.email}).then(emailcheck => {
if (emailcheck){
//Give error
}else{
//Run this
}
})
I am successfully authenticating and logging in with the google OAuth API.
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const passportInit = require('./app/routes/auth')
const session = require('express-session');
module.exports = (app, db, passport) => {
app.use(session({secret: "ahskfjdhkjshadkjfhlajsdhlfkj"}));
passportInit(passport)
app.use(passport.initialize());
app.use(passport.session())
app.get('/', (req, res) => {
if (req.session.token) {
res.cookie('token', req.session.token);
res.json({
status: 'session cookie set'
});
console.log(req.session.token);
console.log(JSON.stringify(req.user))
} else {
res.cookie('token', '')
res.json({
status: 'session cookie not set'
});
}
});
app.get('/auth/google', passport.authenticate('google', {
scope: ['https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/youtube']
}));
app.get('/auth/google/callback',
passport.authenticate('google', {
failureRedirect: '/' // Fail: To err Page
}),
(req, res) => {
req.session.token = req.user.token;
userString = JSON.stringify(req.user);
userObjectValue = JSON.parse(userString);
userId = userObjectValue['profile'].id;
userName = userObjectValue['profile'].name;
userGivenName = userName.givenName;
const details = {'userId': userId};
db.collection('users').findOne(details, (err, item) => {
if (item == null) {
res.redirect('http://localhost:80/Register.html');
} else {
if(item['rolle'] == 'yt') {
res.redirect('http://localhost:80/YT_Welcome.html');
} else {
res.redirect('http://localhost:80/WT_Welcome.html');
}
}
});
}
);
app.get('/logout', (req, res) => {
req.logout();
req.session.token = null;
res.redirect('/');
});
}
Now I want to make a POST request from my frontend to my NodeJS backend.
Frontend-Request:
function registerWT() {
console.log('registerWT started...')
var rolle = 'wt';
var json = {"rolle": rolle};
$.ajax({
url: 'http://localhost:8000/user',
type: 'POST',
data: JSON.stringify(json),
contentType: 'application/json; charset=utf-8',
dataType: 'JSON',
async: false,
success: function (msg) {
var js = JSON.stringify(msg);
var state = msg['state'];
if (state == true) {
console.log('successfully created new user')
} else {
console.log('failed to create new user')
}
}
});
Backend-API:
var ObjectID = require('mongodb').ObjectID;
const passport = require('passport');
const passportInit = require('./auth');
module.exports = (app, db) => {
app.post('/user', (req, res) => {
console.log("POST USER REACHED!"); // This is printed out...
console.log(req.body.rolle); //is printed out correctly
console.log(req.user); // Is undefined...
if (!req.isAuthenticated()) { return res.send({'error':'unauthorized'}) } //Authentication fails...
console.log(req.user.userId);
console.log(req.userGivenName);
console.log(req.body.rolle);
userId = req.user.userId;
userGivenName = req.user.userGivenName;
userRolle= req.body.rolle;
const details = { userId: userId, userGivenName: userGivenName, rolle: userRolle };
db.collection('users').insert(details, (err, result) => {
if (err) {
res.send({ 'error': 'An error has occurred' });
} else {
res.send(result.ops[0]);
}
});
});
}
From my understanding the user authentication data should be send automaticly with every request I am doing from my frontend to my backend, since I logged in via google before. Is this correct or do I miss to include something in my frontend JS request code?
What is interessting is that after I logged in, I have to trouble with navigating to /user. So there is no problem with manualy doing a get request to this API, where I am also checking for authentication.
app.get('/user', (req, res) => {
if (!req.isAuthenticated()) { return res.send({'error':'unauthorized'}) }
db.collection('users').find({}).toArray((err, item) => {
if (err) {
res.send({'error':'An error has occurred'});
} else {
res.json(item)
}
});
});
But when I am making a Get request with JavaScript, the authentication fails again...
JavaScript get request:
function getTest() {
console.log('YT');
$.ajax({
url: 'http://localhost:8000/user',
type: 'GET',
async: false,
success: function (msg) {
var state = msg['state']; //nehmen den Wert von state aus der JSON
if (state == true) {
console.log('successfully created new user')
} else {
console.log('failed to create new user')
}
}
});
}
Does someone know what I am doing wrong here?
Edit:
My passportInit:
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const keys = require('../../config/keys')
module.exports = (passport) => {
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
passport.use(new GoogleStrategy({
clientID: keys.google.clientID,
clientSecret: keys.google.cientSecret,
callbackURL: keys.google.callback
},
(token, refreshToken, profile, done) => {
return done(null, {
profile: profile,
token: token
});
}));
};
Edit2: Added cors package:
const MongoClient = require('mongodb').MongoClient;
const bodyParser = require('body-parser');
const db = require('./config/db');
const keys = require('./config/keys')
const passport = require('passport');
const express = require('express');
const app = express();
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const cors = require('cors')
const port = 8000;
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors({ origin: 'http://localhost' })); //configuring cors
MongoClient.connect(db.url, (err, database) => {
if (err) return console.log(err)
app.options('*', cors()) // enable pre-flight across-the-board
require('./authenticate')(app, database,passport);
require('./app/routes')(app, database, passport);
app.listen(port, () => {
console.log('We are live on ' + port);
});
});
I finally was able to solve the problem.
No, Browser Plug-in or something like this is needed!
Please see my Code below.
Frontend-Request:
function register() {
var role = 'wt';
var json = {"role": role};
$.ajax({
url: 'http://localhost:8000/user',
type: 'POST',
data: JSON.stringify(json),
contentType: 'application/json; charset=utf-8',
dataType: 'JSON',
xhrFields: {
withCredentials: true
},
crossDomain: true,
async: false,
success: function (msg) {
var state = msg['state'];
if (state == true) {
console.log('successfully created new WT')
location.href = 'http://localhost:80/WT_Welcome.html'
} else {
console.log('failed to create new WT')
location.href = 'http://localhost:80/index.html'
}
}
});
}
Backend Server.js
const MongoClient = require('mongodb').MongoClient;
const bodyParser = require('body-parser');
const db = require('./config/db');
const keys = require('./config/keys')
const passport = require('passport');
const express = require('express');
const app = express();
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const port = 8000;
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
MongoClient.connect(db.url, (err, database) => {
if (err) return console.log(err)
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', "http://localhost");
res.header('Access-Control-Allow-Methods','GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Credentials', true)
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
})
require('./authenticate')(app, database,passport);
require('./app/routes')(app, database, passport);
app.listen(port, () => {
console.log('We are live on ' + port);
});
});
Backend API:
app.post('/user', (req, res) => {
if (!req.isAuthenticated()) { return res.send({'error':'unauthorized'}) } //Authentication fails...
userString = JSON.stringify(req.user);
userObject = JSON.parse(userString)
userId = userObjectValue['profile'].id;
userName = userObjectValue['profile'].name; //not used
userGivenName = userName.givenName;
userRolle= req.body.rolle;
const details = { userId: userId, userGivenName: userGivenName, rolle: userRolle };
console.log(details);
db.collection('users').insert(details, (err, result) => {
if (err) {
res.send({ 'state': false });
} else {
res.send({'state' : true});
}
});
});