From b782f379c5d6c30d2a93fc7c96ebe3721ceafeda Mon Sep 17 00:00:00 2001 From: kagabof Date: Thu, 25 Jul 2019 15:09:11 +0200 Subject: [PATCH] - Added model permission. - Added contoller for the admin to create a role. - Added middleware to check if user is admin. - Added test for role. - Added role in the token. - Changed role data type in the user model. --- package.json | 2 +- src/controllers/authController.js | 3 +- src/controllers/helpers/validateRoles.js | 3 ++ src/controllers/rolesController.js | 19 +++++++ src/controllers/userController.js | 9 ++-- src/middlewares/roleCheck.js | 7 +++ src/migrations/20190624-create-userschema.js | 2 +- src/migrations/20190703214859-create-user.js | 2 +- .../20190725090430-create-permissions.js | 12 +++++ src/models/Permissions.js | 11 ++++ src/models/User.js | 4 +- src/routes/index.js | 3 ++ src/routes/roleRoutes.js | 12 +++++ src/seeders/20190703161356-users.js | 5 +- src/seeders/20190725062644-initUsers.js | 11 ++++ src/test/mock/users.js | 5 +- src/test/roleTest.js | 52 +++++++++++++++++++ src/test/user.js | 2 +- 18 files changed, 151 insertions(+), 13 deletions(-) create mode 100644 src/controllers/helpers/validateRoles.js create mode 100644 src/controllers/rolesController.js create mode 100644 src/middlewares/roleCheck.js create mode 100644 src/migrations/20190725090430-create-permissions.js create mode 100644 src/models/Permissions.js create mode 100644 src/routes/roleRoutes.js create mode 100644 src/seeders/20190725062644-initUsers.js create mode 100644 src/test/roleTest.js diff --git a/package.json b/package.json index b7a4a33..be44899 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "start": "npm run create && NODE_ENV=staging babel-node ./src/index.js", "dev": "nodemon --exec babel-node ./src/index.js", "create": "NODE_ENV=staging sequelize db:migrate", - "migrate": "sequelize db:migrate:undo:all && sequelize db:migrate", + "migrate": "sequelize db:migrate:undo:all && sequelize db:migrate && sequelize db:seed:all", "seqinit": "sequelize init", "build": "npm run clear && babel src --out-dir build", "clear": "rm -rf build", diff --git a/src/controllers/authController.js b/src/controllers/authController.js index 5511326..eb865c5 100644 --- a/src/controllers/authController.js +++ b/src/controllers/authController.js @@ -24,7 +24,8 @@ export default class AuthController { const payload = { username: user.username, id: user.id, - email: user.email }; + email: user.email, + roles: user.roles }; const token = await generateToken(payload); return res.status(200).json({ message: 'Logged in successfully', diff --git a/src/controllers/helpers/validateRoles.js b/src/controllers/helpers/validateRoles.js new file mode 100644 index 0000000..c3209ef --- /dev/null +++ b/src/controllers/helpers/validateRoles.js @@ -0,0 +1,3 @@ +const validateRoles = data => (data.permissions && data.role && data.tablesAllowed) +&& data; +export default validateRoles; diff --git a/src/controllers/rolesController.js b/src/controllers/rolesController.js new file mode 100644 index 0000000..8faa9ae --- /dev/null +++ b/src/controllers/rolesController.js @@ -0,0 +1,19 @@ +/* eslint-disable require-jsdoc */ +import model from '../models'; + +const { Permissions } = model; + +class PermitionsController { + static async createRole(req, res) { + try { + const permissions = req.body.permissions.split(','); + const tablesAllowed = req.body.tablesAllowed.split(','); + const role = await Permissions.create({ role: req.body.role, permissions, tablesAllowed }); + return role && res.status(201).json({ message: 'Role successfully created' }); + } catch (error) { + return res.status(400).json({ error: 'tableAllowed, role, and permissions are required ' }); + } + } +} + +export default PermitionsController; diff --git a/src/controllers/userController.js b/src/controllers/userController.js index 060493d..2ca0be4 100644 --- a/src/controllers/userController.js +++ b/src/controllers/userController.js @@ -29,12 +29,14 @@ export default class UserController { const user = await User.create({ username, email, password: hashedPasword, - verified }); + verified, + roles: ['user'] }); const payload = { username: user.username, id: user.id, email: user.email, - verified: user.verified }; + verified: user.verified, + roles: user.roles }; const token = await generateToken(payload); const mailSend = await MailSender.sendMail(user.email, user.username, token); @@ -60,7 +62,8 @@ export default class UserController { const payload = { username: user.username, userId: user.id, - email: user.email }; + email: user.email, + role: user.role }; const token = await generateToken(payload); // @sends a message to an existing email in our database with the below email template const message = `
You are receiving this because you (or someone else) requested the reset of your password.
diff --git a/src/middlewares/roleCheck.js b/src/middlewares/roleCheck.js new file mode 100644 index 0000000..4bb3e05 --- /dev/null +++ b/src/middlewares/roleCheck.js @@ -0,0 +1,7 @@ + +const checkAdmin = (req, res, next) => ( + req.user.roles.includes('admin') + ? next() + : res.status(403).json({ message: 'Not allowed to perform the action' })); + +export default checkAdmin; diff --git a/src/migrations/20190624-create-userschema.js b/src/migrations/20190624-create-userschema.js index 3218574..d6a4fb8 100644 --- a/src/migrations/20190624-create-userschema.js +++ b/src/migrations/20190624-create-userschema.js @@ -34,6 +34,6 @@ gender: { type: Sequelize.STRING, allowNull: true }, provider: { type: Sequelize.STRING, allowNull: true }, -role: { type: Sequelize.STRING, +roles: { type: Sequelize.ARRAY(Sequelize.STRING), allowNull: true }, }), down: queryInterface => queryInterface.dropTable('User') }; diff --git a/src/migrations/20190703214859-create-user.js b/src/migrations/20190703214859-create-user.js index 3218574..d6a4fb8 100644 --- a/src/migrations/20190703214859-create-user.js +++ b/src/migrations/20190703214859-create-user.js @@ -34,6 +34,6 @@ gender: { type: Sequelize.STRING, allowNull: true }, provider: { type: Sequelize.STRING, allowNull: true }, -role: { type: Sequelize.STRING, +roles: { type: Sequelize.ARRAY(Sequelize.STRING), allowNull: true }, }), down: queryInterface => queryInterface.dropTable('User') }; diff --git a/src/migrations/20190725090430-create-permissions.js b/src/migrations/20190725090430-create-permissions.js new file mode 100644 index 0000000..9df2f88 --- /dev/null +++ b/src/migrations/20190725090430-create-permissions.js @@ -0,0 +1,12 @@ +module.exports = { up: (queryInterface, Sequelize) => queryInterface.createTable('Permissions', { id: { allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER }, +role: { type: Sequelize.STRING }, +permissions: { allowNull: false, type: Sequelize.ARRAY(Sequelize.STRING) }, +tablesAllowed: { allowNull: false, type: Sequelize.ARRAY(Sequelize.STRING) }, +createdAt: { allowNull: false, + type: Sequelize.DATE }, +updatedAt: { allowNull: false, + type: Sequelize.DATE } }), +down: (queryInterface, Sequelize) => queryInterface.dropTable('Permissions') }; diff --git a/src/models/Permissions.js b/src/models/Permissions.js new file mode 100644 index 0000000..b245097 --- /dev/null +++ b/src/models/Permissions.js @@ -0,0 +1,11 @@ + + +module.exports = (sequelize, DataTypes) => { + const Permissions = sequelize.define('Permissions', { role: { type: DataTypes.STRING, + allowNull: false }, + tablesAllowed: { type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: false }, + permissions: { type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: false } }, {}); + return Permissions; +}; diff --git a/src/models/User.js b/src/models/User.js index e48b6fc..311787b 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -1,4 +1,4 @@ -export default (sequelize) => { +export default (sequelize, DataTypes) => { const User = sequelize.define('User', { username: { type: String, unique: true, required: true }, firstName: { allowNull: true, type: String }, @@ -15,7 +15,7 @@ export default (sequelize) => { dateOfBirth: { allowNull: true, type: Date }, gender: { type: String }, provider: { type: String }, - role: { type: String } }, + roles: { type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true } }, { timestamps: true, tableName: 'User' }); User.associate = (models) => { diff --git a/src/routes/index.js b/src/routes/index.js index 7a3acb9..f7ab177 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -17,11 +17,13 @@ import socialAPIRoute from './socialAPIRoute'; import optinAndOptOut from './optinAndOptOut'; import bookmarkRoute from './bookmarksRoutes'; import readerStatsRoute from './readerStatsRoute'; +import role from './roleRoutes'; const { verifyToken } = Auth; const router = express.Router(); +router.use('/api', role); router.use('/api', bookmarkRoute); // article routes @@ -50,4 +52,5 @@ router.patch('/api/users/verification/:userToken', UserController.verifyUser); router.patch('/api/users/:username', verifyToken, bodyValidation, usernameAvailability, UserProfile.updateProfile); router.get('/api/users/', verifyToken, UserProfile.getAllUser); + export default router; diff --git a/src/routes/roleRoutes.js b/src/routes/roleRoutes.js new file mode 100644 index 0000000..8d2ebfa --- /dev/null +++ b/src/routes/roleRoutes.js @@ -0,0 +1,12 @@ +import express from 'express'; +import Auth from '../middlewares/Auth'; +import checkAdmin from '../middlewares/roleCheck'; +import roles from '../controllers/rolesController'; + +const role = express.Router(); + +const { verifyToken } = Auth; + +role.post('/role', verifyToken, checkAdmin, roles.createRole); + +export default role; diff --git a/src/seeders/20190703161356-users.js b/src/seeders/20190703161356-users.js index 0e64d2f..94fd3bd 100644 --- a/src/seeders/20190703161356-users.js +++ b/src/seeders/20190703161356-users.js @@ -2,9 +2,10 @@ module.exports = { up: queryInterface => queryInterface.bulkInsert('User', [{ us firstName: 'john', lastName: 'Doe', email: 'michael.nzube@andela.com', - password: 'hashing', + password: 'process.env.JAWANS_ADMIN', following: true, bio: 'bios', - image: 'image' }], {}), + image: 'image', + roles: ['user'] }], {}), down: queryInterface => queryInterface.bulkDelete('User', null, {}) }; diff --git a/src/seeders/20190725062644-initUsers.js b/src/seeders/20190725062644-initUsers.js new file mode 100644 index 0000000..87b8fbc --- /dev/null +++ b/src/seeders/20190725062644-initUsers.js @@ -0,0 +1,11 @@ +module.exports = { up: queryInterface => queryInterface.bulkInsert('User', [{ username: 'john', + firstName: 'kagabo', + lastName: 'prince', + email: 'faustin.kagabo@andela.com', + password: '$2b$12$ohLYwcyFvN9o/fnRSd4G1.vcdNvt6SDJpiyTpOxiz38Y/wG4hNeza', + following: true, + bio: 'bios', + image: 'image', + roles: ['admin'] }], {}), + +down: queryInterface => queryInterface.bulkDelete('User', null, {}) }; diff --git a/src/test/mock/users.js b/src/test/mock/users.js index 5973bca..9c6e19e 100644 --- a/src/test/mock/users.js +++ b/src/test/mock/users.js @@ -7,4 +7,7 @@ user2: { firstName: 'james', lastName: 'bond', email: 'jamesbond@ahjawans.com', username: 'james_bond', - password: 'James@12345', } }; + password: 'James@12345', }, +userAdmin: { username: 'john', + password: 'Kagabo1@', + email: 'faustin.kagabo@andela.com' } }; diff --git a/src/test/roleTest.js b/src/test/roleTest.js new file mode 100644 index 0000000..a779176 --- /dev/null +++ b/src/test/roleTest.js @@ -0,0 +1,52 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import app from '../index'; +import mock from './mock/users'; + +chai.use(chaiHttp); +chai.should(); +let tokenGen; +describe('role', () => { + it('admin signin', (done) => { + chai.request(app) + .post('/api/users/login') + .send(mock.userAdmin) + .end((req, res) => { + // eslint-disable-next-line prefer-destructuring + res.should.have.status(200); + res.body.data.should.have.property('token'); + tokenGen = res.body.data.token; + done(); + }); + }); + + it('admin should create a role', (done) => { + const role = { tablesAllowed: 'Articles,User', + role: 'admin', + permissions: 'GET,DELETE' }; + chai.request(app) + .post('/api/role') + .set('token', tokenGen) + .send(role) + .end((req, res) => { + res.should.have.status(201); + res.body.should.have.property('message'); + done(); + }); + }); + + it('should not create a role if user is not an admin', (done) => { + const role = { tablesAllowed: 'Articles,User', + role: 'admin', + permissions: 'GET,DELETE' }; + chai.request(app) + .post('/api/role') + .set('token', `${tokenGen}dgfwe`) + .send(role) + .end((req, res) => { + res.should.have.status(401); + res.body.should.have.property('error'); + done(); + }); + }); +}); diff --git a/src/test/user.js b/src/test/user.js index fd60cd2..78c375a 100644 --- a/src/test/user.js +++ b/src/test/user.js @@ -354,7 +354,7 @@ describe('User Profile view amend', () => { chai.request(app) .patch(`/api/users/${userGen}`) .set('token', tokenGen) - .send({ id: 16, + .send({ id: 18, username: 'shaluchandwani', firstName: 'Shalu', lastName: 'chandwani',