Skip to content

Commit

Permalink
feat(roles): add moderator and admis roles
Browse files Browse the repository at this point in the history
- moderator can delete comments and articles
- admin can CRUD users

[Finishes #16624101]
  • Loading branch information
Deschant Kounou committed Jun 26, 2019
1 parent 2593414 commit f03b0a2
Show file tree
Hide file tree
Showing 14 changed files with 429 additions and 202 deletions.
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,22 @@ DATABASE_TEST_URL=
AUTHOSHAVEN_USER=
AUTHOSHAVEN_PASS=
BASE_URL=

FCBK_ID=
FCBK_APP_SECRET=

TWITTER_ID=
TWITTER_APP_SECRET=

GOOGLE_ID=
GOOGLE_SECRET=
SECRET=
SECRET_KEY=
SUPER_ADMIN_PSW=

TEST_USER_PSW=
IS_ADMIN=

CLOUD_NAME=
CLOUDINARY_API_ID=
CLOUDINARY_API_SECRET=
27 changes: 7 additions & 20 deletions src/api/controllers/auth.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import nodemailer from 'nodemailer';
import { omit } from 'lodash';
import TokenHelper from '../../helpers/Token.helper';
import Mailhelper from '../../helpers/SendMail.helper';
import HashHelper from '../../helpers/hashHelper';
Expand All @@ -24,16 +25,7 @@ class AuthController {
* @returns {Object} - Response object
*/
static async register(req, res) {
const {
firstName,
lastName,
email,
password,
username,
dob,
bio,
gender
} = req.body;
let { body } = req;

if (Object.keys(req.body).length === 0) {
return res.status(400).json({
Expand All @@ -42,17 +34,12 @@ class AuthController {
});
}

body = await omit(body, ['roles']);

const userObject = {
firstName,
lastName,
email,
password: HashHelper.hashPassword(password),
username,
dob,
bio,
gender,
verified: false,
isAdmin: false
...body,
password: HashHelper.hashPassword(body.password),
verified: false
};

const newUser = await User.create(userObject);
Expand Down
12 changes: 3 additions & 9 deletions src/api/controllers/comments.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,7 @@ export default class comments {
* @returns {Object} - Response object
*/
static async deleteComment(req, res) {
const { id, firstName } = req.user;
const findUser = await models.User.findAll({
where: {
id
}
});
const { id, firstName, roles } = req.user;
const { commentId } = req.params;
const findComment = await models.Comment.findAll({
where: {
Expand All @@ -133,7 +128,6 @@ export default class comments {
commentId
}
});
const { isAdmin } = findUser[0].dataValues;
const { userId } = findComment[0].dataValues;
if (nestedComments[0]) {
await models.Comment.update(
Expand All @@ -147,14 +141,14 @@ export default class comments {
message: 'Comment deleted'
});
});
} else if (userId === id || isAdmin === true) {
} else if (userId === id || roles.includes('moderator' || 'admin')) {
await models.Comment.destroy({
where: {
id: commentId
}
}).then(() => {
return res.status(200).json({
message: 'Comment deleted!'
message: roles.includes('moderator' || 'admin') ? 'Comment deleted by moderator' : 'Comment deleted!'
});
});
}
Expand Down
23 changes: 22 additions & 1 deletion src/api/controllers/profiles.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { omit } from 'lodash';
import models from '../../sequelize/models';
import uploadHelper from '../../helpers/uploadHelper';

Expand Down Expand Up @@ -32,8 +33,13 @@ export default class ProfilesController {
* @return {object} returns an object containing the updated user profile
*/
static async updateProfile(req, res) {
const { body, user, file } = req;
let { body } = req;
let uploadedImage;
const { user, file } = req;

if (!user.roles.includes('admin')) {
body = await omit(body, ['roles']);
}

const updatedProfile = { ...body };

Expand Down Expand Up @@ -62,6 +68,21 @@ export default class ProfilesController {
}
}

/**
* @param {object} req
* @param {object} res
* @return {object} returns a res status with message
*/
static async deleteProfile(req, res) {
const { params } = req;
try {
await User.destroy({ where: { id: params.id } });
return res.status(200).json({ status: 200, message: `User ${params.id} deleted.` });
} catch (error) {
return res.status(500).json({ status: 500, error: `${error}` });
}
}

/**
* @Author - Audace Uhiriwe
* @param {object} req
Expand Down
9 changes: 6 additions & 3 deletions src/api/routes/userRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import validateBody from '../../middleware/validateBody';
import upload from '../../handlers/multer';

const userRouter = Router();
const { updateProfile } = ProfilesController;
const { verifyToken } = Auth;
const { updateProfile, deleteProfile } = ProfilesController;
const { verifyToken, checkOwnership, checkIsAdmin } = Auth;

userRouter.put('/', verifyToken, validateBody('updateUser'), upload.single('image'), updateProfile);
userRouter
.route('/:id')
.put(verifyToken, checkOwnership, validateBody('updateUser'), upload.single('image'), updateProfile)
.delete(verifyToken, checkIsAdmin, deleteProfile);

export default userRouter;
45 changes: 45 additions & 0 deletions src/middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,49 @@ export default class Auth {
return res.status(500).json({ error });
}
}

/**
* Check ownership
* @param {Object} req - Request
* @param {Object} res - Response
* @param {Function} next -Next
* @returns {Object} The response object
*/
static async checkOwnership(req, res, next) {
const { user, params } = req;
if ((user.id === parseInt(params.id, 10)) || (user.roles.includes('admin'))) {
return next();
}
return res.status(401).json({ message: 'You are not admin or owner of this profile' });
}

/**
* Check checkIsAdmin
* @param {Object} req - Request
* @param {Object} res - Response
* @param {Function} next -Next
* @returns {Object} The response object
*/
static async checkIsAdmin(req, res, next) {
const { user } = req;
if (user && user.roles.includes('admin')) {
return next();
}
return res.status(403).json({ message: 'You are not an admin!' });
}

/**
* Check checkIsModerator
* @param {Object} req - Request
* @param {Object} res - Response
* @param {Function} next -Next
* @returns {Object} The response object
*/
static async checkIsModerator(req, res, next) {
const { user } = req;
if (user && user.roles.includes('moderator' || 'admin')) {
return next();
}
return res.status(401).json({ message: 'You are not a moderator!' });
}
}
17 changes: 11 additions & 6 deletions src/middleware/checkOwner.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,25 @@ class checkOwner {
* @param {res} - response
*/
static async articleOwner(req, res, next) {
const { id } = req.user;
const { id, roles } = req.user;
const { slug } = req.params;

// @check if that user is verified
const { dataValues } = await User.findOne({ where: { id } });
if (dataValues.verified === false) return res.status(403).send({ error: 'Please Verify your account, first!' });

// @check if the article's slug exist
const result = await Article.findOne({ where: { slug } });
if (result === null) return res.status(404).send({ error: 'Slug Not found!' });

if (roles.includes('moderator')) {
req.foundArticle = result.dataValues;
return next();
}

// @check if that user is verified
const { dataValues } = await User.findOne({ where: { id } });
if (dataValues.verified === false) return res.status(403).send({ error: 'Please Verify your account, first!' });

// @check if the user who logged in - is the owner of that slug
const response = await Article.findOne({ where: { slug, authorId: id } });
if (!response) return res.status(403).send({ error: 'Sorry!, you are not the owner of this slug' });
if (!response) return res.status(403).send({ message: 'Sorry!, You are not the owner of this article' });

req.foundArticle = response.dataValues;
return next();
Expand Down
14 changes: 14 additions & 0 deletions src/sequelize/migrations/20190624093422-add-admin-moderator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default {
up: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('Users', 'isAdmin');
return queryInterface.addColumn('Users', 'roles', {
type: Sequelize.ARRAY(Sequelize.STRING),
defaultValue: ['user'],
});
},

down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('Users', 'roles');
return queryInterface.addColumn('Users', 'isAdmin', { type: Sequelize.STRING });
}
};
2 changes: 1 addition & 1 deletion src/sequelize/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = (sequelize, DataTypes) => {
provider: DataTypes.STRING,
socialId: DataTypes.STRING,
verified: DataTypes.BOOLEAN,
isAdmin: DataTypes.BOOLEAN
roles: DataTypes.ARRAY(DataTypes.STRING),
},
{}
);
Expand Down
Loading

0 comments on commit f03b0a2

Please sign in to comment.