From 9e8c8b5541dc44af5626724e6d53e30b78df96ae Mon Sep 17 00:00:00 2001 From: Araan Branco Date: Fri, 10 Mar 2017 07:53:01 -0300 Subject: [PATCH] Add more stuff --- controllers/auth.js | 20 ++++++++++++++++++ controllers/user.js | 15 ++++++++++++++ helpers/bcrypt.js | 13 ++++++++++++ helpers/jwt.js | 14 +++++++++++++ index.js | 4 ++++ models/index.js | 2 +- models/schemas/user.js | 5 +++-- models/user.js | 25 ++++++++++++++++++++++ package.json | 5 +++++ plugins/auth.js | 47 ++++++++++++++++++++++++++++++++++++++++++ routes/auth.js | 22 ++++++++++++++++++++ routes/user.js | 26 +++++++++++++++++++++++ 12 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 controllers/auth.js create mode 100644 controllers/user.js create mode 100644 helpers/bcrypt.js create mode 100644 helpers/jwt.js create mode 100644 plugins/auth.js create mode 100644 routes/auth.js create mode 100644 routes/user.js diff --git a/controllers/auth.js b/controllers/auth.js new file mode 100644 index 0000000..08923e9 --- /dev/null +++ b/controllers/auth.js @@ -0,0 +1,20 @@ + +import Boom from 'boom'; +import * as db from '../helpers/db'; + +export const login = async(request, reply) => { + const { User } = db.connect('sofanerd').connection.models; + let { username, password } = request.payload; + let user = await User.findOne({ + username + }); + + if(!user || !await user.checkPassword(password)) { + return reply(Boom.unauthorized('Credentials invalid!')); + } + + reply({ + token: user.getToken(), + user + }); +}; \ No newline at end of file diff --git a/controllers/user.js b/controllers/user.js new file mode 100644 index 0000000..e6b250f --- /dev/null +++ b/controllers/user.js @@ -0,0 +1,15 @@ + +import Boom from 'boom'; +import * as db from '../helpers/db'; + +export const create = async(request, reply) => { + const { User } = db.connect('sofanerd').connection.models; + + let user = new User(request.payload); + user = await user.save(); + + reply({ + token: user.getToken(), + user + }); +}; \ No newline at end of file diff --git a/helpers/bcrypt.js b/helpers/bcrypt.js new file mode 100644 index 0000000..4aee542 --- /dev/null +++ b/helpers/bcrypt.js @@ -0,0 +1,13 @@ + +import bcrypt from 'bcrypt'; +import Bluebird from 'bluebird'; + +// Generate Hash with Bcrypt +export const hash = (value) => bcrypt.hashSync(value, process.env.NODE_BCRYPT_SALT); + +// Compare hashs passwords +export const compare = async (password, hash) => { + console.log(password, hash); + let compareAsync = Bluebird.promisify(bcrypt.compare); + return await compareAsync(password, hash); +}; \ No newline at end of file diff --git a/helpers/jwt.js b/helpers/jwt.js new file mode 100644 index 0000000..5654579 --- /dev/null +++ b/helpers/jwt.js @@ -0,0 +1,14 @@ + +import jwt from 'jsonwebtoken'; + +export const generate = (payload, audience) => { + let aud = audience ? audience : 'api'; + return jwt.sign(payload, process.env.NODE_JWT_SALT, { + algorithm: 'HS256', + audience: aud + }); +}; + +export const decode = (token) => jwt.decode(token, { complete: true }); + +export const verify = (token, options) => jwt.verify(token, process.env.NODE_JWT_SALT, options); \ No newline at end of file diff --git a/index.js b/index.js index 1024395..113ca9a 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,9 @@ import Hapi from 'hapi'; import dotenv from 'dotenv'; import Debug from 'debug'; +import JWT from 'hapi-auth-jwt2'; import Models from './models'; +import Auth from './plugins/auth'; import Routes from './plugins/routes'; dotenv.config(); @@ -19,6 +21,8 @@ server.connection({ }); let initializer = [ + JWT, + Auth, Models, Routes ]; diff --git a/models/index.js b/models/index.js index ed63a4d..0c8994f 100644 --- a/models/index.js +++ b/models/index.js @@ -7,7 +7,7 @@ const debug = Debug('sofanerd.models'); const register = (server, options, next) => { const dbConenction = db.connect('sofanerd').connection; - debug('Models initialize'); + debug('Inicialize Models'); require('./movie').model(dbConenction); require('./serie').model(dbConenction); require('./user').model(dbConenction); diff --git a/models/schemas/user.js b/models/schemas/user.js index 28511d9..8302f56 100644 --- a/models/schemas/user.js +++ b/models/schemas/user.js @@ -1,5 +1,6 @@ import mongoose from 'mongoose'; +import { hash } from '../../helpers/bcrypt'; const Schema = mongoose.Schema; @@ -7,8 +8,8 @@ const schema = new Schema({ username: { type: String }, name: { type: String }, email: { type: String }, - password: { type: String }, - roles: { type: Array, default: ['user', 'authenticated'] }, + password: { type: String, set: hash }, + roles: { type: Array, default: ['user'] }, favorites: [], collections: [], reactions: [{ diff --git a/models/user.js b/models/user.js index fe35cca..2572ea3 100644 --- a/models/user.js +++ b/models/user.js @@ -1,9 +1,34 @@ import Debug from 'debug'; +import Boom from 'boom'; +import _ from 'lodash'; import schema from './schemas/user'; +import * as jwt from '../helpers/jwt'; +import { compare } from '../helpers/bcrypt'; const debug = Debug('sofanerd.models.user'); export const model = (connection) => { + + // Generate Token JWT for the user + schema.methods.getToken = function({ aud = 'api' } = {}) { + let data = _.pick(this.toObject(), '_id'); + return jwt.generate(data, aud); + }; + + // Check Password + schema.methods.checkPassword = async function(password) { + if(!this.password) { + throw Boom.badRequest('User not have password'); + } + + const comparePassword = await compare(password, this.password); + if(!comparePassword) { + throw Boom.badRequest('Password not match'); + } + + return true; + }; + return connection.model('User', schema); }; \ No newline at end of file diff --git a/package.json b/package.json index 2a5bc0e..5009403 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,17 @@ "dependencies": { "babel-cli": "^6.23.0", "babel-preset-latest": "^6.22.0", + "bcrypt": "^1.0.2", "bluebird": "^3.5.0", + "boom": "^4.2.0", "debug": "^2.6.1", "dotenv": "^4.0.0", "glob": "^7.1.1", "hapi": "^16.1.0", + "hapi-auth-jwt2": "^7.2.4", "joi": "^10.2.2", + "jsonwebtoken": "^7.3.0", + "lodash": "^4.17.4", "mongoose": "^4.8.6" } } diff --git a/plugins/auth.js b/plugins/auth.js new file mode 100644 index 0000000..79d32fe --- /dev/null +++ b/plugins/auth.js @@ -0,0 +1,47 @@ + +import Debug from 'debug'; +import Boom from 'boom'; +import * as db from '../helpers/db'; + +const debug = Debug('sofanerd.authStrategy'); + +const register = (server, options, next) => { + try { + debug('Inicialize plugins'); + + server.auth.strategy('token-api', 'jwt', { + key: process.env.NODE_JWT_SALT, + async validateFunc(decoded, request, reply) { + const { User } = db.connect('sofanerd').connection.models; + + let user = await User.findOne({ + _id: decoded._id + }); + + if(!user) { + return reply(Boom.notFound('User not found'), false); + } else { + return reply(null, true, user); + } + }, + verifyOptions: { + algorithms: ['HS256'], + audience: 'api' + } + }); + + debug('Plugins registered'); + next(); + } catch (err) { + debug(err); + throw err; + } +}; + +register.attributes = { + name: 'auth' +}; + +export default { + register +}; \ No newline at end of file diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..667df6f --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,22 @@ + +import Debug from 'debug'; +import Joi from 'joi'; +import * as Controller from '../controllers/auth'; + +const debug = Debug('sofanerd.routes.auth'); + +export default [ + { + method: 'POST', + path: '/api/auth', + config: { + validate: { + payload: { + username: Joi.string().trim().lowercase().required(), + password: Joi.string().trim().token().min(5).max(40).required() + } + } + }, + handler: Controller.login + } +]; \ No newline at end of file diff --git a/routes/user.js b/routes/user.js new file mode 100644 index 0000000..b32fae3 --- /dev/null +++ b/routes/user.js @@ -0,0 +1,26 @@ + +import Debug from 'debug'; +import Joi from 'joi'; +import * as Controller from '../controllers/user'; + +const debug = Debug('sofanerd.routes.auth'); + +const userCreatePayloadValidation = Joi.object({ + username: Joi.string().trim().lowercase().min(3).max(20).required(), + name: Joi.string().min(1).max(50).required(), + email: Joi.string().email().min(1).max(40).required(), + password: Joi.string().token().min(6).max(40).required() +}); + +export default [ + { + method: 'POST', + path: '/api/user', + config: { + validate: { + payload: userCreatePayloadValidation + } + }, + handler: Controller.create + } +]; \ No newline at end of file