Skip to content

Instantly share code, notes, and snippets.

@weihanchen
Last active November 19, 2016 00:07
Show Gist options
  • Save weihanchen/f4b04adb5f75a463dbdc07d082b0d44e to your computer and use it in GitHub Desktop.
Save weihanchen/f4b04adb5f75a463dbdc07d082b0d44e to your computer and use it in GitHub Desktop.
NodeJS_User_Authentication
module.exports = {
'secret': process.env.SECRET_KEY || 'user_auth_demo',
'database': process.env.MONGO_CONNECTION || 'mongodb://username:password@localhost:27017/user_auth_demo'
};
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
};
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 });
}
};
};
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);
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);
{
"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"
}
'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;
}
'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.' });
})
}
})
}
}
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