{{post.text}}
12 | Posted by @{{post.created_by}} 13 | {{post.created_at | date:"h:mma 'on' MMM d, y"}} 14 |├── .gitignore ├── chirp ├── .DS_Store ├── app.js ├── bin │ └── www ├── models │ └── models.js ├── package.json ├── passport-init.js ├── public │ ├── javascripts │ │ └── chirpApp.js │ ├── login.html │ ├── main.html │ ├── register.html │ ├── signup.html │ └── stylesheets │ │ └── style.css ├── routes │ ├── api.js │ ├── authenticate.js │ └── index.js └── views │ ├── error.ejs │ └── index.ejs ├── module-2 ├── completed │ ├── app.js │ ├── package.json │ └── public │ │ ├── index.html │ │ ├── javascripts │ │ └── chirpApp.js │ │ ├── login.html │ │ ├── main.html │ │ ├── register.html │ │ └── stylesheets │ │ └── style.css ├── helloworld.html └── readme.md ├── module-3 ├── .DS_Store ├── Finished │ ├── .DS_Store │ ├── app.js │ ├── bin │ │ └── www │ ├── package.json │ ├── passport-init.js │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ ├── routes │ │ ├── api.js │ │ └── authenticate.js │ └── views │ │ ├── error.ejs │ │ └── index.ejs ├── README.md └── ScreenShots │ ├── ss1.png │ ├── ss2.png │ ├── ss3.png │ ├── ss4.png │ └── ss5.png ├── module-4 ├── .DS_Store ├── Finished │ ├── .DS_Store │ ├── app.js │ ├── bin │ │ └── www │ ├── models │ │ └── models.js │ ├── package.json │ ├── passport-init.js │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ ├── routes │ │ ├── api.js │ │ └── authenticate.js │ └── views │ │ ├── error.ejs │ │ └── index.ejs ├── README.md ├── ScreenShots │ ├── ss1.png │ └── ss2.png └── Start │ ├── .DS_Store │ ├── app.js │ ├── bin │ └── www │ ├── package.json │ ├── passport-init.js │ ├── public │ └── stylesheets │ │ └── style.css │ ├── routes │ ├── api.js │ └── authenticate.js │ └── views │ ├── error.ejs │ └── index.ejs ├── module-5 ├── completed │ ├── .DS_Store │ ├── app.js │ ├── bin │ │ └── www │ ├── models │ │ └── models.js │ ├── package.json │ ├── passport-init.js │ ├── public │ │ ├── index.html │ │ ├── javascripts │ │ │ └── chirpApp.js │ │ ├── login.html │ │ ├── main.html │ │ ├── register.html │ │ └── stylesheets │ │ │ └── style.css │ ├── routes │ │ ├── api.js │ │ ├── authenticate.js │ │ └── index.js │ └── views │ │ ├── error.ejs │ │ └── index.ejs ├── readme.md └── start │ ├── .DS_Store │ ├── app.js │ ├── bin │ └── www │ ├── models │ └── models.js │ ├── package.json │ ├── passport-init.js │ ├── public │ └── stylesheets │ │ └── style.css │ ├── routes │ ├── api.js │ ├── authenticate.js │ └── index.js │ └── views │ ├── error.ejs │ └── index.ejs ├── module-6 ├── README.md └── ScreenShots │ ├── ss1.png │ ├── ss10.png │ ├── ss11.png │ ├── ss12.png │ ├── ss13.png │ ├── ss14.png │ ├── ss15.png │ ├── ss2.png │ ├── ss3.png │ ├── ss4.png │ ├── ss5.png │ ├── ss6.png │ ├── ss7.png │ ├── ss8.png │ └── ss9.png ├── readme.md └── slides ├── module1.pptx ├── module2.pptx ├── module3.pptx ├── module4.pptx ├── module5.pptx └── module6.pptx /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules/ 3 | demo-runthrough 4 | 5 | -------------------------------------------------------------------------------- /chirp/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/chirp/.DS_Store -------------------------------------------------------------------------------- /chirp/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var session = require('express-session'); 8 | var passport = require('passport'); 9 | //initialize mongoose schemas 10 | require('./models/models'); 11 | var index = require('./routes/index'); 12 | var api = require('./routes/api'); 13 | var authenticate = require('./routes/authenticate')(passport); 14 | var mongoose = require('mongoose'); //add for Mongo support 15 | mongoose.connect('mongodb://localhost/test-chirp'); //connect to Mongo 16 | var app = express(); 17 | 18 | // view engine setup 19 | app.set('views', path.join(__dirname, 'views')); 20 | app.set('view engine', 'ejs'); 21 | 22 | // uncomment after placing your favicon in /public 23 | //app.use(favicon(__dirname + '/public/favicon.ico')); 24 | app.use(logger('dev')); 25 | app.use(session({ 26 | secret: 'keyboard cat' 27 | })); 28 | app.use(bodyParser.json()); 29 | app.use(bodyParser.urlencoded({ extended: false })); 30 | app.use(cookieParser()); 31 | app.use(express.static(path.join(__dirname, 'public'))); 32 | app.use(passport.initialize()); 33 | app.use(passport.session()); 34 | 35 | app.use('/', index); 36 | app.use('/auth', authenticate); 37 | app.use('/api', api); 38 | 39 | // catch 404 and forward to error handler 40 | app.use(function(req, res, next) { 41 | var err = new Error('Not Found'); 42 | err.status = 404; 43 | next(err); 44 | }); 45 | 46 | //// Initialize Passport 47 | var initPassport = require('./passport-init'); 48 | initPassport(passport); 49 | 50 | // error handlers 51 | 52 | // development error handler 53 | // will print stacktrace 54 | if (app.get('env') === 'development') { 55 | app.use(function(err, req, res, next) { 56 | res.status(err.status || 500); 57 | res.render('error', { 58 | message: err.message, 59 | error: err 60 | }); 61 | }); 62 | } 63 | 64 | // production error handler 65 | // no stacktraces leaked to user 66 | app.use(function(err, req, res, next) { 67 | res.status(err.status || 500); 68 | res.render('error', { 69 | message: err.message, 70 | error: {} 71 | }); 72 | }); 73 | 74 | 75 | module.exports = app; 76 | -------------------------------------------------------------------------------- /chirp/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('Start'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 3000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /chirp/models/models.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var postSchema = new mongoose.Schema({ 5 | created_by: String, //should be changed to ObjectId, ref "User" 6 | created_at: {type: Date, default: Date.now}, 7 | text: String 8 | }); 9 | 10 | var userSchema = new mongoose.Schema({ 11 | username: String, 12 | password: String, //hash created from password 13 | created_at: {type: Date, default: Date.now} 14 | }) 15 | 16 | 17 | mongoose.model('Post', postSchema); 18 | mongoose.model('User', userSchema); 19 | -------------------------------------------------------------------------------- /chirp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Start", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "bcrypt-nodejs": "0.0.3", 10 | "body-parser": "~1.8.1", 11 | "cookie-parser": "~1.3.3", 12 | "debug": "^2.0.0", 13 | "ejs": "~0.8.5", 14 | "express": "~4.9.0", 15 | "express-session": "^1.10.3", 16 | "mongoose": "^3.8.23", 17 | "morgan": "~1.3.0", 18 | "passport": "^0.2.1", 19 | "passport-local": "^1.0.0", 20 | "serve-favicon": "~2.1.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chirp/passport-init.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var User = mongoose.model('User'); 3 | var LocalStrategy = require('passport-local').Strategy; 4 | var bCrypt = require('bcrypt-nodejs'); 5 | 6 | module.exports = function(passport){ 7 | 8 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 9 | passport.serializeUser(function(user, done) { 10 | console.log('serializing user:',user.username); 11 | done(null, user._id); 12 | }); 13 | 14 | passport.deserializeUser(function(id, done) { 15 | User.findById(id, function(err, user) { 16 | console.log('deserializing user:',user.username); 17 | done(err, user); 18 | }); 19 | }); 20 | 21 | passport.use('login', new LocalStrategy({ 22 | passReqToCallback : true 23 | }, 24 | function(req, username, password, done) { 25 | // check in mongo if a user with username exists or not 26 | User.findOne({ 'username' : username }, 27 | function(err, user) { 28 | // In case of any error, return using the done method 29 | if (err) 30 | return done(err); 31 | // Username does not exist, log the error and redirect back 32 | if (!user){ 33 | console.log('User Not Found with username '+username); 34 | return done(null, false); 35 | } 36 | // User exists but wrong password, log the error 37 | if (!isValidPassword(user, password)){ 38 | console.log('Invalid Password'); 39 | return done(null, false); // redirect back to login page 40 | } 41 | // User and password both match, return user from done method 42 | // which will be treated like success 43 | return done(null, user); 44 | } 45 | ); 46 | } 47 | )); 48 | 49 | passport.use('signup', new LocalStrategy({ 50 | passReqToCallback : true // allows us to pass back the entire request to the callback 51 | }, 52 | function(req, username, password, done) { 53 | 54 | // find a user in mongo with provided username 55 | User.findOne({ 'username' : username }, function(err, user) { 56 | // In case of any error, return using the done method 57 | if (err){ 58 | console.log('Error in SignUp: '+err); 59 | return done(err); 60 | } 61 | // already exists 62 | if (user) { 63 | console.log('User already exists with username: '+username); 64 | return done(null, false); 65 | } else { 66 | // if there is no user, create the user 67 | var newUser = new User(); 68 | 69 | // set the user's local credentials 70 | newUser.username = username; 71 | newUser.password = createHash(password); 72 | 73 | // save the user 74 | newUser.save(function(err) { 75 | if (err){ 76 | console.log('Error in Saving user: '+err); 77 | throw err; 78 | } 79 | console.log(newUser.username + ' Registration succesful'); 80 | return done(null, newUser); 81 | }); 82 | } 83 | }); 84 | }) 85 | ); 86 | 87 | var isValidPassword = function(user, password){ 88 | return bCrypt.compareSync(password, user.password); 89 | }; 90 | // Generates hash using bCrypt 91 | var createHash = function(password){ 92 | return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); 93 | }; 94 | 95 | }; 96 | -------------------------------------------------------------------------------- /chirp/public/javascripts/chirpApp.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('chirpApp', ['ngRoute', 'ngResource']).run(function($rootScope) { 2 | $rootScope.authenticated = false; 3 | $rootScope.current_user = ''; 4 | 5 | $rootScope.signout = function(){ 6 | $http.get('auth/signout'); 7 | $rootScope.authenticated = false; 8 | $rootScope.current_user = ''; 9 | }; 10 | }); 11 | 12 | app.config(function($routeProvider){ 13 | $routeProvider 14 | //the timeline display 15 | .when('/', { 16 | templateUrl: 'main.html', 17 | controller: 'mainController' 18 | }) 19 | //the login display 20 | .when('/login', { 21 | templateUrl: 'login.html', 22 | controller: 'authController' 23 | }) 24 | //the signup display 25 | .when('/register', { 26 | templateUrl: 'register.html', 27 | controller: 'authController' 28 | }); 29 | }); 30 | 31 | app.factory('postService', function($resource){ 32 | return $resource('/api/posts/:id'); 33 | }); 34 | 35 | app.controller('mainController', function(postService, $scope, $rootScope){ 36 | $scope.posts = postService.query(); 37 | $scope.newPost = {created_by: '', text: '', created_at: ''}; 38 | 39 | $scope.post = function() { 40 | $scope.newPost.created_by = $rootScope.current_user; 41 | $scope.newPost.created_at = Date.now(); 42 | postService.save($scope.newPost, function(){ 43 | $scope.posts = postService.query(); 44 | $scope.newPost = {created_by: '', text: '', created_at: ''}; 45 | }); 46 | }; 47 | }); 48 | 49 | app.controller('authController', function($scope, $http, $rootScope, $location){ 50 | $scope.user = {username: '', password: ''}; 51 | $scope.error_message = ''; 52 | 53 | $scope.login = function(){ 54 | $http.post('/auth/login', $scope.user).success(function(data){ 55 | if(data.state == 'success'){ 56 | $rootScope.authenticated = true; 57 | $rootScope.current_user = data.user.username; 58 | $location.path('/'); 59 | } 60 | else{ 61 | $scope.error_message = data.message; 62 | } 63 | }); 64 | }; 65 | 66 | $scope.register = function(){ 67 | $http.post('/auth/signup', $scope.user).success(function(data){ 68 | if(data.state == 'success'){ 69 | $rootScope.authenticated = true; 70 | $rootScope.current_user = data.user.username; 71 | $location.path('/'); 72 | } 73 | else{ 74 | $scope.error_message = data.message; 75 | } 76 | }); 77 | }; 78 | }); -------------------------------------------------------------------------------- /chirp/public/login.html: -------------------------------------------------------------------------------- 1 |
8 | -------------------------------------------------------------------------------- /chirp/public/main.html: -------------------------------------------------------------------------------- 1 |{{post.text}}
12 | Posted by @{{post.created_by}} 13 | {{post.created_at | date:"h:mma 'on' MMM d, y"}} 14 |<%= error.stack %>4 | -------------------------------------------------------------------------------- /chirp/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
{{post.text}}
11 | Posted by @{{post.created_by}} 12 | {{post.created_at | date:"h:mma 'on' MMM d, y"}} 13 |{{post.created_by}} says {{post.text}} at {{post.created_at}}
125 |{{post.text}}
160 | Posted by @{{post.created_by}} 161 | {{post.created_at | date:"h:mma 'on' MMM d, y"}} 162 |{{post.text}}
306 | Posted by @{{post.created_by}} 307 | {{post.created_at | date:"h:mma 'on' MMM d, y"}} 308 |<%= error.stack %>4 | -------------------------------------------------------------------------------- /module-3/Finished/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Welcome to <%= title %>
10 | 11 | 12 | -------------------------------------------------------------------------------- /module-3/README.md: -------------------------------------------------------------------------------- 1 | # Module 3: Introduction to Node.js and Express 2 | 3 | ## Objective 4 | 5 | By the end of this module you will know: 6 | 7 | - The basic advantages of [Node.js](http://nodejs.org) 8 | - What the [Node Package Manager](http://npmjs.org) (npm) is 9 | - Creating [Express](http://expressjs.com) API Routes 10 | - How to add authentication to APIs using [passport](http://npmjs.org/packages/passport) 11 | - The basics of Route [Middleware](http://expressjs.com/guide/using-middleware.html) 12 | 13 | 14 | ## Introduction 15 | 16 | In this section, we'll transition to the back-end of the Chirp! application. To start the beginnings of the back-end we'll need to create a new express application, add new routes to service our angular.js application and add authentication to protect some of our apis. 17 | 18 | ## Node.js 19 | 20 | Node.js or simply Node is an asynchronous I/O engine which allows you to write fast, scalable applications on the server using javascript. Its implemented on top of [Chromium's V8 Javascript](http://code.google.com/p/v8) runtime. 21 | 22 | Node is single threaded and uses what is called an Event Loop to manage asynchronous tasks. We won't cover that aspect in detail in this module. 23 | 24 | ## Node Package Manager (npm) 25 | 26 | Npm is the package manager for Node. The package manager is responsible for hosting open source modules for the use by Node community. Every application contains a **package.json** file within its directory which is a manifest of the dependencies required for the application to run. 27 | 28 | ## Getting Started 29 | 30 | We will Start developing in the [Start](./Start) directory and after this module we will be left with what can be found in the [Finished])(./Finished) directory. 31 | 32 | ### Generating a blank Express Application 33 | 34 | The first thing we need to do is generate an express application using Node.js. To do this we'll execute the following commands from this directory: 35 | 36 | ```bash 37 | # create a new express application 38 | mkdir ./Start 39 | cd ./Start 40 | express --ejs . 41 | ``` 42 | 43 | Express will output the files that it creates: 44 | 45 | ``` 46 | create : . 47 | create : ./package.json 48 | create : ./app.js 49 | create : ./public 50 | create : ./public/javascripts 51 | create : ./public/images 52 | create : ./public/stylesheets 53 | create : ./public/stylesheets/style.css 54 | create : ./routes 55 | create : ./routes/index.js 56 | create : ./routes/users.js 57 | create : ./views 58 | create : ./views/index.ejs 59 | create : ./views/error.ejs 60 | create : ./bin 61 | create : ./bin/www 62 | 63 | install dependencies: 64 | $ cd . && npm install 65 | 66 | run the app: 67 | $ DEBUG=Start ./bin/www 68 | ``` 69 | 70 | Now install the dependencies for the application. Npm will read the dependencies out of package.json and place them into a directory named **node_modules**. Execute the command: 71 | 72 | ```bash 73 | # install dependencies described in package.json 74 | npm install 75 | ``` 76 | 77 | In the **routes** folder delete the **user.js** file, and create two new javascript files, **authenticate.js** and **api.js** 78 | 79 | ```bash 80 | # navigate to routes folder 81 | cd ./routes 82 | # remove users.js 83 | rm users.js 84 | # remove index.js 85 | rm index.js 86 | # create authentication.js and api.js files 87 | touch authentication.js 88 | touch api.js 89 | ``` 90 | 91 | Let's take a look at our generated **app.js** which is the main application file: 92 | 93 | ```js 94 | var express = require('express'); 95 | var path = require('path'); 96 | var favicon = require('serve-favicon'); 97 | var logger = require('morgan'); 98 | var cookieParser = require('cookie-parser'); 99 | var bodyParser = require('body-parser'); 100 | 101 | var routes = require('./routes/index'); 102 | var users = require('./routes/users'); 103 | 104 | var app = express(); 105 | 106 | // view engine setup 107 | app.set('views', path.join(__dirname, 'views')); 108 | app.set('view engine', 'ejs'); 109 | 110 | // uncomment after placing your favicon in /public 111 | //app.use(favicon(__dirname + '/public/favicon.ico')); 112 | app.use(logger('dev')); 113 | app.use(bodyParser.json()); 114 | app.use(bodyParser.urlencoded({ extended: false })); 115 | app.use(cookieParser()); 116 | app.use(express.static(path.join(__dirname, 'public'))); 117 | 118 | app.use('/', routes); 119 | app.use('/users', users); 120 | 121 | // catch 404 and forward to error handler 122 | app.use(function(req, res, next) { 123 | var err = new Error('Not Found'); 124 | err.status = 404; 125 | next(err); 126 | }); 127 | 128 | // error handlers 129 | 130 | // development error handler 131 | // will print stacktrace 132 | if (app.get('env') === 'development') { 133 | app.use(function(err, req, res, next) { 134 | res.status(err.status || 500); 135 | res.render('error', { 136 | message: err.message, 137 | error: err 138 | }); 139 | }); 140 | } 141 | 142 | // production error handler 143 | // no stacktraces leaked to user 144 | app.use(function(err, req, res, next) { 145 | res.status(err.status || 500); 146 | res.render('error', { 147 | message: err.message, 148 | error: {} 149 | }); 150 | }); 151 | 152 | 153 | module.exports = app; 154 | ``` 155 | 156 | This is the boiler plate code required to run our express application. The [require]() statements allow us to import javascript code. We'll use these to import our API routing code and register them with Express. Let's remove the current require statements to **index.js** and **users.js**: 157 | 158 | ```js 159 | var routes = require('./routes/index'); 160 | var users = require('./routes/users'); 161 | ``` 162 | 163 | And replace them with: 164 | 165 | ```js 166 | var api = require('./routes/api'); 167 | //We will uncomment this after implementing authenticate 168 | //var authenticate = require('./routes/authenticate'); 169 | ``` 170 | 171 | To register the route handlers with Express, we'll have to remove the current registrations with **index** and **users**: 172 | 173 | ```js 174 | app.use('/', routes); 175 | app.use('/users', users); 176 | ``` 177 | 178 | and replace it with 179 | 180 | ```js 181 | //app.use('/auth', authenticate); 182 | app.use('/api', api); 183 | ``` 184 | 185 | Now we're setup to load **authenticate.js** and **api.js** as application routers. Now we need to implement it. 186 | 187 | ## Implementing the RESTful API 188 | 189 | RESTful APIs follow a convention which present *resources* to the client. In our case a **Post** is a resource and because of this we will implement a **/posts** API which will 190 | First we'll implement placeholder route handlers for the **/posts** api within **api.js**. 191 | 192 | Every router begins with a require to express, and using the express Router class. At the end of the router implementation we **export** this module as the Router to be consumed by the code we added in **app.js** 193 | 194 | ``` 195 | var express = require('express'); 196 | var router = express.Router(); 197 | 198 | // Some implementation.... 199 | 200 | 201 | module.exports = router; 202 | ``` 203 | 204 | Now between these two things we need to add the **/posts** api handlers. The way this works in express is by registering the handler to a specific HTTP method, such as GET, PUT, POST or DELETE. Let's add the handlers for the **/posts** route: 205 | 206 | 207 | ```js 208 | var express = require('express'); 209 | var router = express.Router(); 210 | 211 | //api for all posts 212 | router.route('/posts') 213 | 214 | //create a new post 215 | .post(function(req, res){ 216 | 217 | //TODO create a new post in the database 218 | req.send({message:"TODO create a new post in the database"}); 219 | }) 220 | 221 | .get(function(req, res){ 222 | 223 | //TODO get all the posts in the database 224 | req.send({message:"TODO get all the posts in the database"}); 225 | }) 226 | 227 | 228 | 229 | module.exports = router; 230 | ``` 231 | 232 | Now that we got our **/posts** api completed we need to create some apis for individual posts. We can do this by building off of our **/posts** path and adding an ID specific route for an individual post. We use the ':' notation in our route name which tells Express that that particular part of the route will be treated as a parameter: 233 | 234 | ```js 235 | //api for all posts 236 | router.route('/posts') 237 | 238 | //create a new post 239 | .post(function(req, res){ 240 | 241 | //TODO create a new post in the database 242 | res.send({message:"TODO create a new post in the database"}); 243 | }) 244 | 245 | .get(function(req, res){ 246 | 247 | //TODO get all the posts in the database 248 | res.send({message:"TODO get all the posts in the database"}); 249 | }) 250 | 251 | ``` 252 | 253 | Finally our full router implementation looks like this: 254 | 255 | ```js 256 | var express = require('express'); 257 | var router = express.Router(); 258 | 259 | //api for all posts 260 | router.route('/posts') 261 | 262 | //create a new post 263 | .post(function(req, res){ 264 | 265 | //TODO create a new post in the database 266 | res.send({message:"TODO create a new post in the database"}); 267 | }) 268 | 269 | .get(function(req, res){ 270 | 271 | //TODO get all the posts in the database 272 | res.send({message:"TODO get all the posts in the database"}); 273 | }) 274 | 275 | //api for a specfic post 276 | router.route('/posts/:id') 277 | 278 | //create 279 | .put(function(req,res){ 280 | return res.send({message:'TODO modify an existing post by using param ' + req.param.id}); 281 | }) 282 | 283 | .get(function(req,res){ 284 | return res.send({message:'TODO get an existing post by using param ' + req.param.id}); 285 | }) 286 | 287 | .delete(function(req,res){ 288 | return res.send({message:'TODO delete an existing post by using param ' + req.param.id}) 289 | }); 290 | 291 | module.exports = router; 292 | ``` 293 | 294 | To start the application, got to the **./Start** folder and execute: 295 | 296 | ```bash 297 | # starts the application via npm 298 | npm start 299 | ``` 300 | 301 | ## Testing Your APIs 302 | 303 | To test your API's we'll use [Advanced Rest Client](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo) a Chrome browser application that allows us to test our API's without having to write code. You can also use [Postman](https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm) another great application to do the same. 304 | 305 | Open Chrome and either install or open Advanced Rest Client. We will make a request to each API we implemented ensure we get the correct place-holder message: 306 | 307 |  308 | 309 |  310 | 311 | Note that because in app.js we assigned express to use the **api** router at **/api** all routes in this file will have the prefix ''**/api**' 312 | 313 | Ensure that you get the correct 'TODO' message for the remaining **/api/posts**. 314 | 315 | To test the **/api/posts/:id** routes you can simply use any string for the :id part of the path. When we implement the API with MongoDB, we'll use the generated ID's from MongoDB. 316 | 317 | # Adding the Authentication APIs 318 | 319 | Most applications require some type of authentication to provide some basic identity to users. Adding local authentication is quite easy. It involves using browser [sessions](http://en.wikipedia.org/wiki/Session_(computer_science)) as semi-permanent data in which users are authenticated and some APIs to allow the creation of users in the database. 320 | 321 | 322 | ## Installing the Modules 323 | 324 | Passport is the library we will use to handle storing users within HTTP sessions. Express-session is middleware for handling sessions and bcrypt will allow us to store our passwords as hashes as its never a good idea to store passwords. First we have to install a few modules. Navigate to the [./Start](./Start) folder and execute: 325 | 326 | ```js 327 | # install express session middleware 328 | npm install express-session --save 329 | # install passport 330 | npm install passport --save 331 | # install passport-local strategy for handling authentication 332 | npm install passport-local --save 333 | # install bcrypt-nodejs for handling creating password hashes 334 | npm install bcrypt-nodejs --save 335 | ``` 336 | 337 | ## Express Middleware 338 | 339 | Middleware are units of code which have access to both the **request** **response** and the next route middleware in line. These code units are executed before the route handler has the opportunity to execute and they can be assigned at the router level or to the entire express application. 340 | 341 | You can think of these things as 'middlemen' standing between your client and your route handler to provide some general functionality. 342 | 343 | ### Session Middleware 344 | 345 | We will use the **express-generator** middleware at the application level to handle maintaining sessions. To do this go to **app.js** and add the following require statement at the top: 346 | 347 | ```js 348 | var session = require('express-session'); 349 | ``` 350 | 351 | Now change your middleware section to use the session module: 352 | 353 | ```js 354 | // uncomment after placing your favicon in /public 355 | //app.use(favicon(__dirname + '/public/favicon.ico')); 356 | app.use(logger('dev')); 357 | //Add this portion to your middleware section 358 | app.use(session({ 359 | secret: 'keyboard cat' 360 | })); 361 | // 362 | app.use(bodyParser.json()); 363 | app.use(bodyParser.urlencoded({ extended: false })); 364 | app.use(cookieParser()); 365 | app.use(express.static(path.join(__dirname, 'public'))); 366 | ``` 367 | 368 | It's important to note that sometimes the order of the middleware matters. In our case we can place it at the very top. 369 | 370 | The session manager uses a **secret** to maintain sessions. In practice, you should keep this secret value outside of your code repository in an environment variable. 371 | 372 | ### Bootstrapping Passport 373 | 374 | To bootstrap passport first we'll require passport in **app.js**: 375 | 376 | ```js 377 | var passport = require('passport'); 378 | ``` 379 | 380 | Now, add passport as an application level middleware. We'll add it to the bottom of the middleware chain: 381 | 382 | ```js 383 | // uncomment after placing your favicon in /public 384 | //app.use(favicon(__dirname + '/public/favicon.ico')); 385 | app.use(logger('dev')); 386 | app.use(session({ 387 | secret: 'keyboard cat' 388 | })); 389 | app.use(bodyParser.json()); 390 | app.use(bodyParser.urlencoded({ extended: false })); 391 | app.use(cookieParser()); 392 | app.use(express.static(path.join(__dirname, 'public'))); 393 | app.use(passport.initialize()); 394 | app.use(passport.session()); 395 | ``` 396 | 397 | ### Initializing Passport 398 | 399 | We need to Initialize Passport. Passport has it's own middleware handlers in which you assign at 'Strategy'. We will use a Local strategy since we'll just be using local accounts and not any higher level authentication such as Social authentication via Facebook. You can read more about this on the [Passport documentation](http://npmjs.org/packages/passport). 400 | 401 | In order to properly initialize passport we need to initialize it with its own middleware which will tell passport how to persist users in our datastore. Next module we'll introduce [mongodb](http://mongodb.com) a NoSQL document oriented database to store our users. In the mean-time we'll just store the users in memory using a javascript object. 402 | 403 | Add a new javascript file within the [./Start](./Start) directory 404 | 405 | ``` 406 | #create passport-init.js file 407 | touch passport-init.js 408 | ``` 409 | 410 | Now copy and paste this boiler plate passport initialization code which creates the middleware authentication handlers to the Passport specification and exports it as a module: 411 | 412 | ```js 413 | var LocalStrategy = require('passport-local').Strategy; 414 | var bCrypt = require('bcrypt-nodejs'); 415 | //temporary data store 416 | var users = {}; 417 | module.exports = function(passport){ 418 | 419 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 420 | passport.serializeUser(function(user, done) { 421 | console.log('serializing user:',user.username); 422 | return done(null, user.username); 423 | }); 424 | 425 | passport.deserializeUser(function(username, done) { 426 | 427 | return done('we have not implemented this', false); 428 | 429 | }); 430 | 431 | passport.use('login', new LocalStrategy({ 432 | passReqToCallback : true 433 | }, 434 | function(req, username, password, done) { 435 | 436 | return done('we have not implemented this', false); 437 | } 438 | )); 439 | 440 | passport.use('signup', new LocalStrategy({ 441 | passReqToCallback : true // allows us to pass back the entire request to the callback 442 | }, 443 | function(req, username, password, done) { 444 | 445 | return done('we have not implemented this', false); 446 | 447 | }) 448 | ); 449 | 450 | var isValidPassword = function(user, password){ 451 | return bCrypt.compareSync(password, user.password); 452 | }; 453 | // Generates hash using bCrypt 454 | var createHash = function(password){ 455 | return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); 456 | }; 457 | 458 | }; 459 | ``` 460 | 461 | The code above registers LocalStrategy instances for the signup and login actions in passport. These functions will be responsible for providing passport the user objects from our data storage. Obviously the code above is not complete. 462 | 463 | We also have two utility functions **createHash** **isValidPassword** for hashing and checking passwords. 464 | 465 | At top near your require statements in **passport-init.js** add a declaration for the **users** js object: 466 | 467 | ``` 468 | var LocalStrategy = require('passport-local').Strategy; 469 | var bCrypt = require('bcrypt-nodejs'); 470 | //temporary data store 471 | var users = {}; 472 | ``` 473 | 474 | Let's implement the **signup** action first: 475 | 476 | ```js 477 | passport.use('signup', new LocalStrategy({ 478 | passReqToCallback : true // allows us to pass back the entire request to the callback 479 | }, 480 | function(req, username, password, done) { 481 | 482 | findOrCreateUser = function(){ 483 | 484 | return done('we have not implemented this', false); 485 | }; 486 | 487 | return findOrCreateUser(); 488 | }) 489 | ); 490 | ``` 491 | 492 | The **signup** handler is pretty self explanatory. All we need to do is check if the username is on the user object we created otherwise we store the username and password as a key entry in the users object: 493 | 494 | ```js 495 | passport.use('signup', new LocalStrategy({ 496 | passReqToCallback : true // allows us to pass back the entire request to the callback 497 | }, 498 | function(req, username, password, done) { 499 | 500 | if (users[username]){ 501 | console.log('User already exists with username: ' + username); 502 | return done(null, false); 503 | } 504 | 505 | //store user in memory 506 | users[username] = { 507 | username: username, 508 | password: createHash(password) 509 | } 510 | 511 | console.log(users[username].username + ' Registration successful'); 512 | return done(null, users[username]); 513 | }) 514 | ); 515 | ``` 516 | 517 | To conform to passports specifications, we must call the **done** callback function on every exit point of the function. 518 | 519 | The **login** handler is also quite simple. Just check if the user is in the data store and if the password matches the hash stored: 520 | 521 | ```js 522 | passport.use('login', new LocalStrategy({ 523 | passReqToCallback : true 524 | }, 525 | function(req, username, password, done) { 526 | 527 | if(users[username]){ 528 | console.log('User Not Found with username '+username); 529 | return done(null, false); 530 | } 531 | 532 | if(isValidPassword(users[username], password)){ 533 | //sucessfully authenticated 534 | return done(null, users[username]); 535 | } 536 | else{ 537 | console.log('Invalid password '+username); 538 | return done(null, false) 539 | } 540 | } 541 | )); 542 | ``` 543 | 544 | Finally the serialize and deserialize handlers need to be provided a unique ID for each user. We can just use the username because those will be unique: 545 | 546 | ```js 547 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 548 | passport.serializeUser(function(user, done) { 549 | console.log('serializing user:',user.username); 550 | //return the unique id for the user 551 | done(null, user.username); 552 | }); 553 | 554 | //Desieralize user will call with the unique id provided by serializeuser 555 | passport.deserializeUser(function(username, done) { 556 | 557 | return done(null, users[username]); 558 | 559 | }); 560 | ``` 561 | Putting it all together our **passport-init.js** looks like: 562 | 563 | ```js 564 | var User = require('./models/models'); 565 | var mongoose = require('mongoose'); 566 | var User = mongoose.model('User'); 567 | var LocalStrategy = require('passport-local').Strategy; 568 | var bCrypt = require('bcrypt-nodejs'); 569 | users = {}; 570 | module.exports = function(passport){ 571 | 572 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 573 | passport.serializeUser(function(user, done) { 574 | console.log('serializing user:',user.username); 575 | //return the unique id for the user 576 | done(null, user.username); 577 | }); 578 | 579 | //Desieralize user will call with the unique id provided by serializeuser 580 | passport.deserializeUser(function(username, done) { 581 | 582 | return done(null, users[username]); 583 | 584 | }); 585 | 586 | passport.use('login', new LocalStrategy({ 587 | passReqToCallback : true 588 | }, 589 | function(req, username, password, done) { 590 | 591 | if(users[username]){ 592 | console.log('User Not Found with username '+username); 593 | return done(null, false); 594 | } 595 | 596 | if(isValidPassword(users[username], password)){ 597 | //sucessfully authenticated 598 | return done(null, users[username]); 599 | } 600 | else{ 601 | console.log('Invalid password '+username); 602 | return done(null, false) 603 | } 604 | } 605 | )); 606 | 607 | passport.use('signup', new LocalStrategy({ 608 | passReqToCallback : true // allows us to pass back the entire request to the callback 609 | }, 610 | function(req, username, password, done) { 611 | 612 | if (users[username]){ 613 | console.log('User already exists with username: ' + username); 614 | return done(null, false); 615 | } 616 | 617 | users[username] = { 618 | username: username, 619 | password: createHash(password) 620 | } 621 | 622 | console.log(users[username].username + ' Registration successful'); 623 | return done(null, users[username]); 624 | }) 625 | ); 626 | 627 | var isValidPassword = function(user, password){ 628 | return bCrypt.compareSync(password, user.password); 629 | }; 630 | // Generates hash using bCrypt 631 | var createHash = function(password){ 632 | return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); 633 | }; 634 | 635 | ``` 636 | 637 | Finally we have to initialize passport with the authentication strategies we've defined in **passport-init.js**. Add this segment of code after the middleware section in app.js: 638 | 639 | ```js 640 | //// Initialize Passport 641 | var initPassport = require('./passport-init'); 642 | initPassport(passport); 643 | ``` 644 | 645 | This will call the passport initialization and allow those functions to be called whenever a user needs to authenticate. 646 | 647 | ## Integrating Passport 648 | 649 | Now that we have our strategies implemented we need to integrate it to some authentication routes. 650 | 651 | ### Implementing the Auth Routes 652 | 653 | First, let's uncomment the require to **authenticate.js** require within **app.js** and pass it the **passport** module: 654 | 655 | ```js 656 | var authenticate = require('./routes/authenticate')(passport); 657 | ``` 658 | 659 | **Authenticate.js** will be written as a router, similar to **api.js** except it will expose a function that will take the **passport** module and return the router: 660 | 661 | ```js 662 | var express = require('express'); 663 | var router = express.Router(); 664 | 665 | module.exports = function(passport){ 666 | 667 | //sends successful login state back to angular 668 | router.get('/success', function(req, res){ 669 | res.send({state: 'success', user: req.user ? req.user : null}); 670 | }); 671 | 672 | //sends failure login state back to angular 673 | router.get('/failure', function(req, res){ 674 | res.send({state: 'failure', user: null, message: "Invalid username or password"}); 675 | }); 676 | 677 | //log in 678 | router.post('/login', passport.authenticate('login', { 679 | successRedirect: '/auth/success', 680 | failureRedirect: '/auth/failure' 681 | })); 682 | 683 | //sign up 684 | router.post('/signup', passport.authenticate('signup', { 685 | successRedirect: '/auth/success', 686 | failureRedirect: '/auth/failure' 687 | })); 688 | 689 | //log out 690 | router.get('/signout', function(req, res) { 691 | req.logout(); 692 | res.redirect('/'); 693 | }); 694 | 695 | return router; 696 | 697 | } 698 | ``` 699 | 700 | Above we've implemented a **/login**, **/signup** and **signout** route. Passport provides the **successRedirect** and **failureRedirect** fields to redirect the client to the correct endpoint after an attempted sign-in. 701 | 702 | Notice the **req.logout** function within the **/signout** route handler. This is middleware added by the passport library to facilitate logging a user out of their session. 703 | 704 | ### Protecting APIs with Authentication using Middleware 705 | 706 | Now that we've added our authentication APIs we can actually create our own middle ware to protect some of our APIs. 707 | 708 | For Chirp! we want to allow anyone to read posts, but modifying and creating new posts is exclusively for registered users. 709 | 710 | Instead of checking in each of our route handlers we'd like to protect if the user is authenticated, we just have to add add one middleware function that will do it. 711 | 712 | Within **api.js** add this function and middleware registration: 713 | 714 | ```js 715 | //Used for routes that must be authenticated. 716 | function isAuthenticated (req, res, next) { 717 | // if user is authenticated in the session, call the next() to call the next request handler 718 | // Passport adds this method to request object. A middleware is allowed to add properties to 719 | // request and response objects 720 | 721 | //allow all get request methods 722 | if(req.method === "GET"){ 723 | return next(); 724 | } 725 | if (req.isAuthenticated()){ 726 | return next(); 727 | } 728 | 729 | // if the user is not authenticated then redirect him to the login page 730 | return res.redirect('/#login'); 731 | }; 732 | 733 | //Register the authentication middleware 734 | router.use('/posts', isAuthenticated); 735 | ``` 736 | 737 | Now, all requests that aren't GET (generally, requests that do not make any changes) are allowed. However any POST or PUT requests for either **/posts** or **posts/id** will receive a redirect and in our case we'll point the client to the front-end login route. 738 | 739 | ## Testing the Authenticated APIs 740 | 741 | ### Negative Testing the Posts API 742 | 743 | We can test the APIs by using a rest client like we did previously. First let's try a [negative test]() with an API that we know we should be authenticated to: 744 | 745 |  746 | 747 | After we make the request the response should be a 301 redirect to the /#login front-end route: 748 | 749 |  750 | 751 | The GET endpoint should be unaffected and behave as before. 752 | 753 | ### Positive Testing the Posts API 754 | 755 | Now, if we signup and login we should be able to call the POST method on the **/api/posts** endpoint: 756 | 757 |  758 |  759 | 760 | # Conclusion 761 | 762 | We've created all the necessary routes in express to service our angular application. In this section we've covered how to get started using node, generating an express application, creating routes, using passport for authentication and using middleware to protect our apis. 763 | 764 | In the next section we'll fully implement the API route handlers using MongoDB to store Post and User documents for persistent storage. 765 | -------------------------------------------------------------------------------- /module-3/ScreenShots/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-3/ScreenShots/ss1.png -------------------------------------------------------------------------------- /module-3/ScreenShots/ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-3/ScreenShots/ss2.png -------------------------------------------------------------------------------- /module-3/ScreenShots/ss3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-3/ScreenShots/ss3.png -------------------------------------------------------------------------------- /module-3/ScreenShots/ss4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-3/ScreenShots/ss4.png -------------------------------------------------------------------------------- /module-3/ScreenShots/ss5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-3/ScreenShots/ss5.png -------------------------------------------------------------------------------- /module-4/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-4/.DS_Store -------------------------------------------------------------------------------- /module-4/Finished/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-4/Finished/.DS_Store -------------------------------------------------------------------------------- /module-4/Finished/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var session = require('express-session'); 8 | var passport = require('passport'); 9 | //initialize mongoose schemas 10 | require('./models/models'); 11 | var api = require('./routes/api'); 12 | var authenticate = require('./routes/authenticate')(passport); 13 | var mongoose = require('mongoose'); //add for Mongo support 14 | mongoose.connect('mongodb://localhost/test-chirp'); //connect to Mongo 15 | var app = express(); 16 | 17 | // view engine setup 18 | app.set('views', path.join(__dirname, 'views')); 19 | app.set('view engine', 'ejs'); 20 | 21 | // uncomment after placing your favicon in /public 22 | //app.use(favicon(__dirname + '/public/favicon.ico')); 23 | app.use(logger('dev')); 24 | app.use(session({ 25 | secret: 'keyboard cat' 26 | })); 27 | app.use(bodyParser.json()); 28 | app.use(bodyParser.urlencoded({ extended: false })); 29 | app.use(cookieParser()); 30 | app.use(express.static(path.join(__dirname, 'public'))); 31 | app.use(passport.initialize()); 32 | app.use(passport.session()); 33 | 34 | app.use('/auth', authenticate); 35 | app.use('/api', api); 36 | 37 | // catch 404 and forward to error handler 38 | app.use(function(req, res, next) { 39 | var err = new Error('Not Found'); 40 | err.status = 404; 41 | next(err); 42 | }); 43 | 44 | //// Initialize Passport 45 | var initPassport = require('./passport-init'); 46 | initPassport(passport); 47 | 48 | // error handlers 49 | 50 | // development error handler 51 | // will print stacktrace 52 | if (app.get('env') === 'development') { 53 | app.use(function(err, req, res, next) { 54 | res.status(err.status || 500); 55 | res.render('error', { 56 | message: err.message, 57 | error: err 58 | }); 59 | }); 60 | } 61 | 62 | // production error handler 63 | // no stacktraces leaked to user 64 | app.use(function(err, req, res, next) { 65 | res.status(err.status || 500); 66 | res.render('error', { 67 | message: err.message, 68 | error: {} 69 | }); 70 | }); 71 | 72 | 73 | module.exports = app; 74 | -------------------------------------------------------------------------------- /module-4/Finished/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('Start'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 3000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /module-4/Finished/models/models.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var postSchema = new mongoose.Schema({ 5 | created_by: { type: Schema.ObjectId, ref: 'User' }, //should be changed to ObjectId, ref "User" 6 | created_at: {type: Date, default: Date.now}, 7 | text: String 8 | }); 9 | 10 | var userSchema = new mongoose.Schema({ 11 | username: String, 12 | password: String, //hash created from password 13 | created_at: {type: Date, default: Date.now} 14 | }) 15 | 16 | 17 | mongoose.model('Post', postSchema); 18 | mongoose.model('User', userSchema); 19 | -------------------------------------------------------------------------------- /module-4/Finished/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Start", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "bcrypt-nodejs": "0.0.3", 10 | "body-parser": "~1.8.1", 11 | "cookie-parser": "~1.3.3", 12 | "debug": "^2.0.0", 13 | "ejs": "~0.8.5", 14 | "express": "~4.9.0", 15 | "express-session": "^1.10.3", 16 | "mongoose": "^3.8.23", 17 | "morgan": "~1.3.0", 18 | "passport": "^0.2.1", 19 | "passport-local": "^1.0.0", 20 | "serve-favicon": "~2.1.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /module-4/Finished/passport-init.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var User = mongoose.model('User'); 3 | var LocalStrategy = require('passport-local').Strategy; 4 | var bCrypt = require('bcrypt-nodejs'); 5 | 6 | module.exports = function(passport){ 7 | 8 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 9 | passport.serializeUser(function(user, done) { 10 | console.log('serializing user:',user.username); 11 | done(null, user._id); 12 | }); 13 | 14 | passport.deserializeUser(function(id, done) { 15 | User.findById(id, function(err, user) { 16 | console.log('deserializing user:',user.username); 17 | done(err, user); 18 | }); 19 | }); 20 | 21 | passport.use('login', new LocalStrategy({ 22 | passReqToCallback : true 23 | }, 24 | function(req, username, password, done) { 25 | // check in mongo if a user with username exists or not 26 | User.findOne({ 'username' : username }, 27 | function(err, user) { 28 | // In case of any error, return using the done method 29 | if (err) 30 | return done(err); 31 | // Username does not exist, log the error and redirect back 32 | if (!user){ 33 | console.log('User Not Found with username '+username); 34 | return done(null, false); 35 | } 36 | // User exists but wrong password, log the error 37 | if (!isValidPassword(user, password)){ 38 | console.log('Invalid Password'); 39 | return done(null, false); // redirect back to login page 40 | } 41 | // User and password both match, return user from done method 42 | // which will be treated like success 43 | return done(null, user); 44 | } 45 | ); 46 | } 47 | )); 48 | 49 | passport.use('signup', new LocalStrategy({ 50 | passReqToCallback : true // allows us to pass back the entire request to the callback 51 | }, 52 | function(req, username, password, done) { 53 | 54 | // find a user in mongo with provided username 55 | User.findOne({ 'username' : username }, function(err, user) { 56 | // In case of any error, return using the done method 57 | if (err){ 58 | console.log('Error in SignUp: '+err); 59 | return done(err); 60 | } 61 | // already exists 62 | if (user) { 63 | console.log('User already exists with username: '+username); 64 | return done(null, false); 65 | } else { 66 | // if there is no user, create the user 67 | var newUser = new User(); 68 | 69 | // set the user's local credentials 70 | newUser.username = username; 71 | newUser.password = createHash(password); 72 | 73 | // save the user 74 | newUser.save(function(err) { 75 | if (err){ 76 | console.log('Error in Saving user: '+err); 77 | throw err; 78 | } 79 | console.log(newUser.username + ' Registration succesful'); 80 | return done(null, newUser); 81 | }); 82 | } 83 | }); 84 | }) 85 | ); 86 | 87 | var isValidPassword = function(user, password){ 88 | return bCrypt.compareSync(password, user.password); 89 | }; 90 | // Generates hash using bCrypt 91 | var createHash = function(password){ 92 | return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); 93 | }; 94 | 95 | }; 96 | -------------------------------------------------------------------------------- /module-4/Finished/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /module-4/Finished/routes/api.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var mongoose = require( 'mongoose' ); 4 | var Post = mongoose.model('Post'); 5 | //Used for routes that must be authenticated. 6 | function isAuthenticated (req, res, next) { 7 | // if user is authenticated in the session, call the next() to call the next request handler 8 | // Passport adds this method to request object. A middleware is allowed to add properties to 9 | // request and response objects 10 | 11 | //allow all get request methods 12 | if(req.method === "GET"){ 13 | return next(); 14 | } 15 | if (req.isAuthenticated()){ 16 | return next(); 17 | } 18 | 19 | // if the user is not authenticated then redirect him to the login page 20 | return res.redirect('/#login'); 21 | }; 22 | 23 | //Register the authentication middleware 24 | router.use('/posts', isAuthenticated); 25 | 26 | router.route('/posts') 27 | //creates a new post 28 | .post(function(req, res){ 29 | 30 | var post = new Post(); 31 | post.text = req.body.text; 32 | post.created_by = req.body.created_by; 33 | post.save(function(err, post) { 34 | if (err){ 35 | return res.send(500, err); 36 | } 37 | return res.json(post); 38 | }); 39 | }) 40 | //gets all posts 41 | .get(function(req, res){ 42 | console.log('debug1'); 43 | Post.find(function(err, posts){ 44 | console.log('debug2'); 45 | if(err){ 46 | return res.send(500, err); 47 | } 48 | return res.send(200,posts); 49 | }); 50 | }); 51 | 52 | //post-specific commands. likely won't be used 53 | router.route('/posts/:id') 54 | //gets specified post 55 | .get(function(req, res){ 56 | Post.findById(req.params.id, function(err, post){ 57 | if(err) 58 | res.send(err); 59 | res.json(post); 60 | }); 61 | }) 62 | //updates specified post 63 | .put(function(req, res){ 64 | Post.findById(req.params.id, function(err, post){ 65 | if(err) 66 | res.send(err); 67 | 68 | post.created_by = req.body.created_by; 69 | post.text = req.body.text; 70 | 71 | post.save(function(err, post){ 72 | if(err) 73 | res.send(err); 74 | 75 | res.json(post); 76 | }); 77 | }); 78 | }) 79 | //deletes the post 80 | .delete(function(req, res) { 81 | Post.remove({ 82 | _id: req.params.id 83 | }, function(err) { 84 | if (err) 85 | res.send(err); 86 | res.json("deleted :("); 87 | }); 88 | }); 89 | 90 | module.exports = router; -------------------------------------------------------------------------------- /module-4/Finished/routes/authenticate.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | module.exports = function(passport){ 5 | 6 | //sends successful login state back to angular 7 | router.get('/success', function(req, res){ 8 | res.send({state: 'success', user: req.user ? req.user : null}); 9 | }); 10 | 11 | //sends failure login state back to angular 12 | router.get('/failure', function(req, res){ 13 | res.send({state: 'failure', user: null, message: "Invalid username or password"}); 14 | }); 15 | 16 | //log in 17 | router.post('/login', passport.authenticate('login', { 18 | successRedirect: '/auth/success', 19 | failureRedirect: '/auth/failure' 20 | })); 21 | 22 | //sign up 23 | router.post('/signup', passport.authenticate('signup', { 24 | successRedirect: '/auth/success', 25 | failureRedirect: '/auth/failure' 26 | })); 27 | 28 | //log out 29 | router.get('/signout', function(req, res) { 30 | req.logout(); 31 | res.redirect('/'); 32 | }); 33 | 34 | return router; 35 | 36 | } -------------------------------------------------------------------------------- /module-4/Finished/views/error.ejs: -------------------------------------------------------------------------------- 1 |<%= error.stack %>4 | -------------------------------------------------------------------------------- /module-4/Finished/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Welcome to <%= title %>
10 | 11 | 12 | -------------------------------------------------------------------------------- /module-4/README.md: -------------------------------------------------------------------------------- 1 | # Module 4: Integrating MongoDB 2 | 3 | ## Objective 4 | 5 | By the end of this module you will know: 6 | 7 | - What a Document Oriented Database is 8 | - How to use [MongoDB](http://mongodb.org) from Node.js 9 | - Using [Mongoose](http://npmjs.org/packages/mongoose) to create Schemas and Models 10 | - Integrate MongoDB to route handlers 11 | 12 | ## Introduction 13 | 14 | In the previous section we implemented authentication and the routing necessary for the apis of our application. Next we need actually imeplement the authentication scheme so that users are persisted in MongoDB as well as integrated MongoDB to the **/api/posts** routes to manipulate Posts within MongoDB. 15 | 16 | This module picks up where [Module 3](../module-3) left off in the [Start](./Start) directory. 17 | 18 | ## Document Oriented Databases 19 | 20 | Document Oriented Databases are quite different from tabular datastores in that items are stored as Documents, and data is generally enclosed within a single document. Rather than having tables and rows you have collections and documents in order to reduce the amount of joins you have to make. 21 | 22 | Feel free to read more about this in our MongoDB MVA material [here](https://github.com/sedouard/mongodb-mva/tree/master/module1_intro_doc_dbs). 23 | 24 | ## MongoDB 25 | 26 | MongoDB is a popular open source implementation of a Document Oriented Database. It contains collections and documents. Its easy to get started running MongoDB. After you've installed it go to your installation directory and spin up the local server: 27 | 28 | ```bash 29 | # within /bin folder of your mongodb installation execute: 30 | mkdir data 31 | ./mongod 32 | ``` 33 | 34 | Note some systems may require you to run as `sudo`. 35 | 36 | The 'data' directory is required for mongoldb to store its data. It is also possible to store mongodb data elsewhere by specifying the `--dbpath` argument. 37 | 38 | You'll get some output similar to below which will let you know that the MongoDB server is running: 39 | 40 | ``` 41 | 2015-02-16T22:40:43.080-0800 [clientcursormon] mem (MB) res:36 virt:3550 42 | 2015-02-16T22:40:43.080-0800 [clientcursormon] mapped (incl journal view):1056 43 | 2015-02-16T22:40:43.080-0800 [clientcursormon] connections:0 44 | 2015-02-16T22:45:43.287-0800 [clientcursormon] mem (MB) res:13 virt:3550 45 | 2015-02-16T22:45:43.287-0800 [clientcursormon] mapped (incl journal view):1056 46 | 2015-02-16T22:45:43.288-0800 [clientcursormon] connections:0 47 | 2015-02-16T22:50:43.500-0800 [clientcursormon] mem (MB) res:13 virt:3550 48 | 2015-02-16T22:50:43.500-0800 [clientcursormon] mapped (incl journal view):1056 49 | 2015-02-16T22:50:43.500-0800 [clientcursormon] connections:0 50 | 2015-02-16T22:55:43.771-0800 [clientcursormon] mem (MB) res:13 virt:3550 51 | 2015-02-16T22:55:43.771-0800 [clientcursormon] mapped (incl journal view):1056 52 | 2015-02-16T22:55:43.771-0800 [clientcursormon] connections:0 53 | 2015-02-16T23:00:44.000-0800 [clientcursormon] mem (MB) res:13 virt:3550 54 | 2015-02-16T23:00:44.000-0800 [clientcursormon] mapped (incl journal view):1056 55 | 2015-02-16T23:00:44.000-0800 [clientcursormon] connections:0 56 | 2015-02-16T23:05:44.217-0800 [clientcursormon] mem (MB) res:13 virt:3550 57 | 2015-02-16T23:05:44.217-0800 [clientcursormon] mapped (incl journal view):1056 58 | 2015-02-16T23:05:44.217-0800 [clientcursormon] connections:0 59 | 2015-02-16T23:10:44.458-0800 [clientcursormon] mem (MB) res:13 virt:3550 60 | 2015-02-16T23:10:44.458-0800 [clientcursormon] mapped (incl journal view):1056 61 | 2015-02-16T23:10:44.458-0800 [clientcursormon] connections:0 62 | 2015-02-16T23:15:44.691-0800 [clientcursormon] mem (MB) res:13 virt:3550 63 | ``` 64 | 65 | ## Creating Models with Mongoose 66 | 67 | Now that we have our database running we should specify exactly what we're going to put into it. MongoDB data sort of looks like JSON objects. With data self enclosing as one unit. These units are called *documents* and are stored in *collections*. 68 | 69 | We will use [mongoose](http://npmjs.org/packages/mongoose) as our Object Data Mapper that will allow us to specify a schema for our Post and User objects. Don't worry MongoDB is flexible and allows us to change our schema at any time. Mongoose jus helps us write it down and enforce it. 70 | 71 | First we need to install mongoose: 72 | 73 | ```bash 74 | npm install mongoose --save 75 | ``` 76 | 77 | Navigate to the [Start](./Start) directory and create a new folder called **models** and then a new file called **models.js**: 78 | 79 | ```bash 80 | cd ./Start 81 | mkdir models 82 | cd models 83 | touch models.js 84 | ``` 85 | 86 | Now open **models.js** and require mongoose. 87 | 88 | ```js 89 | var mongoose = require('mongoose'); 90 | ``` 91 | 92 | We will now create two [schemas](http://mongoosejs.com/docs/guide.html), one for Post and the other for User. Schemas are the way we specify to mongoose what kind of objects it can expect us to place in MongoDB. There is a 1:1 relationship between a Schema and a MongoDB [Collection](http://mongodb.github.io/node-mongodb-native/api-generated/collection.html). 93 | 94 | First lets add the User schema to this file: 95 | 96 | ```js 97 | var userSchema = new mongoose.Schema({ 98 | username: String, 99 | password: String, //hash created from password 100 | created_at: {type: Date, default: Date.now} 101 | }); 102 | ``` 103 | 104 | This schema defines a User which contains a `username` and `password` of type `String` and a `created_at` property of type `Date`. 105 | 106 | We also need a post schema to represent our 'Cheeps' entries when a user 107 | 'Cheeps' a message: 108 | 109 | ``` 110 | var postSchema = new mongoose.Schema({ 111 | created_by: { type: Schema.ObjectId, ref: 'User' }, 112 | created_at: {type: Date, default: Date.now}, 113 | text: String 114 | }); 115 | ``` 116 | 117 | The `created_by` is special type of field which references a User document in the Users collection. 118 | 119 | Finally we need to use the schemas to register a User and Post [model]() with mongoose. 120 | 121 | ```js 122 | mongoose.model('Post', postSchema); 123 | mongoose.model('User', userSchema); 124 | ``` 125 | 126 | At the end of the requires section of **app.js** add this bootstrapping code which imports the mongoose module and connects us to the local database: 127 | 128 | ```js 129 | var mongoose = require('mongoose'); //add for Mongo support 130 | mongoose.connect('mongodb://localhost/test-chirp'); //connect to Mongo 131 | ``` 132 | 133 | ## Implementing the Authentication API to use MongoDB 134 | 135 | In Module 3 we used an in-memory object to store the users. This was good as a proof of concept to test the routing layer in Express, however we need users to be persistent even if the server restarts. We can do this by changing the implementation to use MongoDB. 136 | 137 | First, at the top of the **authenticate.js** file add mongoose and grab the Users schema: 138 | 139 | ```js 140 | var mongoose = require('mongoose'); 141 | var User = mongoose.model('User'); 142 | var LocalStrategy = require('passport-local').Strategy; 143 | var bCrypt = require('bcrypt'); 144 | //temporary data store 145 | var users = {}; 146 | ``` 147 | Also remove the `users` object as we won't be needing it anymore. 148 | 149 | #### Login 150 | 151 | Now lets start with our current `login` handler: 152 | 153 | ```js 154 | passport.use('login', new LocalStrategy({ 155 | passReqToCallback : true 156 | }, 157 | function(req, username, password, done) { 158 | 159 | if(users[username]){ 160 | console.log('User Not Found with username '+username); 161 | return done(null, false); 162 | } 163 | 164 | if(isValidPassword(users[username], password)){ 165 | //sucessfully authenticated 166 | return done(null, users[username]); 167 | } 168 | else{ 169 | console.log('Invalid password '+username); 170 | return done(null, false) 171 | } 172 | } 173 | )); 174 | ``` 175 | 176 | Everywhere we use the `users` dictionary we need to replace with a call to Mongoose. This is pretty easy since we have access to the Users model. First we'll fetch the user requested by passport: 177 | 178 | ```js 179 | 180 | User.findOne({ 'username' : username }, 181 | function(err, user) { 182 | // In case of any error, return using the done method 183 | if (err) 184 | return done(err); 185 | // Username does not exist, log the error and redirect back 186 | if (!user){ 187 | console.log('User Not Found with username '+username); 188 | return done(null, false); 189 | } 190 | // User exists but wrong password, log the error 191 | if (!isValidPassword(user, password)){ 192 | console.log('Invalid Password'); 193 | return done(null, false); // redirect back to login page 194 | } 195 | // User and password both match, return user from done method 196 | // which will be treated like success 197 | return done(null, user); 198 | } 199 | ); 200 | ``` 201 | 202 | The findOne function is a convince function in mongoose (and within the mongodb driver itself) which finds the first element that matches the query. 203 | 204 | A query object is provided to the function and is used to instruct MongoDb which document(s) to return. In our case MongoDB will retrieve the first user document which contains the `username` that matches the username requested by passport, which in turn is the username that the user will enter in the browser to login. 205 | 206 | Once we execute, the callback provided by Node.js will provide us either with and error `err` or the `user`. If `user` is null we know that that user doesn't exist in the database. If we did get a user object we can then call the same `isValidPassword` function to validate the provided password. If this works we can call the `done` callback and provide the user object returned b MongoDb to passport. 207 | 208 | #### Signup 209 | 210 | We can do something similar with the Signup API. Let's look at our original implementation: 211 | 212 | ```js 213 | passport.use('signup', new LocalStrategy({ 214 | passReqToCallback : true // allows us to pass back the entire request to the callback 215 | }, 216 | function(req, username, password, done) { 217 | 218 | if (users[username]){ 219 | console.log('User already exists with username: ' + username); 220 | return done(null, false); 221 | } 222 | 223 | //store user in memory 224 | users[username] = { 225 | username: username, 226 | password: createHash(password) 227 | } 228 | 229 | console.log(users[username].username + ' Registration successful'); 230 | return done(null, users[username]); 231 | }) 232 | ); 233 | ``` 234 | 235 | Similar to the `login` mongodb implementation we need to fetch the user using the `findOne` function. If mongodb returns a user, we know that the user already exists and that we cannot register another user with the same name. Otherwise we create a new `User` set the `username` and `password` and save it to the database: 236 | 237 | ```js 238 | // find a user in mongo with provided username 239 | User.findOne({ 'username' : username }, function(err, user) { 240 | // In case of any error, return using the done method 241 | if (err){ 242 | console.log('Error in SignUp: '+err); 243 | return done(err); 244 | } 245 | // already exists 246 | if (user) { 247 | console.log('User already exists with username: '+username); 248 | return done(null, false); 249 | } else { 250 | // if there is no user, create the user 251 | var newUser = new User(); 252 | 253 | // set the user's local credentials 254 | newUser.username = username; 255 | newUser.password = createHash(password); 256 | 257 | // save the user 258 | newUser.save(function(err) { 259 | if (err){ 260 | console.log('Error in Saving user: '+err); 261 | throw err; 262 | } 263 | console.log(newUser.username + ' Registration successful'); 264 | return done(null, newUser); 265 | }); 266 | } 267 | }); 268 | ``` 269 | #### Serialize User 270 | 271 | Passport requires that we provide a unique ID which specifies each user in order to serialize them into the session. MongoDB creates a field called _id on every object it creates. We can use this as a strong guarantee of a unique identifier. 272 | 273 | Take look at our original in-memory implementation of the serialization handler: 274 | 275 | ```js 276 | passport.serializeUser(function(user, done) { 277 | console.log('serializing user:',user.username); 278 | //return the unique id for the user 279 | done(null, user.username); 280 | }); 281 | ``` 282 | 283 | Previously we had just used the `username` field as the key. Instead we'll use mongodbs `_id` field instead: 284 | 285 | ```js 286 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 287 | passport.serializeUser(function(user, done) { 288 | console.log('serializing user:',user.username); 289 | done(null, user._id); 290 | }); 291 | ``` 292 | 293 | #### Deserialize User 294 | 295 | Passport also requires that we return a User given a key. Looking our original implementation we used `username`: 296 | 297 | ```js 298 | //Desieralize user will call with the unique id provided by serializeuser 299 | passport.deserializeUser(function(username, done) { 300 | 301 | return done(null, users[username]); 302 | 303 | }); 304 | ``` 305 | 306 | This is easy to do since mongoldb since passport will provide us with the `_id` field of the user we originally provided. By using the **findById** we can retrieve it from MongoDB: 307 | 308 | ``` 309 | passport.deserializeUser(function(id, done) { 310 | User.findById(id, function(err, user) { 311 | console.log('deserializing user:',user.username); 312 | done(err, user); 313 | }); 314 | }); 315 | ``` 316 | #### Putting it all together 317 | 318 | Putting everything together the full MongoDB implementation of the passport-init.js strategies is: 319 | 320 | ```js 321 | var mongoose = require('mongoose'); 322 | var User = mongoose.model('User'); 323 | var LocalStrategy = require('passport-local').Strategy; 324 | var bCrypt = require('bcrypt'); 325 | 326 | module.exports = function(passport){ 327 | 328 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 329 | passport.serializeUser(function(user, done) { 330 | console.log('serializing user:',user.username); 331 | done(null, user._id); 332 | }); 333 | 334 | passport.deserializeUser(function(id, done) { 335 | User.findById(id, function(err, user) { 336 | console.log('deserializing user:',user.username); 337 | done(err, user); 338 | }); 339 | }); 340 | 341 | passport.use('login', new LocalStrategy({ 342 | passReqToCallback : true 343 | }, 344 | function(req, username, password, done) { 345 | // check in mongo if a user with username exists or not 346 | User.findOne({ 'username' : username }, 347 | function(err, user) { 348 | // In case of any error, return using the done method 349 | if (err) 350 | return done(err); 351 | // Username does not exist, log the error and redirect back 352 | if (!user){ 353 | console.log('User Not Found with username '+username); 354 | return done(null, false); 355 | } 356 | // User exists but wrong password, log the error 357 | if (!isValidPassword(user, password)){ 358 | console.log('Invalid Password'); 359 | return done(null, false); // redirect back to login page 360 | } 361 | // User and password both match, return user from done method 362 | // which will be treated like success 363 | return done(null, user); 364 | } 365 | ); 366 | } 367 | )); 368 | 369 | passport.use('signup', new LocalStrategy({ 370 | passReqToCallback : true // allows us to pass back the entire request to the callback 371 | }, 372 | function(req, username, password, done) { 373 | 374 | // find a user in mongo with provided username 375 | User.findOne({ 'username' : username }, function(err, user) { 376 | // In case of any error, return using the done method 377 | if (err){ 378 | console.log('Error in SignUp: '+err); 379 | return done(err); 380 | } 381 | // already exists 382 | if (user) { 383 | console.log('User already exists with username: '+username); 384 | return done(null, false); 385 | } else { 386 | // if there is no user, create the user 387 | var newUser = new User(); 388 | 389 | // set the user's local credentials 390 | newUser.username = username; 391 | newUser.password = createHash(password); 392 | 393 | // save the user 394 | newUser.save(function(err) { 395 | if (err){ 396 | console.log('Error in Saving user: '+err); 397 | throw err; 398 | } 399 | console.log(newUser.username + ' Registration succesful'); 400 | return done(null, newUser); 401 | }); 402 | } 403 | }); 404 | }) 405 | ); 406 | 407 | var isValidPassword = function(user, password){ 408 | return bCrypt.compareSync(password, user.password); 409 | }; 410 | // Generates hash using bCrypt 411 | var createHash = function(password){ 412 | return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); 413 | }; 414 | 415 | }; 416 | ``` 417 | 418 | ## Implementing the Posts APIs with MongoDB 419 | 420 | In the previous module we didn't implement the route handlers for the **/posts** and **/posts/:id** routes, we simply just put TODO's. Now we'll actually implement them since we have a place to store our Data. 421 | 422 | ### **/api/posts** 423 | First, lets import mongoose and our Post model: 424 | 425 | ```js 426 | var mongoose = require( 'mongoose' ); 427 | var Post = mongoose.model('Post'); 428 | ``` 429 | 430 | Within **/routes/api.js** lets implement the .post handler for the **/posts** api route. First we'll create a new post, fill out the `text` and `created_by` fields and save it into mongodb. When the callback is executed we know that the post has been saved and we can respond successfully with the saved post body back to the client: 431 | 432 | ``` 433 | //creates a new post 434 | .post(function(req, res){ 435 | 436 | var post = new Post(); 437 | post.text = req.body.text; 438 | post.created_by = req.body.created_by; 439 | post.save(function(err, post) { 440 | if (err){ 441 | return res.send(500, err); 442 | } 443 | return res.json(post); 444 | }); 445 | }) 446 | ``` 447 | 448 | We'll do the same thing with the GET **/posts** API. In this case we'll call the `find` function on the Post collection without specifying a query object. This will return all the posts from the database. We can then simply send those to the client and it will automatically be sent as JSON. 449 | 450 | ```js 451 | //gets all posts 452 | .get(function(req, res){ 453 | Post.find(function(err, posts){ 454 | if(err){ 455 | return res.send(500, err); 456 | } 457 | return res.send(posts); 458 | }); 459 | }); 460 | ``` 461 | 462 | 463 | ### **/api/posts/:id** 464 | 465 | To complete this router's implementation we'll have to add the mongodb implementation for post-specific commands. Let's start with the GET **/posts/:id** route: 466 | 467 | ```js 468 | router.route('/posts/:id') 469 | //gets specified post 470 | .get(function(req, res){ 471 | Post.findById(req.params.id, function(err, post){ 472 | if(err) 473 | res.send(err); 474 | res.json(post); 475 | }); 476 | }) 477 | ``` 478 | 479 | As demonstrated in Module 3, the express routing layer will provide `params.id` because of the `:id` notation on the route. If the client calls the api with the mongodb ID **findById** will retrieve the correct document and return it as a json object. 480 | 481 | The PUT method is similar in that we will attempt to retrieve the document by ID. We'll then modify the the existing document with the data provided in the request body and save it back to the database: 482 | 483 | ``` 484 | //updates specified post 485 | .put(function(req, res){ 486 | Post.findById(req.params.id, function(err, post){ 487 | if(err) 488 | res.send(err); 489 | 490 | post.created_by = req.body.created_by; 491 | post.text = req.body.text; 492 | 493 | post.save(function(err, post){ 494 | if(err) 495 | res.send(err); 496 | 497 | res.json(post); 498 | }); 499 | }); 500 | }) 501 | ``` 502 | 503 | Finally, the DELETE method will simply delete the post out of the database using the **remove** function in the mongoose model: 504 | 505 | ```js 506 | //deletes the post 507 | .delete(function(req, res) { 508 | Post.remove({ 509 | _id: req.params.id 510 | }, function(err) { 511 | if (err) 512 | res.send(err); 513 | res.json("deleted :("); 514 | }); 515 | }); 516 | 517 | ``` 518 | Putting everything together our full **api.js** implementation looks like: 519 | 520 | ```js 521 | var express = require('express'); 522 | var router = express.Router(); 523 | 524 | //Used for routes that must be authenticated. 525 | function isAuthenticated (req, res, next) { 526 | // if user is authenticated in the session, call the next() to call the next request handler 527 | // Passport adds this method to request object. A middleware is allowed to add properties to 528 | // request and response objects 529 | 530 | //allow all get request methods 531 | if(req.method === "GET"){ 532 | return next(); 533 | } 534 | if (req.isAuthenticated()){ 535 | return next(); 536 | } 537 | 538 | // if the user is not authenticated then redirect him to the login page 539 | return res.redirect('/#login'); 540 | }; 541 | 542 | //Register the authentication middleware 543 | router.use('/posts', isAuthenticated); 544 | 545 | router.route('/posts') 546 | //creates a new post 547 | .post(function(req, res){ 548 | 549 | var post = new Post(); 550 | post.text = req.body.text; 551 | post.created_by = req.body.created_by; 552 | post.save(function(err, post) { 553 | if (err){ 554 | return res.send(500, err); 555 | } 556 | return res.json(post); 557 | }); 558 | }) 559 | //gets all posts 560 | .get(function(req, res){ 561 | Post.find(function(err, posts){ 562 | if(err){ 563 | return res.send(500, err); 564 | } 565 | return res.send(posts); 566 | }); 567 | }); 568 | 569 | //post-specific commands. likely won't be used 570 | router.route('/posts/:id') 571 | //gets specified post 572 | .get(function(req, res){ 573 | Post.findById(req.params.id, function(err, post){ 574 | if(err) 575 | res.send(err); 576 | res.json(post); 577 | }); 578 | }) 579 | //updates specified post 580 | .put(function(req, res){ 581 | Post.findById(req.params.id, function(err, post){ 582 | if(err) 583 | res.send(err); 584 | 585 | post.created_by = req.body.created_by; 586 | post.text = req.body.text; 587 | 588 | post.save(function(err, post){ 589 | if(err) 590 | res.send(err); 591 | 592 | res.json(post); 593 | }); 594 | }); 595 | }) 596 | //deletes the post 597 | .delete(function(req, res) { 598 | Post.remove({ 599 | _id: req.params.id 600 | }, function(err) { 601 | if (err) 602 | res.send(err); 603 | res.json("deleted :("); 604 | }); 605 | }); 606 | 607 | module.exports = router; 608 | ``` 609 | 610 | ## Testing with the REST Client 611 | 612 | As before we can test with the Advanced REST Client Tool. For example after we authenticate we can verify we can create a post: 613 | 614 |  615 |  616 | 617 | -------------------------------------------------------------------------------- /module-4/ScreenShots/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-4/ScreenShots/ss1.png -------------------------------------------------------------------------------- /module-4/ScreenShots/ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-4/ScreenShots/ss2.png -------------------------------------------------------------------------------- /module-4/Start/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-4/Start/.DS_Store -------------------------------------------------------------------------------- /module-4/Start/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var session = require('express-session'); 8 | var passport = require('passport'); 9 | var api = require('./routes/api'); 10 | var authenticate = require('./routes/authenticate')(passport); 11 | 12 | var app = express(); 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')); 16 | app.set('view engine', 'ejs'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(__dirname + '/public/favicon.ico')); 20 | app.use(logger('dev')); 21 | app.use(session({ 22 | secret: 'keyboard cat' 23 | })); 24 | app.use(bodyParser.json()); 25 | app.use(bodyParser.urlencoded({ extended: false })); 26 | app.use(cookieParser()); 27 | app.use(express.static(path.join(__dirname, 'public'))); 28 | app.use(passport.initialize()); 29 | app.use(passport.session()); 30 | 31 | app.use('/auth', authenticate); 32 | app.use('/api', api); 33 | 34 | // catch 404 and forward to error handler 35 | app.use(function(req, res, next) { 36 | var err = new Error('Not Found'); 37 | err.status = 404; 38 | next(err); 39 | }); 40 | 41 | //// Initialize Passport 42 | var initPassport = require('./passport-init'); 43 | initPassport(passport); 44 | 45 | // error handlers 46 | 47 | // development error handler 48 | // will print stacktrace 49 | if (app.get('env') === 'development') { 50 | app.use(function(err, req, res, next) { 51 | res.status(err.status || 500); 52 | res.render('error', { 53 | message: err.message, 54 | error: err 55 | }); 56 | }); 57 | } 58 | 59 | // production error handler 60 | // no stacktraces leaked to user 61 | app.use(function(err, req, res, next) { 62 | res.status(err.status || 500); 63 | res.render('error', { 64 | message: err.message, 65 | error: {} 66 | }); 67 | }); 68 | 69 | 70 | module.exports = app; 71 | -------------------------------------------------------------------------------- /module-4/Start/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('Start'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 3000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /module-4/Start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Start", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "bcrypt-nodejs": "0.0.3", 10 | "body-parser": "~1.8.1", 11 | "cookie-parser": "~1.3.3", 12 | "debug": "^2.0.0", 13 | "ejs": "~0.8.5", 14 | "express": "~4.9.0", 15 | "express-session": "^1.10.3", 16 | "morgan": "~1.3.0", 17 | "passport": "^0.2.1", 18 | "passport-local": "^1.0.0", 19 | "serve-favicon": "~2.1.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /module-4/Start/passport-init.js: -------------------------------------------------------------------------------- 1 | var LocalStrategy = require('passport-local').Strategy; 2 | var bCrypt = require('bcrypt-nodejs'); 3 | //temporary data store 4 | var users = {}; 5 | module.exports = function(passport){ 6 | 7 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 8 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 9 | passport.serializeUser(function(user, done) { 10 | console.log('serializing user:',user.username); 11 | //return the unique id for the user 12 | done(null, user.username); 13 | }); 14 | 15 | //Desieralize user will call with the unique id provided by serializeuser 16 | passport.deserializeUser(function(username, done) { 17 | 18 | return done(null, users[username]); 19 | 20 | }); 21 | 22 | passport.use('login', new LocalStrategy({ 23 | passReqToCallback : true 24 | }, 25 | function(req, username, password, done) { 26 | 27 | if(!users[username]){ 28 | console.log('User Not Found with username '+username); 29 | return done(null, false); 30 | } 31 | 32 | if(isValidPassword(users[username], password)){ 33 | //sucessfully authenticated 34 | return done(null, users[username]); 35 | } 36 | else{ 37 | console.log('Invalid password '+username); 38 | return done(null, false) 39 | } 40 | } 41 | )); 42 | 43 | passport.use('signup', new LocalStrategy({ 44 | passReqToCallback : true // allows us to pass back the entire request to the callback 45 | }, 46 | function(req, username, password, done) { 47 | 48 | if (users[username]){ 49 | console.log('User already exists with username: ' + username); 50 | return done(null, false); 51 | } 52 | 53 | //store user in memory 54 | users[username] = { 55 | username: username, 56 | password: createHash(password) 57 | } 58 | 59 | console.log(users[username].username + ' Registration successful'); 60 | return done(null, users[username]); 61 | }) 62 | ); 63 | 64 | var isValidPassword = function(user, password){ 65 | return bCrypt.compareSync(password, user.password); 66 | }; 67 | // Generates hash using bCrypt 68 | var createHash = function(password){ 69 | return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); 70 | }; 71 | 72 | }; 73 | -------------------------------------------------------------------------------- /module-4/Start/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /module-4/Start/routes/api.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | //Used for routes that must be authenticated. 5 | function isAuthenticated (req, res, next) { 6 | // if user is authenticated in the session, call the next() to call the next request handler 7 | // Passport adds this method to request object. A middleware is allowed to add properties to 8 | // request and response objects 9 | 10 | //allow all get request methods 11 | if(req.method === "GET"){ 12 | return next(); 13 | } 14 | if (req.isAuthenticated()){ 15 | return next(); 16 | } 17 | 18 | // if the user is not authenticated then redirect him to the login page 19 | return res.redirect('/#login'); 20 | }; 21 | 22 | //Register the authentication middleware 23 | router.use('/posts', isAuthenticated); 24 | 25 | //api for all posts 26 | router.route('/posts') 27 | 28 | //create a new post 29 | .post(function(req, res){ 30 | 31 | //TODO create a new post in the database 32 | res.send({message:"TODO create a new post in the database"}); 33 | }) 34 | 35 | .get(function(req, res){ 36 | 37 | //TODO get all the posts in the database 38 | res.send({message:"TODO get all the posts in the database"}); 39 | }) 40 | 41 | //api for a specfic post 42 | router.route('/posts/:id') 43 | 44 | //create 45 | .put(function(req,res){ 46 | return res.send({message:'TODO modify an existing post by using param ' + req.param.id}); 47 | }) 48 | 49 | .get(function(req,res){ 50 | return res.send({message:'TODO get an existing post by using param ' + req.param.id}); 51 | }) 52 | 53 | .delete(function(req,res){ 54 | return res.send({message:'TODO delete an existing post by using param ' + req.param.id}) 55 | }); 56 | 57 | module.exports = router; -------------------------------------------------------------------------------- /module-4/Start/routes/authenticate.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | module.exports = function(passport){ 5 | 6 | //sends successful login state back to angular 7 | router.get('/success', function(req, res){ 8 | res.send({state: 'success', user: req.user ? req.user : null}); 9 | }); 10 | 11 | //sends failure login state back to angular 12 | router.get('/failure', function(req, res){ 13 | res.send({state: 'failure', user: null, message: "Invalid username or password"}); 14 | }); 15 | 16 | //log in 17 | router.post('/login', passport.authenticate('login', { 18 | successRedirect: '/auth/success', 19 | failureRedirect: '/auth/failure' 20 | })); 21 | 22 | //sign up 23 | router.post('/signup', passport.authenticate('signup', { 24 | successRedirect: '/auth/success', 25 | failureRedirect: '/auth/failure' 26 | })); 27 | 28 | //log out 29 | router.get('/signout', function(req, res) { 30 | req.logout(); 31 | res.redirect('/'); 32 | }); 33 | 34 | return router; 35 | 36 | } -------------------------------------------------------------------------------- /module-4/Start/views/error.ejs: -------------------------------------------------------------------------------- 1 |<%= error.stack %>4 | -------------------------------------------------------------------------------- /module-4/Start/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Welcome to <%= title %>
10 | 11 | 12 | -------------------------------------------------------------------------------- /module-5/completed/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwz/chirp/41a91ee3e2dd18a5e27cae3b4ebd2943053583a7/module-5/completed/.DS_Store -------------------------------------------------------------------------------- /module-5/completed/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var session = require('express-session'); 8 | var passport = require('passport'); 9 | //initialize mongoose schemas 10 | require('./models/models'); 11 | var index = require('./routes/index'); 12 | var api = require('./routes/api'); 13 | var authenticate = require('./routes/authenticate')(passport); 14 | var mongoose = require('mongoose'); //add for Mongo support 15 | mongoose.connect('mongodb://localhost/test-chirp'); //connect to Mongo 16 | var app = express(); 17 | 18 | // view engine setup 19 | app.set('views', path.join(__dirname, 'views')); 20 | app.set('view engine', 'ejs'); 21 | 22 | // uncomment after placing your favicon in /public 23 | //app.use(favicon(__dirname + '/public/favicon.ico')); 24 | app.use(logger('dev')); 25 | app.use(session({ 26 | secret: 'keyboard cat' 27 | })); 28 | app.use(bodyParser.json()); 29 | app.use(bodyParser.urlencoded({ extended: false })); 30 | app.use(cookieParser()); 31 | app.use(express.static(path.join(__dirname, 'public'))); 32 | app.use(passport.initialize()); 33 | app.use(passport.session()); 34 | 35 | app.use('/', index); 36 | app.use('/auth', authenticate); 37 | app.use('/api', api); 38 | 39 | // catch 404 and forward to error handler 40 | app.use(function(req, res, next) { 41 | var err = new Error('Not Found'); 42 | err.status = 404; 43 | next(err); 44 | }); 45 | 46 | //// Initialize Passport 47 | var initPassport = require('./passport-init'); 48 | initPassport(passport); 49 | 50 | // error handlers 51 | 52 | // development error handler 53 | // will print stacktrace 54 | if (app.get('env') === 'development') { 55 | app.use(function(err, req, res, next) { 56 | res.status(err.status || 500); 57 | res.render('error', { 58 | message: err.message, 59 | error: err 60 | }); 61 | }); 62 | } 63 | 64 | // production error handler 65 | // no stacktraces leaked to user 66 | app.use(function(err, req, res, next) { 67 | res.status(err.status || 500); 68 | res.render('error', { 69 | message: err.message, 70 | error: {} 71 | }); 72 | }); 73 | 74 | 75 | module.exports = app; 76 | -------------------------------------------------------------------------------- /module-5/completed/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('Start'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 3000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /module-5/completed/models/models.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var postSchema = new mongoose.Schema({ 5 | created_by: String, //should be changed to ObjectId, ref "User" 6 | created_at: {type: Date, default: Date.now}, 7 | text: String 8 | }); 9 | 10 | var userSchema = new mongoose.Schema({ 11 | username: String, 12 | password: String, //hash created from password 13 | created_at: {type: Date, default: Date.now} 14 | }) 15 | 16 | 17 | mongoose.model('Post', postSchema); 18 | mongoose.model('User', userSchema); 19 | -------------------------------------------------------------------------------- /module-5/completed/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Start", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "bcrypt-nodejs": "0.0.3", 10 | "body-parser": "~1.8.1", 11 | "cookie-parser": "~1.3.3", 12 | "debug": "^2.0.0", 13 | "ejs": "~0.8.5", 14 | "express": "~4.9.0", 15 | "express-session": "^1.10.3", 16 | "mongoose": "^3.8.23", 17 | "morgan": "~1.3.0", 18 | "passport": "^0.2.1", 19 | "passport-local": "^1.0.0", 20 | "serve-favicon": "~2.1.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /module-5/completed/passport-init.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var User = mongoose.model('User'); 3 | var LocalStrategy = require('passport-local').Strategy; 4 | var bCrypt = require('bcrypt-nodejs'); 5 | 6 | module.exports = function(passport){ 7 | 8 | // Passport needs to be able to serialize and deserialize users to support persistent login sessions 9 | passport.serializeUser(function(user, done) { 10 | console.log('serializing user:',user.username); 11 | done(null, user._id); 12 | }); 13 | 14 | passport.deserializeUser(function(id, done) { 15 | User.findById(id, function(err, user) { 16 | console.log('deserializing user:',user.username); 17 | done(err, user); 18 | }); 19 | }); 20 | 21 | passport.use('login', new LocalStrategy({ 22 | passReqToCallback : true 23 | }, 24 | function(req, username, password, done) { 25 | // check in mongo if a user with username exists or not 26 | User.findOne({ 'username' : username }, 27 | function(err, user) { 28 | // In case of any error, return using the done method 29 | if (err) 30 | return done(err); 31 | // Username does not exist, log the error and redirect back 32 | if (!user){ 33 | console.log('User Not Found with username '+username); 34 | return done(null, false); 35 | } 36 | // User exists but wrong password, log the error 37 | if (!isValidPassword(user, password)){ 38 | console.log('Invalid Password'); 39 | return done(null, false); // redirect back to login page 40 | } 41 | // User and password both match, return user from done method 42 | // which will be treated like success 43 | return done(null, user); 44 | } 45 | ); 46 | } 47 | )); 48 | 49 | passport.use('signup', new LocalStrategy({ 50 | passReqToCallback : true // allows us to pass back the entire request to the callback 51 | }, 52 | function(req, username, password, done) { 53 | 54 | // find a user in mongo with provided username 55 | User.findOne({ 'username' : username }, function(err, user) { 56 | // In case of any error, return using the done method 57 | if (err){ 58 | console.log('Error in SignUp: '+err); 59 | return done(err); 60 | } 61 | // already exists 62 | if (user) { 63 | console.log('User already exists with username: '+username); 64 | return done(null, false); 65 | } else { 66 | // if there is no user, create the user 67 | var newUser = new User(); 68 | 69 | // set the user's local credentials 70 | newUser.username = username; 71 | newUser.password = createHash(password); 72 | 73 | // save the user 74 | newUser.save(function(err) { 75 | if (err){ 76 | console.log('Error in Saving user: '+err); 77 | throw err; 78 | } 79 | console.log(newUser.username + ' Registration succesful'); 80 | return done(null, newUser); 81 | }); 82 | } 83 | }); 84 | }) 85 | ); 86 | 87 | var isValidPassword = function(user, password){ 88 | return bCrypt.compareSync(password, user.password); 89 | }; 90 | // Generates hash using bCrypt 91 | var createHash = function(password){ 92 | return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); 93 | }; 94 | 95 | }; 96 | -------------------------------------------------------------------------------- /module-5/completed/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |{{post.text}}
12 | Posted by @{{post.created_by}} 13 | {{post.created_at | date:"h:mma 'on' MMM d, y"}} 14 |<%= error.stack %>4 | -------------------------------------------------------------------------------- /module-5/completed/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
<%= error.stack %>4 | -------------------------------------------------------------------------------- /module-5/start/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |