📜 ⬆️ ⬇️

About security in Meteor and not only (part 1)

For the development of applications framework Meteor, there are a number of techniques and tools designed to ensure security. In the first part we will talk about more well-known things - hiding the server part of the code, autopublish / insecure packages, hiding collection fields during publishing and the built-in account system, looking inside the Meteor.users collection. In the second , about the loginToken issued to the client, the allow / deny rules when the database is modified by the client, trusted and untrusted code, server methods, HTTPS, force-ssl and browser-policy (Content Security Policy and X-Frame-Options), the built-in data validation mechanism (the check () function and the audit-arguments-check package).

Everything described below applies to the current version of Meteor 0.7.0.1 with Meteorite installed. By the way, if the examples of this article will not work in a newer version of Meteor, using Meteorite, examples of this article can also be viewed by specifying the --release key at startup:
$ mrt --release 0.7.0.1 


Hiding the server part of the code


Let's begin, perhaps, with the simplest - separation of client and server parts of the code. Although Meteor allows for the sharing of code by the client and server, it only makes sense to do so when it’s really necessary. In addition to structuring the project itself, which one way or another will be needed as its volume grows, and reducing the amount of data transferred to the client, this allows you to hide the server-side code.
The server, client, client / compatibility, public, private, lib, tests subdirectories (and main. * Files) are handled in a special way. This is described in sufficient detail in the documentation: docs.meteor.com/#structuringyourapp . For code running only on the server, the server directory is intended, the files from which are not transferred to the client. Similarly, the code from the files in the client directory is not uploaded to the server.
Some code, such as declarations of collections, must be accessible to both the client and the server. Such code can be placed in subdirectories with any names other than the reserved ones (you can also place it in lib, the difference is that files from this directory are loaded before others).
In this example, we will place collection declarations in the collections subdirectory.
Create a new application:
 $ mrt create littlesec 

And delete the automatically created files:
 $ cd littlesec $ rm littlesec.* 

Create the server, client, collections subdirectories. In collections, add the file test.js with the announcement of the test collection:
 Test = new Meteor.Collection('test'); 

Actually, the project can already be launched:
 $ mrt 

And open in browser: localhost : 3000
We will see only a blank page, but the Test object, even if it is empty, will already be accessible from the console:
 > Test.find().count() 0 

Add the home.html file to the client subdirectory:
 <head> <title>littlesec</title> </head> <body> {{> home}} </body> <template name="home"> {{#each test}} <li> <ul>_id:'{{_id}}' name:'{{name}}' value:'{{value}}'</ul> </li> {{else}} <p>Collection Test is empty</p> {{/each}} </template> 


And home.js:
 Template.home.test = function() { return Test.find({}); } 

In the server subdirectory, the startup.js file:
  Meteor.startup(function(){ if (!Test.find({}).count()) { var testValues = [ {name: 'First', value: 1}, {name: 'Second', value: 2}, {name: 'Third', value: 3} ]; testValues.forEach( function(testValue) { Test.insert(testValue); }); } }); 

Now the test collection is available to both the client and the server, and the server code is not transferred to the client. The _id field is a database identifier that is automatically generated when a new record is inserted.
')
At the moment, the collection is available to absolutely everyone for both reading and writing, which can be checked from the console:
 > Test.findOne({}) 

AND:
 > Test.insert({name: 'Fourth', value: 4}) 


Removing autopublish / insecure packages


We got full access to the collection declared by us thanks to the autopublish and insecure packages connected by default to the project when creating them. The first one “publishes” all existing collections on the server, the second one gives full rights to change them. This greatly facilitates familiarity with Meteor and rapid prototyping, but, of course, is unacceptable in real life, therefore:
 $ mrt remove autopublish $ mrt remove insecure 

The Test object in the browser is still available, but there is no more data:
 > Test.find().count() 0 

Now, in the normal mode, without autopublish, the client gets access to the server data, the server must publish them, and the client must subscribe.
Add a collection to the server / startup.js file (it can be placed both outside the function added by Meteor.startup () and inside it; the difference will be in the moment the publication is created - it is important that the published collection already exists):
 Meteor.publish('test', function() { return Test.find(); } ); 

If you publish the collection while the autopublish package is still connected to the project, a warning message will be displayed at the start of Meteor:
 I20140131-11:36:07.343(4)? ** You've set up some data subscriptions with Meteor.publish(), but I20140131-11:36:07.388(4)? ** you still have autopublish turned on. <..> 


Add the following subscription to the client / home.js file:
  Meteor.subscribe('test'); 

Now the data in the browser has appeared again, however, there is no more right to change them:
 > Test.insert({name: 'Fifth', value: 5}) "BDgB258TovmqS7YbY" insert failed: Access denied 

Note that the record is first inserted into the local collection, its identifier is returned (“BDgB258TovmqS7YbY”), and this record is displayed in the browser, then an error message is received from the server, and the insertion into the local collection is “rolled back”. The display in the browser takes place thanks to the delay compensation mechanism, and the error occurs due to the allow / deny rules checking (about them later); Without the insecure package, no default changes are allowed.

Hiding fields when publishing


The collection does not necessarily publish in full. Until now, all fields from the server collection were displayed. You can, for example, hide the _id field, this will limit the possibility of data manipulation, since on the client side you need to specify the record identifier (see also the data change restriction by_id) or other fields, such as implemented in the Meteor collection. users (see below):
 Meteor.publish('test', function() { var projection = {_id: 0, value: 1}; return Test.find({}, {fields: projection} ); // return Test.find(); } ); 

Hide the _id field in this way, unfortunately, does not work out - it is probably used to establish the correspondence between the server and local collections. In the current version, this will lead to an error, just returned an empty collection before.
Of course, hiding the fields will also have a positive effect on the amount of data transferred.

Meteor Account System


All of the above applies to unauthorized users. And what opportunities appear if the user is authorized?
Add to the project support for Meteor account system:
 $ mrt add accounts-base $ mrt add accounts-ui $ mrt add accounts-google 

In addition to accounts-google, there are official packages for a number of authorization providers, including Facebook, Twitter, Github, and others. Informal packages are supported by other providers, for example, Vkontakte.
Use in the home.html ready template user authorization button:
 {{loginButtons}} 

And currentUser to display user information:
 {{currentUser._id}} {{currentUser.profile.name}} 

And, using the step-by-step instruction that will be displayed when you first click on the button, register your application with Google, after which we will be able to immediately authorize the user.
Add a package to support Facebook:
 $ mrt add accounts-facebook 

Then we register the second user.
Accounts are stored in the Meteor.users collection, but if we access it in the browser, we will see only one document, although there are already two users registered:
 > Meteor.users.find().count() 1 

The fact is that by default (if the autopublish package is disabled, everything is a bit more complicated with it) the client has access only to a part of the fields of his own document of this collection:
 > Meteor.users.findOne()) {"_id":"8fLXBYpNGLqDwAahg","profile":{"name":"< >"}} 

The same document is available through the variable Meteor.user, and its identifier is Meteor.userId ()
 > Meteor.user() {"_id":"8fLXBYpNGLqDwAahg","profile":{"name":"< >"}} 

There is also a corresponding function (helper) Handlebars currentUser, with which we display information about the current user.

If you look into the sources of the accounts-base package, the Meteor.users collection is created on the server as follows:
 Meteor.users = new Meteor.Collection("users", {_preventAutopublish: true}); 

And published as follows:
 // Publish the current user's record to the client. Meteor.publish(null, function() { if (this.userId) { return Meteor.users.find( {_id: this.userId}, {fields: {profile: 1, username: 1, emails: 1}}); } else { return null; } }, /*suppress autopublish warning*/{is_auto: true}); 

If the user is authorized, this.userId contains his identifier, according to which the profile subdocument, username field and emails subdocument are returned from the database (the last two are not populated by default). Otherwise, no data will be published. This means that by default an authorized user has access to only a part of the fields and only his own document from the database.

The link to the image of a Google user from the social network is stored in the subdocument services.google.picture, and you can access the image from Facebook using the identifier services.facebook.id. To get access to them, they need to be additionally published, for example, like this (add to server / startup.js):
 Meteor.publish(null, function() { if (this.userId) { var projection = { 'services.google.picture': 1, 'services.facebook.id': 1, 'services.vk.photo': 1 }; return Meteor.users.find( { _id: this.userId }, { fields: projection } ); } else { return null; } }); 

And the link to the picture is in home.html:
  {{#if currentUser.services.facebook}} <img src="http://graph.facebook.com/{{currentUser.services.facebook.id}}/picture/?type=square"> <!-- small || normal || large || square --> {{else}}{{#if currentUser.services.google}} <img src={{currentUser.services.google.picture}}> {{else}}{{#if currentUser.services.vk}} <img src={{currentUser.services.vk.photo}}> {{/if}}{{/if}}{{/if}} 

Selected fields are now available to the client:
 > Meteor.users.findOne() { "_id":"8fLXBYpNGLqDwAahg", "profile": { "name":"< >" }, "services": { "facebook": { "id": "<  Facebook>" } } } 

Instead of explicitly specifying the fields, you could publish the entire user document by specifying projection = {} or the subdocument services, specifying projection = {services: 1}. However, in this case, the client will receive obviously unnecessary data for it, such as accessToken or loginTokens.
If you need to display information about other users, you can publish the selected fields of all documents in the collection, for example, by adding one more to the existing publications:
 Meteor.publish(null, function() { var projection = { 'profile.name': 1 }; return Meteor.users.find( { }, { fields: projection } ); }); 

The result will be the union of all publications, and now all users, including unauthorized, will have the names of all registered users, and the current user will have additional fields of his document:
 Meteor.users.find().fetch() [ { "_id":"8fLXBYpNGLqDwAahg", "profile": { "name":"< >" } }, { "_id":"kL7Fkuk29ci4vz8q4", "profile": { "name":"< >" }, "services": { "google":{ "picture":"<  >" } } } ] 

Source: https://habr.com/ru/post/211002/


All Articles