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.
- Update Notes, Folder and Tags models and seed data individual users
- Protect against invalid
folderId
ortag
references
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.
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.
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.
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.
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.
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:
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.
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.
Using Node JWT Auth as a guide, create integration tests to verify Users are validated and created properly.
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.