diff --git a/package.json b/package.json index acc29cd2e3d6ff9a605fde13cfaa6774243b6307..a7056dc915f19875538b5f4de96bda8d4fa764d9 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "apicache": "0.0.14", + "bcrypt": "^0.8.7", "body-parser": "^1.13.1", "chai": "^3.5.0", "chai-http": "^3.0.0", @@ -23,11 +24,13 @@ "faker": "^2.1.5", "forever": "^0.15.2", "js2xmlparser": "^1.0.0", + "jwt-simple": "^0.5.0", "method-override": "^2.3.3", "mocha": "^2.5.3", "monetdb-pool": "0.0.8", "mongoose": "^4.6.0", "nconf": "^0.6.x", + "passport": "^0.3.2", "squel": "^5.4.2", "winston": "^2.2.0" }, diff --git a/src/libs/app.js b/src/libs/app.js index 5133df46dc0ebe60b00718ae57e9aea0dd6cbaf3..fa685039b525ff430eca6841908666b40d8b0300 100644 --- a/src/libs/app.js +++ b/src/libs/app.js @@ -16,7 +16,9 @@ const cache = require('apicache').options({ debug: config.debug }).middleware; const app = express(); -const api = require(`${libs}/routes/api`); +const api = require('./routes/api'); + +const passport = require('passport'); const mongoose = require(`${libs}/db/mongoose`); @@ -28,8 +30,6 @@ app.use(cookieParser()); // Enable Cross-Origin Resource Sharing (CORS) app.use(cors()); app.use(methodOverride()); -// Enable cache for 1 day -app.use(cache('1 day')); // Enable maximum compression app.use(compression(9)); // Middleware tha adds the squel object to req diff --git a/src/libs/db/mongoose.js b/src/libs/db/mongoose.js index 5be072989a67d56b69a9ce0cb184581ae4ecc42c..400898c67fc7d43cfa0cf3f2b9faa9176166a088 100644 --- a/src/libs/db/mongoose.js +++ b/src/libs/db/mongoose.js @@ -6,6 +6,8 @@ const log = require(`${libs}/log`)(module); const mongoose = require('mongoose'); +mongoose.Promise = global.Promise; + module.exports = () => { // Get mongodb URI (ip and port) in config file const mongoUri = process.env.MONGO_URI || config.mongodb.uri; diff --git a/src/libs/middlewares/passport.js b/src/libs/middlewares/passport.js new file mode 100644 index 0000000000000000000000000000000000000000..76cdf5988286f2851c1a11c5f6d4cafc34308b51 --- /dev/null +++ b/src/libs/middlewares/passport.js @@ -0,0 +1,28 @@ +const JwtStrategy = require('passport-jwt').Strategy; +const ExtractJwt = require('passport-jwt').ExtractJwt; +const libs = `${process.cwd()}/libs`; +const config = require(`${libs}/config`); +const User = require(`${libs}/models/user`) + +module.exports = function(passport){ + var opts = {}; + opts.jwtFromRequest = ExtractJwt.fromAuthHeader(); + opts.secretOrKey = config.get('mongodb:secret'); + passport.use(new JwtStrategy(opts, function(jwt_payload, done){ + User.find({id: jwt_payload.id}, function(err, user){ + if (err) { + return done(err); + } + + if (!user) { + return done(null, false, {message: 'Unknown user'}); + } + + if (!user.comparePassword(password)) { + return done(null, false, {message: 'Invalid password'}); + } + + return done(null, user); + }); + })); +}; diff --git a/src/libs/models/user.js b/src/libs/models/user.js new file mode 100644 index 0000000000000000000000000000000000000000..8c4f92a204ef9c3b410fb656c16c4af9ad6c7152 --- /dev/null +++ b/src/libs/models/user.js @@ -0,0 +1,89 @@ +const mongoose = require('mongoose'); +const bcrypt = require('bcrypt'); +const libs = `${process.cwd()}/libs`; +const log = require(`${libs}/log`)(module); +const Schema = mongoose.Schema; + +// set up a mongoose model +var UserSchema = new Schema({ + email: { + type: String, + unique: true, + required: true + }, + password: { + type: String, + required: true + }, + name: { + type: String, + required: true + }, + cpf:{ + type: String, + unique: true, + required: true + }, + schooling: { + type: String, + required: true + }, + course: { + type: String, + }, + segment: { + type: String, + required: true + }, + role: { + type: String, + required: true + }, + institution_name: { + type: String, + required: true + }, + state: { + type: String, + required: true + }, + city: { + type: String, + required: true + }, + receive_emails: { + type: Boolean + } + +}); + +UserSchema.pre('save', function (next) { + var user = this; + if (this.isModified('password') || this.isNew) { + bcrypt.genSalt(10, function (err, salt) { + if (err) { + return next(err); + } + bcrypt.hash(user.password, salt, function (err, hash) { + if (err) { + return next(err); + } + user.password = hash; + next(); + }); + }); + } else { + return next(); + } +}); + +UserSchema.methods.comparePassword = function (passw, cb) { + bcrypt.compare(passw, this.password, function (err, isMatch) { + if (err) { + return cb(err); + } + cb(null, isMatch); + }); +}; + +module.exports = mongoose.model('User', UserSchema); diff --git a/src/libs/routes/api.js b/src/libs/routes/api.js index cb474d86ccf45991988531055fa016f083896296..4fc68cf2276302f91d8950720b90cffe5c0eea41 100644 --- a/src/libs/routes/api.js +++ b/src/libs/routes/api.js @@ -2,6 +2,12 @@ const express = require('express'); const api = express(); +const libs = `${process.cwd()}/libs`; + +const config = require(`${libs}/config`); + +const cache = require('apicache').options({ debug: config.get('debug') }).middleware; + const enrollment = require('./enrollment'); const state = require('./state'); @@ -14,16 +20,20 @@ const school = require('./school'); const simulation = require('./simulation'); +const user = require('./user'); + api.get('/', (req, res) => { res.json({ msg: 'SimCAQ API is running' }); }); // mount API routes -api.use('/enrollment', enrollment); -api.use('/state', state); -api.use('/region', region); -api.use('/city', city); -api.use('/school', school); + +api.use('/user', user); api.use('/simulation', simulation); +api.use('/enrollment', cache('1 day'), enrollment); +api.use('/state', cache('15 day'), state); +api.use('/region', cache('15 day'), region); +api.use('/city', cache('15 day'), city); +api.use('/school', cache('15 day'), school); module.exports = api; diff --git a/src/libs/routes/user.js b/src/libs/routes/user.js new file mode 100644 index 0000000000000000000000000000000000000000..c043a66b029197b592469d6ec10c26c7c33c819e --- /dev/null +++ b/src/libs/routes/user.js @@ -0,0 +1,113 @@ +const express = require('express'); + +const userApp = express(); + +const libs = `${process.cwd()}/libs`; + +const config = require(`${libs}/config`); + +const log = require(`${libs}/log`)(module); + +const User = require(`${libs}/models/user`); + +const jwt = require('jwt-simple'); + +const required_fields = ["email", "password", "name", "cpf", "schooling", "segment", "role", "institution_name", "state", "city"]; + + +function emailSyntax(email) { + const regex = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + return regex.test(email); +} + +userApp.post('/', (req, res, next) => { + if (!req.body.email) { + res.json({success: false, msg: 'Please pass email.'}); + } else { + next(); + } + +}, (req, res, next) => { + if (!req.body.password) { + res.json({success: false, msg: 'Please pass password.'}); + } else { + next(); + } + +}, (req, res, next) => { + if(!emailSyntax(req.body.email)){ + res.json({success: false, msg: 'Invalid email syntax.'}); + } else { + next(); + } + +}, (req, res, next) => { + for (let property of required_fields){ + // if(!Object.prototype.hasOwnProperty.call(req.body, property)){ + // res.json({success: false, msg: 'Please fill out all mandatory fields.'}); + // return; + // } + } + next(); +}, (req, res, next) => { + var newUser = new User({ + email: req.body.email, + password: req.body.password, + name: req.body.name, + cpf: req.body.cpf, + schooling: req.body.schooling, + course: req.body.course, + segment: req.body.segment, + role: req.body.role, + institution_name: req.body.institution_name, + state: req.body.state, + city: req.body.city, + receive_emails: req.body.receive_emails + }); + + // save the user + newUser.save((err) => { + if (err) { + res.json({success: false, msg: 'Email already in use.'}); + } else { + res.json({success: true, msg: 'Successful created new user.'}); + } + }); +}); + +userApp.post('/authenticate', (req, res, next) => { + if (!req.body.email || !req.body.password) { + res.json({success: false, msg: 'Please pass email and password.'}); + } else { + next(); + } + +}, (req, res, next) => { + User.findOne({ + email: req.body.email + }, (err, user) => { + if (err) throw err; + + if(!user){ + res.json({success: false, msg: 'Authentication failed. User not found.'}); + } + else { + user.comparePassword(req.body.password, (err, isMatch) => { + if (isMatch && !err) { + var secret = config.get('mongodb:secret'); + + // if user is found and password is right create a token + var token = jwt.encode(user, secret); + + //returns user info including token as json + res.json({success: true, token: 'JWT ' + token}); + } + else { + res.json({success: false, msg: 'Authentication failed. Wrong password'}); + } + }); + } + }); +}); + +module.exports = userApp;