Adding Security to Schema and Models

We have seen the usage of mongoose.Schema in Chapter 22, and Chapter 24. A short recap of something you know:

Example 46.1. A Schema Example in Action, myg171/mongooseUser0.js
"use strict";
/*
 * include more sophisticated mongodb functionality
 * mongoose enforces schemas, mongodb doesn't
 */
const mongoose = require("mongoose");

/*
 * create schema for user object
 * build corresponding model as an object
 * Wex19, lesson 17
 */
const userSchema = mongoose.Schema({    // with simple attribute: types
    name: String,
    email: String,
    zipcode: Number,
    created: Date
});
const User0 = mongoose.model("User", userSchema, 'user'); // create model
//     /\                     /\        /\        /\
//      |                      |         |         |
//      |                      |         |         --- collection name in db
//      |                      |         ------------- schema  from above
//      |                      ----------------------- name of model in mongo
//      ---------------------------------------------- my variable name for the model
/*
 * connect to mongodb server
 */
const dbname = "testUser0";
const constr = `mongodb://localhost:27017/${dbname}`;
const conparam = {
    useNewUrlParser: true,
    useUnifiedTopology: true
};
mongoose.connect(constr, conparam);
const db = mongoose.connection;
db.once("open", function() {
    console.log("Connected to server by mongoose")
});


/*
 * create concrete object
 */
let user0 = new User0({
    name: "Niels",
    email: "nmla@iba.dk",
    zipcode: 6000,
    created: "2020-03-12"
});
/*
 * insert user0 object into database, the C of CRUD
 * save is a mongoose method
 */
user0.save(function(error, savedDocument) {
    if (error) console.log("primary:\n" + error);
    console.log(savedDocument);
});


/*
 * alternative way of inserting, the C of CRUD
 * create below includes the save functionality
 */
User0.create(
    {
        name: "nmla",
        email: "nmla@iba.dk",
        zipcode: 6000,
        created: "2020-03-12"
    },
    function(error, savedDocument) {
        if (error) console.log("alternative:\n" + error);
        console.log(savedDocument);
        db.close();     // if forgotten batch job doesn't stop by itself
                        // must be here to preserve asynchronicity
    }
);


console.log("Asynchronous? If I come first, yes!");

Looking at code lines 13 through 18 userSchema is the definition of a mongoose.Schema, and mongoose.model defines the mongoose object User from the schema, and assigns user as its collection name for the MongoDB database. The type designations are in shorthand notation.

The surrounding code makes it testable from the CLI. Test it and check that it works by looking into the database with the mongo CLI client. You may also try changing one of the strings to a number to verify that no validation takes place. The types given in the schema is a mild way of adding thumb screws to what the user may input. The application just seems to apply regular JavaScript coercion to the user entered data.


Example 46.2. Tightening the Schema Rules, myg171/mongooseUser1.js
"use strict";
/*
 * include more sophisticated mongodb functionality
 * mongoose enforces schemas, mongodb doesn't
 */
const mongoose = require("mongoose");

/*
 * create schema for user object
 * build corresponding model as an object
 * Wex19, lesson 17
 */
const userSchema = mongoose.Schema({        // with complex attributes
    name: {
        type: String,                       // coercion toString
        required: true                      // must be present AND have value (not null)
    },
    email: {
        type: String,
        required: true,
        lowercase: true,                    // coerces (?) to lower case
        unique: true                        // not a validator(?)
    },
    zipcode: {
        type: Number,
        min: [1000, "Zip code too short"],  // validator and its error msg
        max: 9999                           // validator, relies on std error msg
    },
    created: {
        type: Date,                         // if value invalid, force to start UNIX era
        default: Date.now                   // default value if none given
    }
});
const User0 = mongoose.model("User", userSchema, 'user'); // create model
//     /\                     /\        /\        /\
//      |                      |         |         |
//      |                      |         |         --- collection name in db
//      |                      |         ------------- schema  from above
//      |                      ----------------------- name of model in mongo
//      ---------------------------------------------- my variable name for the model
/*
 * connect to mongodb server
 */
const dbname = "testUser0";
const constr = `mongodb://localhost:27017/${dbname}`;
const conparam = {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useCreateIndex: true
};
mongoose.connect(constr, conparam);
const db = mongoose.connection;
db.once("open", function() {
    console.log("Connected to server by mongoose")
});


/*
 * create concrete object
 */
let user0 = new User0({
    name: "Niels",
    email: "nmla@iba.dk",
    zipcode: 6000
});
/*
 * insert user0 object into database, the C of CRUD
 * save is a mongoose method
 */
user0.save(function(error, savedDocument) {
    if (error) console.log("primary:\n" + error);
    console.log(savedDocument);
});


/*
 * alternative way of inserting, the C of CRUD
 * create below includes the save functionality
 */
User0.create(
    {
        name: "nmla",
        email: "nmla@iba.dk",
        zipcode: 9000
    },
    function(error, savedDocument) {
        if (error) console.log("alternative:\n" + error);
        console.log(savedDocument);
        db.close();     // if forgotten batch job doesn't stop by itself
                        // must be here to preserve asynchronicity
    }
);


console.log("Asynchronous? If I come first, yes!");

Here, in lines 14 through 31, you will find rules for the properties of the object to be specified in a complex syntax. In the previous example properties were defined with shorthand notation. Here each property is specified in longhand, ie the full syntax as an object with several properties making room for more nuances.


In our previous work with objects in JavaScript we defined objects by creating prototypes by adding properties, and methods to the prototype given by the language. Then in turn we created instances by using Object.create(User); if User was the object in question. With the objects created from mongoose schemas we can achieve the same. So far we showed the creation of properties. The next example will add methods.

Example 46.3. Separating Object and Adding methods, myg171/User.js
"use strict";

const mongoose = require("mongoose");
/*
 * create schema for user object
 * build corresponding model as an object
 * Wex19, lesson 17
 */
const userSchema = mongoose.Schema({        // with complex attributes
    name: {
        type: String,                       // coercion toString
        required: true                      // must be present AND have value (not null)
    },
    email: {
        type: String,
        required: true,
        lowercase: true,                    // coerces (?) to lower case
        unique: true                        // if not a validator, what?
    },
    zipcode: {
        type: Number,
        min: [1000, "Zip code too short"],  // validator and its error msg
        max: 9999                           // validator, relies on std error msg
    },
    created: {
        type: Date,                         // if value invalid, force to start UNIX era
        default: Date.now                   // default value if none given
    }
});

userSchema.methods.getInfo = function () {
    return `Name: ${this.name}, Email: ${this.email}, Zipcode: ${this.zipcode}`;
}

userSchema.methods.findNeighbours = function () {
    return this.model("User").find({zipcode: this.zipcode}).exec();
}

module.exports = mongoose.model("User", userSchema, 'user');

Implementing and testing the extended constraints.

Example 46.4. Using Enhanced Object, myg171/mongooseUser5.js
"use strict";
/*
 * include more sophisticated mongodb functionality
 * mongoose enforces schemas, mongodb doesn't
 */
const mongoose = require("mongoose");
const User0 = require("./User.js");
let foo = process.argv[2] || "foo";
let bar = Number(process.argv[3]) || 6000;
/*
 * connect to mongodb server
 */
const dbname = "testUser0";
const constr = `mongodb://localhost:27017/${dbname}`;
const conparam = {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useCreateIndex: true
};
mongoose.connect(constr, conparam);
const db = mongoose.connection;
db.once("open", function() {
    console.log("Connected to server by mongoose")
});


/*
 * create concrete object
 */
let user0 = new User0({
    name: foo,
    email: `${foo}@iba.dk`,
    zipcode: bar
});


/*
 * insert user0 object into database, the C of CRUD
 * save is a mongoose method
 */
user0.save(function(error, savedDocument) {
    if (error) console.log("primary:\n" + error);
    db.close();
});



console.log("Asynchronous? If I come first, yes!");

Implementing and testing one of the methods.

Example 46.5. Using Enhanced Object I, myg171/mongooseUser6.js
"use strict";
/*
 * include more sophisticated mongodb functionality
 * mongoose enforces schemas, mongodb doesn't
 */
const mongoose = require("mongoose");
const User = require("./User.js");
let foobar = process.argv[2] || "foobar";
/*
 * connect to mongodb server
 */
const dbname = "testUser0";
const constr = `mongodb://localhost:27017/${dbname}`;
const conparam = {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useCreateIndex: true
};
mongoose.connect(constr, conparam);
const db = mongoose.connection;
db.once("open", function() {
    console.log("Connected to server by mongoose")
});

let user0;
User.findOne({
    name: foobar
}).then(function (result) {
    user0 = result;
    console.log(user0.getInfo());           // using object method
})



console.log("Asynchronous? If I come first, yes!");

Implementing and testing the other method. You will notice the code to assure asynchronous behaviour because that method involves reading the database.

Example 46.6. Using Enhanced Object II, myg171/mongooseUser7.js
"use strict";
/*
 * include more sophisticated mongodb functionality
 * mongoose enforces schemas, mongodb doesn't
 */
const mongoose = require("mongoose");
const User = require("./User.js");
let foobar = process.argv[2] || "foobar";
/*
 * connect to mongodb server
 */
const dbname = "testUser0";
const constr = `mongodb://localhost:27017/${dbname}`;
const conparam = {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useCreateIndex: true
};
mongoose.connect(constr, conparam);
const db = mongoose.connection;
db.once("open", function() {
    console.log("Connected to server by mongoose")
});

let user0;
User.findOne({
    name: foobar
}).then(function (result) {
    user0 = result;
    return user0.findNeighbours();           // using another object method
}).then(function (result) {
    console.log(result);
    db.close();
}).catch(function (err) {
    console.error(err);
});


console.log("Asynchronous? If I come first, yes!");