├── .gitignore ├── Gruntfile.js ├── INSTALL.txt ├── README.md ├── index.js ├── lib ├── authentication.js ├── registration.js └── utility.js ├── models ├── application.js ├── log.js └── user.js ├── package.json └── test ├── auth_spec.js ├── mocha.opts ├── registration_spec.js └── user_spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | node_modules 4 | rethinkdb_data -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var db = require("secondthought"); 2 | var assert = require("assert"); 3 | 4 | module.exports = function(grunt){ 5 | 6 | grunt.initConfig({ 7 | jshint : { 8 | files : ['lib/**/*js', 'models/**/*.js'] 9 | }, 10 | watch : { 11 | files: ['lib/**/*js', 'models/**/*.js'], 12 | tasks : ['jshint'] 13 | } 14 | 15 | 16 | }); 17 | 18 | //this installs the database 19 | grunt.registerTask("installDb", function(){ 20 | var done = this.async(); 21 | db.connect({db : "membership"}, function(err,db){ 22 | db.install(['users', 'logs','sessions'], function(err,tableResult){ 23 | assert.ok(err === null, err); 24 | console.log("DB Installed: " + tableResult); 25 | done(); 26 | }); 27 | }); 28 | 29 | 30 | }); 31 | 32 | 33 | //adding a comment 34 | grunt.loadNpmTasks("grunt-contrib-jshint"); 35 | grunt.loadNpmTasks("grunt-contrib-watch"); 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /INSTALL.txt: -------------------------------------------------------------------------------- 1 | Hello! And thank you for downloading the source for the Node Application Patterns tutorial at Pluralsite. There are a couple of things to do before you get started... 2 | 3 | FIRST 4 | ----- 5 | 6 | Make sure you have Rethinkdb installed. You can get it (for free) from http://rethinkdb.com. 7 | You should have Node installed - the latest would be perfect. 8 | To run the test suite here, you have to have 2 modules installed globally: 9 | 10 | -- mocha (npm install mocha -g) 11 | -- should (npm install should -g) 12 | 13 | If you get an error running these commands, it's because you don't have permissions to write to the global module store. You can remedy this on a Mac using: 14 | 15 | -- sudo chown [your user name] -R /usr/local/lib 16 | -- sudo chown [your user name] -R /usr/local/bin 17 | 18 | Next, you'll need to run Grunt so that means you need to have it installed. You can install it using these commands: 19 | 20 | -- npm install grunt -g 21 | -- npm install grunt-cli -g 22 | 23 | You should be all set at this point! 24 | 25 | SECOND 26 | ------ 27 | 28 | Now that your environment is setup, you'll need to load up the modules required for this app to run. You can do this by running 29 | 30 | -- npm install 31 | 32 | Make sure you run it in the project directory. 33 | 34 | Fire up rethinkdb (calling "rethinkdb" in the command line) and run your tests using the "mocha" command! 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Source Code for Pluralsight's Node Application Patterns Course 2 | =========================================== 3 | 4 | This is the source code for Tekpub's old Node Application Patterns course, which was migrated to Pluralsight in the fall of 2013. There are some differences in approach to normal Pluralsight courses - this is because I did things a bit differently back then. 5 | 6 | If you have any questions or issues, feel free to leave them here. 7 | 8 | Installation 9 | ========== 10 | 11 | Just follow the instructions on the INSTALL.txt file... 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var events = require("events"); 2 | var util = require("util"); 3 | var Registration = require("./lib/registration"); 4 | var Authentication = require("./lib/authentication"); 5 | var db = require("secondthought"); 6 | 7 | var Membership = function (dbName) { 8 | var self = this; 9 | events.EventEmitter.call(self); 10 | 11 | self.findUserByToken = function(token,next){ 12 | db.connect({db : dbName}, function(err,db){ 13 | db.users.first({authenticationToken : token}, next); 14 | }); 15 | }; 16 | 17 | self.authenticate = function(email,password, next){ 18 | db.connect({db : dbName}, function(err,db){ 19 | var auth = new Authentication(db); 20 | 21 | auth.on("authenticated", function(authResult){ 22 | self.emit("authenticated",authResult); 23 | }); 24 | auth.on("not-authenticated", function(authResult){ 25 | self.emit("not-authenticated",authResult); 26 | }); 27 | auth.authenticate({email : email, password : password}, next); 28 | }); 29 | }; 30 | 31 | self.register = function(email,password,confirm,next){ 32 | db.connect({db : dbName}, function(err,db){ 33 | var reg = new Registration(db); 34 | 35 | reg.on("registered", function(regResult){ 36 | self.emit("registered",regResult); 37 | }); 38 | reg.on("not-registered", function(regResult){ 39 | self.emit("not-registered",regResult); 40 | }); 41 | reg.applyForMembership({email : email, password : password, confirm : confirm}, next); 42 | }); 43 | }; 44 | 45 | return self; 46 | }; 47 | util.inherits(Membership, events.EventEmitter); 48 | module.exports = Membership; -------------------------------------------------------------------------------- /lib/authentication.js: -------------------------------------------------------------------------------- 1 | var events = require("events"); 2 | var util = require("util"); 3 | var bc = require("bcrypt-nodejs"); 4 | var User = require("../models/user"); 5 | var Log = require("../models/log"); 6 | var assert = require("assert"); 7 | 8 | //A wrapper object for the result of Authentication 9 | var AuthResult =function(creds){ 10 | var result = { 11 | creds : creds, 12 | success : false, 13 | message : "Invalid email or password", 14 | user : null, 15 | log : null 16 | }; 17 | 18 | return result; 19 | }; 20 | 21 | //The prototype which our module here will export. Takes a db instance 22 | var Authentication = function (db) { 23 | var self = this; 24 | var continueWith = null; 25 | events.EventEmitter.call(self); 26 | 27 | //validate credentials 28 | var validateCredentials = function(authResult){ 29 | if(authResult.creds.email && authResult.creds.password){ 30 | self.emit("creds-ok", authResult); 31 | }else{ 32 | self.emit("invalid",authResult); 33 | } 34 | }; 35 | 36 | //find the user 37 | var findUser = function(authResult){ 38 | db.users.first({email : authResult.creds.email}, function(err,found){ 39 | assert.ok(err === null, err); 40 | if(found){ 41 | authResult.user = new User(found); 42 | self.emit("user-found",authResult); 43 | }else{ 44 | self.emit("invalid",authResult); 45 | } 46 | }) 47 | }; 48 | 49 | //compare the password 50 | var comparePassword = function(authResult){ 51 | var matched = bc.compareSync(authResult.creds.password,authResult.user.hashedPassword); 52 | if(matched){ 53 | self.emit("password-accepted",authResult); 54 | }else{ 55 | self.emit("invalid",authResult); 56 | } 57 | }; 58 | 59 | //bump the stats 60 | var updateUserStats = function(authResult){ 61 | 62 | //set the updates on the authResult 63 | var user = authResult.user; 64 | user.signInCount+=1; 65 | user.lastLoginAt = user.currentLoginAt; 66 | user.currentLoginAt = new Date(); 67 | 68 | //now save them 69 | var updates = { 70 | signInCount : user.signInCount, 71 | lastLoginAt : user.lastLoginAt, 72 | currentLoginAt : user.currentLoginAt 73 | }; 74 | db.users.updateOnly(updates,authResult.user.id,function(err,updates){ 75 | assert.ok(err === null, err); 76 | self.emit("stats-updated",authResult); 77 | }); 78 | }; 79 | 80 | //create a log entry 81 | var createLog = function(authResult){ 82 | 83 | var log = new Log({subject : "Authentication", 84 | userId : authResult.user.id, 85 | entry : "Successfully logged in" 86 | }); 87 | 88 | db.logs.save(log,function(err,newLog){ 89 | authResult.log = newLog; 90 | self.emit("log-created",authResult); 91 | }); 92 | 93 | }; 94 | 95 | //If everything goes well, this will be called 96 | var authOk = function(authResult){ 97 | authResult.success = true; 98 | authResult.message = "Welcome!"; 99 | self.emit("authenticated",authResult); 100 | self.emit("completed",authResult); 101 | if(continueWith){ 102 | continueWith(null,authResult); 103 | } 104 | }; 105 | 106 | //if anything fails this will be called 107 | var authNotOk = function(authResult){ 108 | authResult.success = false; 109 | self.emit("not-authenticated",authResult); 110 | self.emit("completed",authResult); 111 | if(continueWith){ 112 | continueWith(null,authResult); 113 | } 114 | }; 115 | 116 | //The event chain for our auth procedure 117 | self.on("login-received", validateCredentials); 118 | self.on("creds-ok", findUser); 119 | self.on("user-found",comparePassword); 120 | self.on("password-accepted",updateUserStats); 121 | self.on("stats-updated",createLog); 122 | self.on("log-created",authOk); 123 | 124 | //The event chain for auth failure 125 | self.on("invalid",authNotOk); 126 | 127 | //Entry point - the only thing exported on our module 128 | self.authenticate = function(creds,next){ 129 | continueWith = next; 130 | var authResult = new AuthResult(creds); 131 | self.emit("login-received", authResult); 132 | }; 133 | 134 | }; 135 | util.inherits(Authentication, events.EventEmitter); 136 | module.exports = Authentication; 137 | -------------------------------------------------------------------------------- /lib/registration.js: -------------------------------------------------------------------------------- 1 | var User = require("../models/user"); 2 | var Application = require("../models/application"); 3 | var assert = require("assert"); 4 | var bc = require("bcrypt-nodejs"); 5 | var Log = require("../models/log"); 6 | var Emitter = require("events").EventEmitter; 7 | var util = require("util"); 8 | 9 | //A wrapper object for our registration call result 10 | var RegResult = function(){ 11 | var result = { 12 | success : false, 13 | message : null, 14 | user : null 15 | }; 16 | return result; 17 | }; 18 | 19 | //This is the prototype which we will export 20 | var Registration = function(db){ 21 | Emitter.call(this); 22 | var self = this; 23 | var continueWith = null; 24 | 25 | var validateInputs = function(app){ 26 | 27 | //make sure there's an email and password 28 | if(!app.email || !app.password){ 29 | app.setInvalid("Email and password are required"); 30 | self.emit("invalid",app); 31 | }else if(app.password !== app.confirm){ 32 | app.setInvalid("Passwords don't match"); 33 | self.emit("invalid",app); 34 | }else{ 35 | app.validate(); 36 | self.emit("validated",app); 37 | } 38 | 39 | }; 40 | 41 | var checkIfUserExists = function(app){ 42 | db.users.exists({email : app.email}, function(err,exists){ 43 | assert.ok(err === null); 44 | if(exists){ 45 | app.setInvalid("This email already exists"); 46 | self.emit("invalid",app); 47 | }else{ 48 | self.emit("user-doesnt-exist",app); 49 | } 50 | }); 51 | }; 52 | 53 | var createUser = function(app){ 54 | var user = new User(app); 55 | user.status = "approved"; 56 | user.hashedPassword = bc.hashSync(app.password); 57 | user.signInCount = 1; 58 | db.users.save(user,function(err,newUser){ 59 | assert.ok(err === null, err); 60 | app.user = newUser; 61 | self.emit("user-created",app); 62 | }); 63 | }; 64 | 65 | var addLogEntry = function(app){ 66 | var log = new Log({ 67 | subject : "Registration", 68 | userId : app.user.id, 69 | entry : "Successfully Registered" 70 | }); 71 | 72 | db.logs.save(log, function(err,newLog){ 73 | app.log = newLog; 74 | self.emit("log-created",app); 75 | }); 76 | }; 77 | 78 | self.applyForMembership = function(args, next){ 79 | continueWith = next; 80 | var app = new Application(args); 81 | self.emit("application-received",app); 82 | }; 83 | 84 | //the final call if everything works as expected 85 | var registrationOk = function(app){ 86 | var regResult = new RegResult(); 87 | regResult.success = true; 88 | regResult.message = "Welcome!"; 89 | regResult.user = app.user; 90 | regResult.log = app.log; 91 | self.emit("registered", regResult); 92 | if(continueWith){ 93 | continueWith(null,regResult); 94 | } 95 | }; 96 | 97 | //the final call if anything fails 98 | var registrationNotOk = function(app){ 99 | var regResult = new RegResult(); 100 | regResult.success = false; 101 | regResult.message = app.message; 102 | self.emit("not-registered", regResult); 103 | if(continueWith){ 104 | continueWith(null,regResult); 105 | } 106 | }; 107 | 108 | 109 | //The event chain for a successful registration 110 | self.on("application-received",validateInputs); 111 | self.on("validated", checkIfUserExists); 112 | self.on("user-doesnt-exist",createUser); 113 | self.on("user-created",addLogEntry); 114 | self.on("log-created",registrationOk); 115 | 116 | //the event chain for a non-successful registration 117 | self.on("invalid",registrationNotOk); 118 | 119 | return self; 120 | }; 121 | util.inherits(Registration,Emitter); 122 | module.exports = Registration; 123 | -------------------------------------------------------------------------------- /lib/utility.js: -------------------------------------------------------------------------------- 1 | exports.randomString = function(stringLength){ 2 | stringLength = stringLength || 12; 3 | var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; 4 | var result = ''; 5 | for (var i=0; i