mongoose icon indicating copy to clipboard operation
mongoose copied to clipboard

How to retrieve current logged user from mongoose middlewares?

Open nikzanda opened this issue 10 months ago • 5 comments

Prerequisites

  • [x] I have written a descriptive issue title

Mongoose version

8.3.2

Node.js version

20.10.0

MongoDB version

6.0.2

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

14.3.1

Issue

I need to retrieve the user who made a request to my webApp endpoint in which I run an update on a mongoose model. I want to use the user's id in a pre('save', ...) in order to save an activity log, or maybe in order to auto update a lastModifiedBy field in each of my collections (least preferred option, and I don't want to set it manually using model.set function).

image

nikzanda avatar Apr 22 '24 08:04 nikzanda

You should have the userId somewhere in the session right? Are you authenticating the users that request that endpoint?

humblewolfstudio avatar Apr 26 '24 10:04 humblewolfstudio

You should have the userId somewhere in the session right? Are you authenticating the users that request that endpoint?

Yes, I have an express web application and users log in the app by JWT authentication, so I have the userId available in every method in the req variable.

nikzanda avatar Apr 26 '24 10:04 nikzanda

session is typically used for MongoDB transactions.

The easiest way to pass data to pre('save') middleware is by adding the data to the document's $locals property. That's a property on the document that Mongoose doesn't store in MongoDB and is used for storing ephemeral data for getters, virtuals, middleware, etc.

// In Express route handler:
doc.$locals.userId = req.userId;

// In schema definition:
schema.pre('save', function() {
  this.$locals.userId; // from `req.userId`
});

Does this help?

vkarpov15 avatar Apr 29 '24 22:04 vkarpov15

session is typically used for MongoDB transactions.

The easiest way to pass data to pre('save') middleware is by adding the data to the document's $locals property. That's a property on the document that Mongoose doesn't store in MongoDB and is used for storing ephemeral data for getters, virtuals, middleware, etc.

// In Express route handler:
doc.$locals.userId = req.userId;

// In schema definition:
schema.pre('save', function() {
  this.$locals.userId; // from `req.userId`
});

Does this help?

That's exactly what I was looking for, thanks. The only downside is that I have to set it manually before every save directly on the document: the optimal solution would be to set it automatically one time for every db operation at the beginning of a user's request, for example directly in an express middleware. Is there a way to do so? Moreover, using $locals does not work with all the functions performed directly on the database eg. updateMany, deleteMany, findOneAndUpdate, updateOne, etc... because there's the need to retrieve the document before. Any ideas on how to manage this use case?

nikzanda avatar Apr 30 '24 12:04 nikzanda

To automatically make userId accessible from middleware without explicitly passing it every time, you'd something like async local storage.

For queries, you can set the userId as an option and then access it from middleware:

// In Express route handler:
await TestModel.findOne().setOptions({ userId });

// In schema definition:
schema.pre('findOne', function() {
  this.options.userId; // from `req.userId`
});

Are you trying to use Mongoose middleware to implement authorization?

vkarpov15 avatar May 05 '24 20:05 vkarpov15

This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 5 days

github-actions[bot] avatar May 20 '24 00:05 github-actions[bot]

To automatically make userId accessible from middleware without explicitly passing it every time, you'd something like async local storage.

For queries, you can set the userId as an option and then access it from middleware:

// In Express route handler:
await TestModel.findOne().setOptions({ userId });

// In schema definition:
schema.pre('findOne', function() {
  this.options.userId; // from `req.userId`
});

Are you trying to use Mongoose middleware to implement authorization?

Thanks, I will try with async local storage.

No, I'm not implementing authorization inside Mongoose middleware, I want to store log for each user operations in every model.

nikzanda avatar May 20 '24 07:05 nikzanda