Chapter 11: Publish & Subscribe

Publish & Subscribe

Something we haven’t talked about yet is security.

This isn’t the most exciting topic in the world, but if we don’t think about security, we can’t share our projects with the world (well, we could, but it would be incredibly irresponsible). As such, we’re going to spend the next couple of chapters talking about two fundamental security topics.

It is worth mentioning, however, that security is one of those topics that can appear weird and confusing at first, but will suddenly “click” later down the lie. If things don’t sink-in right away, don’t let it crush your enthusiasm for building stuff with Meteor. Just keep persisting. The moment will come where it all makes sense.

In this chapter specifically, we’re going to talk about “publications” and “subscriptions”, and to explain these interwoven concepts, it’s best if we perform a demonstration.

To begin the demonstration:

  1. Create two user accounts.
  2. Under each account, add three players to each list.
  3. Logout of both accounts.

After following these steps, a total of six players should exist inside the database, and those players should “belong” to a total of two users.

Next, use the find and fetch functions on the “PlayersList” collection:

PlayersList.find().fetch();

You’ll notice that, as we’ve seen before, all of the data from the collection is returned. This means we can see all of the data that belongs to both of the users.

But is is a problem. Because unless we prevent users from accessing this data, every user will have the same, unrestricted access to everything inside the database. At the moment, there’s nothing stopping users from using the find and fetch functions to full effect.

You might be wondering though:

Why does this feature exist within Meteor?

Why are users allowed unrestricted access to all of this data?

The answer is convenience. Being able to navigate through all the data in our project’s database is extremely useful during the development process. It’s just that, before we share the application with the world, we’ll have to:

  1. Prevent users from accessing all of the data by default.
  2. Precisely define what data should be available to users.

This is what we’ll be handling for the rest of the chapter.

autopublish

The functionality that allows users to have unrestricted access to the data inside our database is contained within a packaged known as “autopublish”, which is included with every Meteor project by default. If we remove this package, users won’t be able to access any data through the Console, but we’ll also break the application in the process, so we’ll have to take a couple of further steps to get it working again.

To remove the “autopublish” package, run the following command:

meteor remove autopublish

If you’re logged-out of both user accounts when the package is removed, you won’t notice anything different at first, but try using the find and fetch functions:

PlayersList.find().fetch();

You’ll notice that we can no longer navigate through the data inside the collection. The only thing that’s returned is an empty array. It looks like the data has been deleted, but that’s not the case. It’s just been secured.

But the problem now is that the data is too secure, because if we login to either of the user accounts, we’ll see that the data is also inaccessible from inside the interface:

To fix this, we’ll need to precisely define what data users should be able to see.

isServer

In this book, we’ve mostly been writing code inside the isClient conditional. This is because we’ve mostly been writing code that specifically affects the interface – helpers, events, etc.

There are, however, situations where we’ll want to run code on the server.

To demonstrate one of these situations, create a console.log statement inside the isServer conditional, and pass through a find and fetch function for the “PlayersList” collection:

console.log(PlayersList.find().fetch());

After saving the file, the output will appear inside the command line, which isn’t a surprise – only client-side code runs in the Console – but notice that we don’t have any trouble retrieving any of the data from the “PlayersList” collection. Even though we removed the “autopublish” package, this statement remains unaffected.

Why?

Well, code that is executed on the server is inherently trusted. So while we’ve stopped users of the application from accessing data from their browser, we – as the administrators of the server – continue to have complete access to all of the data while interacting directly with the server.

The reason this matters will soon become clear.

Publications, Part 1

In this section, we’re going to publish the data that’s inside the “PlayersList” collection, and conceptually, you can think of publishing data as specifying what data should be available to users.

To achieve this, delete the console.log statement from the isServer conditional and replace it with the following Meteor.publish function:

Meteor.publish();

Then, as the first argument of this function, pass through a string of “thePlayers”:

Meteor.publish('thePlayers');

This string is simply a name that we’ll soon use as a reference.

As the second argument, pass through a function:

Meteor.publish('thePlayers', function(){
    // inside the publish function
});

It’s within this function that we specify what data should be available to users of the application. In this case, we’ll return all of the data from the “PlayersList” collection by using a return statement and a find function:

Meteor.publish('thePlayers', function(){
    return PlayersList.find();
});

This code will basically duplicate the functionality of the “autopublish” package, in that it will make all data available to users, and this isn’t exactly what we want, but it’s a step in the right direction.

By publishing data, we’re essentially transmitting that data from the server and other into the ether. But for the data to become available to users from inside their browser, we must subscribe to that data from the client-side.

This is easier to understand in practice.

Inside the isClient conditional, write the following:

Meteor.subscribe();

This is the Meteor.subscribe function, and the only argument we need to pass into this function is the name of a publish function.

In this case, we’ll pass through the name of “thePlayers”:

Meteor.subscribe('thePlayers');

Save the file, switch back to the browser, and use the find and fetch functions on the “PlayersList” collection:

PlayersList.find().fetch();

You’ll notice that, once again, we have access to all of the data from the project’s database, meaning our application is working like it did before we removed the “autopublish” package.

This isn’t exactly what we want, but we have made some important progress.

Publications, Part 2

The goal now is to make it so the Meteor.publish function only publishes data that belongs to the currently logged-in user.

This means:

  1. Logged-in users will only have access to their own data.
  2. Logged-out users won’t have access to any data.

In the end, the application will be functional but secure.

To achieve this, we’ll need to access the unique ID of the currently logged-in user from within the Meteor.publish function. But while inside this function, we can’t use the Meteor.userId function that we’ve used in the past. Instead, we have to use the following statement:

this.userId;

But while the syntax is different, the end result is essentially the same. This statement returns the unique ID of the currently logged-in user.

Inside the Meteor.publish function, create a “currentUserId” variable and make it equal to this.userId:

Meteor.publish('thePlayers', function(){
    var currentUserId = this.userId;
    return PlayersList.find();
});

Then change the find function so it only retrieves documents where the createdBy field is equal to the ID of the currently logged-in user:

Meteor.publish('thePlayers', function(){
    var currentUserId = this.userId;
    return PlayersList.find({ createdBy: currentUserId });
});

Save the file, then use the find and fetch functions on the “PlayersList” collection:

PlayersList.find().fetch();

If you’re logged-out, you’ll only see the data that belongs to the current user’s account, and if you’re logged-out, you won’t see any data. This is because the return statement inside the Meteor.publish function can only return documents that hold the unique ID of the current user. (If a user is not logged in, this.userId returns null and no documents can be returned.)

Summary

In this chapter, we’ve learned that:

  • By default, all of the data inside a Meteor project’s database is available to all users of that project. This is convenient during development, but it’s a security hole that needs to be fixed before launching the application.
  • This functionality is contained within a package known as “autopublish”, and if when remove this package the project is more secure, but it also breaks the application, so then we have to get it working again.
  • The Meteor.publish function is used on the server to define what data should be available to users of the application.
  • The Meteor.subscribe function is used on the client to retrieve the data that’s published from the server.
  • Inside the Meteor.publish function, we can retrieve the ID of the current user ID with a reference to this.userId.

To browse the project’s code in its current state, click here.