From d4bda147f82dd592fe0a19b1ef70ed81e22d96bf Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Sun, 11 Dec 2016 18:40:40 +0200 Subject: [PATCH 01/10] initial commit --- configs/config.js | 39 ++++++++ handlers/changePasswordHandler.js | 23 +++++ handlers/getProfilesHandler.js | 41 ++++++++ handlers/loginFbHandler.js | 22 +++++ handlers/loginHandler.js | 29 ++++++ handlers/putLocationHandler.js | 16 +++ handlers/registerFbHandler.js | 34 +++++++ handlers/registerHandler.js | 21 ++++ lib/database.js | 15 +++ lib/requests.js | 156 ++++++++++++++++++++++++++++++ lib/tests.js | 77 +++++++++++++++ lib/utils.js | 26 +++++ lib/validate.js | 17 ++++ models/user.js | 42 ++++++++ package.json | 29 ++++++ routes/router.js | 156 ++++++++++++++++++++++++++++++ server.js | 49 ++++++++++ 17 files changed, 792 insertions(+) create mode 100644 configs/config.js create mode 100644 handlers/changePasswordHandler.js create mode 100644 handlers/getProfilesHandler.js create mode 100644 handlers/loginFbHandler.js create mode 100644 handlers/loginHandler.js create mode 100644 handlers/putLocationHandler.js create mode 100644 handlers/registerFbHandler.js create mode 100644 handlers/registerHandler.js create mode 100644 lib/database.js create mode 100644 lib/requests.js create mode 100644 lib/tests.js create mode 100644 lib/utils.js create mode 100644 lib/validate.js create mode 100644 models/user.js create mode 100644 package.json create mode 100644 routes/router.js create mode 100644 server.js diff --git a/configs/config.js b/configs/config.js new file mode 100644 index 0000000..8cb9f09 --- /dev/null +++ b/configs/config.js @@ -0,0 +1,39 @@ +module.exports = { + goodOptions: { + ops: { + interval: 1000 + }, + reporters: { + myConsoleReporter: [{ + module: 'good-squeeze', + name: 'Squeeze', + args: [{ log: '*', response: '*' }] + }, { + module: 'good-console' + }, 'stdout'] + } + }, + swaggerOptions: { + info: { + title: 'MointainServer API Documentation', + description: `Api Documentation for the Mountain Server` + }, + expanded: 'full' + }, + mongo: { + url: '127.0.0.1:27017', + database: 'pss-api' + }, + credentials: { + 'facebook': { + 'AppID': '686766988166832', + 'AppSecret': '86a865c0281e1e6363decfaff5d7b8f7', + 'verificationURI': 'https://graph.facebook.com/me?access_token' + } + }, + JWT: { + secret: 'neverShareYourSecret' + }, + SALT_WORK_FACTOR: 10, + apiURL: 'http://localhost:8001' +} diff --git a/handlers/changePasswordHandler.js b/handlers/changePasswordHandler.js new file mode 100644 index 0000000..3c7081e --- /dev/null +++ b/handlers/changePasswordHandler.js @@ -0,0 +1,23 @@ +'use strict' +const Boom = require('boom') +const JWT = require('jsonwebtoken') +const bcrypt = require('bcrypt') +const config = require('../configs/config') +const User = require('../models/user.js').User + +module.exports = function (request, reply) { + const email = request.auth.credentials.email + const newPassword = request.payload.password.toString() + const confirmedPassword = request.payload.confirmed_password.toString() + + if (newPassword === confirmedPassword) { + User.findOneAndUpdate({email: email}, {password: bcrypt.hashSync(newPassword, config.SALT_WORK_FACTOR)}) + .then((result) => { + const token = JWT.sign({id: result.id, email: result.email}, config.JWT.secret) + return reply({token: token}).state('session', token, config.cookie_options) + }) + .catch(err => { return reply(Boom.badData(err.message)) }) + } else { + return reply(Boom.unauthorized('passwords do not match')) + } +} diff --git a/handlers/getProfilesHandler.js b/handlers/getProfilesHandler.js new file mode 100644 index 0000000..d7e877c --- /dev/null +++ b/handlers/getProfilesHandler.js @@ -0,0 +1,41 @@ +'use strict' +const Boom = require('boom') +const User = require('../models/user.js').User + +module.exports = function (request, reply) { + const role = request.auth.credentials.role + + switch (role) { + case 'Administrator': { + User.find() + .then(result => { + return reply(result) + }) + .catch(err => { + return reply(Boom.system(err.message)) + }) + } + break + case 'MointainDispatcher': { + User.find({role: 'MountainRescuer'}).select('location') + .then(result => { + return reply(result) + }) + .catch(err => { + return reply(Boom.system(err.message)) + }) + } + break + case 'User': + case 'MountainRescuer': { + User.findOne({_id: request.auth.credentials.id}).select('location') + .then(result => { + return reply(result) + }) + .catch(err => { + return reply(Boom.system(err.message)) + }) + } + break + } +} diff --git a/handlers/loginFbHandler.js b/handlers/loginFbHandler.js new file mode 100644 index 0000000..0caf577 --- /dev/null +++ b/handlers/loginFbHandler.js @@ -0,0 +1,22 @@ +const Boom = require('boom') +const JWT = require('jsonwebtoken') +const config = require('../configs/config') +const Req = require('../lib/requests').Req +const req = new Req(config) +const User = require('../models/user.js').User + +module.exports = function (request, reply) { + const fbToken = request.payload.fb_token + + req.requestFbVerifyToken(fbToken).then((result) => { + User.findOne({ username: result.id }) + .then((np) => { + if (!np) return reply(Boom.unauthorized(null, 'Custom')) + else { + const token = JWT.sign({ id: np._id, email: np.email, role: np.role }, config.JWT.secret) + return reply({ token: token }) + } + }) + }) + .catch(err => { return reply(Boom.unauthorized(err.message)) }) +} diff --git a/handlers/loginHandler.js b/handlers/loginHandler.js new file mode 100644 index 0000000..3566a50 --- /dev/null +++ b/handlers/loginHandler.js @@ -0,0 +1,29 @@ +'use strict' +const Boom = require('boom') +const JWT = require('jsonwebtoken') +const bcrypt = require('bcrypt') +const config = require('../configs/config') +const User = require('../models/user.js').User + +module.exports = function (request, reply) { + const email = request.payload.email + const username = request.payload.username + const password = request.payload.password.toString() + if (!email) var query = {username: username} + else query = {email: email} + + User.findOne(query) + .then((np) => { + bcrypt.compare(password, np.password, (err, result) => { + if (!err && result) { + const token = JWT.sign({ id: np._id, email: np.email, role: np.role }, config.JWT.secret) + return reply({token: token, fullname: np.full_name}) + } else if (!result) { + return reply(Boom.unauthorized('Wrong username/password')) + } else { + return err + } + }) + }) + .catch((err) => { return reply(Boom.unauthorized(err.message)) }) +} diff --git a/handlers/putLocationHandler.js b/handlers/putLocationHandler.js new file mode 100644 index 0000000..f9b9e2c --- /dev/null +++ b/handlers/putLocationHandler.js @@ -0,0 +1,16 @@ +'use strict' +const Boom = require('boom') +const User = require('../models/user.js').User + +module.exports = function (request, reply) { + const userId = request.auth.credentials.id + const location = request.payload + + User.findOneAndUpdate({_id: userId}, {location: {lat: location.lat, lng: location.lng}}) + .then((result) => { + return reply({result: result}) + }) + .catch(err => { + return reply(Boom.badData(err.message)) + }) +} diff --git a/handlers/registerFbHandler.js b/handlers/registerFbHandler.js new file mode 100644 index 0000000..550bfad --- /dev/null +++ b/handlers/registerFbHandler.js @@ -0,0 +1,34 @@ +const Boom = require('boom') +const JWT = require('jsonwebtoken') +const config = require('../configs/config') +const Req = require('../lib/requests').Req +const addUser = require('../lib/utils').addUser + +let req = new Req(config) + +module.exports = function (request, reply) { + const fbToken = request.payload.fb_token + const role = request.payload.role + + req.requestFbSignUpToken(fbToken) + .then((userData) => { + let name = userData.name.split(' ') + let signUpData = { + username: userData.id, + email: userData.email, + first_name: name.shift(), + last_name: name.pop(), + password: '' + } + signUpData.location = request.payload.location + signUpData.role = role + + addUser(request, signUpData) + .then((np) => { + const userToken = { token: JWT.sign({ id: np._id, email: np.email, role: np.role }, config.JWT.secret) } + return reply(userToken) + }) + .catch((err) => { return reply(Boom.unauthorized(err.message)) }) + }) + .catch((err) => { return reply(Boom.unauthorized(err.message)) }) +} diff --git a/handlers/registerHandler.js b/handlers/registerHandler.js new file mode 100644 index 0000000..f95a4b5 --- /dev/null +++ b/handlers/registerHandler.js @@ -0,0 +1,21 @@ +'use strict' +const Boom = require('boom') +const JWT = require('jsonwebtoken') +const config = require('../configs/config') +const addUser = require('../lib/utils').addUser + +module.exports = function (request, reply) { + const signUpData = request.payload.user + signUpData.location = request.payload.location + + if (signUpData.password === signUpData.confirmed_password) { + addUser(request, signUpData) + .then((np) => { + const token = JWT.sign({ id: np._id, email: np.email, role: np.role }, config.JWT.secret) + return reply({token: token, fullname: np.full_name}) + }) + .catch((err) => { return reply(Boom.unauthorized(err.message)) }) + } else { + return reply(Boom.unauthorized('passwords do not match')) + } +} diff --git a/lib/database.js b/lib/database.js new file mode 100644 index 0000000..e14559a --- /dev/null +++ b/lib/database.js @@ -0,0 +1,15 @@ +'use strict' +const Mongoose = require('mongoose') +const db = Mongoose.connection +const config = require('../configs/config') + +Mongoose.Promise = global.Promise +Mongoose.connect(`mongodb://${config.mongo.url}/${config.mongo.database}`) + +db.on('error', console.error.bind(console, 'connection error')) +db.once('open', function callback () { + console.log('Connection with database succeeded.') +}) + +exports.Mongoose = Mongoose +exports.db = db diff --git a/lib/requests.js b/lib/requests.js new file mode 100644 index 0000000..3376ecc --- /dev/null +++ b/lib/requests.js @@ -0,0 +1,156 @@ +'use strict' +const request = require('request') + +class Req { + constructor (config) { + this.config = config + this.requestData = (object) => { + return new Promise((resolve, reject) => { + request(object, (e, r, body) => { + if (!e && r.statusCode === 200) { + let oBody = JSON.parse(body) + resolve(oBody) + } else { + if (!e) { + reject(new Error(`statusCode : ${r.statusCode} + body: ${r.body}`)) + } else { + reject(new Error(`Error: ${e}`)) + } + } + }) + }) + } + } + + requestToken (obj) { + return new Promise((resolve, reject) => { + let loginRequest = { + method: 'POST', + url: `${this.config.apiURL}/login`, + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify(obj) + } + + this.requestData(loginRequest) + .then((token) => resolve(token)) + .catch((e) => reject(e)) + }) + } + + requestFbVerifyToken (fbToken) { + return new Promise((resolve, reject) => { + let fbRequest = { + method: 'GET', + url: `${this.config.credentials.facebook.verificationURI}=${fbToken}` + } + + this.requestData(fbRequest) + .then((result) => resolve(result)) + .catch((e) => reject(e)) + }) + } + + requestFbSignUpToken (fbToken) { + return new Promise((resolve, reject) => { + let fbSingUpRequest = { + method: 'GET', + url: `${this.config.credentials.facebook.verificationURI}=${fbToken}&fields=name,email` + } + this.requestData(fbSingUpRequest) + .then((result) => resolve(result)) + .catch((e) => reject(e)) + }) + } + + // === TEST === + + requestProfiles (token) { + return new Promise((resolve, reject) => { + let getProfilesRequest = { + method: 'GET', + url: `${this.config.apiURL}/profiles`, + headers: { + 'content-type': 'application/json', + 'Authentication-Token': `${token.token}` + } + } + + this.requestData(getProfilesRequest) + .then((result) => resolve(result)) + .catch((e) => reject(e)) + }) + } + + requestFbLogin (fbToken) { + return new Promise((resolve, reject) => { + let fbSignInRequest = { + method: 'POST', + url: `${this.config.apiURL}/login/facebook`, + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify({ + fb_token: fbToken + }) + } + this.requestData(fbSignInRequest) + .then((result) => resolve(result)) + .catch((e) => reject(e)) + }) + } + + requestFbRegister (userdata) { + return new Promise((resolve, reject) => { + let userFbSingUpRequest = { + method: 'POST', + url: `${this.config.apiURL}/register/facebook`, + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify(userdata) + } + + this.requestData(userFbSingUpRequest) + .then((result) => resolve(result)) + .catch((e) => reject(e)) + }) + } + + register (user) { + return new Promise((resolve, reject) => { + let loginRequest = { + method: 'POST', + url: `${this.config.apiURL}/register`, + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify(user) + } + this.requestData(loginRequest) + .then((result) => resolve(result)) + .catch((e) => reject(e)) + }) + } + + requestPutLocation (token, location) { + return new Promise((resolve, reject) => { + let locationRequest = { + method: 'PUT', + url: `${this.config.apiURL}/location`, + headers: { + 'content-type': 'application/json', + 'Authentication-Token': `${token.token}` + }, + body: JSON.stringify(location) + } + this.requestData(locationRequest) + .then((result) => resolve(result)) + .catch((e) => reject(e)) + }) + } +} + +module.exports = {Req: Req} diff --git a/lib/tests.js b/lib/tests.js new file mode 100644 index 0000000..c1a4953 --- /dev/null +++ b/lib/tests.js @@ -0,0 +1,77 @@ +let Req = require('./requests').Req +const config = require('../configs/config') +let req = new Req(config) + +// == login == + +let user = { +// email: 'vgenev@dmail.com', + username: 'vgenev', + password: '!23$' +} + +// req.requestToken(user).then(r => console.log(r)).catch(e => console.log(e)) + +// == register == + +let newUser = { + user: { + first_name: 'Valentin', + last_name: 'Genev', + phone_number: '+359099990', + username: 'vgenev', + password: '!23$', + confirmed_password: '!23$', + email: 'vgenev@dmail.com', + role: 'User' + }, + location: { + lat: 12.4455, + lng: 23.4553 + } +} + +// req.register(newUser).then(r => console.log(r)).catch(e => console.log(e)) + +// == change location == + +let location = { + lat: 51.232, + lng: 22.212 +} + +// normal login +// req.requestToken(user) +// .then(t => req.requestPutLocation({token: t.token}, location) +// .then((res) => { +// console.log(res) +// })) +// .catch(e => console.log(e)) + +// fb login +// req.requestFbLogin(fbToken) +// .then(t => req.requestPutLocation({token: t.token}, location) +// .then((res) => { +// console.log(res) +// })) +// .catch(e => console.log(e)) + +const fbToken = 'EAAJwnGZAfhrABANOL0d6nkgpTKMb8Sk5vXPZCHX6piZC41wV7IEnhCgZBTbOxstn7nt5di03KAYem2IdtpHMoZBHIi4nt4BYrJrSZAdhW6PCuys93zAV7WfSl9J1bAridZCOViZAvOmQpb4cXMW3UiZCK7t32xoNf6xBZCHuQKfW2MDwZDZD' + +// req.requestFbLogin(fbToken).then(r => console.log(r)).catch(e => console.log(e)) + +let fbUser = { + fb_token: fbToken, + role: 'User', + location: location +} + +// req.requestFbRegister(fbUser).then(r => console.log(r)).catch(e => console.log(e)) + +req.requestToken(user) + .then(t => req.requestProfiles({token: t.token}) + .then((res) => { + console.log(res) + })) + .catch(e => console.log(e)) + diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..41159d9 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,26 @@ +const bcrypt = require('bcrypt') +const User = require('../models/user.js').User +const config = require('../configs/config') + +module.exports = { + addUser: function (request, signUpData) { + return new Promise((resolve, reject) => { + var user = new User() + // var location = new Location() + Object.assign(user, signUpData) + // Object.assign(location, locationData) + user.password = bcrypt.hashSync(signUpData.password, config.SALT_WORK_FACTOR) + user.save() + .then((result) => { + // location.save() + resolve(result) + }) + .catch(error => { + reject(error) + }) + }) + }, + test: function () { + console.log('test') + } +} diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..e896ff4 --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,17 @@ +'use strict' +const User = require('../models/user.js').User + +const validate = function (decoded, request, callback) { + User.findOne({email: decoded.email}) + .then((user) => { + if ((decoded.email) === (user.email)) { + return callback(null, true) + } else { + return callback(null, false) + } + }) +} + +module.exports = { + validate: validate +} diff --git a/models/user.js b/models/user.js new file mode 100644 index 0000000..5d7c0c3 --- /dev/null +++ b/models/user.js @@ -0,0 +1,42 @@ +'use strict' +// first_name, last_name, email_address, phone_number, password, username, +// location (latitude, longitude), role:Role Object, facebook_linked, twitter_linked, google_linked + +const Mongoose = require('../lib/database').Mongoose +const db = require('../lib/database').db +const roles = ['Administrator', 'MointainDispatcher', 'MountainRescuer', 'User'] + +const userSchema = new Mongoose.Schema({ + first_name: { type: String }, + last_name: { type: String }, + phone_number: { type: String }, + username: { type: String, unique: true, required: true }, + password: { type: String }, + email: { type: String, unique: true, required: true }, + location: {lat: Number, lng: Number}, + role: {type: String, enum: roles}, + fb_linked: {type: String}, + google_linked: {type: String}, + twitter_linked: {type: String} +}) + +userSchema.virtual('full_name').get(function () { + return `${this.first_name} ${this.last_name}` +}) + +// const locationSchema = new Mongoose.Schema({ +// lat: {type: Number, required: true}, +// lng: {type: Number, required: true}, +// last_seen: {type: Date, default: Date.now}, +// _user: {type: Number, ref: 'User'} +// }) + +// const Location = db.model('Location', locationSchema, 'Locations') + +const User = db.model('User', userSchema, 'Users') + +module.exports = { + User: User, + Roles: roles // , + // Location: Location +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..68297aa --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "pss-api", + "version": "0.0.1", + "description": "pss-tool-api", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Valentin Genev", + "license": "ISC", + "dependencies": { + "bcrypt": "^0.8.7", + "boom": "^4.1.0", + "good": "^7.0.2", + "good-console": "^6.3.1", + "good-http": "^6.1.1", + "good-squeeze": "^5.0.1", + "hapi": "^15.1.1", + "hapi-auth-jwt2": "^7.1.3", + "hapi-swagger": "^7.4.0", + "hoek": "^4.1.0", + "inert": "^4.0.2", + "joi": "^9.0.4", + "jsonwebtoken": "^7.1.9", + "mongoose": "^4.7.1", + "request": "^2.79.0", + "vision": "^4.1.1" + } +} diff --git a/routes/router.js b/routes/router.js new file mode 100644 index 0000000..4e79b20 --- /dev/null +++ b/routes/router.js @@ -0,0 +1,156 @@ +'use strict' +const Joi = require('joi') +const loginHandler = require('../handlers/loginHandler') +const registerHandler = require('../handlers/registerHandler') +const changePasswordHandler = require('../handlers/changePasswordHandler') +const putLocationHandler = require('../handlers/putLocationHandler') +const loginFbHandler = require('../handlers/loginFbHandler') +const registerFbHandler = require('../handlers/registerFbHandler') +const getProfilesHandler = require('../handlers/getProfilesHandler') + +const route = [{ + method: 'POST', + path: '/login', + handler: loginHandler, + config: { + payload: { + output: 'data', + parse: true + }, + auth: false, + validate: { + payload: { + email: Joi.string().email(), + username: Joi.string(), + password: Joi.string().required() + } + } + } +}, + { + method: 'POST', + path: '/register', + config: { + handler: registerHandler, + payload: { + output: 'data', + parse: true + }, + auth: false, + validate: { + payload: { + user: Joi.object({ + email: Joi.string().email().required(), + password: Joi.string().required(), + confirmed_password: Joi.string().required(), + phone_number: Joi.string(), + username: Joi.string(), + role: Joi.string().required().allow('Administrator', 'MointainDispatcher', 'MountainRescuer', 'User'), + first_name: Joi.string(), + last_name: Joi.string() + }), + location: Joi.object({ + lat: Joi.number(), + lng: Joi.number() + }) + } + } + } + }, + { + method: 'PUT', + path: '/location', + config: { + payload: { + output: 'data', + parse: true + }, + handler: putLocationHandler, + auth: 'jwt', + validate: { + payload: { + lat: Joi.number().required(), + lng: Joi.number().required() + }, + headers: Joi.object({ + 'content-type': Joi.string().regex(/(application\/json)/).required() + }).unknown() + } + } + }, + { + method: 'POST', + path: '/login/facebook', + handler: loginFbHandler, + config: { + payload: { + output: 'data', + parse: true + }, + auth: false, + validate: { + payload: { + fb_token: Joi.string().required() + }, + headers: Joi.object({ + 'content-type': Joi.string().regex(/(application\/json)/).required() + }).unknown() + } + } + }, + { + method: 'POST', + path: '/register/facebook', + config: { + handler: registerFbHandler, + payload: { + output: 'data', + parse: true + }, + auth: false, + validate: { + payload: { + fb_token: Joi.string().token().required(), + role: Joi.string().required().allow('Administrator', 'MointainDispatcher', 'MountainRescuer', 'User'), + location: Joi.object({ + lat: Joi.number(), + lng: Joi.number() + }) + }, + headers: Joi.object({ + 'content-type': Joi.string().regex(/(application\/json)/).required() + }).unknown() + } + } + }, + { + method: 'GET', + path: '/profiles', + config: { + auth: 'jwt', + handler: getProfilesHandler + } + }, + { + method: 'POST', + path: '/changepass', + config: { + handler: changePasswordHandler, + payload: { + output: 'data', + parse: true + }, + auth: 'jwt', + validate: { + payload: { + password: Joi.string().required(), + confirmed_password: Joi.string().required() + } + } + } + } +] + +module.exports = { + route: route +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..2cfb9e0 --- /dev/null +++ b/server.js @@ -0,0 +1,49 @@ +'use strict' +// Load modules +const Hapi = require('hapi') +const Hoek = require('hoek') +const hapiAuthJWT = require('hapi-auth-jwt2') +const Good = require('good') +const Swagger = require('hapi-swagger') + +// Own modules +const config = require('./configs/config') +const router = require('./routes/router').route +const validate = require('./lib/validate').validate + +for (let route of router) { + route.config.tags = [] + route.config.tags.push('api') +} + +const server = new Hapi.Server() +server.connection({ port: 8001 }) + +server.register([ + require('inert'), + require('vision'), + { register: Good, options: config.goodOptions }, + { register: Swagger, options: config.swaggerOptions } +], (err) => { + Hoek.assert(!err, err) +}) + +server.register(hapiAuthJWT, (err) => { + Hoek.assert(!err, err) + + server.auth.strategy('jwt', 'jwt', + { key: config.JWT.secret, + validateFunc: validate, + verifyOptions: { ignoreExpiration: true }, + headerKey: 'authentication-token' + }) + + server.auth.default('jwt') + server.route(router) + + server.start((err) => { + Hoek.assert(!err, err) + console.log('Server started at:', server.info.uri) + }) +}) + From 774149d5805ad1494ab88a43ded976102fdf025e Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Sun, 11 Dec 2016 18:52:11 +0200 Subject: [PATCH 02/10] adding token header for the swagger --- .gitignore | 13 +++++++++++++ .vscode/launch.json | 21 +++++++++++++++++++++ routes/router.js | 16 +++++++++++++--- 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index 5148e52..402c766 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ npm-debug.log* pids *.pid *.seed +*.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov @@ -29,9 +30,21 @@ build/Release # Dependency directories node_modules jspm_packages +typings +typings.json # Optional npm cache directory .npm +# Optional eslint cache +.eslintcache + # Optional REPL history .node_repl_history + +# Output of 'npm pack' +*.tgz +*.zip + +# Temp data +.tmp \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8e64b70 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible Node.js debug attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceRoot}/server.js", + "cwd": "${workspaceRoot}" + }, + { + "type": "node", + "request": "attach", + "name": "Attach to Process", + "port": 5858 + } + ] +} \ No newline at end of file diff --git a/routes/router.js b/routes/router.js index 4e79b20..f5ce64e 100644 --- a/routes/router.js +++ b/routes/router.js @@ -73,7 +73,8 @@ const route = [{ lng: Joi.number().required() }, headers: Joi.object({ - 'content-type': Joi.string().regex(/(application\/json)/).required() + 'content-type': Joi.string().regex(/(application\/json)/).required(), + 'Authentication-Token': Joi.string().token() }).unknown() } } @@ -128,7 +129,12 @@ const route = [{ path: '/profiles', config: { auth: 'jwt', - handler: getProfilesHandler + handler: getProfilesHandler, + validate: { + headers: Joi.object({ + 'Authentication-Token': Joi.string().token() + }).unknown() + } } }, { @@ -145,7 +151,11 @@ const route = [{ payload: { password: Joi.string().required(), confirmed_password: Joi.string().required() - } + }, + headers: Joi.object({ + 'content-type': Joi.string().regex(/(application\/json)/).required(), + 'Authentication-Token': Joi.string().token() + }).unknown() } } } From 2cdcd12cc2ef147aa1b4c04cb9040a1271f522c6 Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Sun, 11 Dec 2016 19:05:27 +0200 Subject: [PATCH 03/10] content-type !required (swagger)+put loc new reply --- handlers/putLocationHandler.js | 2 +- routes/router.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/handlers/putLocationHandler.js b/handlers/putLocationHandler.js index f9b9e2c..9ae08fd 100644 --- a/handlers/putLocationHandler.js +++ b/handlers/putLocationHandler.js @@ -8,7 +8,7 @@ module.exports = function (request, reply) { User.findOneAndUpdate({_id: userId}, {location: {lat: location.lat, lng: location.lng}}) .then((result) => { - return reply({result: result}) + return reply({location: location}) }) .catch(err => { return reply(Boom.badData(err.message)) diff --git a/routes/router.js b/routes/router.js index f5ce64e..3792e01 100644 --- a/routes/router.js +++ b/routes/router.js @@ -73,7 +73,7 @@ const route = [{ lng: Joi.number().required() }, headers: Joi.object({ - 'content-type': Joi.string().regex(/(application\/json)/).required(), + 'Content-Type': Joi.string().regex(/(application\/json)/), 'Authentication-Token': Joi.string().token() }).unknown() } @@ -94,7 +94,7 @@ const route = [{ fb_token: Joi.string().required() }, headers: Joi.object({ - 'content-type': Joi.string().regex(/(application\/json)/).required() + 'Content-Type': Joi.string().regex(/(application\/json)/) }).unknown() } } @@ -119,7 +119,7 @@ const route = [{ }) }, headers: Joi.object({ - 'content-type': Joi.string().regex(/(application\/json)/).required() + 'Content-Type': Joi.string().regex(/(application\/json)/) }).unknown() } } @@ -153,7 +153,7 @@ const route = [{ confirmed_password: Joi.string().required() }, headers: Joi.object({ - 'content-type': Joi.string().regex(/(application\/json)/).required(), + 'Content-Type': Joi.string().regex(/(application\/json)/), 'Authentication-Token': Joi.string().token() }).unknown() } From f6f5aaf813f956cab0541bd8f1c4f0741681a96e Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Sun, 11 Dec 2016 19:09:17 +0200 Subject: [PATCH 04/10] ignore VSCode dirs --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 402c766..ef80a67 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,8 @@ typings.json *.zip # Temp data -.tmp \ No newline at end of file +.tmp + +# VS Code +.vscode +launch.json \ No newline at end of file From b1e47eba820993f05f662bc7c52ad72a9d4a629c Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Sun, 11 Dec 2016 23:03:11 +0200 Subject: [PATCH 05/10] username no longer required + fb secret removed --- .gitignore | 5 ++++- configs/config.js | 4 ++-- lib/tests.js | 27 ++++++++++++++------------- models/user.js | 2 +- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index ef80a67..0f72e49 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,7 @@ typings.json # VS Code .vscode -launch.json \ No newline at end of file +launch.json + +#secrets +secret.js \ No newline at end of file diff --git a/configs/config.js b/configs/config.js index 8cb9f09..2848d1d 100644 --- a/configs/config.js +++ b/configs/config.js @@ -26,8 +26,8 @@ module.exports = { }, credentials: { 'facebook': { - 'AppID': '686766988166832', - 'AppSecret': '86a865c0281e1e6363decfaff5d7b8f7', + 'AppID': 'facebookAppId', + 'AppSecret': 'facebookAppSecret', // 'verificationURI': 'https://graph.facebook.com/me?access_token' } }, diff --git a/lib/tests.js b/lib/tests.js index c1a4953..861e8de 100644 --- a/lib/tests.js +++ b/lib/tests.js @@ -19,19 +19,20 @@ let newUser = { first_name: 'Valentin', last_name: 'Genev', phone_number: '+359099990', - username: 'vgenev', + username: 'vg', password: '!23$', confirmed_password: '!23$', - email: 'vgenev@dmail.com', + email: 'vgenev@gmail.com', role: 'User' - }, - location: { - lat: 12.4455, - lng: 23.4553 } + // }, + // location: { + // lat: 12.4455, + // lng: 23.4553 + // } } -// req.register(newUser).then(r => console.log(r)).catch(e => console.log(e)) +req.register(newUser).then(r => console.log(r)).catch(e => console.log(e)) // == change location == @@ -68,10 +69,10 @@ let fbUser = { // req.requestFbRegister(fbUser).then(r => console.log(r)).catch(e => console.log(e)) -req.requestToken(user) - .then(t => req.requestProfiles({token: t.token}) - .then((res) => { - console.log(res) - })) - .catch(e => console.log(e)) +// req.requestToken(user) +// .then(t => req.requestProfiles({token: t.token}) +// .then((res) => { +// console.log(res) +// })) +// .catch(e => console.log(e)) diff --git a/models/user.js b/models/user.js index 5d7c0c3..5e0841c 100644 --- a/models/user.js +++ b/models/user.js @@ -10,7 +10,7 @@ const userSchema = new Mongoose.Schema({ first_name: { type: String }, last_name: { type: String }, phone_number: { type: String }, - username: { type: String, unique: true, required: true }, + username: { type: String, unique: true }, password: { type: String }, email: { type: String, unique: true, required: true }, location: {lat: Number, lng: Number}, From 91d1e9e3f663a8316673a2b4779d393823370cd4 Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Tue, 13 Dec 2016 12:22:17 +0200 Subject: [PATCH 06/10] bugfixes --- handlers/getProfilesHandler.js | 6 +++--- handlers/loginHandler.js | 1 + handlers/registerFbHandler.js | 4 ++-- lib/tests.js | 2 +- lib/validate.js | 6 +----- models/user.js | 2 +- server.js | 17 ++++------------- 7 files changed, 13 insertions(+), 25 deletions(-) diff --git a/handlers/getProfilesHandler.js b/handlers/getProfilesHandler.js index d7e877c..418fbfc 100644 --- a/handlers/getProfilesHandler.js +++ b/handlers/getProfilesHandler.js @@ -3,11 +3,11 @@ const Boom = require('boom') const User = require('../models/user.js').User module.exports = function (request, reply) { - const role = request.auth.credentials.role - + // const role = request.auth.credentials.role + const role = 'Administrator' switch (role) { case 'Administrator': { - User.find() + User.find({}, { password: 0 }) .then(result => { return reply(result) }) diff --git a/handlers/loginHandler.js b/handlers/loginHandler.js index 3566a50..c9cb6cc 100644 --- a/handlers/loginHandler.js +++ b/handlers/loginHandler.js @@ -9,6 +9,7 @@ module.exports = function (request, reply) { const email = request.payload.email const username = request.payload.username const password = request.payload.password.toString() + if (!email) var query = {username: username} else query = {email: email} diff --git a/handlers/registerFbHandler.js b/handlers/registerFbHandler.js index 550bfad..acebf64 100644 --- a/handlers/registerFbHandler.js +++ b/handlers/registerFbHandler.js @@ -14,7 +14,7 @@ module.exports = function (request, reply) { .then((userData) => { let name = userData.name.split(' ') let signUpData = { - username: userData.id, + fb_linked: userData.id, email: userData.email, first_name: name.shift(), last_name: name.pop(), @@ -28,7 +28,7 @@ module.exports = function (request, reply) { const userToken = { token: JWT.sign({ id: np._id, email: np.email, role: np.role }, config.JWT.secret) } return reply(userToken) }) - .catch((err) => { return reply(Boom.unauthorized(err.message)) }) +// .catch((err) => { return reply(Boom.unauthorized(err.message)) }) }) .catch((err) => { return reply(Boom.unauthorized(err.message)) }) } diff --git a/lib/tests.js b/lib/tests.js index 861e8de..d1ee5ff 100644 --- a/lib/tests.js +++ b/lib/tests.js @@ -22,7 +22,7 @@ let newUser = { username: 'vg', password: '!23$', confirmed_password: '!23$', - email: 'vgenev@gmail.com', + email: 'v12232323g22v@gmail.com', role: 'User' } // }, diff --git a/lib/validate.js b/lib/validate.js index e896ff4..380d843 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -1,7 +1,7 @@ 'use strict' const User = require('../models/user.js').User -const validate = function (decoded, request, callback) { +module.exports = function (decoded, request, callback) { User.findOne({email: decoded.email}) .then((user) => { if ((decoded.email) === (user.email)) { @@ -11,7 +11,3 @@ const validate = function (decoded, request, callback) { } }) } - -module.exports = { - validate: validate -} diff --git a/models/user.js b/models/user.js index 5e0841c..b93aedb 100644 --- a/models/user.js +++ b/models/user.js @@ -10,7 +10,7 @@ const userSchema = new Mongoose.Schema({ first_name: { type: String }, last_name: { type: String }, phone_number: { type: String }, - username: { type: String, unique: true }, + username: { type: String, index: true, unique: true, sparse: true }, password: { type: String }, email: { type: String, unique: true, required: true }, location: {lat: Number, lng: Number}, diff --git a/server.js b/server.js index 2cfb9e0..3d99b72 100644 --- a/server.js +++ b/server.js @@ -2,14 +2,11 @@ // Load modules const Hapi = require('hapi') const Hoek = require('hoek') -const hapiAuthJWT = require('hapi-auth-jwt2') -const Good = require('good') -const Swagger = require('hapi-swagger') // Own modules const config = require('./configs/config') const router = require('./routes/router').route -const validate = require('./lib/validate').validate +const validate = require('./lib/validate') for (let route of router) { route.config.tags = [] @@ -22,25 +19,19 @@ server.connection({ port: 8001 }) server.register([ require('inert'), require('vision'), - { register: Good, options: config.goodOptions }, - { register: Swagger, options: config.swaggerOptions } + require('hapi-auth-jwt2'), + { register: require('good'), options: config.goodOptions }, + { register: require('hapi-swagger'), options: config.swaggerOptions } ], (err) => { Hoek.assert(!err, err) -}) - -server.register(hapiAuthJWT, (err) => { - Hoek.assert(!err, err) - server.auth.strategy('jwt', 'jwt', { key: config.JWT.secret, validateFunc: validate, verifyOptions: { ignoreExpiration: true }, headerKey: 'authentication-token' }) - server.auth.default('jwt') server.route(router) - server.start((err) => { Hoek.assert(!err, err) console.log('Server started at:', server.info.uri) From 8d03a21dab77f54bbea50fed1b8cb58422c1642b Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Tue, 20 Dec 2016 15:36:33 +0200 Subject: [PATCH 07/10] refactor, new routes and adding scope for roles --- configs/config.js | 2 + handlers/changePasswordHandler.js | 23 ----- handlers/changeRoleHandler.js | 14 +++ handlers/getProfilesHandler.js | 5 +- handlers/getUserProfile.js | 13 +++ handlers/loginFbHandler.js | 2 +- handlers/loginHandler.js | 2 +- handlers/registerFbHandler.js | 2 +- handlers/registerHandler.js | 2 +- handlers/updateProfileHandler.js | 29 ++++++ lib/requests.js | 36 ++++++- lib/tests.js | 33 ++++-- lib/validate.js | 3 +- routes/router.js | 130 +++--------------------- routes/userRoutes.js | 162 ++++++++++++++++++++++++++++++ 15 files changed, 298 insertions(+), 160 deletions(-) delete mode 100644 handlers/changePasswordHandler.js create mode 100644 handlers/changeRoleHandler.js create mode 100644 handlers/getUserProfile.js create mode 100644 handlers/updateProfileHandler.js create mode 100644 routes/userRoutes.js diff --git a/configs/config.js b/configs/config.js index 2848d1d..2c2ca86 100644 --- a/configs/config.js +++ b/configs/config.js @@ -1,3 +1,5 @@ +'use strict' + module.exports = { goodOptions: { ops: { diff --git a/handlers/changePasswordHandler.js b/handlers/changePasswordHandler.js deleted file mode 100644 index 3c7081e..0000000 --- a/handlers/changePasswordHandler.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict' -const Boom = require('boom') -const JWT = require('jsonwebtoken') -const bcrypt = require('bcrypt') -const config = require('../configs/config') -const User = require('../models/user.js').User - -module.exports = function (request, reply) { - const email = request.auth.credentials.email - const newPassword = request.payload.password.toString() - const confirmedPassword = request.payload.confirmed_password.toString() - - if (newPassword === confirmedPassword) { - User.findOneAndUpdate({email: email}, {password: bcrypt.hashSync(newPassword, config.SALT_WORK_FACTOR)}) - .then((result) => { - const token = JWT.sign({id: result.id, email: result.email}, config.JWT.secret) - return reply({token: token}).state('session', token, config.cookie_options) - }) - .catch(err => { return reply(Boom.badData(err.message)) }) - } else { - return reply(Boom.unauthorized('passwords do not match')) - } -} diff --git a/handlers/changeRoleHandler.js b/handlers/changeRoleHandler.js new file mode 100644 index 0000000..94d2e36 --- /dev/null +++ b/handlers/changeRoleHandler.js @@ -0,0 +1,14 @@ +const Boom = require('boom') +const User = require('../models/user.js').User + +module.exports = function (request, reply) { + const newRole = request.payload.role + User.findOneAndUpdate({_id: request.payload.userId}, { role: newRole }) + .then(result => { + delete result._doc.password + return reply(result) + }) + .catch(err => { + return reply(Boom.badData(err.message)) + }) +} diff --git a/handlers/getProfilesHandler.js b/handlers/getProfilesHandler.js index 418fbfc..7402fbc 100644 --- a/handlers/getProfilesHandler.js +++ b/handlers/getProfilesHandler.js @@ -3,8 +3,7 @@ const Boom = require('boom') const User = require('../models/user.js').User module.exports = function (request, reply) { - // const role = request.auth.credentials.role - const role = 'Administrator' + const role = request.auth.credentials.scope switch (role) { case 'Administrator': { User.find({}, { password: 0 }) @@ -17,7 +16,7 @@ module.exports = function (request, reply) { } break case 'MointainDispatcher': { - User.find({role: 'MountainRescuer'}).select('location') + User.find({role: 'MountainRescuer'}, { password: 0 }).select('location') .then(result => { return reply(result) }) diff --git a/handlers/getUserProfile.js b/handlers/getUserProfile.js new file mode 100644 index 0000000..df6d293 --- /dev/null +++ b/handlers/getUserProfile.js @@ -0,0 +1,13 @@ +'use strict' +const Boom = require('boom') +const User = require('../models/user.js').User + +module.exports = function (request, reply) { + User.findOne({_id: request.params.userId}, { password: 0 }) + .then(result => { + return reply(result) + }) + .catch(err => { + return reply(Boom.badData(err.message)) + }) +} diff --git a/handlers/loginFbHandler.js b/handlers/loginFbHandler.js index 0caf577..cccc5da 100644 --- a/handlers/loginFbHandler.js +++ b/handlers/loginFbHandler.js @@ -13,7 +13,7 @@ module.exports = function (request, reply) { .then((np) => { if (!np) return reply(Boom.unauthorized(null, 'Custom')) else { - const token = JWT.sign({ id: np._id, email: np.email, role: np.role }, config.JWT.secret) + const token = JWT.sign({ id: np._id, email: np.email }, config.JWT.secret) return reply({ token: token }) } }) diff --git a/handlers/loginHandler.js b/handlers/loginHandler.js index c9cb6cc..a404e73 100644 --- a/handlers/loginHandler.js +++ b/handlers/loginHandler.js @@ -17,7 +17,7 @@ module.exports = function (request, reply) { .then((np) => { bcrypt.compare(password, np.password, (err, result) => { if (!err && result) { - const token = JWT.sign({ id: np._id, email: np.email, role: np.role }, config.JWT.secret) + const token = JWT.sign({ id: np._id, email: np.email }, config.JWT.secret) return reply({token: token, fullname: np.full_name}) } else if (!result) { return reply(Boom.unauthorized('Wrong username/password')) diff --git a/handlers/registerFbHandler.js b/handlers/registerFbHandler.js index acebf64..5d6ad69 100644 --- a/handlers/registerFbHandler.js +++ b/handlers/registerFbHandler.js @@ -25,7 +25,7 @@ module.exports = function (request, reply) { addUser(request, signUpData) .then((np) => { - const userToken = { token: JWT.sign({ id: np._id, email: np.email, role: np.role }, config.JWT.secret) } + const userToken = { token: JWT.sign({ id: np._id, email: np.email }, config.JWT.secret) } return reply(userToken) }) // .catch((err) => { return reply(Boom.unauthorized(err.message)) }) diff --git a/handlers/registerHandler.js b/handlers/registerHandler.js index f95a4b5..6e7e64a 100644 --- a/handlers/registerHandler.js +++ b/handlers/registerHandler.js @@ -11,7 +11,7 @@ module.exports = function (request, reply) { if (signUpData.password === signUpData.confirmed_password) { addUser(request, signUpData) .then((np) => { - const token = JWT.sign({ id: np._id, email: np.email, role: np.role }, config.JWT.secret) + const token = JWT.sign({ id: np._id, email: np.email }, config.JWT.secret) return reply({token: token, fullname: np.full_name}) }) .catch((err) => { return reply(Boom.unauthorized(err.message)) }) diff --git a/handlers/updateProfileHandler.js b/handlers/updateProfileHandler.js new file mode 100644 index 0000000..41aba7e --- /dev/null +++ b/handlers/updateProfileHandler.js @@ -0,0 +1,29 @@ +'use strict' +const Boom = require('boom') +const JWT = require('jsonwebtoken') +const bcrypt = require('bcrypt') +const config = require('../configs/config') +const User = require('../models/user.js').User + +module.exports = function (request, reply) { + let newProfileData = {} + const query = { email: request.auth.credentials.email } + for (let key of Object.keys(request.payload)) { + newProfileData[key] = request.payload[key] + } + if (newProfileData.email === query.email) { delete newProfileData.email } + if ((newProfileData.new_password) && (newProfileData.new_password === newProfileData.confirmed_password)) { + newProfileData.password = bcrypt.hashSync(newProfileData.new_password, config.SALT_WORK_FACTOR) + delete newProfileData.newPassword + delete newProfileData.confirmedPassword + } + + User.findOneAndUpdate(query, newProfileData) + .then((result) => { + const token = JWT.sign({id: result.id, email: result.email}, config.JWT.secret) + return reply({token: token}).state('session', token, config.cookie_options) + }) + .catch(err => { + return reply(Boom.badData(err.message)) + }) +} diff --git a/lib/requests.js b/lib/requests.js index 3376ecc..8d34563 100644 --- a/lib/requests.js +++ b/lib/requests.js @@ -33,7 +33,6 @@ class Req { }, body: JSON.stringify(obj) } - this.requestData(loginRequest) .then((token) => resolve(token)) .catch((e) => reject(e)) @@ -135,6 +134,41 @@ class Req { }) } + requestUpdateProfile (token, user) { + return new Promise((resolve, reject) => { + let loginRequest = { + method: 'POST', + url: `${this.config.apiURL}/profile/update`, + headers: { + 'content-type': 'application/json', + 'Authentication-Token': `${token.token}` + }, + body: JSON.stringify(user) + } + this.requestData(loginRequest) + .then((result) => resolve(result)) + .catch((e) => reject(e)) + }) + } + + requestChangeRole (adminToken, obj) { + return new Promise((resolve, reject) => { + let loginRequest = { + method: 'POST', + url: `${this.config.apiURL}/set-role`, + headers: { + 'content-type': 'application/json', + 'Authentication-Token': `${adminToken}` + }, + body: JSON.stringify(obj) + } + this.requestData(loginRequest) + .then((result) => resolve(result)) + .catch((e) => reject(e)) + }) + } + + requestPutLocation (token, location) { return new Promise((resolve, reject) => { let locationRequest = { diff --git a/lib/tests.js b/lib/tests.js index d1ee5ff..bac16cf 100644 --- a/lib/tests.js +++ b/lib/tests.js @@ -5,9 +5,9 @@ let req = new Req(config) // == login == let user = { -// email: 'vgenev@dmail.com', - username: 'vgenev', - password: '!23$' + email: 'vvvv@gma.com', + // username: 'vg11', + password: '!234' } // req.requestToken(user).then(r => console.log(r)).catch(e => console.log(e)) @@ -16,13 +16,13 @@ let user = { let newUser = { user: { - first_name: 'Valentin', - last_name: 'Genev', + first_name: 'Admin', + last_name: 'Admin', phone_number: '+359099990', - username: 'vg', - password: '!23$', - confirmed_password: '!23$', - email: 'v12232323g22v@gmail.com', + username: 'user', + password: '!23#', + confirmed_password: '!23#', + email: 'user@gma.com', role: 'User' } // }, @@ -32,7 +32,7 @@ let newUser = { // } } -req.register(newUser).then(r => console.log(r)).catch(e => console.log(e)) +//req.register(newUser).then(r => console.log(r)).catch(e => console.log(e)) // == change location == @@ -76,3 +76,16 @@ let fbUser = { // })) // .catch(e => console.log(e)) +// req.requestToken(user) +// .then(t => req.requestUpdateProfile({token: t.token}, newUser.user) +// .then((res) => { +// console.log(res) +// })) +// .catch(e => console.log(e)) + +const adminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4NTkyZmZiNGY0ZjEzZWVhNTJhYTUxNyIsImVtYWlsIjoiYWRtaW5AZ21hLmNvbSIsImlhdCI6MTQ4MjIzOTk5NX0.Y-gfoG5up7SjSv9bLSd5QcgyTPIuuQ8AYopCiBbtDHc' +const notAdminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4NTkzMzg2MTc4ZGU0ZjQzYmM2NTA4YiIsImVtYWlsIjoidXNlckBnbWEuY29tIiwiaWF0IjoxNDgyMjQwOTAyfQ.k8hJxsLT_wGAVgW2ZIj-fN3h25jzNIhQoke-atvQcPQ' + +req.requestChangeRole(notAdminToken, {userId: "584fc66792ed55114fee44a0", role: 'User'}) +.then(r => console.log(r)) +.catch(e => console.log(e)) diff --git a/lib/validate.js b/lib/validate.js index 380d843..c04a25e 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -5,7 +5,8 @@ module.exports = function (decoded, request, callback) { User.findOne({email: decoded.email}) .then((user) => { if ((decoded.email) === (user.email)) { - return callback(null, true) + decoded.scope = user.role + return callback(null, true, decoded) } else { return callback(null, false) } diff --git a/routes/router.js b/routes/router.js index 3792e01..fd33fd8 100644 --- a/routes/router.js +++ b/routes/router.js @@ -1,62 +1,10 @@ 'use strict' const Joi = require('joi') -const loginHandler = require('../handlers/loginHandler') -const registerHandler = require('../handlers/registerHandler') -const changePasswordHandler = require('../handlers/changePasswordHandler') const putLocationHandler = require('../handlers/putLocationHandler') -const loginFbHandler = require('../handlers/loginFbHandler') -const registerFbHandler = require('../handlers/registerFbHandler') -const getProfilesHandler = require('../handlers/getProfilesHandler') +const changeRoleHandler = require('../handlers/changeRoleHandler') +const userRoutes = require('./userRoutes') -const route = [{ - method: 'POST', - path: '/login', - handler: loginHandler, - config: { - payload: { - output: 'data', - parse: true - }, - auth: false, - validate: { - payload: { - email: Joi.string().email(), - username: Joi.string(), - password: Joi.string().required() - } - } - } -}, - { - method: 'POST', - path: '/register', - config: { - handler: registerHandler, - payload: { - output: 'data', - parse: true - }, - auth: false, - validate: { - payload: { - user: Joi.object({ - email: Joi.string().email().required(), - password: Joi.string().required(), - confirmed_password: Joi.string().required(), - phone_number: Joi.string(), - username: Joi.string(), - role: Joi.string().required().allow('Administrator', 'MointainDispatcher', 'MountainRescuer', 'User'), - first_name: Joi.string(), - last_name: Joi.string() - }), - location: Joi.object({ - lat: Joi.number(), - lng: Joi.number() - }) - } - } - } - }, +const routes = [ { method: 'PUT', path: '/location', @@ -81,86 +29,32 @@ const route = [{ }, { method: 'POST', - path: '/login/facebook', - handler: loginFbHandler, + path: '/set-role', + handler: changeRoleHandler, config: { payload: { output: 'data', parse: true }, - auth: false, - validate: { - payload: { - fb_token: Joi.string().required() - }, - headers: Joi.object({ - 'Content-Type': Joi.string().regex(/(application\/json)/) - }).unknown() - } - } - }, - { - method: 'POST', - path: '/register/facebook', - config: { - handler: registerFbHandler, - payload: { - output: 'data', - parse: true + auth: { + strategies: ['jwt'], + scope: ['Administrator'] }, - auth: false, validate: { payload: { - fb_token: Joi.string().token().required(), - role: Joi.string().required().allow('Administrator', 'MointainDispatcher', 'MountainRescuer', 'User'), - location: Joi.object({ - lat: Joi.number(), - lng: Joi.number() - }) + userId: Joi.string().required(), + role: Joi.string().required().allow('Administrator', 'MointainDispatcher', 'MountainRescuer', 'User') }, headers: Joi.object({ 'Content-Type': Joi.string().regex(/(application\/json)/) }).unknown() } } - }, - { - method: 'GET', - path: '/profiles', - config: { - auth: 'jwt', - handler: getProfilesHandler, - validate: { - headers: Joi.object({ - 'Authentication-Token': Joi.string().token() - }).unknown() - } - } - }, - { - method: 'POST', - path: '/changepass', - config: { - handler: changePasswordHandler, - payload: { - output: 'data', - parse: true - }, - auth: 'jwt', - validate: { - payload: { - password: Joi.string().required(), - confirmed_password: Joi.string().required() - }, - headers: Joi.object({ - 'Content-Type': Joi.string().regex(/(application\/json)/), - 'Authentication-Token': Joi.string().token() - }).unknown() - } - } } ] +let route = routes.concat(userRoutes.userRoute) + module.exports = { route: route } diff --git a/routes/userRoutes.js b/routes/userRoutes.js new file mode 100644 index 0000000..5d618b9 --- /dev/null +++ b/routes/userRoutes.js @@ -0,0 +1,162 @@ +const Joi = require('joi') +const loginHandler = require('../handlers/loginHandler') +const registerHandler = require('../handlers/registerHandler') +const loginFbHandler = require('../handlers/loginFbHandler') +const registerFbHandler = require('../handlers/registerFbHandler') +const updateProfileHandler = require('../handlers/updateProfileHandler') +const getProfilesHandler = require('../handlers/getProfilesHandler') +const getUserProfile = require('../handlers/getUserProfile') + +const UserRoutes = [ + { + method: 'POST', + path: '/login', + handler: loginHandler, + config: { + payload: { + output: 'data', + parse: true + }, + auth: false, + validate: { + payload: { + email: Joi.string().email(), + username: Joi.string(), + password: Joi.string().required() + } + } + } + }, + { + method: 'POST', + path: '/register', + config: { + handler: registerHandler, + payload: { + output: 'data', + parse: true + }, + auth: false, + validate: { + payload: { + user: Joi.object({ + email: Joi.string().email().required(), + password: Joi.string().required(), + confirmed_password: Joi.string().required(), + phone_number: Joi.string(), + username: Joi.string(), + role: Joi.string().required().allow('Administrator', 'MointainDispatcher', 'MountainRescuer', 'User'), + first_name: Joi.string(), + last_name: Joi.string() + }), + location: Joi.object({ + lat: Joi.number(), + lng: Joi.number() + }) + } + } + } + }, + { + method: 'POST', + path: '/login/facebook', + handler: loginFbHandler, + config: { + payload: { + output: 'data', + parse: true + }, + auth: false, + validate: { + payload: { + fb_token: Joi.string().required() + }, + headers: Joi.object({ + 'Content-Type': Joi.string().regex(/(application\/json)/) + }).unknown() + } + } + }, + { + method: 'POST', + path: '/register/facebook', + config: { + handler: registerFbHandler, + payload: { + output: 'data', + parse: true + }, + auth: false, + validate: { + payload: { + fb_token: Joi.string().token().required(), + role: Joi.string().required().allow('Administrator', 'MointainDispatcher', 'MountainRescuer', 'User'), + location: Joi.object({ + lat: Joi.number(), + lng: Joi.number() + }) + }, + headers: Joi.object({ + 'Content-Type': Joi.string().regex(/(application\/json)/) + }).unknown() + } + } + }, + { + method: 'GET', + path: '/profile/{userId}', + config: { + auth: 'jwt', + handler: getUserProfile, + validate: { + headers: Joi.object({ + 'Authentication-Token': Joi.string().token() + }).unknown() + } + } + }, + { + method: 'POST', + path: '/profile/update', + config: { + handler: updateProfileHandler, + payload: { + output: 'data', + parse: true + }, + auth: 'jwt', + validate: { + payload: { + email: Joi.string().email(), + new_password: Joi.string(), + confirmed_password: Joi.string(), + phone_number: Joi.string(), + username: Joi.string(), + first_name: Joi.string(), + last_name: Joi.string() + }, + headers: Joi.object({ + 'Content-Type': Joi.string().regex(/(application\/json)/), + 'Authentication-Token': Joi.string().token() + }).unknown() + } + } + }, + { + method: 'GET', + path: '/profiles', + config: { + auth: 'jwt', + handler: getProfilesHandler, + validate: { + headers: Joi.object({ + 'Authentication-Token': Joi.string().token() + }).unknown() + } + } + } +] + +module.exports = { + userRoute: UserRoutes +} From 38fa3c94a826cffa4bbf6cc493ed61f06272aee0 Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Tue, 3 Jan 2017 23:14:17 +0200 Subject: [PATCH 08/10] test the keys --- lib/tests.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/tests.js b/lib/tests.js index bac16cf..c7abe92 100644 --- a/lib/tests.js +++ b/lib/tests.js @@ -5,9 +5,9 @@ let req = new Req(config) // == login == let user = { - email: 'vvvv@gma.com', + email: 'admin@gmail.com', // username: 'vg11', - password: '!234' + password: '!23#' } // req.requestToken(user).then(r => console.log(r)).catch(e => console.log(e)) @@ -17,13 +17,13 @@ let user = { let newUser = { user: { first_name: 'Admin', - last_name: 'Admin', + last_name: 'GENEV', phone_number: '+359099990', - username: 'user', - password: '!23#', - confirmed_password: '!23#', - email: 'user@gma.com', - role: 'User' +// username: 'admin', + // password: '!23#', + // confirmed_password: '!23#', + email: 'addddddd@gmail.com', +// role: 'Administrator' } // }, // location: { @@ -32,7 +32,7 @@ let newUser = { // } } -//req.register(newUser).then(r => console.log(r)).catch(e => console.log(e)) + //req.register(newUser).then(r => console.log(r)).catch(e => console.log(e)) // == change location == @@ -76,16 +76,16 @@ let fbUser = { // })) // .catch(e => console.log(e)) -// req.requestToken(user) -// .then(t => req.requestUpdateProfile({token: t.token}, newUser.user) -// .then((res) => { -// console.log(res) -// })) -// .catch(e => console.log(e)) +req.requestToken(user) + .then(t => req.requestUpdateProfile({token: t.token}, newUser.user) + .then((res) => { + console.log(res) + })) + .catch(e => console.log(e)) -const adminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4NTkyZmZiNGY0ZjEzZWVhNTJhYTUxNyIsImVtYWlsIjoiYWRtaW5AZ21hLmNvbSIsImlhdCI6MTQ4MjIzOTk5NX0.Y-gfoG5up7SjSv9bLSd5QcgyTPIuuQ8AYopCiBbtDHc' +const adminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4NTkzNWFjNDdiYzA0ZjY3NzEyNDAwZiIsImVtYWlsIjoiYWRtaW5AZ21haWwuY29tIiwiaWF0IjoxNDgyMjQxNDUyfQ.9pNH79kPHN-MB0KznqtnY2ANVfubUPlS60QiCaDm8kc' const notAdminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4NTkzMzg2MTc4ZGU0ZjQzYmM2NTA4YiIsImVtYWlsIjoidXNlckBnbWEuY29tIiwiaWF0IjoxNDgyMjQwOTAyfQ.k8hJxsLT_wGAVgW2ZIj-fN3h25jzNIhQoke-atvQcPQ' -req.requestChangeRole(notAdminToken, {userId: "584fc66792ed55114fee44a0", role: 'User'}) -.then(r => console.log(r)) -.catch(e => console.log(e)) +// req.requestChangeRole(adminToken, {userId: "585934da31286ff5722e2bb2", role: 'Administrator'}) +// .then(r => console.log(r)) +// .catch(e => console.log(e)) From a861945ab09121a84c52ed450237490901fdfb33 Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Tue, 3 Jan 2017 23:16:26 +0200 Subject: [PATCH 09/10] test commit --- configs/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/config.js b/configs/config.js index 2c2ca86..faca8b7 100644 --- a/configs/config.js +++ b/configs/config.js @@ -1,4 +1,5 @@ 'use strict' +// this is just a test module.exports = { goodOptions: { From 08ad1d77d6ccd062b6f7ed91ce6df5ee28e29d82 Mon Sep 17 00:00:00 2001 From: Valentin Genev Date: Wed, 25 Jan 2017 13:26:27 +0200 Subject: [PATCH 10/10] change location model --- handlers/putLocationHandler.js | 20 ++++++++++++--- lib/tests.js | 46 ++++++++++++++++++---------------- models/location.js | 35 ++++++++++++++++++++++++++ models/user.js | 16 ++++-------- routes/router.js | 4 ++- 5 files changed, 83 insertions(+), 38 deletions(-) create mode 100644 models/location.js diff --git a/handlers/putLocationHandler.js b/handlers/putLocationHandler.js index 9ae08fd..14a9666 100644 --- a/handlers/putLocationHandler.js +++ b/handlers/putLocationHandler.js @@ -1,14 +1,26 @@ 'use strict' const Boom = require('boom') -const User = require('../models/user.js').User +const Location = require('../models/location.js').Location module.exports = function (request, reply) { const userId = request.auth.credentials.id const location = request.payload - - User.findOneAndUpdate({_id: userId}, {location: {lat: location.lat, lng: location.lng}}) + let updateValue = { + lat: location.lat, + lng: location.lng, + altitude: location.altitude + } + const options = { + upsert: true, + setDefaultsOnInsert: true + } + location.coordinateAccuracy ? (updateValue.coordinateAccuracy = location.coordinateAccuracy) : {} + Location.findOneAndUpdate({ + _user: userId + }, updateValue, options) .then((result) => { - return reply({location: location}) + if (result) return reply({location: location}) + else return reply(Boom.badData('User has no initial location')) }) .catch(err => { return reply(Boom.badData(err.message)) diff --git a/lib/tests.js b/lib/tests.js index c7abe92..8c83da4 100644 --- a/lib/tests.js +++ b/lib/tests.js @@ -5,8 +5,8 @@ let req = new Req(config) // == login == let user = { - email: 'admin@gmail.com', - // username: 'vg11', +// email: 'admin@gmail.com', + username: 'vg11', password: '!23#' } @@ -16,14 +16,14 @@ let user = { let newUser = { user: { - first_name: 'Admin', + first_name: 'GOGO', last_name: 'GENEV', phone_number: '+359099990', -// username: 'admin', - // password: '!23#', - // confirmed_password: '!23#', - email: 'addddddd@gmail.com', -// role: 'Administrator' + username: 'vg11', + password: '!23#', + confirmed_password: '!23#', + email: 'gogogenev@gmail.com', + role: 'User' } // }, // location: { @@ -32,22 +32,24 @@ let newUser = { // } } - //req.register(newUser).then(r => console.log(r)).catch(e => console.log(e)) +//req.register(newUser).then(r => console.log(r)).catch(e => console.log(e)) // == change location == let location = { lat: 51.232, - lng: 22.212 + lng: 22.212, + altitude: 145, + coordinateAccuracy: 3 } // normal login -// req.requestToken(user) -// .then(t => req.requestPutLocation({token: t.token}, location) -// .then((res) => { -// console.log(res) -// })) -// .catch(e => console.log(e)) +req.requestToken(user) + .then(t => req.requestPutLocation({token: t.token}, location) + .then((res) => { + console.log(res) + })) + .catch(e => console.log(e)) // fb login // req.requestFbLogin(fbToken) @@ -76,12 +78,12 @@ let fbUser = { // })) // .catch(e => console.log(e)) -req.requestToken(user) - .then(t => req.requestUpdateProfile({token: t.token}, newUser.user) - .then((res) => { - console.log(res) - })) - .catch(e => console.log(e)) +// req.requestToken(user) +// .then(t => req.requestUpdateProfile({token: t.token}, newUser.user) +// .then((res) => { +// console.log(res) +// })) +// .catch(e => console.log(e)) const adminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4NTkzNWFjNDdiYzA0ZjY3NzEyNDAwZiIsImVtYWlsIjoiYWRtaW5AZ21haWwuY29tIiwiaWF0IjoxNDgyMjQxNDUyfQ.9pNH79kPHN-MB0KznqtnY2ANVfubUPlS60QiCaDm8kc' const notAdminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4NTkzMzg2MTc4ZGU0ZjQzYmM2NTA4YiIsImVtYWlsIjoidXNlckBnbWEuY29tIiwiaWF0IjoxNDgyMjQwOTAyfQ.k8hJxsLT_wGAVgW2ZIj-fN3h25jzNIhQoke-atvQcPQ' diff --git a/models/location.js b/models/location.js new file mode 100644 index 0000000..eb7d998 --- /dev/null +++ b/models/location.js @@ -0,0 +1,35 @@ +'use strict' + +const Mongoose = require('../lib/database').Mongoose +const db = require('../lib/database').db + +const locationSchema = new Mongoose.Schema({ + lat: Number, + lng: Number, + altitude: { + type: Number, + validate: { + validator: Number.isInteger, + message: '{VALUE} is not an integer value' + } + }, + coordinateAccuracy: { + type: Number, + validate: { + validator: Number.isInteger, + message: '{VALUE} is not an integer value' + } + }, + _user: { + type: Mongoose.Schema.ObjectId, + ref: 'User' + } +}, { + timestamps: true +}) + +const Location = db.model('Location', locationSchema, 'Locations') + +module.exports = { + Location: Location +} diff --git a/models/user.js b/models/user.js index b93aedb..c293a7b 100644 --- a/models/user.js +++ b/models/user.js @@ -6,6 +6,9 @@ const Mongoose = require('../lib/database').Mongoose const db = require('../lib/database').db const roles = ['Administrator', 'MointainDispatcher', 'MountainRescuer', 'User'] +const locationModel = { +} + const userSchema = new Mongoose.Schema({ first_name: { type: String }, last_name: { type: String }, @@ -13,7 +16,7 @@ const userSchema = new Mongoose.Schema({ username: { type: String, index: true, unique: true, sparse: true }, password: { type: String }, email: { type: String, unique: true, required: true }, - location: {lat: Number, lng: Number}, + location: locationModel, role: {type: String, enum: roles}, fb_linked: {type: String}, google_linked: {type: String}, @@ -24,19 +27,10 @@ userSchema.virtual('full_name').get(function () { return `${this.first_name} ${this.last_name}` }) -// const locationSchema = new Mongoose.Schema({ -// lat: {type: Number, required: true}, -// lng: {type: Number, required: true}, -// last_seen: {type: Date, default: Date.now}, -// _user: {type: Number, ref: 'User'} -// }) - -// const Location = db.model('Location', locationSchema, 'Locations') const User = db.model('User', userSchema, 'Users') module.exports = { User: User, - Roles: roles // , - // Location: Location + Roles: roles } diff --git a/routes/router.js b/routes/router.js index fd33fd8..cd1a6e0 100644 --- a/routes/router.js +++ b/routes/router.js @@ -18,7 +18,9 @@ const routes = [ validate: { payload: { lat: Joi.number().required(), - lng: Joi.number().required() + lng: Joi.number().required(), + altitude: Joi.number().integer().required(), + coordinateAccuracy: Joi.number().integer().optional() }, headers: Joi.object({ 'Content-Type': Joi.string().regex(/(application\/json)/),