I am trying to implement a reset password flow in my application. In this post request, I am first checking if the database has the "linkPass" for the passed email which stores the expiration of the link. If not first check that the email is associated with a user account. If yes it generates a link and sends it to their email. If not (and that is where the problem occurs) it sends the client back to the form and displays a message.
So as you can see every condition redirects you to the same page, but if the user does not exist I get the cannot set headers error. I know what is the problem just don't know how to resolve it. Tried to return the redirections but didn't help...
app.post(
"/requireNewPassword",
tryCatch(async (req, res) => {
const { email, modulName, continueUrl } = req.body;
const ipAddress = await axios
.get("https://api.ipify.org")
.then((res) => {
return res.data;
})
.catch((err) => console.log(err));
const [linkPass] = await getLinkPasses(email, "last");
let linkAvailable = false;
//check if linkPass is available and expired
if (!linkPass || linkPass.expiration < Date.now()) {
linkAvailable = false;
const user = await admin
.auth()
.getUserByEmail(email)
.then(async (user) => {
return user;
})
.catch((err) => {
//if user does not exist
console.log(err.message);
if (err.code === "auth/user-not-found") {
linkAvailable = true;
console.log(`${email} does not exist!`);
return res.redirect(
`/?email=${email}&linkAvailable=${linkAvailable}`
);
}
});
//if user exits
if (user) {
const link_id = await generateLinkPass(email, 5, true);
//get firebase oobCode
const pwdResetLink = await admin
.auth()
.generatePasswordResetLink(email)
.then((link) => {
const queryParams = new URLSearchParams(link);
const oobCode = queryParams.get("oobCode");
const apiKey = queryParams.get("apiKey");
const recoveryLink = `${URL_PWD}/resetPassword/? link_id=${link_id}&email=${email}&apiKey=${apiKey}&oobCode=${oobCode}&continueUrl=${continueUrl}`;
console.log("Password recovery link has been created");
return recoveryLink;
})
.catch((err) => {
//if cannot generate pwdResetLink
console.log(
JSON.parse(
err.errorInfo.message.match(/Raw server response: "(.*)"/)[1]
).error.message
);
});
if (pwdResetLink) {
axios
.post(`${URL_EFDS}/sendEmailTemplate`, {
recipient: {
name: user.displayName || "John Doe",
email: email,
ip_address: ipAddress,
},
recoveryLink: pwdResetLink,
moduleName: modulName,
apiKey: EFDS_APIKEY,
templateType: "forgottenPassword",
})
.then((res) => console.log(res.data))
.catch((err) => console.log(err.data));
console.log(`Password recovery email has been sent to ${email}`);
return res.redirect(
`/?email=${email}&linkAvailable=${linkAvailable}`
);
} else {
//if pwdResetLink does not exsist
console.log("pwdResetLink does not exsist");
return res.redirect("partials/error", { err });
}
} else {
console.log("user does not exist");
const link_id = await generateLinkPass(email, 0, false);
return res.redirect(`/?email=${email}&linkAvailable=${linkAvailable}`); //this line causing the error
}
} else {
console.log("Valid link is already available");
linkAvailable = true;
//hiaba allitom true-ra a linkAvailablet, a get requestnel figyeli hogy az emailhez letrajott e linkPass, de nem jott letre, igy nem mutat uzenetet
return res.redirect(`/?email=${email}&linkAvailable=${linkAvailable}`);
}
})
);
i've been struggling a lot with fastify-passport library, mainly because it seems that nobody uses it and there aren't any good examples or issues related to it
anyhow, i have some routes defined like this:
const adminRoutes = [
{
handler: (req,res) => {console.log(req.user) },
url: "/logout",
method: "GET"
}
]
this route is then registered by fastify like this (do note that there are more routes, however, this is just a code snippet)
adminRoutes.forEach((route, index) => {
fastify.route(route)
})
i am using passport local strategy to autenticate, it's configured like this
fastifyPassport.use('login', new passportLocal(async function (username, password, done) {
try {
let data = await dbQuery(`SELECT * FROM \`users\` WHERE username="${username}"`)
if (data[0].length > 0) {
data = data[0][0]
if (username === data.username && bCrypt.compareSync(password, data.hashedPassword)) {
return done(null, username)
} else return done(null, false)
}
} catch (error) {
console.log(error)
done(error);
}
}))
this seems to work, in all my tests, the strategy did what it had to do, when the right user and password is passed all the checks seems to pass and gets all the way down to return done(null, username)
this is my serializer and deserializer
fastifyPassport.registerUserSerializer(async (user, request) => {
return user
});
fastifyPassport.registerUserDeserializer(async (username, request) => {
let data = await dbQuery(`SELECT * FROM users WHERE username="${username}"`);
return data[0][0]
});
i've checked with both a debugger and console logs, they don't seem to ever get called
in fact when i go to /logout my console throws a null
also no session cookie get generated (uncertain of this, sometimes it seems to generate, other times it doesn't)
the complete code is quite long, however, it's probably necessary to see whats the issue
so here is it
this is the server
require('dotenv').config()
const fastify = require('fastify')({ logger: false })
const fastifyPassport = require('fastify-passport')
const fastifySecureSession = require('fastify-secure-session')
const passportLocal = require('passport-local').Strategy
const BannedEverywhere = ["DROP", "CREATE"]
const bCrypt = require('bcryptjs')
const fs = require('fs')
const path = require('path')
const port = process.env.PORT || 3000
const routes = require('./routes')
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: process.env.DB_PASSWD,
database: 'users',
port: 3306
});
console.log("Server Connected!")
function dbQuery(dataExpression) {
if (BannedEverywhere.some(i => dataExpression.includes(i))) {
return "invalid"
}
return pool.query(dataExpression)
}
fastify.register(require('fastify-cors'), {
origin: (origin, cb) => {
cb(null, true);
return
}
})
fastify.register(fastifySecureSession, { key: fs.readFileSync(path.join(__dirname, 'secret-key'))})
fastify.register(fastifyPassport.initialize())
fastify.register(fastifyPassport.secureSession())
fastifyPassport.registerUserSerializer(async (user, request) => {
return user
});
fastifyPassport.registerUserDeserializer(async (username, request) => {
let data = await dbQuery(`SELECT * FROM users WHERE username="${username}"`);
return data[0][0]
});
fastifyPassport.use('login', new passportLocal(async function (username, password, done) {
try {
let data = await dbQuery(`SELECT * FROM \`users\` WHERE username="${username}"`)
if (data[0].length > 0) {
data = data[0][0]
if (username === data.username && bCrypt.compareSync(password, data.hashedPassword)) {
console.log("got here")
return done(null, username)
} else return done(null, false)
}
} catch (error) {
console.log(error)
done(error);
}
}))
const postData = async (req, reply, err, user, info, status) => {
if (err !== null) { console.warn("ah habido un error: " + err) }
else if (user) {
const params = req.body
const newData = {
universidad: params.universidad,
facultad: params.facultad,
nombreExamen: params.nombreExamen,
fechaExamen: params.fechaExamen,
convocatoriaEspecial: params.convocatoriaEspecial,
convocatoriaExtraordinaria: params.convocatoriaExtraordinaria,
curso: params.curso
}
const response = await dbQuery('INSERT INTO `examenes` VALUES("'
+ newData.universidad + '","' + newData.facultad + '","'
+ newData.nombreExamen + '","' + newData.fechaExamen + '",'
+ newData.convocatoriaEspecial + ',' + newData.convocatoriaExtraordinaria + ','
+ newData.curso + ")")
return reply.send({ status: 200, newData, response })
}
}
const deleteData = async (req, reply, err, user, info, status) => {
if (err !== null) { console.warn("ah habido un error: " + err) }
else if (user) {
const { universidad, facultad, nombreExamen, fechaExamen, curso } = req.body
const response = await dbQuery('DELETE FROM `examenes` WHERE universidad="' + universidad + '" and facultad="' + facultad + '" and nombreExamen="' + nombreExamen + '" and date(fechaExamen)="' + fechaExamen + '" and curso=' + curso)
return reply.send({ status: 200, response })
}
}
const logout = async (req, reply, err, user, info, status) => {
if (err !== null) { console.warn("ah habido un error: " + err) }
console.log(req)
console.log("--------------------------------")
console.log(user)
console.log("--------------------------------")
console.log(info)
console.log("--------------------------------")
console.log(status)
}
fastify.get(
"/login",
(req, reply) => {
return reply.sendFile('./login/index.html')
}
)
fastify.post(
"/login",
{preValidation: fastifyPassport.authenticate('login',(req, reply)=>{
reply.send({redirect: "/"})
})},
() => {}
)
const adminRoutes = [
{
handler: () => {},
preValidation: fastifyPassport.authenticate("login", deleteData),
url: '/api/deleteData',
method: 'POST'
},
{
handler: () => {},
preValidation: fastifyPassport.authenticate("login", postData),
url: '/api/postData',
method: 'POST'
},
{
handler: () => {},
preValidation: fastifyPassport.authenticate("login", (req, reply) => { return reply.sendFile('./entry/index.html') }),
url: '/entry',
method: 'GET'
},
{
handler: (req,res) => {console.log(req.user) },
url: "/logout",
method: "GET"
}
]
const start = async () => {
try {
await fastify.listen(port)
} catch (err) {
console.error(err)
process.exit(1)
}
}
fastify.register(require('fastify-static'), {
root: __dirname,
prefix: '/', // optional: default '/'
})
routes.forEach((route, index) => {
fastify.route(route)
})
adminRoutes.forEach((route, index) => {
fastify.route(route)
})
start()
before you comment, i know cors shouldn't be left like that to go in production, don't worry, i know
also logout function was just a test, to try solve this very issue
this is the code that calls the login post request
const HOST = location.origin;
const axiosApp = axios.create({
baseURL: HOST,
});
document
.querySelector("#submit")
.addEventListener("click", async function () {
let response = await axiosApp.post(`${HOST}/login`, {
username: document.querySelector("#username").value,
password: document.querySelector("#password").value,
});
console.log(response);
if(response.status == 200) {
document.location.href = response.data.redirect
}
});
unrelated, bannedEverywhere is just a rudimentary "security" check, i do plan to improve it, for now it's just a better than nothing, and, i do plan to change all the var + string + var chains with template strings
answering my own question:
when you add a callback like that in the preValidation
{preValidation: fastifyPassport.authenticate('login',(req, reply)=>{
reply.send({redirect: "/"})
})}
fastify-passport no longer handles serialization and deserialization on his own, that comes with... a lot more issues, changing the prevalidation to
{preValidation: fastifyPassport.authenticate('login'{successRedirect:"/"})
makes you have to handle 302 codes in the browser, which can be problematic to say the least
I´ve the file cases.js to create the route I want:
const express = require("express");
const { requireSignin } = require("../controllers/auth");
const { getCases } = require("../controllers/cases");
const { scrapingData } = require("../scrapingData");
const router = express.Router();
router.get("/cases", requireSignin, scrapingData, getCases);
module.exports = router;
requireSignin from controllers/auth looks like this:
exports.requireSignin = expressJwt({
secret: process.env.JWT_SECRET,
userProperty: "auth",
});
scrapingData as middleware I have:
const updateMongoRecords = async () => {
mongoose.Promise = global.Promise;
mongoose.set("debug", true);
Case.deleteMany({}, (err, result) => {
if (err) {
console.log(err);
} else {
console.log("Successfully deleted all records");
}
});
const dataPath = Path.join(__dirname, "files", "covid-data.csv");
try {
let headers = Object.keys(Case.schema.paths).filter(
(k) => ["_id", "__v"].indexOf(k) === -1
);
await new Promise((resolve, reject) => {
let buffer = [],
counter = 0;
let stream = fs
.createReadStream(dataPath)
.pipe(csv())
.on("error", reject)
.on("data", async (doc) => {
stream.pause();
buffer.push(doc);
counter++;
// log(doc);
try {
if (counter > 10000) {
await Case.insertMany(buffer);
buffer = [];
counter = 0;
}
} catch (e) {
stream.destroy(e);
}
stream.resume();
})
.on("end", async () => {
try {
if (counter > 0) {
await Case.insertMany(buffer);
buffer = [];
counter = 0;
resolve();
}
} catch (e) {
stream.destroy(e);
}
});
});
} catch (e) {
console.error(e);
} finally {
process.exit();
}
};
exports.scrapingData = async (req, res, next) => {
const url = "https://covid.ourworldindata.org/data/owid-covid-data.csv";
const path = Path.resolve(__dirname, "files", "covid-data.csv");
const response = await Axios({
method: "GET",
url: url,
responseType: "stream",
});
response.data.pipe(fs.createWriteStream(path));
return new Promise((resolve, reject) => {
response.data.on("end", () => {
resolve(updateMongoRecords());
next();
});
response.data.on("error", (err) => {
reject(err);
});
});
};
And getCases.js inside controllers/cases I have:
const Case = require("../models/case");
exports.getCases = async (req, res) => {
const cases = await Case.find().then((cases) => res.json(cases));
};
With this code I am able to fetch in the route /cases all the cases in the client side (like postman) and it shows all of them. But the problem is that I can´t make any other requests (like signing out, which works fine if I don´t make the get request for the cases like before) afterwards because client (postman) gives the error: GET http://localhost:5000/signout
Error: connect ECONNREFUSED 127.0.0.1:5000
in case you want to see the code for signout is like this:
const express = require("express");
const { signup, signin, signout } = require("../controllers/auth");
const router = express.Router();
router.post("/signup", userSignupValidator, signup);
router.post("/signin", userSigninValidator, signin);
router.get("/signout", signout);
inside controllers/auth.js:
exports.signout = (req, res) => {
res.clearCookie("t");
return res.json({ message: "Signout successfully done" });
};
Any help on this??
i have been facing this error of 401 unauthorized error when i tried to mount my isLoggedinMiddlware.js, and even when i can manage to print the token stored, it stil says its
authorised. Any advice or help would be appreciated! Have a nice day.
this is my isLoggedinMiddleware.js
const jwt = require("jsonwebtoken");
const JWT_SECRET = process.env.JWT_SECRET;
module.exports = (req, res, next) => {
const authHeader = req.headers.authorization;
if (authHeader === null || authHeader === undefined || !authHeader.startsWith("Bearer ")) {
res.status(401).send();
return;
}
const token = authHeader.replace("Bearer ", "");
jwt.verify(token, JWT_SECRET, { algorithms: ["HS256"] }, (error, decodedToken) => {
if (error) {
res.status(401).send();
return;
}
req.decodedToken = decodedToken;
next();
});
};
this is my post api
app.post("/listings/",isLoggedInMiddleware,(req,res)=>{
listings.insert(req.body,(error,result)=>{
if(error){
console.log(error)
console.log(req.body)
console.log(isLoggedInMiddleware)
res.status(500).send('Internal Server Error')
return;
}
console.log(result)
res.status(201).send({"Listing Id":result.insertId})
})
})
This is my front end
const baseUrl = "http://localhost:3000";
const loggedInUserID = parseInt(localStorage.getItem("loggedInUserID"));
const token = localStorage.getItem("token")
console.log(token)
if(token === null || isNaN(loggedInUserID)){
window.location.href = "/login/"
}else{
$('#logoff').click(function(){
event.preventDefault();
localStorage.removeItem('token')
localStorage.removeItem('loggedInUserID')
window.alert('Logging out now')
window.location.href = "/login/"
})
$(document).ready(function () {
$('#submitbtn').click((event) => {
const loggedInUserID = parseInt(localStorage.getItem("loggedInUserID"));
// middleware = {headers:{'Authorization':'Bearer '+token},data:{id: loggedInUserID}}
event.preventDefault();
const itemName = $("#itemName").val();
const itemDescription = $("#itemDescription").val();
const price = $('#price').val();
const image = $('#image').val();
const requestBody = {
itemName: itemName,
itemDescription: itemDescription,
price: price,
fk_poster_id: loggedInUserID,
imageUrl: image
}
console.log(requestBody);
axios.post(`${baseUrl}/listings/`,{headers:{'Authorization':'Bearer '+token},data:{id: loggedInUserID}}, requestBody)
.then((response) => {
window.alert("successfully Created")
})
.catch((error) => {
window.alert("Error")
console.log(requestBody)
})
})
})
}
i can managed to get the token i stored when i log in, however, it stills say 401 unauthorised.
The code should work like that: If target username and logged in user username is in the same fight then the fight should not be created and user should be redirected to /arena. But the problem is that even though when I finish the fight then fast hit F5 the new fight with the same user and same target creates again. What could cause this problem ?
app.get('/arena/fight/user/:username', async (req, res) => {
User.findById(req.session.userId).then((user) => {
if(req.params.username === user.username) {
return res.redirect('/arena');
}
});
const target = await User.findOne({username: req.params.username});
const user = await User.findById(req.session.userId);
const fight = await TempFight.findOne({user: user.username});
if(fight && target.username === fight.target) {
return res.redirect('/arena');
} else {
const battleId = uuid4();
const newtempFight = TempFight({
target: target.username,
user: user.username,
code: battleId
});
await newtempFight.save();
}
if(req.session.userId) {
res.render(__dirname + '/views/arena/battle', {messages, user, enemy, userItems, enemyItems, liveitems, fights, lostFights});
} else {
return res.redirect('/');
}
});
Try this
app.get('/arena/fight/user/:username', async (req, res) => {
const user = await User.findById(req.session.userId);
if(req.params.username === user.username) {
return res.redirect('/arena');
}
const target = await User.findOne({username: req.params.username});
const fight = await TempFight.findOne({user: user.username});
if(fight && target.username === fight.target) {
return res.redirect('/arena');
} else {
const battleId = uuid4();
const newtempFight = TempFight({
target: target.username,
user: user.username,
code: battleId
});
await newtempFight.save();
}
if(req.session.userId) {
res.render(__dirname + '/views/arena/battle', {messages, user,
enemy, userItems, enemyItems, liveitems, fights, lostFights});
} else {
return res.redirect('/');
}
});