mongoose transaction not working as expected - mongodb

I have this code where I create a mongoose transaction. However it's not working as I expect. As you can see here, I'm testing the transaction by throwing an error before it can complete. However, for some reason, the account is always persisted rather than rolling back the transaction. Why
export const signUp = catchAsync(async (req: Request, res: Response, next: NextFunction) => {
const createdAt = req.body.createdAt ? new Date(req.body.createdAt) : new Date()
const session = await mongoose.startSession()
try {
session.startTransaction()
const account: IAccount = await Account.create<ICreateAccountInput>(
[
{
createdAt,
accountName: req.body.accountName,
accountOwnerName: req.body.accountOwnerName,
accountOwnerEmail: req.body?.accountOwnerEmail,
contactName: req.body?.contactName || undefined,
contactNumber: req.body.contactNumber || undefined,
contactEmail: req.body.contactEmail || undefined,
},
],
{ session },
)[0]
// throw error to test transaction cancellation
throw new Error('Test error')
const accountAdmin: IUser = await User.create<ICreateUserInput>(
[
{
createdAt,
accountId: account._id,
username: req.body.accountOwnerName,
email: req.body.accountOwnerEmail,
role: UserRoles.AccountAdmin,
password: req.body.password,
passwordConfirm: req.body.passwordConfirm,
},
],
{ session },
)[0]
await session.commitTransaction()
createSendToken(accountAdmin, 201, res, account)
} catch (e) {
session.abortTransaction()
throw e
} finally {
session.endSession()
}
})
Here is my catchAsync function in case this is the issue. It just catches errors so I don't have to write try/catch blocks everywhere:
export const catchAsync = fn => {
return (req, res, next) => {
fn(req, res, next).catch(next)
}
}

That code creates a session and transaction, but the account create is not using that session, so it is not within the transaction.
To correct this, wrap the object passed to create in an array and add the session option to the call:
Account.create<ICreateAccountInput>([{
createdAt,
accountName: req.body.accountName,
accountOwnerName: req.body.accountOwnerName,
accountOwnerEmail: req.body?.accountOwnerEmail,
contactName: req.body?.contactName || undefined,
contactNumber: req.body.contactNumber || undefined,
contactEmail: req.body.contactEmail || undefined,
}],
{session: session}
)

Related

Persist session id in passport-saml login login callback

I'm using passport-saml and express-session. I login with my original session id but when the idp response reach the login callback handler, I have another sessionId. Also, since my browser has the session cookie with the original session id, it cannot use the new session id in the login callback, so I cannot authenticate.
interface SamlProvider {
name: string;
config: SamlConfig;
}
const providers: SamlProvider[] = [
{
name: process.env.SAML_ENTITY_ID_1!,
config: {
path: "/login/callback",
entryPoint: process.env.SAML_SSO_ENDPOINT_1,
issuer: process.env.SAML_ENTITY_ID_1,
cert: process.env.SAML_CERT_1!,
...(process.env.NODE_ENV === "production" && { protocol: "https" }),
disableRequestedAuthnContext: true,
},
},
{
name: process.env.SAML_ENTITY_ID_2!,
config: {
path: "/login/callback",
entryPoint: process.env.SAML_SSO_ENDPOINT_2,
issuer: process.env.SAML_ENTITY_ID_2,
cert: process.env.SAML_CERT_2!,
...(process.env.NODE_ENV === "production" && { protocol: "https" }),
disableRequestedAuthnContext: true,
},
},
];
export const samlStrategy = (sessionStore: session.Store) =>
new MultiSamlStrategy(
{
passReqToCallback: true, // makes req available in callback
getSamlOptions: function (request, done) {
// Find the provider
const relayState = request.query.RelayState || request.body.RelayState;
const provider = providers.find((p) => p.name === relayState);
if (!provider) {
return done(Error("saml identity provider not found"));
}
return done(null, provider.config);
},
},
async function (
req: Request,
profile: Profile | null | undefined,
done: VerifiedCallback
) {
if (profile && profile.nameID) {
const { nameID, nameIDFormat } = profile;
const email = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
] as string;
const firstName = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
] as string;
const lastName = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
] as string;
// Check if user is in risk database
const user = await myUserService.getByEmail(email);
if (!user) return done(new UserNotFoundError());
// If user has existing session, delete that existing session
sessionStore.all!((err: any, obj: any) => {
const sessions = obj as Array<{
sid: string;
passport?: { user?: { email?: string } };
}>;
const existingSess = sessions.find(
(sess) =>
sess.passport &&
sess.passport.user &&
sess.passport.user.email &&
sess.passport.user.email === email
);
if (existingSess && existingSess.sid) {
sessionStore.destroy(existingSess.sid, (err: any) => {
console.error(err);
return done(Error("failed to delete existing user session"));
});
}
});
return done(null, { nameID, nameIDFormat, email, firstName, lastName });
}
return done(Error("invalid saml response"));
}
);
Here's my login and login callback
app.post("/login/callback", async function (req, res, next) {
passport.authenticate("saml", (err: any, user: ISessionUser) => {
if (err) {
// TODO: Handle specific errors
logger.info({ label: "SAML Authenticate Error:", error: err });
return next(err);
} else {
req.logIn(user, (err) => {
if (err) {
logger.info({ label: "Login Error:", data: err });
return next(err);
}
res.redirect("/");
});
}
})(req, res, next);
});
app.get(
"/auth/saml/login",
passport.authenticate("saml", { failureRedirect: "/", failureFlash: true }),
function (req, res) {
res.redirect("/");
}
);
I experienced a similar issue using Microsoft 365 for authentication. The answer was to pass a randomly-generated nonce to the authentication request - this gets passed back to your app in the callback request. With SAML I think it depends on the provider whether they support such a flow, but it is good practice. You can also use a cookie to maintain state in your app, instead of, or additional to, the session id.

Mongodb .post unable to add data to the collection

I am trying to take user input and then add a drug(medicine) to MongoDB. But it is not working and I am getting the error "Add proper parameter first". The user input should be patient name, drug name, dosage, frequency, adherence, and reason for not taking medicine. Please help!
app.post("/add-drug", (req, res) => {
try {
if (req.body && req.body.patient_name && req.body.drug_name && req.body.dosage && req.body.frequency && req.body.adherence && req.body.reason) {
let new_drug = new drug();
new_drug.patient_name = req.body.patient_name
new_drug.drug_name = req.body.drug_name;
new_drug.dosage = req.body.dosage;
new_drug.frequency = req.body.frequency;
new_drug.adherence = req.body.adherence;
new_drug.reason = req.body.reason;
new_drug.user_id = req.user.id;
new_drug.save((err, data) => {
if (err) {
res.status(400).json({
errorMessage: err,
status: false
});
} else {
res.status(200).json({
status: true,
title: 'Drug Added successfully.'
});
}
});
} else {
res.status(400).json({
errorMessage: 'Add proper parameter first!',
status: false
});
}
} catch (e) {
res.status(400).json({
errorMessage: 'Something went wrong!',
status: false
});
}
});
The model file looks like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
drugSchema = new Schema( {
patient_name: String,
drug_name: String,
dosage: Number,
frequency: Number,
adherence: Number,
reason: String,
user_id: Schema.ObjectId,
}),
drug = mongoose.model('drug', drugSchema);
module.exports = drug;
it is supposed to be <field>:<value> inside your app.post method, not <field>=<value>
The new_drug.save() method is asynchronous, so it returns a promise that you can await on:
app.post("/add-drug", async(req, res) => {
//...
await new_drug.save();
})

Google Calendar API (Saving events in MongoDB, Express JS)

I can't figure out how to save fetched events from Calendar API. I was able to print out array of events in console. I would require save multiple events at once and have verification if they already exist in database with unique id.
Here's my event.js scheme in express js.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const EventSchema = new Schema({
id: {
type: String,
required: false,
unique:true
},
start: {
type: String
},
end: {
type: String
},
status: {
type: String
},
creator: {
type: Array
},
description: {
type: String
}
});
module.exports = Event = mongoose.model('events', EventSchema);
Here's my event.js router in express js.
router.post("/google/get", async (req, res, next) => {
const {
google
} = require('googleapis')
const {
addWeeks
} = require('date-fns')
const {
OAuth2
} = google.auth
const oAuth2Client = new OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET
)
oAuth2Client.setCredentials({
refresh_token: process.env.GOOGLE_REFRESH_TOKEN,
})
const calendar = google.calendar({
version: 'v3',
auth: oAuth2Client
})
calendar.events.list({
calendarId: 'MY CALENDAR ID',
timeMin: new Date().toISOString(),
timeMax: addWeeks(new Date(), 1).toISOString(),
singleEvents: true,
orderBy: 'startTime',
},
function (err, response) {
if (err) {
console.log("The API returned an error: " + err)
return
}
var events = response.data.items
events.forEach(function (event) {
var start = event.start.dateTime || event.start.date
console.log("%s - %s", start, event.summary)
})
}
)
In Mongoose, in order to save something to a database, all you need to do is to instantiate the model that you created. Your event schema exports Event as a model that you can then treat as a regular object. So you would do something along the lines of:
let currentEvent = new Event({id, start, end, status, creator, description});
currentEvent.save();
Once that is done, it should be stored in your MongoDB. I assume that as the code for this is not visible it is already set up and working. You can just run the above inside of your for loop with some minor tweaks to grab each value correctly and it should sort your issue out!
As for your unique ID and making sure that it doesn't already exist in your database, you can use the same model to find values by checking the id against your database and seeing if it exists. As follows:
Event.findById(id, (err, event) => {
if(event == null) {
let currentEvent = new Event({id, start, end, status, creator, description});
currentEvent.save();
} else {
alert("Error, this event already exists")
}
});
I believe something like this should work, however I might have it wrong with how to check if the event exists, I can't remember if it returns null or something different, so just console log the value of event and check to see what it returns if there isn't an event that exists with that ID, and just re-run your if statement with that instead.

Uncaught (in promise) TypeError: Cannot use 'in' operator to search for 'validateStatus' in

I am getting ** Uncaught (in promise) TypeError: Cannot use 'in' operator to search for 'validateStatus' in 5f8425a33a14f026f80133ed** where 5f8425a33a14f026f80133ed is the id passed to the axios url
I want to display the services based on the user id. My url works perfectly in postman but when i access it from the veux store it gives an error.
services.js (store)
import axios from 'axios';
const state = {
services : {},
status: '',
error: null
};
const getters = {
services : state => { return state.services }
};
const actions = {
async fetchServices({commit}, userId) {
let res = await axios.get('http://localhost:5000/api/services/displayUser' , userId)
commit('setProducts', res.data)
return res;
}
};
const mutations = {
setProducts (state, items) {
state.services= items
},
};
export default {
state,
actions,
mutations,
getters
};
This is how I am calling the action :
computed: {
...mapGetters(["services"]),
},
methods: {
...mapActions(["fetchServices"]),
getData(){
this.fetchServices(this.user._id)
},
},
async created() {
await this.getProfile();
await this.getData();
}
The axios route is defined as
router.get('/displayUser', (req,res) => {
const query = user = req.body ;
Services.find(query)
.exec((err, services) => res.json(services))
})
the error screenshot :
Error screenshot
GET request should not have a body. Either use query params, indicate an id in a path, or use POST request.
In case of query params this may look like this:
let res = await axios.get('http://localhost:5000/api/services/displayUser' , { params: { userId })
router.get('/displayUser', (req,res) => {
const query = user = req.query;
Services.find(query)
.exec((err, services) => res.json(services))
})
This worked for me too:
In front end: Vue Js
let res = axios.get("http://localhost:3000/api/v1/role/getRoleByName",
{ params: { roleName: "name of role you want to send as params" },
});
In back end: Node Js
router.get('/getRoleByName', (req,res)=>{
let roleName = req.query.roleName;
roleModule.getRoleByName(roleName).then(data =>{
response.json(res,data)
}
).catch(err=> {
response.badRequest(res, err);
})
});
it's a silly mistake axios.post req.
async addTodo({ commit }, title) {
try {
const res = await axios.post(BASE_URL, { title, complete: false });
commit("newTodo", res.data);
} catch (err) {
console.log(err.message);
}
},

Error constructing as per schema

I have the following defined in my server.js,
//server.js
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var userSchema = new Schema({
"userName": {
type: String,
unique: true
},
"password": String,
"loginHistory": [{
"userAgent": String,
"dateTime": Date
}]
});
var User;
module.exports.initialize = () => {
return new Promise(function (resolve, reject) {
let db = mongoose.createConnection("mongodb://<dbuser>:<dbpassword>#ds237409.mlab.com:37409/web322_a6");
db.on('error', (err)=>{
reject(err); // reject the promise with the provided error
});
db.once('open', () => {
User = db.model("users", userSchema);
resolve();
});
})
};
I have a function that is called when posting to my app.post('/register') route, and it basically builds a new User, then assigns it to the passed data, and resolves it afterwards.
module.exports.registerUser = (userData) => {
return new Promise((resolve, reject) => {
if (userData.password != userData.password2) {
reject("Passwords do not match!");
}
let newUser = new User(userData);//<!-- 'Error: TypeError: User is not a constructor'
newUser.save((err) => {
if(err.code == 11000) {
reject("Username already taken");
} else {
reject("Error creating User: " + err);
}
// exit the program after saving
//process.exit();
resolve();
});
})
}
At first I thought I've misdefined User, but I seem to have initialized it properly, as per the MongoDB documentation. Any thoughts? It keeps throwing Error: TypeError: User is not a constructor
EDIT: /post / register
app.post("/register", (req, res) => {
console.log("entering1");
dataServiceAuth.registerUser(req.body).then((data) => {
res.render('register', {successMessage: "User Created"});
}).catch((err) => {
console.log("Error: " + err);
res.render('register', {errorMessage: err, userName: req.body.userName});
})
});
My error was in,
let db = mongoose.createConnection("mongodb://<dbuser>:<dbpassword>#ds237409.mlab.com:37409/web322_a6");
The greater than and less than signs are not to be used. Proper string:
let db = mongoose.createConnection("mongodb://dbuser:dbpassword#ds237409.mlab.com:37409/web322_a6");