Skip to content

Instantly share code, notes, and snippets.

@cklanac
Last active November 3, 2018 08:48
Show Gist options
  • Save cklanac/28115177cc1b33cd08cea1ce5786528b to your computer and use it in GitHub Desktop.
Save cklanac/28115177cc1b33cd08cea1ce5786528b to your computer and use it in GitHub Desktop.
Challenge 18: Multiuser

In this challenge you will update the Noteful to support multiple users. Each user will have their own set of notes, folder and tags. And you must ensure that a user cannot read, modify or delete another user's items.

Requirements

  • Update Notes, Folder and Tags models and seed data individual users
  • Protect against invalid folderId or tag references

Update Note schema and endpoints

To get started, update the Note schema to support a User reference. Reseed the database with Notes that contain a userId. And update all of the notes routes to reference the current user id.

Update Note Schema and reseed the DB

The User reference in the Note schema is similar to the Folder reference. Each Note will refer to a User similar to how a Note can refer to a folder. However, the Folder reference is optional whereas the User reference is required.

Your task. In the /models/notes.js file, add a userId. And set the type to ObjectId, reference the User model and set required to be true. Below is an example.

  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }

Next you will need to update your /db/seed/users.json with a digest (aka hash) of a password. You can generate new digests with the following script. Create a /utils/hash-password.js script and add the following. Then execute the script to get a digest.

const bcrypt = require('bcryptjs');
const password = 'baseball';

/* Hash a password with cost-factor 10, then run compare to verify */
bcrypt.hash(password, 10)
  .then(digest => {
    console.log('digest:', digest);
    return digest;
  })
  .then(hash => {
    return bcrypt.compare(password, hash);
  })
  .then(valid => {
    console.log('isValid: ', valid);
  })
  .catch(err => {
    console.error('error', err);
  });

Update /db/seed/users.json by replacing the plain-text password with a digest (aka hash) of a password. Below is an example.

  {
    "_id": "333333333333333333333300",
    "fullname": "Bob User",
    "username": "bobuser",
    "password": "$2a$10$QJCIX42iD5QMxLRgHHBJre2rH6c6nI24UysmSYtkmeFv6X8uS1kgi"
  },

Note: The digest above is for the string 'password'.

Finally, update /utils/seed-database.js script. Require the User model and seed data. And add an insertMany and createIndexes similar to Folders and Tags.

Verify the seed database script worked by checking the database. You should see the users from the /db/seed/users.json along with the hashed password.

Using Postman, test the update by POSTing a username and password to the /api/login endpoint. You should be able to login and receive a JWT.

Update /notes endpoints

Next, update the /notes endpoints to ensure that a user can only interact with their own notes.

In each endpoint, capture the current user id from req.user and update the query. Note, you may need to change the Mongoose method. For instance, the GET /notes/:id example below uses Note.findOne({ _id: id, userId }) instead of Note.findById(id).

router.get('/:id', (req, res, next) => {
  const { id } = req.params;
  const userId = req.user.id;

  if (!mongoose.Types.ObjectId.isValid(id)) {
    const err = new Error('The `id` is not valid');
    err.status = 400;
    return next(err);
  }

  Note.findOne({ _id: id, userId })
    .populate('tags')
    .then(result => {
      if (result) {
        res.json(result);
      } else {
        next();
      }
    })
    .catch(err => {
      next(err);
    });
});

Your turn.

Update the other Note endpoints and check the changes using Postman. Verify that a user can only view notes that belong to them, and ensure that a user cannot create, modify or delete a note which does not belong to them.

Note: At this point, a user can create or update a note and assign it a folder or tag created by another user. We'll tackle this problem at the towards the end of the challenge.

Update Folder and Tag schemas and endpoints

The notes are updated but folders and tags are still shared among all the users. Currently, a user can view, modify and delete another user's folders and tags. To prevent this, update the Folder and Tag schemas to include a userId that reference the User model, similar to the Note schema with one exception. Folders and Tags must be unique per user. In other words, the database can contain multiple Folders or Tags with the same name. But each user may only not have duplicate folders or tags.

Add User Id reference to Folder and Tag schemas

Your task, update the folder and tag schemas to include a userId that references the User model. Refer to the Note schema as an example.

Create Folder and Tag Compound Indexes on schemas

Currently, the folder and tag names are set to unique: true. This restricts the database to one item with that name for all users. The requirement is folder or tag names should be unique for each user. The solution is to use compound indexes.

Your task, update the folder and tag schemas. Remove the unique: true option from the name and create a unique compound index using userId and the name.

Below is an example:

folderSchema.index({ name: 1, userId: 1}, { unique: true });

More information is available in the Mongoose and Mongo documentation:

Update /folders and /tags endpoints

The next challenge is to update the folder and tags endpoints and queries with the current user id. But before working on the endpoints, update the seed data and repopulate the database.

Your challenge, update the /folders and /tags endpoints to ensure that a user can only interact with their own items. Like notes, capture the current user id from req.user and update the query in each endpoint.

Check your changes in Postman. Verify that a user can only view items that belong to them, and ensure that a user cannot create, modify or delete a folder or tag which does not belong to them.

Validate Folders and Tags Ids

The application still has one issue - a user can create or update a note and assign it a folder or tag belonging to another user. This breaks the isolation between users.

Your challenge is to update the POST and PUT /api/notes endpoints to verify that the incoming note's folder id and tags belong to the current user before updating the database.

For folders, verify the folderId is a valid ObjectId and the item belongs to the current user. If the validation fails, then return an error message 'The folderId is not valid' with status 400.

For tags, first verify the tags property is an Array, if validation fails, then return 'The tags property must be an array' with a status 400. Then verify that each tag id in the array is a valid ObjectId. And verify all the tags belong to the current user. If the validation fails, then return an error message 'The tags array contains an invalid id' with status 400.

Test the endpoint using Postman. Attempt to POST and PUT notes with both valid and invalid folder ids and tags ids.

Create User Tests

Using Node JWT Auth as a guide, create integration tests to verify Users are validated and created properly.

Create a User tests

Create a /test/users.test.js file and copy in the starter from this gist

The setup has been completed for you. Your challenge is to update your POST user endpoint and complete the rest of the tests and assertions. Again you can use the Node JWT Auth as a guide.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment