Created
December 10, 2016 19:04
-
-
Save bryantee/b03defdb5d616fcaac10fd6f0074374a to your computer and use it in GitHub Desktop.
unit-testing-authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use static'; | |
const express = require('express'); | |
const mongoose = require('mongoose'); | |
const config = require('./config'); | |
const bodyParser = require('body-parser'); | |
const ghRobot = require('./ghRobot'); | |
const request = require('request'); | |
const bcrypt = require('bcryptjs'); | |
const passport = require('passport'); | |
const LocalStrategy = require('passport-local').Strategy; | |
const cookieParser = require('cookie-parser'); | |
const session = require('express-session'); | |
const util = require('util'); | |
// Get models | |
const User = require('./models/user'); | |
// Declare global so function can be | |
// used throughout after import | |
let updateByUser; | |
const app = express(); | |
app.use(express.static('public')); | |
passport.use('local', new LocalStrategy( | |
function(username, password, done) { | |
User.findOne({ username: username }, (err, user) => { | |
if (err) { return done(err); } | |
if (!user) { | |
return done(null, false, {message: "Incorrect username"}); | |
} | |
user.validatePassword(password, error => { | |
if (err) { | |
return done(null, false, {message: "Incorrect username"}); | |
} else { | |
return done(null, user); | |
} | |
}); | |
}); | |
} | |
)); | |
passport.serializeUser(function(user, done) { | |
console.log('Serialize'); | |
// console.log(`User: ${user}`); | |
console.log(`User id: ${user.id}`); | |
done(null, user.id); | |
}); | |
passport.deserializeUser(function(id, done) { | |
console.log(`id: ${id}`); | |
User.findById(id, function(err, user) { | |
console.log('deserializeUser'); | |
done(err, user); | |
}); | |
}); | |
app.use(cookieParser()); | |
app.use(bodyParser.json()); | |
app.use(session({ | |
secret: 'toast', | |
resave: true, | |
saveUninitialized: true, | |
})); | |
app.use(passport.initialize()); | |
app.use(passport.session()); | |
//////////////////////////// | |
// Express Routes for API // | |
//////////////////////////// | |
// Request update to github commit info | |
// returns 200 to let client know db update complete | |
app.post('/user/update/:username', (req, res) => { | |
let username = req.body.username; | |
updateByUser(username, () => { | |
res.sendStatus(200); | |
}); | |
}); | |
// Updating Goal for user | |
// Currently takes JSON object with username and new goal | |
// Returns JSON object with new goal | |
app.put('/users/:user/goal', (req, res) => { | |
let user = req.params.user; | |
let username = req.body.username; | |
let goal = req.body.currentGoal; | |
let query = { | |
username: user | |
}; | |
let update = { | |
$set: { | |
currentGoal: goal | |
} | |
}; | |
User.findOneAndUpdate(query, update, {}, (err, result) => { | |
if (!result) { | |
return res.status(404).send('No matching user: ' + user); | |
} else if (err) { | |
return res.status(500).send('Error: ' + err); | |
} else { | |
res.status(201).json({ currentGoal: goal }); | |
} | |
}); | |
}); | |
// Get all user info for dashboard | |
app.get('/users/:user', (req, res) => { | |
let username = req.params.user; | |
let query = { | |
username | |
}; | |
User.findOne(query, (err, result) => { | |
if (!result) { | |
return res.status(404).send('Bad username: ' + username); | |
} else if (err) { | |
return res.status(500).send('Error: ', err); | |
} | |
// TODO: repackage object literal to deliver to client | |
// ex: object includes password (hashed) | |
result['password'] = 'PRIVATE'; | |
res.status(200).json(result); | |
}); | |
}); | |
// signup user | |
app.post('/users', (req, res) => { | |
let username = req.body.username.trim(); | |
let password = req.body.password.trim(); | |
bcrypt.genSalt(10, (err, salt) => { | |
if (err) { | |
return console.log(`error: ${err}`); | |
} | |
bcrypt.hash(password, salt, (err, hash) => { | |
if (err) { | |
return console.log(`error: ${err}`); | |
} | |
let userObj = { | |
username: username, | |
password: hash, | |
lastCheck: new Date(), | |
highStreak: 0, | |
currentCommitStreakDays: 0, | |
currentGoal: "Double click here to set your goal for the moment." | |
}; | |
const url = "https://api.github.com/users/" + username; | |
request({ | |
url: url, | |
json: true, | |
headers: { | |
'User-Agent': 'javascript' | |
} | |
}, (err, response, body) => { | |
if (err) return console.log(`Error making request to Github: ${err}`); | |
if (response.statusCode !== 200) return console.log(`Status code: ${response.statusCode}`); | |
if (response.statusCode === 200) { | |
userObj.avatar = body.avatar_url; | |
userObj.url = body.url; | |
userObj.bio = body.bio; | |
userObj.location = body.location; | |
User.create(userObj, (err, result) => { | |
if (err) { | |
return res.status(500).json({ | |
message: 'Internal server error' | |
}); | |
} | |
result['password'] = 'PROTECTED'; | |
res.status(201).json(result); | |
}); | |
} | |
}) | |
}); | |
}); | |
}); | |
// authenticate user | |
app.post('/login', passport.authenticate('local'), (req, res) => { | |
if (req.user) { | |
console.log(`Username: ${req.user.username}`); | |
let redirectURL = '/user/' + req.user.username; | |
res.status(200).json({ | |
"success": true, | |
}); | |
} | |
}); | |
// pointless endpoint for testing | |
// will keep in for future tests | |
app.get('/is-login', (req, res) => { | |
res.status(200).json({ | |
user: req.user | |
}); | |
}); | |
// logout user | |
app.get('/logout', (req, res) => { | |
req.logout(); | |
res.status(401).redirect('/'); | |
}); | |
// Get all users (for testing purposes) | |
app.get('/users', (req, res) => { | |
User.find((err, users) => { | |
if (err) { | |
return res.status(500).json({ | |
message: 'Internal server error' | |
}); | |
} | |
res.status(200).json(users); | |
}); | |
}); | |
app.get('*', (req, res) => { | |
res.sendFile(__dirname + '/public/index.html'); | |
}); | |
//////////////////////// | |
// End Routes ////////// | |
//////////////////////// | |
// Startup server | |
const runServer = function(callback) { | |
mongoose.connect(config.DATABASE_URL, err => { | |
if (err && callback) { | |
return callback(err); | |
} | |
console.log(`Connected to db at ${config.DATABASE_URL}`); | |
app.listen(config.PORT, () => { | |
if (callback) { | |
callback(); | |
} | |
}); | |
}); | |
}; | |
// Check if server.js is being called directly or through ./require | |
if (require.main === module) { | |
runServer(err => { | |
if (err) { | |
console.error(err); | |
} | |
updateByUser = ghRobot(); | |
}); | |
} | |
module.exports.app = app; | |
module.exports.runServer = runServer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
global.DATABASE_URL = 'mongodb://localhost/gitchain-test'; | |
const server = require('../server'); | |
const User = require('../models/user'); | |
const chai = require('chai'); | |
const chaiHttp = require('chai-http'); | |
const should = chai.should(); | |
const app = server.app; | |
chai.use(chaiHttp); | |
describe('GitChain API', () => { | |
// seed db | |
before( done => { | |
server.runServer( () => { | |
User.create( | |
{ | |
username: 'jason-voorhees', | |
password: '123', | |
avatar: 'https://avatars.githubusercontent.com/u/10674447?v=3', | |
currentGoal: 'MURDERRR.', | |
currentCommitStreakDays: 5, | |
commitsToday: 1, | |
highStreak: 15, | |
lastCommit: new Date(), | |
lastCheck: new Date() | |
}, | |
{ | |
username: 'freddy-krueger', | |
password: '123', | |
avatar: 'https://avatars.githubusercontent.com/u/10674447?v=3', | |
currentGoal: 'Slice children faces off', | |
currentCommitStreakDays: 10, | |
commitsToday: 7, | |
highStreak: 27, | |
lastCommit: new Date(), | |
lastCheck: new Date() | |
}, function() { | |
done(); | |
}); | |
}); | |
}); | |
// tear down db | |
after( done => { | |
User.remove( () => { | |
done(); | |
}); | |
}); | |
// HAPPY PATH TESTS | |
// it('should return 200 status and html on get "/"', done => { | |
// chai.request(server) | |
// .get('/') | |
// .end((err, res) => { | |
// res.should.have.status(200); | |
// done(); | |
// }); | |
// }); | |
it('should return JSON with values for user on "/users/:user"', done => { | |
chai.request(app) | |
.get('/users') | |
.end((err, res) => { | |
let username = res.body[0].username; | |
chai.request(app) | |
.get('/users/' + username) | |
.end((err, res) => { | |
res.should.have.status(200); | |
res.should.be.json; | |
res.body.should.be.a('object'); | |
res.body.should.have.property('username', 'jason-voorhees'); | |
res.body.should.have.property('avatar'); | |
res.body.should.have.property('currentGoal', 'MURDERRR.'); | |
res.body.should.have.property('currentCommitStreakDays', 5); | |
res.body.should.have.property('commitsToday', 1); | |
res.body.should.have.property('highStreak', 15); | |
res.body.should.have.property('lastCommit'); | |
done(); | |
}) | |
}) | |
}); | |
it('should update goal in database on POST to "/users/:user/goal"', done => { | |
chai.request(app) | |
.put('/users/freddy-krueger/goal') | |
.send({'username': 'freddy-krueger', 'currentGoal': 'Pick all the flowers'}) | |
.end((err, res) => { | |
res.should.have.status(201); | |
res.should.be.json; | |
res.body.should.have.property('currentGoal', 'Pick all the flowers') | |
chai.request(app) | |
.get('/users/freddy-krueger') | |
.end((err, res) => { | |
res.should.have.status(200); | |
res.should.be.json; | |
res.body.should.be.a('object'); | |
res.body.should.have.property('username', 'freddy-krueger'); | |
res.body.should.have.property('avatar'); | |
res.body.should.have.property('currentGoal', 'Pick all the flowers'); | |
res.body.should.have.property('currentCommitStreakDays', 10); | |
res.body.should.have.property('commitsToday', 7); | |
res.body.should.have.property('highStreak', 27); | |
res.body.should.have.property('lastCommit'); | |
done(); | |
}); | |
}); | |
}); | |
it('should add new user to db on POST to "/users" w valid JSON', done => { | |
chai.request(app) | |
.post('/users') | |
.send({ 'username': 'norman-bates', 'password': '123' }) | |
.end((err, res) => { | |
res.should.have.status(201); | |
res.should.be.json; | |
res.body.should.be.a('object'); | |
res.body.should.have.property('username', 'norman-bates'); | |
chai.request(app) | |
.get('/users') | |
.end((err, res) => { | |
let username = res.body[2].username; | |
chai.request(app) | |
.get('/users/' + username) | |
.end((err, res) => { | |
res.should.have.status(200); | |
res.should.be.json; | |
res.body.should.be.a('object'); | |
res.body.should.have.property('username', 'norman-bates'); | |
done(); | |
}); | |
}); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MODEL | |
const mongoose = require('mongoose'); | |
const bcrypt = require('bcryptjs'); | |
const UserSchema = new mongoose.Schema({ | |
username: { | |
type: String, | |
required: true, | |
unique: true | |
}, | |
password: { | |
type: String, | |
required: true | |
}, | |
avatar: { type: String }, | |
currentGoal: { type: String }, | |
currentCommitStreakDays: { type: Number }, | |
commitsToday: { type: Number }, | |
highStreak: { type: Number }, | |
lastCommit: { type: Date }, | |
lastCheck: { type: Date, required: true }, | |
streakDates: [ Date ], | |
bio: { type: String }, | |
url: { type: String }, | |
location: { type: String } | |
}); | |
// TODO: Set instance method for user to check if commit hasn't been made, signaling broken streak | |
UserSchema.methods.validatePassword = function(password, callback) { | |
bcrypt.compare(password, this.password, function(err, isValid) { | |
if (err) { | |
callback(err); | |
return; | |
} | |
callback(null, isValid); | |
}); | |
}; | |
const User = mongoose.model('User', UserSchema); | |
module.exports = User; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment