Mongoose - session abortTransaction is not rolling back - javascript

I don't know what I'm missing but in the block catch session.abortTransaction() not rolling bak anything. I purposely used throw new Error("Error message") after the creation of a user to land in the catch block to trigger the session.abortTransaction() but when I look in the database I see that the user is still created despite the abortion.
Here is my source code
export const signUp = async (req, res) => {
const { email, password, fullname } = req.body;
// Init session
const session = await mongoose.startSession();
// Begin transaction
session.startTransaction();
try {
// Check if user exist in database using his email
const dbUser = await UserModel.findOne({ email }, null, session);
// Return a conflit code if user already exist
if (dbUser) return res.status(409).json({ message: "User already exists" });
// Create tenant
const tenantResult = await TenantModel.create([{ createdAt: new Date() }], null, session);
// Get admin role
const roleResult = await RoleModel.findById('626db17b8645717d657fc4c8', null, session);
//Hash password using jwt
const hashedPassword = await bcrypt.hash(password, 12);
// Store user in db
const userResult = await UserModel.create([
{
email: email,
password: hashedPassword,
fullname: fullname,
tenant: tenantResult[0],
role: roleResult
}
], null, session);
throw new Error("Error message");
if (userResult){
// Create subscription && currency
// Create currency for user
await CurrencyModel.create([
{
name: 'Dollar',
createdAt: new Date(),
symbol: '$',
isoCode: 'USD',
default: true,
userRole: 'setting-list',
createdBy: userResult[0],
}
], null, session);
}
// Generate token using jwt, secret, and user data
const token = jwt.sign( { email: userResult[0].email, id: userResult[0]._id }, secret, { expiresIn: "24h" } );
// Send welcome email to registered user
await sendWelcomeEmail(userResult[0].email, userResult[0].fullname, `Welcome ${userResult[0].fullname} - light-speed.dev`, 'welcome-email.html');
//Commit transaction
await session.commitTransaction();
// Return 201 Created http code with user and token
return res.status(201).json({ userResult, token });
} catch (error) {
// Abort transaction if one of these request throw error
await session.abortTransaction();
console.log(error);
return res.status(400).json({ message: 'Something went wrong!' });
} finally {
// close session
session.endSession();
}
};
Thank you for your help!

I finally figured out why the session doesn't roll back transactions. It's because of the way I specify the session option.
I change these lines
const dbUser = await UserModel.findOne({ email }).session(session);
const tenantResult = await TenantModel.create([{ createdAt: new Date() }], { session: session });
const roleResult = await RoleModel.findById('626db17b8645717d657fc4c8').session(session);
const userResult = await UserModel.create([
{
email: email,
password: hashedPassword,
fullname: fullname,
tenant: tenantResult[0],
role: roleResult
}
], { session: session });
await CurrencyModel.create([
{
name: 'Dollar',
createdAt: new Date(),
symbol: '$',
isoCode: 'USD',
default: true,
userRole: 'setting-list',
createdBy: userResult[0],
}
], { session: session });

Related

Error: Request failed with status code 404 , Axios trying to fetch github user emails using passport-github2

const Raven = require('raven')
const GithubStrategy = require('passport-github2').Strategy
const axios = require('axios');
const models = require('../../../db/models').models
const config = require('../../../../config')
const secrets = config.SECRETS
const debug = require('debug')('oauth:strategies:github')
const { generateReferralCode } = require('../../../utils/referral')
/**
* Authenticate _users_ using their Github Accounts
*/
module.exports = new GithubStrategy({
clientID: secrets.GITHUB_CONSUMER_KEY,
clientSecret: secrets.GITHUB_CONSUMER_SECRET,
callbackURL: config.SERVER_URL + config.GITHUB_CALLBACK,
passReqToCallback: true,
}, async function (req, token, tokenSecret, profile, cb) {
let profileJson = profile._json
console.log(profileJson);
try{
const config = {method:'get',headers:{'Authorization':`Bearer ${token}`}};
const result = await axios.get('https://api.github.com/user/emails',config);
let emailArr = result.data;
let primaryGithubEmail = emailArr.filter((email)=>{return email.primary;});
profileJson.email = primaryGithubEmail[0].email;
}catch(error){
console.log(error);
}
console.log('======== profile after request ============');
console.log(profileJson);
console.log('====================');
let oldUser = req.user
Raven.setContext({ extra: { file: 'githubstrategy' } })
try {
if (oldUser) {
debug('User exists, is connecting Github account')
/*
This means an already logged in users is trying to
connect Github to his account. Let us see if there
are any connections to his Github already
*/
const ghaccount = await models.UserGithub.findOne({ where: { id: profileJson.id } })
if (ghaccount) {
throw new Error('Your Github account is already linked with codingblocks account Id: ' + ghaccount.get('userId'))
} else {
await models.UserGithub.upsert({
id: profileJson.id,
token: token,
tokenSecret: tokenSecret,
username: profileJson.login,
userId: oldUser.id
})
const user = await models.User.findById(oldUser.id)
if (user) {
return cb(null, user.get())
} else {
return cb(null, false, { message: "Could not retrieve existing Github linked account" })
}
}
} else {
/*
This means either -
a. This is a new signup via Github
b. Someone is trying to login via Github
*/
let userGithub = await models.UserGithub.findOne({
include: [models.User],
where: { id: profileJson.id }
})
/*
If userGithub exists then
Case (a): login
*/
if (!userGithub) {
/*
If there is any user with verified email equal to the email comming from github strategy , then create a new entry in userGithub table and login that user
*/
const userWithVerifiedEmail = await models.User.findOne({
where: {
verifiedemail: profileJson.email
}
})
if (userWithVerifiedEmail) {
userGithub = await models.UserGithub.create({
id: profileJson.id,
token: token,
tokenSecret: tokenSecret,
username: profileJson.login,
userId: userWithVerifiedEmail.get('id'),
})
return cb(null, userWithVerifiedEmail.get());
}
/*
Case (b): New Signup
First ensure there aren't already users with the same email
id that comes from Github
*/
let existingUsers = [];
if (profileJson.email) {
existingUsers = await models.User.findAll({
include: [{
model: models.UserGithub,
attributes: ['id'],
required: false
}],
where: {
email: profileJson.email,
'$usergithub.id$': { $eq: null }
}
})
}
if (existingUsers && existingUsers.length > 0) {
let oldIds = existingUsers.map(eu => eu.id).join(',')
return cb(null, false, {
message: `
Your email id "${profileJson.email}" is already used in the following Coding Blocks Account(s):
[ ${oldIds} ]
Please log into your old account and connect Github in it instead.
Use 'Forgot Password' option if you do not remember password of old account`
})
}
/* Check if users with same username exist. Modify username accordingly */
const existCount = await models.User.count({ where: { username: profileJson.login } })
userGithub = await models.UserGithub.create({
id: profileJson.id,
token: token,
tokenSecret: tokenSecret,
username: profileJson.login,
user: {
username: existCount === 0 ? profileJson.login : profileJson.login + "-gh",
firstname: profileJson.name ? profileJson.name.split(' ')[0] : profileJson.login,
email: profileJson.email,
referralCode: generateReferralCode(profileJson.email).toUpperCase(),
photo: profileJson.avatar_url,
verifiedemail: profileJson.email,
marketing_meta: req.session.marketingMeta
}
}, {
include: [models.User],
})
req.visitor.event({
ea: 'successful',
ec: 'signup',
el: 'github'
}).send()
req.session.isNewSignup = true
if (!userGithub) {
return cb(null, false, { message: 'Authentication Failed' })
}
}
return cb(null, userGithub.user.get())
}
} catch (err) {
Raven.captureException(err)
cb(null, false, { message: err.message })
}
})
when I am running this code locally on my system , its working fine , but as soon as I am sending the code to the staging and production i am getting the error
{ Error: Request failed with status code 404
0|oneauth | at createError (/home/codingblocks/servers/khaate/node_modules/axios/lib/core/createError.js:16:15)
0|oneauth | at settle (/home/codingblocks/servers/khaate/node_modules/axios/lib/core/settle.js:17:12)
0|oneauth | at IncomingMessage.handleStreamEnd (/home/codingblocks/servers/khaate/node_modules/axios/lib/adapters/http.js:236:11)
0|oneauth | at IncomingMessage.emit (events.js:203:15)
0|oneauth | at IncomingMessage.EventEmitter.emit (domain.js:466:23)
0|oneauth | at IncomingMessage.wrapped (/home/codingblocks/servers/khaate/node_modules/newrelic/lib/transaction/tracer/index.js:188:22)
...............
I am not able to understand why this error is comming and I am also not able to get the user emails , but locally this code works fine and I am able to fetch all the emails associated with the github of the user.

Bcrypt.compareSync always returning false

Hi so I'm trying to create my first login with sequelize and I'm struggling with hashing and comparing hashes and it always returns false.
I figured I am doing something wrong on the hashes or comparing since I'm learning. I'm using SQL database
Here's my login code, I'm using express session and sequelize:
'processLogin': (req, res) => {
db.User.findOne({
where: {
email: req.body.email
}
})
.then(async user => {
var eSession = req.session.userLogin
let checkPass = await bcrypt.compare(req.body.password, user.password)
console.log(checkPass);
if(checkPass){
eSession = user;
res.send("Success");
}else{
res.render('login', {errors: [{
msg: "Incorrect password"
}]});
}
if(user == undefined){
res.render('login', {errors: [{
msg: "User not found, please Register"
}]});}
})
}
And here is where I actually hashed the passwords on my register:
'save': async (req, res) => {
var a = [req.body.fullname, req.body.email, req.body.number, bcrypt.hashSync(req.body.password, 10), bcrypt.hashSync(req.body.repassword, 10)];
let errors = validationResult(req);
if(errors.isEmpty()){
db.User.create({
full_name: a[0],
email: a[1],
phone_number: a[2],
password: await bcrypt.hash(a[3], 10),
confirm_password: await bcrypt.hash(a[4], 10)
})
.then(users => {
res.send("succes!!");
})
}else{
res.render('register', { errors: errors.errors })
}
}
}
insted of sync why don't you try async and wait unitll it get's hashed or decrypt.
inside async function to hash password.
let hashedPassword = await hash(password, 10);
and inside async function for comparing password
let checkPass = await compare(password, foundUser.password);

I am trying to create a doc to model with mongoose but model.create() does not return any promise

it seems that the create method does not return any promise that then can handle
I tried different things but nothing worked
this is my routes file
const express = require("express")
const router = express.Router();
const controller = require("./controller")
router.post("/signup", controller.create);
module.exports = router;
and this is my model file
const mongoose = require('mongoose');
const User = new mongoose.Schema(
{
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
picture: {
type: String
},
password: {
type: String,
select: false
},
email: {
required: true,
type: String,
unique: true
}
},
{
timestamps: true
}
);
User.index({
firstName: 'text',
lastName: 'text',
});
module.exports = mongoose.model('User', User);
and this is the controller file
const User = require('./model');
const { hash, compareHash } = require('../lib/util');
const { createToken, findUserByToken } = require('../lib/auth');
const cookieIsSecure = process.env.ENVIRONMENT === 'production';
exports.create = async (req, res) => {
const password = await hash(req.body.password);
const rawUser = {
...req.body,
password,
};
User.create(rawUser)
.then(async user => {
return user.save();
})
.then(async user => {
const newUser = user.toObject();
res.send(newUser);
})
.catch(err => {
if (err.code === 11000) {
res.status(400).send({ message: 'A user with this email address has already registered.' });
return;
}
res.status(500).send({ message: 'An unexpected error occurred' });
});
};
it always return the 500 error "an unexpected error occurred"
which is not really specific. and i do not know what is the problem exactly. but I am sure it has something to do with the model.create() it does not return any promise.
Here you are mixing methods. create doesn't want save in it as it's implicit:
https://mongoosejs.com/docs/api.html#model_Model.create
Please try this, I've refactored your code a bit and added much easier to read and use try/catch:
const rawUser = new User({ ...req.body, password});
try {
await rawUser.save();
res.status(201).send(newUser);
} catch(err) {
if (err.code === 11000) return res.status(400).send({ message: 'A user with this email address has already registered.' });
res.status(500).send({ message: 'An unexpected error occurred' });
}
You need to use async/await like this:
exports.create = async (req, res) => {
try {
const password = await hash(req.body.password);
const rawUser = {
...req.body,
password
};
const user = await User.create(rawUser);
const newUser = user.toObject();
res.send(newUser);
} catch (err) {
console.log("ERROR: ", err);
if (err.code === 11000) {
return res.status(400).send({
message: "A user with this email address has already registered."
});
}
res.status(500).send({ message: "An unexpected error occurred" });
}
};

_http_outgoing.js:470 throw new ERR_HTTP_HEADERS_SENT('set'); Error: Cannot set headers after they are sent to the client : Nodejs web application

The Code in Users.js gets an error in the snippet at: qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {.
I've tried adding return statement to make sure I'm not sending the response multiple times. I can see that the data_url when converted to image online shows me a QR code but I'm unable to see that when I'm using Postman.
router.post(
"/",
[
check("name", "Name is required")
.not().isEmpty(),
check("email", "Please include a valid email").isEmail(),
check(
"password",
"Please enter a password with 6 or more characters"
).isLength({ min: 6 })
],
async (req, res) => {
console.log("hi");
console.log(JSON.stringify(req.body));
const errors = validationResult(req);
if (!errors.isEmpty()) {
// return res.status(400).json({ errors: errors.array() });
}
const {
name,
email,
password,
type_of_user,
question1,
answer1,
question2,
answer2
} = req.body;
try {
let user = await User.findOne({ email }); // await User.findOne({ email });
user = new User({
name,
email,
avatar,
password,
type_of_user,
question1,
answer1,
question2,
answer2
});
const salt = await bcrypt.genSalt(10); //await
user.password = await bcrypt.hash(password, salt); // await
user
.save()
.then(result => {
// MFAOptions & secret will generate a secret
const MFAOptions = {
issuer: "xyz",
user: req.body.email,
length: 64
};
const secret = speakEasy.generateSecret(MFAOptions);
const token = jwt.sign(
{
name: user.name,
email: user.email,
twofactor: false
},
config.get("jwtSecret"), // chnaged from process env jwt
{
expiresIn: "1h"
}
);
// update the user that is just created:
user
.update(
{ email: req.body.email },
{
$set: { twoFASecret: secret.base32 }
}
)
.exec()
.then(result => {
console.log(result);
qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {
console.log(data_url);
res.status(200).json({
img: data_url,
token: token
});
});
return;
})
//if anything wrong, throws an error
.catch(err => {
console.log(err);
// res.status(500).json({ error: err });
});
})
// originaly this will end here, but now it should redirect to twoFA route,
// if something wrong, shows an error
.catch(err => {
console.log(err);
// res.status(500).json({ error: err });
});
// user with an id, primise which returns an id
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get("jwtSecret"),
{ expiresIn: 3600 },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
// } //else end
} catch (err) {
console.error(err.message);
res.status(500).send("Server error");
}
}
);
module.exports = router;
I think your problem with executing this line qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {
this calling has callback which means that you will continue in executing the rest of the code and send a response using res.json then after qrcode finish it executes will enter the callback and send another response which is not allowed.
you have multi execution for res.json you need to remove one of them and refactor your code
I tried to refactor your code :
const validation = [check('name', 'Name is required').not().isEmpty(),
check('email', 'Please include a valid email').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 })]
const toDataURL = (otpauth_url) => new Promise((resolve, reject) => {
qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {
if(err)reject(err)
resolve(data_url)
res.status(200).json({
img: data_url,
token,
})
})
});
const signJwt = (payload)=>new Promise((resolve,reject)=>{
return jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: 3600 },
(err, token) => {
if (err) reject(err)
resolve(token)
}
)
})
const postRequest = async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
const { name, email, password, type_of_user, question1, answer1, question2, answer2, } = req.body
try {
let user = await User.findOne({ email })
user = new User({
name,
email,
avatar,
password,
type_of_user,
question1,
answer1,
question2,
answer2,
})
const salt = await bcrypt.genSalt(10) // await
user.password = await bcrypt.hash(password, salt) // await
await user.save()
// MFAOptions & secret will generate a secret
const MFAOptions = {
issuer: 'xyz', user: req.body.email, length: 64,
}
const secret = speakEasy.generateSecret(MFAOptions)
const token = jwt.sign(
{
name: user.name,
email: user.email,
twofactor: false,
},
config.get('jwtSecret'), { expiresIn: '1h', })
// update the user that is just created:
await user.update({ email: req.body.email },
{ $set: { twoFASecret: secret.base32 }, }).exec()
const data_url= await toDataURL(secret.otpauth_url)
if(data_url) return res.status(200).json({
img: data_url,
token,
})
const payload = {
user: {
id: user.id,
},
}
const token= await signJwt(payload)
return res.json({token})
} catch (err) {
console.error(err.message)
return res.status(500).send('Server error')
}
}
router.post('/', validation, postRequest)
module.exports = router

Node.js: Updating a document with Mongoose

I'm trying to update an invitation when the invited user registers. The invitation has an auth property which is a nested object, which itself has a property with the key "used." I'm just trying to explicitly declare the value to be true, and save, using async/await. But it's not updating. Is there a better way to do this?
My function:
exports.invitedSignup = async (req, res, next) =>
{
const { firstName, lastName, company, password, email, companyCode, token } = req.body;
console.log(email);
try
{
const user = await User.findOne({ email });
const invitation = await Invitation.findOne({ email }).sort({ field: 'asc', _id: -1 }).limit(1);
if (user) { return res.status(422).send({ error: "User is already registered" }); };
if (!invitation) { return res.status(422).send({ error: "No invitation on record" }); };
if (token !== invitation.auth.token)
{
return res.status(422).send({ error: "Something has gone wrong, please sign up again" });
}
try
{
invitation.auth.used = true;
const updateInvitation = await invitation.save();
console.log("authorization: " + invitation.auth.used);
} catch (e)
{
return next(e);
}
try
{
const saveUser = new User({
firstName: firstName,
lastName: lastName,
email: req.body.email,
password: password,
company: company,
companyCode: companyCode,
role: 1,
auth: { used: true }
});
const newUser = await saveUser.save();
const { email, firstname, lastname } = newUser;
res.json({ token: tokenForUser(newUser), email, firstName, lastName });
}
catch (e)
{
return next(e);
}
}
catch (e)
{
return next(e);
}
};
The invitation schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const bcrypt = require('bcrypt-nodejs');
//define model
const invitationSchema = new Schema({
email: { type: String, unique: true, lowercase: true, unique: true },
inviter: String,
company: String,
companyCode: String,
created: Date,
auth: {
token: String,
used: Boolean,
expires: Date,
}
});
invitationSchema.pre('save', function (next)
{
const invitation = this;
bcrypt.genSalt(10, (err, salt) =>
{
const tomorrow = new Date();
invitation.created = tomorrow;
tomorrow.setDate(tomorrow.getDate() + 1);
if (err) { return next(err); };
invitation.auth = { token: salt, used: 0, expires: tomorrow };
next();
});
});
//create model class
const ModelClass = mongoose.model('invitation', invitationSchema);
//export model
module.exports = ModelClass;
http://mongoosejs.com/docs/schematypes.html#mixed
person.anything = { x: [3, 4, { y: "changed" }] };
person.markModified('anything');
person.save(); // anything will now get saved

Categories