├── .babelrc ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── package.json └── src ├── api ├── auth.js ├── facets.js └── index.js ├── config ├── dev.js ├── index.js ├── production.js └── test.js ├── db.js ├── index.js ├── lib └── util.js ├── middleware └── index.js ├── models ├── facets.js └── userModel.js ├── schemas └── userSchema.js └── test └── api └── auth.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0", 5 | "env" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | README.md 4 | docker-compose.yml 5 | node_modules 6 | .env 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /logs 3 | /npm-debug.log 4 | /node_modules 5 | .DS_Store 6 | .env 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.4 2 | 3 | # File Author / Maintainer 4 | LABEL authors="Zouhir Chahoud " 5 | 6 | # Update & install required packages 7 | RUN apk add --update nodejs bash git 8 | 9 | # Install app dependencies 10 | COPY package.json /www/package.json 11 | RUN cd /www; npm install 12 | 13 | # Copy app source 14 | COPY . /www 15 | 16 | # Set work directory to /www 17 | WORKDIR /www 18 | 19 | # set your port 20 | ENV PORT 8080 21 | 22 | # expose the port to outside world 23 | EXPOSE 8080 24 | 25 | # start command as per package.json 26 | CMD ["npm", "start"] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jason Miller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Express & ES6 REST API Auth Boilerplate 2 | ================================== 3 | 4 | Boilerplate for building REST APIs with ES6 and Express with JWT authentication. It is based on the great boilerplate [express-es6-rest-api](https://github.com/developit/express-es6-rest-api) by [Jason Miller](https://github.com/developit) 5 | 6 | - ES6 support via [babel](https://babeljs.io) 7 | - REST resources as middleware via [resource-router-middleware](https://github.com/developit/resource-router-middleware) 8 | - CORS support via [cors](https://github.com/troygoode/node-cors) 9 | - Body Parsing via [body-parser](https://github.com/expressjs/body-parser) 10 | - JWT authentication via [passport](https://github.com/jaredhanson/passport) and [passport-jwt](https://github.com/themikenicholson/passport-jwt) 11 | - Password hashing and salting via [bcrypt](https://github.com/kelektiv/node.bcrypt.js) 12 | 13 | > Tip: If you are using [Mongoose](https://github.com/Automattic/mongoose), you can automatically expose your Models as REST resources using [restful-mongoose](https://git.io/restful-mongoose). 14 | 15 | Getting Started 16 | --------------- 17 | 18 | ```sh 19 | # clone it 20 | git clone https://github.com/damianmarek/express-es6-rest-api-auth.git 21 | cd express-es6-rest-api-auth 22 | 23 | # Make it your own 24 | rm -rf .git && git init && npm init 25 | 26 | # Install dependencies 27 | npm install 28 | 29 | ``` 30 | Configuration 31 | ------------- 32 | Make `.env` file to store secrets 33 | 34 | ``` 35 | JWT_SECRET = 36 | MONGO_URL = 37 | ``` 38 | Run server 39 | ---------- 40 | ```sh 41 | # Start development live-reload server 42 | PORT=8080 npm run dev 43 | 44 | # Start production server: 45 | PORT=8080 npm start 46 | ``` 47 | Docker Support 48 | ------ 49 | First you need to ad ENV variables to Dockerfile 50 | 51 | ``` 52 | ENV JWT_SECRET 53 | ENV MONGO_URL 54 | ENV TEST_MONGO_URL 55 | ``` 56 | 57 | ```sh 58 | cd express-es6-rest-api-auth 59 | 60 | Then you can use this commands 61 | 62 | # Build your docker 63 | docker build -t es6/api-service . 64 | # ^ ^ ^ 65 | # tag tag name Dockerfile location 66 | 67 | # run your docker 68 | docker run -p 8080:8080 es6/api-service 69 | # ^ ^ 70 | # bind the port container tag 71 | # to your host 72 | # machine port 73 | 74 | ``` 75 | License 76 | ------- 77 | 78 | MIT 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-es6-rest-api-auth", 3 | "version": "0.3.0", 4 | "description": "Starter project for an ES6 RESTful Express API with JWT auth", 5 | "main": "dist", 6 | "scripts": { 7 | "dev": "nodemon -w src --exec \"babel-node src\"", 8 | "build": "babel src -s -D -d dist", 9 | "start": "node dist", 10 | "prestart": "npm run -s build", 11 | "test": "set NODE_ENV=test&& mocha src --recursive --timeout 10000 --compilers js:babel-register --require babel-polyfill" 12 | }, 13 | "eslintConfig": { 14 | "extends": "eslint:recommended", 15 | "parserOptions": { 16 | "ecmaVersion": 7, 17 | "sourceType": "module", 18 | "ecmaFeatures": { 19 | "experimentalObjectRestSpread": true 20 | } 21 | }, 22 | "env": { 23 | "node": true, 24 | "mocha": true 25 | }, 26 | "rules": { 27 | "no-console": 0, 28 | "no-unused-vars": 0 29 | } 30 | }, 31 | "repository": "damianmarek/express-es6-rest-api-auth", 32 | "author": "Damian Marek ", 33 | "license": "MIT", 34 | "dependencies": { 35 | "bcrypt-nodejs": "0.0.3", 36 | "body-parser": "^1.13.3", 37 | "compression": "^1.5.2", 38 | "cors": "^2.7.1", 39 | "dotenv": "^4.0.0", 40 | "express": "^4.13.3", 41 | "jsonwebtoken": "^7.3.0", 42 | "mongoose": "^4.9.4", 43 | "morgan": "^1.8.0", 44 | "passport": "^0.3.2", 45 | "passport-jwt": "^2.2.1", 46 | "resource-router-middleware": "^0.6.0" 47 | }, 48 | "devDependencies": { 49 | "babel-cli": "^6.9.0", 50 | "babel-core": "^6.9.0", 51 | "babel-polyfill": "^6.23.0", 52 | "babel-preset-env": "^1.4.0", 53 | "babel-preset-es2015": "^6.9.0", 54 | "babel-preset-stage-0": "^6.5.0", 55 | "babel-register": "^6.24.1", 56 | "chai": "^3.5.0", 57 | "chai-http": "^3.0.0", 58 | "eslint": "^3.1.1", 59 | "mocha": "^3.2.0", 60 | "nodemon": "^1.9.2" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/api/auth.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import jwt from 'jsonwebtoken' 3 | import bcrypt from 'bcrypt-nodejs' 4 | import User from '../models/userModel' 5 | import passport from 'passport' 6 | import config from '../config' 7 | 8 | const auth = Router() 9 | 10 | auth.get('/test', passport.authenticate('jwt') , (req, res) => { 11 | res.status(200).json({ message: 'Hello sweetie', auth: req.isAuthenticated() }) 12 | }) 13 | 14 | 15 | auth.post('/login', (req, res) => { 16 | if (!req.body.username || !req.body.password) { 17 | return res.status(400).json({ message: 'Missing required fields' }) 18 | } 19 | User.findOne({ username: req.body.username }) 20 | .then(user => { 21 | if(!user) return res.status(400).json({ message: 'No user' }) 22 | bcrypt.compare(req.body.password, user.password, (err, result) => { 23 | if(result) { 24 | const token = jwt.sign({id: req.body.username}, config.jwtSecret) 25 | return res.status(200).json({ message: 'ok', token }) 26 | } 27 | else { 28 | return res.status(400).json({ message: 'Bad password' }) 29 | } 30 | }) 31 | }) 32 | .catch((err) => { 33 | return res.status(400).json(err) 34 | }) 35 | }) 36 | 37 | auth.post('/register', (req, res) => { 38 | if (!req.body.username || !req.body.password) { 39 | return res.status(400).json({ message: 'Missing required fields' }) 40 | } 41 | 42 | findUser(req.body.username) 43 | .then(() => { 44 | let user = new User({ username: req.body.username, password: req.body.password }) 45 | user.save() 46 | .then(() => { 47 | res.status(200).json(user) 48 | }) 49 | .catch(err => { 50 | res.status(400).json(err) 51 | }) 52 | }) 53 | .catch((err) => { 54 | res.status(400).json({ message: err.message }) 55 | }) 56 | }) 57 | 58 | auth.get('/isLogged', (req, res) => { 59 | jwt.verify(req.headers['authorization'], config.jwtSecret, (err, decoded) => { 60 | if(err) return res.status(401).json({ message: 'Not logged', isLogged: false }) 61 | else return res.status(200).json({ message: 'Logged' , isLogged: true }) 62 | }) 63 | }) 64 | 65 | let findUser = (username) => { 66 | return User.findOne({ username }) 67 | .then(user => { 68 | if(user) throw new Error('User already exists') 69 | }) 70 | .catch((err) => { 71 | throw new Error(err.message) 72 | }) 73 | } 74 | 75 | export default auth 76 | -------------------------------------------------------------------------------- /src/api/facets.js: -------------------------------------------------------------------------------- 1 | import resource from 'resource-router-middleware'; 2 | import facets from '../models/facets'; 3 | 4 | export default ({ config, db }) => resource({ 5 | 6 | /** Property name to store preloaded entity on `request`. */ 7 | id : 'facet', 8 | 9 | /** For requests with an `id`, you can auto-load the entity. 10 | * Errors terminate the request, success sets `req[id] = data`. 11 | */ 12 | load(req, id, callback) { 13 | let facet = facets.find( facet => facet.id===id ), 14 | err = facet ? null : 'Not found'; 15 | callback(err, facet); 16 | }, 17 | 18 | /** GET / - List all entities */ 19 | index({ params }, res) { 20 | res.json(facets); 21 | }, 22 | 23 | /** POST / - Create a new entity */ 24 | create({ body }, res) { 25 | body.id = facets.length.toString(36); 26 | facets.push(body); 27 | res.json(body); 28 | }, 29 | 30 | /** GET /:id - Return a given entity */ 31 | read({ facet }, res) { 32 | res.json(facet); 33 | }, 34 | 35 | /** PUT /:id - Update a given entity */ 36 | update({ facet, body }, res) { 37 | for (let key in body) { 38 | if (key!=='id') { 39 | facet[key] = body[key]; 40 | } 41 | } 42 | res.sendStatus(204); 43 | }, 44 | 45 | /** DELETE /:id - Delete a given entity */ 46 | delete({ facet }, res) { 47 | facets.splice(facets.indexOf(facet), 1); 48 | res.sendStatus(204); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import { version } from '../../package.json'; 2 | import { Router } from 'express'; 3 | import facets from './facets'; 4 | import auth from './auth' 5 | 6 | export default ({ config, db }) => { 7 | let api = Router(); 8 | 9 | // mount the facets resource 10 | api.use('/facets', facets({ config, db })) 11 | api.use('/auth', auth) 12 | // perhaps expose some API metadata at the root 13 | api.get('/', (req, res) => { 14 | res.json({ version }); 15 | }); 16 | 17 | return api; 18 | } 19 | -------------------------------------------------------------------------------- /src/config/dev.js: -------------------------------------------------------------------------------- 1 | const dev = () => { 2 | return { 3 | env: 'dev', 4 | db: process.env.MONGO_URL, 5 | jwtSecret: process.env.JWT_SECRET, 6 | port: process.env.PORT || 8080, 7 | } 8 | } 9 | 10 | export default dev 11 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv' 2 | dotenv.config() 3 | 4 | import dev from './dev' 5 | import production from './production' 6 | import test from './test' 7 | 8 | const config = { 9 | port: 8080, 10 | bodyLimit: "100kb", 11 | corsHeaders: ["Link"] 12 | } 13 | 14 | 15 | export const setupConfig = () => { 16 | let exportCfg 17 | 18 | if(process.env.NODE_ENV === 'test') { 19 | exportCfg = { 20 | ...config, 21 | ...test() 22 | } 23 | } 24 | else if(process.env.NODE_ENV === 'prod') { 25 | exportCfg = { 26 | ...config, 27 | ...production() 28 | } 29 | } 30 | else { 31 | exportCfg = { 32 | ...config, 33 | ...dev() 34 | } 35 | } 36 | 37 | return exportCfg 38 | } 39 | 40 | export default setupConfig() 41 | -------------------------------------------------------------------------------- /src/config/production.js: -------------------------------------------------------------------------------- 1 | const production = () => { 2 | return { 3 | env: 'production', 4 | db: process.env.MONGO_URL, 5 | jwtSecret: process.env.JWT_SECRET, 6 | port: process.env.PORT || 8080 7 | } 8 | } 9 | 10 | export default production 11 | -------------------------------------------------------------------------------- /src/config/test.js: -------------------------------------------------------------------------------- 1 | const test = () => { 2 | return { 3 | env: 'test', 4 | db: process.env.TEST_MONGO_URL, 5 | jwtSecret: process.env.JWT_SECRET, 6 | port: process.env.PORT || 8080 7 | } 8 | } 9 | 10 | export default test 11 | -------------------------------------------------------------------------------- /src/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import config from './config' 3 | 4 | mongoose.Promise = global.Promise 5 | const db = mongoose.connection 6 | 7 | export default callback => { 8 | // connect to a database if needed, then pass it to `callback`: 9 | mongoose.connect(config.db, { 10 | // http://mongoosejs.com/docs/connections.html#use-mongo-client 11 | useMongoClient: true, 12 | }); 13 | callback(db); 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import express from 'express'; 3 | import cors from 'cors'; 4 | import morgan from 'morgan'; 5 | import bodyParser from 'body-parser'; 6 | import { ExtractJwt, Strategy as JwtStrategy } from 'passport-jwt' 7 | 8 | import initializeDb from './db'; 9 | import middleware from './middleware'; 10 | import api from './api'; 11 | import config from './config'; 12 | import passport from 'passport' 13 | import User from './models/userModel' 14 | 15 | let app = express(); 16 | app.server = http.createServer(app); 17 | 18 | // logger 19 | if(process.env.NODE_ENV !== 'test') { 20 | app.use(morgan('dev')); 21 | } 22 | 23 | // 3rd party middleware 24 | app.use(cors({ 25 | exposedHeaders: config.corsHeaders 26 | })); 27 | 28 | app.use(bodyParser.urlencoded({ 29 | extended: true 30 | })) 31 | 32 | app.use(bodyParser.json({ 33 | limit : config.bodyLimit 34 | })); 35 | 36 | app.use(passport.initialize({ session: false })) 37 | 38 | const jwtOptions = { 39 | secretOrKey: config.jwtSecret, 40 | jwtFromRequest: ExtractJwt.fromHeader('authorization'), 41 | } 42 | 43 | passport.serializeUser(function(user, done) { 44 | done(null, user.username); 45 | }) 46 | 47 | passport.deserializeUser(function(username, done) { 48 | User.findOne({ username: username }) 49 | .then((user) => { 50 | return done(user) 51 | }) 52 | .catch(done) 53 | }) 54 | 55 | passport.use('jwt', new JwtStrategy(jwtOptions, (jwt_payload, done) => { 56 | User.findOne({ username: jwt_payload.id }) 57 | .then(user => { 58 | if(user) return done(null, user) 59 | else return done(null, false) 60 | }) 61 | })) 62 | 63 | // connect to db 64 | initializeDb( db => { 65 | 66 | // internal middleware 67 | app.use(middleware({ config, db })); 68 | 69 | // api router 70 | app.use('/api', api({ config, db })); 71 | 72 | app.server.listen(process.env.PORT || config.port); 73 | 74 | console.log(`Started on port ${app.server.address().port}`); 75 | }); 76 | 77 | export default app; 78 | -------------------------------------------------------------------------------- /src/lib/util.js: -------------------------------------------------------------------------------- 1 | 2 | /** Creates a callback that proxies node callback style arguments to an Express Response object. 3 | * @param {express.Response} res Express HTTP Response 4 | * @param {number} [status=200] Status code to send on success 5 | * 6 | * @example 7 | * list(req, res) { 8 | * collection.find({}, toRes(res)); 9 | * } 10 | */ 11 | export function toRes(res, status=200) { 12 | return (err, thing) => { 13 | if (err) return res.status(500).send(err); 14 | 15 | if (thing && typeof thing.toObject==='function') { 16 | thing = thing.toObject(); 17 | } 18 | res.status(status).json(thing); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/middleware/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | export default ({ config, db }) => { 4 | let routes = Router(); 5 | 6 | // add middleware here 7 | 8 | return routes; 9 | } 10 | -------------------------------------------------------------------------------- /src/models/facets.js: -------------------------------------------------------------------------------- 1 | // our example model is just an Array 2 | const facets = []; 3 | export default facets; 4 | -------------------------------------------------------------------------------- /src/models/userModel.js: -------------------------------------------------------------------------------- 1 | import userSchema from '../schemas/userSchema' 2 | import mongoose from 'mongoose' 3 | 4 | const User = mongoose.model('user', userSchema) 5 | export default User 6 | -------------------------------------------------------------------------------- /src/schemas/userSchema.js: -------------------------------------------------------------------------------- 1 | import { Schema } from 'mongoose' 2 | import bcrypt from 'bcrypt-nodejs' 3 | 4 | const userSchema = new Schema({ 5 | username: { type: String, minlength: [8, 'Username must be longer than 7 character']}, 6 | password: { type: String, minlength: [8, 'Password must be longer than 7 character']}, 7 | }) 8 | 9 | userSchema.pre('save', function(next) { 10 | let user = this 11 | let saltRounds = 5 12 | 13 | if (!user.isModified('password')) return next() 14 | 15 | bcrypt.genSalt(saltRounds, (err, salt) => { 16 | if (err) return next(err) 17 | bcrypt.hash(user.password, salt, null, (err, hash) => { 18 | if (err) return next(err) 19 | user.password = hash; 20 | next() 21 | }) 22 | }) 23 | }) 24 | 25 | export default userSchema 26 | -------------------------------------------------------------------------------- /src/test/api/auth.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai' 2 | import chaiHttp from 'chai-http' 3 | import server from '../../index' 4 | import User from '../../models/userModel' 5 | 6 | let should = chai.should() 7 | 8 | chai.use(chaiHttp) 9 | 10 | describe('Auth', () => { 11 | 12 | beforeEach((done) => { 13 | User.remove({}).then(() => { 14 | done() 15 | }).catch(err => { 16 | done() 17 | }) 18 | }) 19 | 20 | 21 | describe('/POST register', () => { 22 | 23 | it('it should not register user without username', done => { 24 | let user = { 25 | password: 'password' 26 | } 27 | chai.request(server) 28 | .post('/api/auth/register') 29 | .send(user) 30 | .end((err, res) => { 31 | res.should.have.status(400) 32 | res.body.should.have.property('message') 33 | done() 34 | }) 35 | }) 36 | 37 | it('it should not register user without password', done => { 38 | let user = { 39 | username: 'username' 40 | } 41 | chai.request(server) 42 | .post('/api/auth/register') 43 | .send(user) 44 | .end((err, res) => { 45 | res.should.have.status(400) 46 | res.body.should.have.property('message') 47 | done() 48 | }) 49 | }) 50 | 51 | it('it should not register user with too short username', done => { 52 | let user = { 53 | username: 'us', 54 | password: 'password' 55 | } 56 | chai.request(server) 57 | .post('/api/auth/register') 58 | .send(user) 59 | .end((err, res) => { 60 | res.should.have.status(400) 61 | res.body.should.have.property('message') 62 | done() 63 | }) 64 | }) 65 | 66 | it('it should not register user with too short password', done => { 67 | let user = { 68 | username: 'username', 69 | password: 'mar' 70 | } 71 | chai.request(server) 72 | .post('/api/auth/register') 73 | .send(user) 74 | .end((err, res) => { 75 | res.should.have.status(400) 76 | res.body.should.have.property('message') 77 | done() 78 | }) 79 | }) 80 | }) 81 | 82 | describe('/POST login', () => { 83 | it('it should return jwt on successful login', done => { 84 | const userData = { 85 | username: 'username', 86 | password: 'password' 87 | } 88 | const user = new User(userData) 89 | user.save() 90 | 91 | chai.request(server) 92 | .post('/api/auth/login') 93 | .send(userData) 94 | .end((err, res) => { 95 | res.should.have.status(200) 96 | res.body.should.have.property('token') 97 | done() 98 | }) 99 | }) 100 | 101 | it('it should not login with incorrect password', done => { 102 | const userData = { 103 | username: 'username', 104 | password: 'password' 105 | } 106 | const wrongUserData = { 107 | username: 'username', 108 | password: 'wrongpassword' 109 | } 110 | 111 | const user = new User(userData) 112 | user.save() 113 | 114 | chai.request(server) 115 | .post('/api/auth/login') 116 | .send(wrongUserData) 117 | .end((err, res) => { 118 | res.should.have.status(400) 119 | res.body.should.have.property('message') 120 | done() 121 | }) 122 | }) 123 | 124 | it('it should not login to nonexistent account', done => { 125 | const userData = { 126 | username: 'username', 127 | password: 'password' 128 | } 129 | 130 | chai.request(server) 131 | .post('/api/auth/login') 132 | .send(userData) 133 | .end((err, res) => { 134 | res.should.have.status(400) 135 | res.body.should.have.property('message') 136 | res.body.message.should.be.eql('No user') 137 | done() 138 | }) 139 | }) 140 | 141 | it('it should not login without username', done => { 142 | const userData = { 143 | password: 'password' 144 | } 145 | 146 | chai.request(server) 147 | .post('/api/auth/login') 148 | .send(userData) 149 | .end((err, res) => { 150 | res.should.have.status(400) 151 | res.body.should.have.property('message') 152 | res.body.message.should.be.eql('Missing required fields') 153 | done() 154 | }) 155 | }) 156 | 157 | 158 | it('it should not login without password', done => { 159 | const userData = { 160 | username: 'username' 161 | } 162 | 163 | chai.request(server) 164 | .post('/api/auth/login') 165 | .send(userData) 166 | .end((err, res) => { 167 | res.should.have.status(400) 168 | res.body.should.have.property('message') 169 | res.body.message.should.be.eql('Missing required fields') 170 | done() 171 | }) 172 | }) 173 | }) 174 | }) 175 | --------------------------------------------------------------------------------