├── .env ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── gulpfile.js ├── knexfile.js ├── package.json ├── src └── server │ ├── app.js │ ├── auth │ ├── _helpers.js │ └── local.js │ ├── config │ ├── error-config.js │ ├── main-config.js │ └── route-config.js │ ├── controllers │ └── index.js │ ├── db │ ├── connection.js │ ├── migrations │ │ └── 20161023205803_users.js │ └── seeds │ │ └── users.js │ ├── routes │ ├── auth.js │ └── index.js │ └── server.js └── test ├── index.js ├── integration ├── routes.auth.test.js └── routes.index.test.js ├── jshint.spec.js ├── mocha.opts └── unit ├── auth.helpers.test.js.js ├── auth.local.test.js.js └── controllers.index.test.js /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | TOKEN_SECRET=\xf8%\xa8\xf2INz\xcc:\x171\xeei\x82\xce\x81Y\xc2HJ\xe5\x01\xf3$ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireSpacesInNamedFunctionExpression": { 3 | "beforeOpeningCurlyBrace": true 4 | }, 5 | "requireSpacesInFunctionExpression": { 6 | "beforeOpeningCurlyBrace": true 7 | }, 8 | "requireSpacesInAnonymousFunctionExpression": { 9 | "beforeOpeningCurlyBrace": true 10 | }, 11 | "requireSpacesInFunctionDeclaration": { 12 | "beforeOpeningCurlyBrace": true 13 | }, 14 | "disallowEmptyBlocks": true, 15 | "disallowSpacesInsideArrayBrackets": true, 16 | "disallowSpacesInsideParentheses": true, 17 | "disallowQuotedKeysInObjects": "allButReserved", 18 | "disallowSpaceAfterObjectKeys": true, 19 | "disallowSpaceAfterPrefixUnaryOperators": true, 20 | "disallowSpaceBeforePostfixUnaryOperators": true, 21 | "disallowSpaceBeforeBinaryOperators": [ 22 | "," 23 | ], 24 | "disallowMixedSpacesAndTabs": true, 25 | "disallowTrailingWhitespace": true, 26 | "disallowTrailingComma": true, 27 | "disallowKeywords": [ "with" ], 28 | "disallowMultipleLineBreaks": true, 29 | "requireSpaceBeforeBlockStatements": true, 30 | "requireParenthesesAroundIIFE": true, 31 | "requireSpacesInConditionalExpression": true, 32 | "requireBlocksOnNewline": 1, 33 | "requireCommaBeforeLineBreak": true, 34 | "requireSpaceBeforeBinaryOperators": true, 35 | "requireSpaceAfterBinaryOperators": true, 36 | "requireLineFeedAtFileEnd": true, 37 | "requireCapitalizedConstructors": true, 38 | "requireDotNotation": true, 39 | "requireCurlyBraces": [ 40 | "do" 41 | ], 42 | "requireSpaceAfterKeywords": [ 43 | "if", 44 | "else", 45 | "for", 46 | "while", 47 | "do", 48 | "switch", 49 | "case", 50 | "return", 51 | "try", 52 | "catch", 53 | "typeof" 54 | ], 55 | "safeContextKeyword": "target", 56 | "validateLineBreaks": "LF", 57 | "validateQuoteMarks": "'", 58 | "validateIndentation": 2, 59 | "excludeFiles": [ 60 | "node_modules/**", 61 | "coverage/*", 62 | "build/client/*" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "esnext": true, 4 | "jasmine": false, 5 | "spyOn": false, 6 | "it": false, 7 | "console": false, 8 | "describe": false, 9 | "expect": false, 10 | "beforeEach": false, 11 | "afterEach": false, 12 | "waits": false, 13 | "waitsFor": false, 14 | "runs": false, 15 | "$": false, 16 | "confirm": false 17 | }, 18 | "esnext": true, 19 | "node" : true, 20 | "browser" : true, 21 | "boss" : false, 22 | "curly": false, 23 | "debug": false, 24 | "devel": false, 25 | "eqeqeq": true, 26 | "evil": true, 27 | "forin": false, 28 | "immed": true, 29 | "laxbreak": false, 30 | "newcap": true, 31 | "noarg": true, 32 | "noempty": false, 33 | "nonew": false, 34 | "nomen": false, 35 | "onevar": true, 36 | "plusplus": false, 37 | "regexp": false, 38 | "undef": true, 39 | "sub": true, 40 | "strict": false, 41 | "white": true, 42 | "unused": false 43 | } 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: 2 | - node_js 3 | node_js: 4 | - '6' 5 | env: 6 | global: 7 | - NODE_ENV=test 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Michael Herman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Token-Based Authentication with Node 2 | 3 | ## Want to learn how to build this project? 4 | 5 | Check out the [blog post](http://mherman.org/blog/2016/10/28/token-based-authentication-with-node/#.WBNVr5MrJE4). 6 | 7 | ## Want to use this project? 8 | 9 | 1. Fork/Clone 10 | 1. Install dependencies - `npm install` 11 | 1. Create two local Postgres databases - `node_token_auth` and `node_token_auth_test` 12 | 1. Migrate - `knex migrate:latest --env development` 13 | 1. Seed - `knex seed:run --env development` 14 | 1. Run the development server - `gulp` 15 | 1. Test - `npm test` 16 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // *** dependencies *** // 2 | 3 | const path = require('path'); 4 | const gulp = require('gulp'); 5 | const jshint = require('gulp-jshint'); 6 | const jscs = require('gulp-jscs'); 7 | const runSequence = require('run-sequence'); 8 | const nodemon = require('gulp-nodemon'); 9 | const plumber = require('gulp-plumber'); 10 | const server = require('tiny-lr')(); 11 | 12 | // *** config *** // 13 | 14 | const paths = { 15 | scripts: [ 16 | path.join('src', '**', '*.js'), 17 | path.join('src', '*.js') 18 | ], 19 | styles: [ 20 | path.join('src', 'client', 'css', '*.css') 21 | ], 22 | server: path.join('src', 'server', 'server.js') 23 | }; 24 | 25 | const lrPort = 35729; 26 | 27 | const nodemonConfig = { 28 | script: paths.server, 29 | ext: 'js css', 30 | ignore: ['node_modules'], 31 | env: { 32 | NODE_ENV: 'development' 33 | } 34 | }; 35 | 36 | // *** default task *** // 37 | 38 | gulp.task('default', () => { 39 | runSequence( 40 | ['jshint'], 41 | ['jscs'], 42 | ['lr'], 43 | ['nodemon'], 44 | ['watch'] 45 | ); 46 | }); 47 | 48 | // *** sub tasks ** // 49 | 50 | gulp.task('jshint', () => { 51 | return gulp.src(paths.scripts) 52 | .pipe(plumber()) 53 | .pipe(jshint({ 54 | esnext: true 55 | })) 56 | .pipe(jshint.reporter('jshint-stylish')) 57 | .pipe(jshint.reporter('fail')); 58 | }); 59 | 60 | gulp.task('jscs', () => { 61 | return gulp.src(paths.scripts) 62 | .pipe(plumber()) 63 | .pipe(jscs()) 64 | .pipe(jscs.reporter()) 65 | .pipe(jscs.reporter('fail')); 66 | }); 67 | 68 | gulp.task('styles', () => { 69 | return gulp.src(paths.styles) 70 | .pipe(plumber()); 71 | }); 72 | 73 | gulp.task('lr', () => { 74 | server.listen(lrPort, (err) => { 75 | if (err) { 76 | return console.error(err); 77 | } 78 | }); 79 | }); 80 | 81 | gulp.task('nodemon', () => { 82 | return nodemon(nodemonConfig); 83 | }); 84 | 85 | gulp.task('watch', () => { 86 | gulp.watch(paths.scripts, ['jshint', 'jscs']); 87 | gulp.watch(paths.styles, ['styles']); 88 | }); 89 | -------------------------------------------------------------------------------- /knexfile.js: -------------------------------------------------------------------------------- 1 | const databaseName = 'node_token_auth'; 2 | 3 | module.exports = { 4 | development: { 5 | client: 'postgresql', 6 | connection: `postgres://localhost:5432/${databaseName}`, 7 | migrations: { 8 | directory: __dirname + '/src/server/db/migrations' 9 | }, 10 | seeds: { 11 | directory: __dirname + '/src/server/db/seeds' 12 | } 13 | }, 14 | test: { 15 | client: 'postgresql', 16 | connection: `postgres://localhost:5432/${databaseName}_test`, 17 | migrations: { 18 | directory: __dirname + '/src/server/db/migrations' 19 | }, 20 | seeds: { 21 | directory: __dirname + '/src/server/db/seeds' 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "private": true, 4 | "scripts": { 5 | "start": "node ./src/server/server", 6 | "test": "./node_modules/mocha/bin/_mocha" 7 | }, 8 | "dependencies": { 9 | "bcryptjs": "^2.3.0", 10 | "body-parser": "~1.15.1", 11 | "connect-flash": "~0.1.1", 12 | "cookie-parser": "~1.4.3", 13 | "debug": "~2.2.0", 14 | "dotenv": "~2.0.0", 15 | "express": "~4.13.4", 16 | "jwt-simple": "^0.5.0", 17 | "knex": "~0.11.10", 18 | "moment": "^2.15.2", 19 | "morgan": "~1.7.0", 20 | "pg": "6.1.0" 21 | }, 22 | "devDependencies": { 23 | "chai": "~3.5.0", 24 | "chai-http": "~3.0.0", 25 | "gulp-jscs": "~4.0.0", 26 | "gulp-jshint": "~2.0.1", 27 | "gulp-nodemon": "~2.1.0", 28 | "gulp-plumber": "~1.1.0", 29 | "jshint": "~2.9.3", 30 | "jshint-stylish": "~2.2.1", 31 | "mocha": "~3.0.2", 32 | "mocha-jscs": "~5.0.1", 33 | "mocha-jshint": "~2.3.1", 34 | "run-sequence": "~1.2.2", 35 | "tiny-lr": "~0.2.1" 36 | }, 37 | "name": "node-token-auth" 38 | } 39 | -------------------------------------------------------------------------------- /src/server/app.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 'use strict'; 4 | 5 | // *** dependencies *** // 6 | const express = require('express'); 7 | 8 | const appConfig = require('./config/main-config.js'); 9 | const routeConfig = require('./config/route-config.js'); 10 | const errorConfig = require('./config/error-config.js'); 11 | 12 | // *** express instance *** // 13 | const app = express(); 14 | 15 | // *** config *** // 16 | appConfig.init(app, express); 17 | routeConfig.init(app); 18 | errorConfig.init(app); 19 | 20 | module.exports = app; 21 | 22 | }()); 23 | -------------------------------------------------------------------------------- /src/server/auth/_helpers.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs'); 2 | const knex = require('../db/connection'); 3 | const localAuth = require('./local'); 4 | 5 | function createUser(req) { 6 | const salt = bcrypt.genSaltSync(); 7 | const hash = bcrypt.hashSync(req.body.password, salt); 8 | return knex('users') 9 | .insert({ 10 | username: req.body.username, 11 | password: hash 12 | }) 13 | .returning('*'); 14 | } 15 | 16 | function getUser(username) { 17 | return knex('users').where({username}).first(); 18 | } 19 | 20 | function comparePass(userPassword, databasePassword) { 21 | return bcrypt.compareSync(userPassword, databasePassword); 22 | } 23 | 24 | function ensureAuthenticated(req, res, next) { 25 | if (!(req.headers && req.headers.authorization)) { 26 | return res.status(400).json({ 27 | status: 'Please log in' 28 | }); 29 | } 30 | // decode the token 31 | const header = req.headers.authorization.split(' '); 32 | const token = header[1]; 33 | localAuth.decodeToken(token, (err, payload) => { 34 | if (err) { 35 | return res.status(401).json({ 36 | status: 'Token has expired' 37 | }); 38 | } else { 39 | return knex('users').where({id: parseInt(payload.sub)}).first() 40 | .then((user) => { 41 | next(); 42 | }) 43 | .catch((err) => { 44 | res.status(500).json({ 45 | status: 'error' 46 | }); 47 | }); 48 | } 49 | }); 50 | } 51 | 52 | module.exports = { 53 | createUser, 54 | getUser, 55 | comparePass, 56 | ensureAuthenticated 57 | }; 58 | -------------------------------------------------------------------------------- /src/server/auth/local.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const jwt = require('jwt-simple'); 3 | 4 | function encodeToken(user) { 5 | const playload = { 6 | exp: moment().add(14, 'days').unix(), 7 | iat: moment().unix(), 8 | sub: user.id 9 | }; 10 | return jwt.encode(playload, process.env.TOKEN_SECRET); 11 | } 12 | 13 | function decodeToken(token, callback) { 14 | const payload = jwt.decode(token, process.env.TOKEN_SECRET); 15 | const now = moment().unix(); 16 | // check if the token has expired 17 | if (now > payload.exp) callback('Token has expired.'); 18 | else callback(null, payload); 19 | } 20 | 21 | module.exports = { 22 | encodeToken, 23 | decodeToken 24 | }; 25 | -------------------------------------------------------------------------------- /src/server/config/error-config.js: -------------------------------------------------------------------------------- 1 | (function (errorConfig) { 2 | 3 | 'use strict'; 4 | 5 | // *** error handling *** // 6 | 7 | errorConfig.init = function (app) { 8 | 9 | // catch 404 and forward to error handler 10 | app.use(function(req, res, next) { 11 | const err = new Error('Not Found'); 12 | err.status = 404; 13 | next(err); 14 | }); 15 | 16 | // development error handler (will print stacktrace) 17 | if (app.get('env') === 'development') { 18 | app.use(function(err, req, res, next) { 19 | res.status(err.status || 500).json({ 20 | message: err.message, 21 | error: err 22 | }); 23 | }); 24 | } 25 | 26 | // production error handler (no stacktraces leaked to user) 27 | app.use(function(err, req, res, next) { 28 | res.status(err.status || 500).json({ 29 | message: err.message, 30 | error: {} 31 | }); 32 | }); 33 | 34 | }; 35 | 36 | })(module.exports); 37 | -------------------------------------------------------------------------------- /src/server/config/main-config.js: -------------------------------------------------------------------------------- 1 | (function(appConfig) { 2 | 3 | 'use strict'; 4 | 5 | // *** main dependencies *** // 6 | const path = require('path'); 7 | const cookieParser = require('cookie-parser'); 8 | const bodyParser = require('body-parser'); 9 | const flash = require('connect-flash'); 10 | const morgan = require('morgan'); 11 | 12 | // *** load environment variables *** // 13 | require('dotenv').config(); 14 | 15 | appConfig.init = function(app, express) { 16 | 17 | // *** view engine *** // 18 | app.set('view engine', 'html'); 19 | 20 | // *** app middleware *** // 21 | if (process.env.NODE_ENV !== 'test') { 22 | app.use(morgan('dev')); 23 | } 24 | 25 | // *** cross domain requests *** // 26 | const allowCrossDomain = function(req, res, next) { 27 | res.header('Access-Control-Allow-Origin', '*'); 28 | res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); 29 | res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); 30 | next(); 31 | }; 32 | 33 | app.use(allowCrossDomain); 34 | app.use(cookieParser()); 35 | app.use(bodyParser.json()); 36 | app.use(bodyParser.urlencoded({ extended: false })); 37 | app.use(flash()); 38 | 39 | }; 40 | 41 | })(module.exports); 42 | -------------------------------------------------------------------------------- /src/server/config/route-config.js: -------------------------------------------------------------------------------- 1 | (function (routeConfig) { 2 | 3 | 'use strict'; 4 | 5 | routeConfig.init = function (app) { 6 | 7 | // *** routes *** // 8 | const routes = require('../routes/index'); 9 | const authRoutes = require('../routes/auth'); 10 | 11 | // *** register routes *** // 12 | app.use('/', routes); 13 | app.use('/auth', authRoutes); 14 | 15 | }; 16 | 17 | })(module.exports); 18 | -------------------------------------------------------------------------------- /src/server/controllers/index.js: -------------------------------------------------------------------------------- 1 | function sum(num1, num2, callback) { 2 | const total = num1 + num2; 3 | if (isNaN(total)) { 4 | const error = 'Something went wrong!'; 5 | callback(error); 6 | } else { 7 | callback(null, total); 8 | } 9 | } 10 | 11 | module.exports = { 12 | sum 13 | }; 14 | -------------------------------------------------------------------------------- /src/server/db/connection.js: -------------------------------------------------------------------------------- 1 | const environment = process.env.NODE_ENV; 2 | const config = require('../../../knexfile.js')[environment]; 3 | module.exports = require('knex')(config); 4 | -------------------------------------------------------------------------------- /src/server/db/migrations/20161023205803_users.js: -------------------------------------------------------------------------------- 1 | exports.up = (knex, Promise) => { 2 | return knex.schema.createTable('users', (table) => { 3 | table.increments(); 4 | table.string('username').unique().notNullable(); 5 | table.string('password').notNullable(); 6 | table.boolean('admin').notNullable().defaultTo(false); 7 | table.timestamp('created_at').notNullable().defaultTo(knex.raw('now()')); 8 | }); 9 | }; 10 | 11 | exports.down = (knex, Promise) => { 12 | return knex.schema.dropTable('users'); 13 | }; 14 | -------------------------------------------------------------------------------- /src/server/db/seeds/users.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs'); 2 | 3 | exports.seed = (knex, Promise) => { 4 | return knex('users').del() 5 | .then(() => { 6 | const salt = bcrypt.genSaltSync(); 7 | const hash = bcrypt.hashSync('johnson123', salt); 8 | return Promise.join( 9 | knex('users').insert({ 10 | username: 'jeremy', 11 | password: hash 12 | }) 13 | ); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/server/routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const localAuth = require('../auth/local'); 5 | const authHelpers = require('../auth/_helpers'); 6 | 7 | router.post('/register', (req, res, next) => { 8 | return authHelpers.createUser(req, res) 9 | .then((user) => { return localAuth.encodeToken(user[0]); }) 10 | .then((token) => { 11 | res.status(200).json({ 12 | status: 'success', 13 | token: token 14 | }); 15 | }) 16 | .catch((err) => { 17 | res.status(500).json({ 18 | status: 'error' 19 | }); 20 | }); 21 | }); 22 | 23 | router.post('/login', (req, res, next) => { 24 | const username = req.body.username; 25 | const password = req.body.password; 26 | return authHelpers.getUser(username) 27 | .then((response) => { 28 | authHelpers.comparePass(password, response.password); 29 | return response; 30 | }) 31 | .then((response) => { return localAuth.encodeToken(response); }) 32 | .then((token) => { 33 | res.status(200).json({ 34 | status: 'success', 35 | token: token 36 | }); 37 | }) 38 | .catch((err) => { 39 | res.status(500).json({ 40 | status: 'error' 41 | }); 42 | }); 43 | }); 44 | 45 | router.get('/user', 46 | authHelpers.ensureAuthenticated, 47 | (req, res, next) => { 48 | res.status(200).json({ 49 | status: 'success' 50 | }); 51 | }); 52 | 53 | module.exports = router; 54 | -------------------------------------------------------------------------------- /src/server/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const indexController = require('../controllers/index'); 5 | 6 | router.get('/', function (req, res, next) { 7 | indexController.sum(1, 2, (error, results) => { 8 | if (error) return next(error); 9 | if (results) { 10 | res.json({ 11 | sum: results 12 | }); 13 | } 14 | }); 15 | }); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 'use strict'; 4 | 5 | const app = require('./app'); 6 | const debug = require('debug')('herman-express:server'); 7 | const http = require('http'); 8 | 9 | const port = normalizePort(process.env.PORT || '3000'); 10 | app.set('port', port); 11 | 12 | const server = http.createServer(app); 13 | 14 | server.listen(port); 15 | server.on('error', onError); 16 | server.on('listening', onListening); 17 | 18 | function normalizePort(val) { 19 | const port = parseInt(val, 10); 20 | if (isNaN(port)) { 21 | return val; 22 | } 23 | if (port >= 0) { 24 | return port; 25 | } 26 | return false; 27 | } 28 | 29 | function onError(error) { 30 | if (error.syscall !== 'listen') { 31 | throw error; 32 | } 33 | const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; 34 | switch (error.code) { 35 | case 'EACCES': 36 | console.error(bind + ' requires elevated privileges'); 37 | process.exit(1); 38 | break; 39 | case 'EADDRINUSE': 40 | console.error(bind + ' is already in use'); 41 | process.exit(1); 42 | break; 43 | default: 44 | throw error; 45 | } 46 | } 47 | 48 | function onListening() { 49 | const addr = server.address(); 50 | const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; 51 | debug('Listening on ' + bind); 52 | } 53 | 54 | }()); 55 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('mocha-jscs')(); 2 | -------------------------------------------------------------------------------- /test/integration/routes.auth.test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | 3 | const chai = require('chai'); 4 | const should = chai.should(); 5 | const chaiHttp = require('chai-http'); 6 | chai.use(chaiHttp); 7 | 8 | const server = require('../../src/server/app'); 9 | const knex = require('../../src/server/db/connection'); 10 | 11 | describe('routes : auth', () => { 12 | 13 | beforeEach(() => { 14 | return knex.migrate.rollback() 15 | .then(() => { return knex.migrate.latest(); }) 16 | .then(() => { return knex.seed.run(); }); 17 | }); 18 | 19 | afterEach(() => { 20 | return knex.migrate.rollback(); 21 | }); 22 | 23 | describe('POST /auth/register', () => { 24 | it('should register a new user', (done) => { 25 | chai.request(server) 26 | .post('/auth/register') 27 | .send({ 28 | username: 'michael', 29 | password: 'herman' 30 | }) 31 | .end((err, res) => { 32 | should.not.exist(err); 33 | res.redirects.length.should.eql(0); 34 | res.status.should.eql(200); 35 | res.type.should.eql('application/json'); 36 | res.body.should.include.keys('status', 'token'); 37 | res.body.status.should.eql('success'); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | 43 | describe('POST /auth/login', () => { 44 | it('should login a user', (done) => { 45 | chai.request(server) 46 | .post('/auth/login') 47 | .send({ 48 | username: 'jeremy', 49 | password: 'johnson123' 50 | }) 51 | .end((err, res) => { 52 | should.not.exist(err); 53 | res.redirects.length.should.eql(0); 54 | res.status.should.eql(200); 55 | res.type.should.eql('application/json'); 56 | res.body.should.include.keys('status', 'token'); 57 | res.body.status.should.eql('success'); 58 | should.exist(res.body.token); 59 | done(); 60 | }); 61 | }); 62 | it('should not login an unregistered user', (done) => { 63 | chai.request(server) 64 | .post('/auth/login') 65 | .send({ 66 | username: 'michael', 67 | password: 'johnson123' 68 | }) 69 | .end((err, res) => { 70 | should.exist(err); 71 | res.status.should.eql(500); 72 | res.type.should.eql('application/json'); 73 | res.body.status.should.eql('error'); 74 | done(); 75 | }); 76 | }); 77 | }); 78 | 79 | describe('GET /auth/user', () => { 80 | it('should return a success', (done) => { 81 | chai.request(server) 82 | .post('/auth/login') 83 | .send({ 84 | username: 'jeremy', 85 | password: 'johnson123' 86 | }) 87 | .end((error, response) => { 88 | should.not.exist(error); 89 | chai.request(server) 90 | .get('/auth/user') 91 | .set('authorization', 'Bearer ' + response.body.token) 92 | .end((err, res) => { 93 | should.not.exist(err); 94 | res.status.should.eql(200); 95 | res.type.should.eql('application/json'); 96 | res.body.status.should.eql('success'); 97 | done(); 98 | }); 99 | }); 100 | }); 101 | it('should throw an error if a user is not logged in', (done) => { 102 | chai.request(server) 103 | .get('/auth/user') 104 | .end((err, res) => { 105 | should.exist(err); 106 | res.status.should.eql(400); 107 | res.type.should.eql('application/json'); 108 | res.body.status.should.eql('Please log in'); 109 | done(); 110 | }); 111 | }); 112 | }); 113 | 114 | }); 115 | -------------------------------------------------------------------------------- /test/integration/routes.index.test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | 3 | const chai = require('chai'); 4 | const should = chai.should(); 5 | const chaiHttp = require('chai-http'); 6 | chai.use(chaiHttp); 7 | 8 | const server = require('../../src/server/app'); 9 | 10 | describe('routes : index', () => { 11 | 12 | beforeEach((done) => { done(); }); 13 | 14 | afterEach((done) => { done(); }); 15 | 16 | describe('GET /', () => { 17 | it('should render the sum', (done) => { 18 | chai.request(server) 19 | .get('/') 20 | .end((err, res) => { 21 | res.redirects.length.should.equal(0); 22 | res.status.should.eql(200); 23 | res.type.should.eql('application/json'); 24 | res.body.should.eql({ sum: 3 }); 25 | res.body.sum.should.eql(3); 26 | done(); 27 | }); 28 | }); 29 | }); 30 | 31 | describe('GET /404', () => { 32 | it('should throw an error', (done) => { 33 | chai.request(server) 34 | .get('/404') 35 | .end((err, res) => { 36 | res.redirects.length.should.equal(0); 37 | res.status.should.equal(404); 38 | res.type.should.equal('application/json'); 39 | res.body.message.should.eql('Not Found'); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /test/jshint.spec.js: -------------------------------------------------------------------------------- 1 | require('mocha-jshint')({ 2 | git: { 3 | modified: true, 4 | commits: 2 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | test/**/*.js 2 | -------------------------------------------------------------------------------- /test/unit/auth.helpers.test.js.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | 3 | const chai = require('chai'); 4 | const should = chai.should(); 5 | const bcrypt = require('bcryptjs'); 6 | 7 | const authHelpers = require('../../src/server/auth/_helpers'); 8 | 9 | describe('auth : helpers', () => { 10 | 11 | describe('comparePass()', () => { 12 | it('should return true if the password is correct', (done) => { 13 | const salt = bcrypt.genSaltSync(); 14 | const hash = bcrypt.hashSync('test', salt); 15 | const results = authHelpers.comparePass('test', hash); 16 | should.exist(results); 17 | results.should.eql(true); 18 | done(); 19 | }); 20 | it('should return false if the password is correct', (done) => { 21 | const salt = bcrypt.genSaltSync(); 22 | const hash = bcrypt.hashSync('test', salt); 23 | const results = authHelpers.comparePass('testing', hash); 24 | should.exist(results); 25 | results.should.eql(false); 26 | done(); 27 | }); 28 | it('should return false if the password empty', (done) => { 29 | const salt = bcrypt.genSaltSync(); 30 | const hash = bcrypt.hashSync('test', salt); 31 | const results = authHelpers.comparePass('', hash); 32 | should.exist(results); 33 | results.should.eql(false); 34 | done(); 35 | }); 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /test/unit/auth.local.test.js.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | 3 | const chai = require('chai'); 4 | const should = chai.should(); 5 | 6 | const localAuth = require('../../src/server/auth/local'); 7 | 8 | describe('auth : local', () => { 9 | 10 | describe('encodeToken()', () => { 11 | it('should return a token', (done) => { 12 | const token = localAuth.encodeToken({id: 1}); 13 | should.exist(token); 14 | token.should.be.a('string'); 15 | done(); 16 | }); 17 | }); 18 | 19 | describe('decodeToken()', () => { 20 | it('should return a payload', (done) => { 21 | const token = localAuth.encodeToken({id: 1}); 22 | should.exist(token); 23 | token.should.be.a('string'); 24 | localAuth.decodeToken(token, (err, res) => { 25 | should.not.exist(err); 26 | res.sub.should.eql(1); 27 | done(); 28 | }); 29 | }); 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/controllers.index.test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | 3 | const chai = require('chai'); 4 | const should = chai.should(); 5 | 6 | const indexController = require('../../src/server/controllers/index'); 7 | 8 | describe('controllers : index', () => { 9 | 10 | describe('sum()', () => { 11 | it('should return a total', (done) => { 12 | indexController.sum(1, 2, (err, total) => { 13 | should.not.exist(err); 14 | total.should.eql(3); 15 | done(); 16 | }); 17 | }); 18 | it('should return an error', (done) => { 19 | indexController.sum(1, 'test', (err, total) => { 20 | should.not.exist(total); 21 | err.should.eql('Something went wrong!'); 22 | done(); 23 | }); 24 | }); 25 | }); 26 | 27 | }); 28 | --------------------------------------------------------------------------------