Design pattern to prevent data disclosure in case of a mistake

@elie yes, this is also an effective solution, which makes me think, to create a models instead of directly using Mongo queries inside methods or publications. Only single CompaniesModel.findCompany(companyId: string) which abstracts the Mongo query as we written with @lucfranken.

This solution significantly reduce the duplicate Mongo selections code and security risk of missing companyId field, but not completely.

Do you use Mongo selections directly in methods/publications or do you prefer to use way of abstraction Mongo queries into “models” (to reduce duplicated queries)? Is it correct approach to use this abstraction?

Thanks a lot for your important inputs, if you have any more, I will be very happy for any information that will bring me closer to the solution.

Sorry I’m a bit late to this party…

I am the author of wildhart:partitioner which is a fork of mizzao:partitioner, which I wrote to be more efficient (without the need for an additional collection), and without a dependency on collection hooks (it uses its own hooks).

Both these packages provide a very robust solution for keeping this data separate. I’ve been using them in various SaaS products for years and have never had any data slip-ups. They work by using low level hooks on the Partitioned collections to add groupId to any new entity created by a Partitioned user, and adds groupId filters to any queries to automatically filter any data you send to the client.

You’d have to be very stupid and deliberately work around these hooks (using Partioner._directOperation) to accidently send the wrong data to the client.

Obviously you still need to write your own tests, but by using these packages you can pretty much develop your app from the point of view of a single company and everything Just Works.

4 Likes

@wildhart its ok, you are not late, because party never ends… : - D

Thanks a lot for your package and informations. That’s exactly what I was looking for! I was opened this thread, because manually define the groupId for all collections and manually write validation on all required places is security hazard I think. I was searching for some possibility to solve it on the Mongo level or somehow wrap the database queries to make the same. I tried mizzao component, but it’s very old, I will try your component!

Thanks a lot for your help : - )

@all I would like to say many thank for all for your help : - ) :index_pointing_at_the_viewer: :handshake:

Hi Chris @wildhart after longer time. I would like to say you many thanks. I integrated your package and it’s exactly, what I was searching for and also the integration is fast and simple. Good job!

1 Like

Hi @wildhart, everything with your package works great except registration, where I’m getting error “Must be logged in to operate on partitioned collection”. Probably I must somewhere set the default group for the user, or somehow wrap “Accounts” package to “Partitioner._directOps” :frowning:

So is this the situation where a new user registers for the first time, so that at the point of creating the new user, the user is signed in yet?

This needs to be handled by using Partioner._directOperation() to create the user new user and then after the user is created you must assign the new user to a new group:

/// server/methods/users.ts
// server only, don't simulate this method on the front-end

const emailRegExp = new RegExp(/^[A-Z0-9._%+-\\']+@(?:[A-Z0-9-]+\.)+[A-Z]{2,}$/i);
const matchEmail = Match.Where(x => emailRegExp.test(x));
const matchStringNotBlank = Match.Where(x => x && typeof x == 'string' && x.length > 0);

Meteor.methods({
	'users.startTrial': function(email: string, name: string) {
		/************************** VALIDATE PERMISSIONS ***********************************/
		check(this.userId, null); // make sure no one is logged in
		/************************** VALIDATE INPUTS ****************************************/
		email = email.trim(); // trim the email before validation - unless your front-end is doing this
		check(email, matchEmail);
		name = name.trim(); // trim the name before validation - unless your front-end is doing this
		check(name, matchStringNotBlank);
		/***********************************************************************************/

		// since no one is logged in we need to:
		//  * use Partitioner.directOperation to operate on the partitioned collection
		//  * and we need to assign the new user a new groupId
		Partitioner.directOperation(() => {
			const userId = Accounts.createUser({
				email,
				profile: {name},
			});
			// might as well use the UserId as the GroupId as well
			Partitioner.setUserGroup(userId, userId);
			Accounts.sendVerificationEmail(userId);
		});
	},
});

Hello @wildhart, thanks a lot for your fast response, I change registration to my own method by your recommendation and everything works :slight_smile: Thanks a lot.