Last active
November 19, 2016 00:07
-
-
Save weihanchen/f4b04adb5f75a463dbdc07d082b0d44e to your computer and use it in GitHub Desktop.
NodeJS_User_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
module.exports = { | |
'secret': process.env.SECRET_KEY || 'user_auth_demo', | |
'database': process.env.MONGO_CONNECTION || 'mongodb://username:password@localhost:27017/user_auth_demo' | |
}; |
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
let adminRoleLevel = Number.MAX_SAFE_INTEGER; | |
let userRoleLevel = 0; | |
module.exports = { | |
'admin_account': process.env.ADMIN_ACCOUNT || 'superadmin', | |
'admin_password': process.env.ADMIN_PASSWORD || 'superadmin', | |
'admin_role_level': adminRoleLevel, | |
'roles': [ | |
{ | |
'role': 'admin', | |
'level': adminRoleLevel | |
}, | |
{ | |
'role': 'user', | |
'level': userRoleLevel | |
} | |
], | |
'user_role_level': userRoleLevel | |
}; |
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
let passport = require("passport"); | |
let passportJWT = require("passport-jwt"); | |
let User = require(__base + 'models/user'); | |
let config = require(__base + 'config/database'); | |
let errorBuilder = require(__base + 'services/error/builder'); | |
let ExtractJwt = passportJWT.ExtractJwt;//extract jwt token | |
let Strategy = passportJWT.Strategy;//策略選擇為jwt | |
let params = { | |
secretOrKey: config.secret, | |
jwtFromRequest: ExtractJwt.fromAuthHeader() //creates a new extractor that looks for the JWT in the authorization header with the scheme 'JWT',e.g JWT + 'token' | |
}; | |
module.exports = function() { | |
let strategy = new Strategy(params, function(payload, done) { | |
//驗證token是否失效 | |
if (payload.exp <= Date.now()) { | |
return done(errorBuilder.unauthorized('Access token has expired'), false); | |
} | |
//根據解析後id取得user,並驗證user是否存在 | |
User.findOne({ _id: payload.iss }, function(err, user) { | |
if (err) return done(err, false); | |
if (user) done(null, user); | |
else done(null, false); | |
}); | |
}); | |
passport.use(strategy); | |
return { | |
initialize: function() { | |
return passport.initialize(); | |
}, | |
authenticate: function() { | |
return passport.authenticate("jwt", { session: false }); | |
} | |
}; | |
}; |
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
let mongoose = require('mongoose'); | |
let Schema = mongoose.Schema; | |
let RoleSchema = new Schema({ | |
role: { | |
type: String, | |
unique: true, | |
required: true | |
}, | |
level: { | |
type: Number, | |
unique: true, | |
required: true | |
} | |
}); | |
module.exports = mongoose.model('Role', RoleSchema); |
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
let mongoose = require('mongoose'); | |
let Schema = mongoose.Schema; | |
let bcrypt = require('bcrypt-nodejs'); | |
// set up a mongoose model | |
let UserSchema = new Schema({ | |
username: { | |
type: String, | |
unique: true, | |
required: true | |
}, | |
displayName: { | |
type: String, | |
unique: true, | |
required: true | |
}, | |
password: { | |
type: String, | |
required: true | |
}, | |
roleId: { | |
type: String, | |
required: true | |
} | |
}); | |
UserSchema.pre('save', function(next) { | |
let user = this; | |
//密碼變更或新密碼時 | |
if (user.isModified('password') || this.isNew) { | |
bcrypt.genSalt(10, function(err, salt) { | |
if (err) { | |
return next(err); | |
} | |
bcrypt.hash(user.password, salt, null, function(err, hash) { | |
if (err) { | |
return next(err); | |
} | |
//使用hash取代明文密碼 | |
user.password = hash; | |
next(); | |
}); | |
}); | |
} else { | |
return next(); | |
} | |
}); | |
/** | |
* mongoose支持擴展方法,因此撰寫密碼驗證 | |
* @param {[string]} password [密碼] | |
* @param {Function} callback [description] | |
* @return {[type]} [description] | |
*/ | |
UserSchema.methods.comparePassword = function(candidatePassword, callback) { | |
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => { | |
if (err) { | |
return callback(err); | |
} | |
callback(null, isMatch); | |
}); | |
}; | |
module.exports = mongoose.model('User', UserSchema); | |
}); | |
} else { | |
return next(); | |
} | |
}); | |
/** | |
* mongoose支持擴展方法,因此撰寫密碼驗證 | |
* @param {[string]} password [密碼] | |
* @param {Function} callback [description] | |
* @return {[type]} [description] | |
*/ | |
UserSchema.methods.comparePassword = function(candidatePassword, callback) { | |
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => { | |
if (err) { | |
return callback(err); | |
} | |
callback(null, isMatch); | |
}); | |
}; | |
module.exports = mongoose.model('User', UserSchema); |
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
{ | |
"name": "user_authentication_api", | |
"description": "api server", | |
"node-main": "./run.js", | |
"dependencies": { | |
"bcrypt-nodejs": "^0.0.3", | |
"body-parser": "^1.15.2", | |
"express": "^4.14.0", | |
"jwt-simple": "^0.5.0", | |
"morgan": "^1.7.0", | |
"moment": "^2.14.1", | |
"mongoose": "^4.6.1", | |
"passport": "^0.3.2", | |
"passport-jwt": "^2.1.0" | |
}, | |
"devDependencies": { | |
"mocha": "^3.0.2", | |
"nodemon": "^1.9.1", | |
"supertest": "^2.0.0", | |
"should": "^11.1.0" | |
}, | |
"scripts": { | |
"dev": "node dev.js", | |
"test": "./node_modules/.bin/mocha test", | |
"product": "node run.js" | |
}, | |
"author": "will.chen", | |
"license": "ISC" | |
} |
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'; | |
let initial_config = require(__base + 'config/initial'); // get initial config file | |
let errorBuilder = require(__base + 'services/error/builder'); | |
// get the mongoose model | |
let User = require(__base + 'models/user'); | |
let Role = require(__base + 'models/role'); | |
exports.initialize = (req, res, next) => { | |
let errorHandler = (error) => { | |
next(error); | |
} | |
setRoles().then(() => { | |
setAdminUser().then(() => { | |
res.json({ | |
success: true, | |
message: 'Successful initialize.' | |
}) | |
}, errorHandler) | |
}, errorHandler) | |
} | |
//private methods | |
function setAdminUser() { | |
let deferred = Promise.defer(); | |
let dbErrorHandler = (error) => { | |
deferred.reject(errorBuilder.badRequest(err.errmsg)); | |
} | |
Role.findOne({ $query: {}, $orderby: { level: -1 } }).exec().then(role => { | |
let adminUser = new User({ | |
displayName: initial_config.admin_account, | |
username: initial_config.admin_account, | |
password: initial_config.admin_password, | |
roleId: role._id | |
}) | |
adminUser.save().then(() => { | |
deferred.resolve(); | |
}).catch(dbErrorHandler); | |
}).catch(dbErrorHandler); | |
return deferred.promise; | |
} | |
function setRoles() { | |
let promises = []; | |
let result = Promise.defer(); | |
let roles = initial_config.roles; | |
roles.forEach(role => { | |
let deferred = Promise.defer(); | |
let newRole = new Role(role); | |
newRole.save(error => { | |
if (error) deferred.reject(errorBuilder.badRequest(error.errmsg)); | |
else deferred.resolve(); | |
}); | |
promises.push(deferred.promise); | |
}) | |
Promise.all(promises).then(() => { | |
result.resolve(); | |
}, error => { | |
result.reject(error); | |
}) | |
return result.promise; | |
} |
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'; | |
let config = require(__base + 'config/database'); // get db config file | |
let errorBuilder = require(__base + 'services/error/builder'); | |
let User = require(__base + 'models/user.js'); // get the mongoose model | |
let jwt = require('jwt-simple'); | |
let moment = require('moment'); | |
exports.login = (req, res, next) => { | |
User.findOne({ | |
username: req.body.username | |
}, (error, user) => { | |
if (error) next(errorBuilder.badRequest(error)); | |
if (!user) next(errorBuilder.badRequest('User not found.')); | |
else { | |
user.comparePassword(req.body.password, (error, isMatch) => { //使用user schema中定義的comparePassword檢查請求密碼是否正確 | |
if (isMatch && !error) { | |
let expires = moment().add(1, 'day').valueOf(); | |
let token = jwt.encode({ | |
iss: user.id, //加密對象 | |
exp: expires | |
}, config.secret); | |
res.json({ success: true, token: 'JWT ' + token }); //JWT for passport-jwt extract fromAuthHeader | |
} else { | |
next(errorBuilder.badRequest('Wrong password.')); | |
} | |
}) | |
} | |
}) | |
} | |
exports.me = (req, res, next) => { //get users/me之前經過中間件驗證用戶權限,當驗證通過便取得正確用戶訊息,直接回傳即可 | |
let responseBody = { | |
username: req.user.username, | |
displayName: req.user.displayName | |
} | |
res.send(responseBody); | |
} | |
exports.signup = (req, res, next) => { | |
let requireProperties = ['displayName', 'password', 'username']; | |
let propertyMissingMsg = ''; | |
let requireValid = requireProperties.every(property => { | |
if (!req.body.hasOwnProperty(property)) { | |
propertyMissingMsg = 'Please pass ' + property; | |
return false; | |
} | |
return true; | |
}) | |
if (!requireValid) { | |
next(errorBuilder.badRequest(propertyMissingMsg)); | |
return; | |
} | |
if (!req.body.username || !req.body.password) next(errorBuilder.badRequest('Please pass username and password')) | |
else { | |
let newUser = new User({ | |
username: req.body.username, | |
displayName: req.body.displayName, | |
password: req.body.password | |
}) | |
User.findOne({ username: newUser.username }, (error, user) => { | |
if (error) next(errorBuilder.internalServerError()); | |
else if (user) { | |
next(errorBuilder.badRequest('username already exist.')); | |
} else { | |
newUser.save((error) => { | |
if (error) next(errorBuilder.internalServerError()); | |
else res.json({ success: true, message: 'Successful signup.' }); | |
}) | |
} | |
}) | |
} | |
} |
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
global.__base = __dirname + '/'; | |
const express = require('express'); | |
const bodyParser = require('body-parser'); | |
const app = express(); | |
//the routing modules | |
const users = require(__base + 'routes/users'); | |
const initial = require(__base + 'routes/initial'); | |
app.set('port', process.env.PORT || 3000); | |
let config = require(__base + 'config/database'); // get db config file | |
let morgan = require('morgan'); | |
let mongoose = require('mongoose'); | |
let jwtauth = require(__base + 'middleware/jwtauth')(); | |
app.use(bodyParser.urlencoded({ extended: false })); | |
app.use(bodyParser.json()); | |
// log to console | |
app.use(morgan('dev')); | |
app.use(jwtauth.initialize()); | |
mongoose.Promise = global.Promise; | |
mongoose.connect(config.database); | |
let apiRoutes = express.Router(); | |
apiRoutes.route('/initialize') | |
.post(initial.initialize) | |
apiRoutes.route('/users') | |
.post(users.signup) | |
apiRoutes.route('/users/login') | |
.post(users.login) | |
apiRoutes.use(jwtauth.authenticate()).route('/users/me') | |
.get(users.me) | |
apiRoutes.use(jwtauth.authenticate()).route('/users/:id') | |
.delete(users.delete) | |
.get(users.info) | |
.put(users.edit) | |
app.use('/api', apiRoutes); | |
app.use(errorHandler); | |
app.listen(app.get('port'), () => { | |
console.log('Express server listening on port ' + app.get('port')); | |
}); | |
function errorHandler(err, req, res, next) { | |
res.status(err.status || 500).json(err); | |
} | |
module.exports = app; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment