separated author and publisher permissions

master
rob 2 years ago
parent f09fc0a496
commit 30a8c3b48e

@ -54,7 +54,7 @@ class PageController extends SiteController {
async pageUpdatePage (req, res, next) {
const { page: pageService } = this.dtp.services;
try {
await pageService.update(res.locals.page, req.body);
await pageService.update(req.user, res.locals.page, req.body);
res.redirect('/admin/page');
} catch (error) {
this.log.error('failed to update page', { newletterId: res.locals.page._id, error });

@ -195,7 +195,7 @@ class PostController extends SiteController {
if (!req.user._id.equals(res.locals.post.author._id)) {
throw new SiteError(403, 'This is not your post');
}
await postService.update(res.locals.post, req.body);
await postService.update(req.user, res.locals.post, req.body);
res.redirect(`/post/${res.locals.post.slug}`);
} catch (error) {
this.log.error('failed to update post', { newletterId: res.locals.post._id, error });
@ -259,6 +259,11 @@ class PostController extends SiteController {
async getView (req, res, next) {
const { comment: commentService, resource: resourceService } = this.dtp.services;
try {
if ((res.locals.post.status !== 'published') &&
!res.locals.post.author._id.equals(req.user._id) &&
!req.user.hasAuthorDashboard) {
throw new SiteError(403, 'The post is not published');
}
await resourceService.recordView(req, 'Post', res.locals.post._id);
res.locals.countPerPage = 20;

@ -22,6 +22,8 @@ module.exports.UserPermissionsSchema = new Schema({
canReport: { type: Boolean, default: true, required: true },
canAuthorPages: { type: Boolean, default: false, required: true },
canAuthorPosts: { type: Boolean, default: false, required: true },
canPublishPages: { type: Boolean, default: false, required: true },
canPublishPosts: { type: Boolean, default: false, required: true },
});
module.exports.UserOptInSchema = new Schema({

@ -29,6 +29,23 @@ const UserSchema = new Schema({
optIn: { type: UserOptInSchema, required: true, select: false },
theme: { type: String, enum: DTP_THEME_LIST, default: 'dtp-light', required: true },
stats: { type: ResourceStats, default: ResourceStatsDefaults, required: true },
}, {
toObject: { virtuals: true },
});
UserSchema.virtual('hasAuthorPermissions').get( function ( ) {
return this.permissions.canAuthorPages || this.permissions.canAuthorPosts;
});
UserSchema.virtual('hasPublishPermissions').get( function ( ) {
return this.permissions.canPublishPages || this.permissions.canPublishPosts;
});
UserSchema.virtual('hasAuthorDashboard').get( function ( ) {
return this.permissions.canAuthorPages ||
this.permissions.cahAuthorPosts ||
this.permissions.canPublishPages ||
this.permissions.canPublishPosts;
});
module.exports = mongoose.model('User', UserSchema);

@ -431,12 +431,17 @@ class CoreNodeService extends SiteService {
}
async getUserByLocalId (userId) {
const user = await CoreUser
const { user: userService } = this.dtp.services;
let user = await CoreUser
.findOne({ _id: userId })
.select('+flags +permissions +optIn')
.populate(this.populateCoreUser)
.lean();
user.type = 'CoreUser';
userService.decorateUserObject(user);
return user;
}
@ -470,6 +475,8 @@ class CoreNodeService extends SiteService {
'permissions.canReport': settings.canReport === 'on',
'permissions.canAuthorPages': settings.canAuthorPages === 'on',
'permissions.canAuthorPosts': settings.canAuthorPosts === 'on',
'permissions.canPublishPages': settings.canPublishPages === 'on',
'permissions.canPublishPosts': settings.canPublishPosts === 'on',
'optIn.system': settings.optInSystem === 'on',
'optIn.marketing': settings.optInMarketing === 'on',

@ -7,7 +7,7 @@
const striptags = require('striptags');
const slug = require('slug');
const { SiteService } = require('../../lib/site-lib');
const { SiteService, SiteError } = require('../../lib/site-lib');
const mongoose = require('mongoose');
const ObjectId = mongoose.Types.ObjectId;
@ -49,6 +49,10 @@ class PageService extends SiteService {
}
async create (author, pageDefinition) {
if (!author.permissions.canAuthorPages) {
throw new SiteError(403, 'You are not permitted to author pages');
}
const page = new Page();
page.title = striptags(pageDefinition.title.trim());
page.slug = this.createPageSlug(page._id, page.title);
@ -69,7 +73,7 @@ class PageService extends SiteService {
return page.toObject();
}
async update (page, pageDefinition) {
async update (user, page, pageDefinition) {
const NOW = new Date();
const updateOp = {
$set: {
@ -77,6 +81,10 @@ class PageService extends SiteService {
},
};
if (!user.permissions.canAuthorPages) {
throw new SiteError(403, 'You are not permitted to author or change pages.');
}
if (pageDefinition.title) {
updateOp.$set.title = striptags(pageDefinition.title.trim());
}
@ -95,9 +103,6 @@ class PageService extends SiteService {
if (pageDefinition.content) {
updateOp.$set.content = pageDefinition.content.trim();
}
if (pageDefinition.status) {
updateOp.$set.status = striptags(pageDefinition.status.trim());
}
updateOp.$set.menu = {
icon: striptags((pageDefinition.menuIcon || 'fa-slash').trim().toLowerCase()),
@ -109,6 +114,15 @@ class PageService extends SiteService {
updateOp.$set.menu.parent = pageDefinition.parentPageId;
}
// if old status is not published and new status is published, we have to
// verify that the calling user has Publisher privileges.
if ((page.status !== 'published') && (pageDefinition.status === 'published')) {
if (!user.permissions.canPublishPages) {
throw new SiteError(403, 'You are not permitted to publish pages');
}
updateOp.$set.status = striptags(pageDefinition.status.trim());
}
await Page.updateOne(
{ _id: page._id },
updateOp,

@ -42,6 +42,10 @@ class PostService extends SiteService {
async createPlaceholder (author) {
const NOW = new Date();
if (!author.permissions.canAuthorPosts) {
throw new SiteError(403, 'You are not permitted to author posts');
}
let post = new Post();
post.created = NOW;
post.authorType = author.type;
@ -51,7 +55,7 @@ class PostService extends SiteService {
await post.save();
post = post.toObject();
post.author = author; // I'll populate it myself
post.author = author; // self-populate instead of calling db
return post;
}
@ -59,6 +63,13 @@ class PostService extends SiteService {
async create (author, postDefinition) {
const NOW = new Date();
if (!author.permissions.canAuthorPosts) {
throw new SiteError(403, 'You are not permitted to author posts');
}
if ((postDefinition.status === 'published') && !author.permisstions.canPublishPosts) {
throw new SiteError(403, 'You are not permitted to publish posts');
}
const post = new Post();
post.created = NOW;
post.authorType = author.type;
@ -78,9 +89,13 @@ class PostService extends SiteService {
return post.toObject();
}
async update (post, postDefinition) {
async update (user, post, postDefinition) {
const { coreNode: coreNodeService } = this.dtp.services;
if (!user.permissions.canAuthorPosts) {
throw new SiteError(403, 'You are not permitted to author posts');
}
const NOW = new Date();
const updateOp = {
$setOnInsert: {
@ -113,6 +128,12 @@ class PostService extends SiteService {
if (!postDefinition.status) {
throw new SiteError(406, 'Must include post status');
}
if (post.status !== 'published' && postDefinition.status === 'published') {
if (!user.permissions.canPublishPosts) {
throw new SiteError(403, 'You are not permitted to publish posts');
}
}
updateOp.$set.status = striptags(postDefinition.status.trim());
updateOp.$set['flags.enableComments'] = postDefinition.enableComments === 'on';
@ -149,6 +170,11 @@ class PostService extends SiteService {
async updateImage (user, post, file) {
const { image: imageService } = this.dtp.services;
if (!user.permissions.canAuthorPosts) {
throw new SiteError(403, 'You are not permitted to change posts');
}
const images = [
{
width: 960,

@ -107,6 +107,8 @@ class UserService extends SiteService {
canReport: userDefinition.canReport || true,
canAuthorPosts: userDefinition.canAuthorPosts || false,
canAuthorPages: userDefinition.canAuthorPages || false,
canPublishPosts: userDefinition.canPublishPosts || false,
canPublishPages: userDefinition.canPublishPages || false,
};
user.optIn = {
@ -248,6 +250,8 @@ class UserService extends SiteService {
'permissions.canReport': userDefinition.canReport === 'on',
'permissions.canAuthorPages': userDefinition.canAuthorPages === 'on',
'permissions.canAuthorPosts': userDefinition.canAuthorPosts === 'on',
'permissions.canPublishPages': userDefinition.canPublishPages === 'on',
'permissions.canPublishPosts': userDefinition.canPublishPosts === 'on',
'optIn.system': userDefinition.optInSystem === 'on',
'optIn.marketing': userDefinition.optInMarketing === 'on',
@ -290,19 +294,22 @@ class UserService extends SiteService {
const accountUsername = await this.filterUsername(accountEmail);
this.log.debug('locating user record', { accountEmail, accountUsername });
const user = await User
let user = await User
.findOne({
$or: [
{ email: accountEmail },
{ username_lc: accountUsername },
]
})
.select('+passwordSalt +password +flags +optIn +permissions')
.lean();
.select('+passwordSalt +password +flags +optIn +permissions');
if (!user) {
throw new SiteError(404, 'Member credentials are invalid');
}
user = user.toObject();
this.log.debug('user debug', { user });
const maskedPassword = crypto.maskPassword(
user.passwordSalt,
account.password,
@ -407,10 +414,19 @@ class UserService extends SiteService {
if (!user) {
throw new SiteError(404, 'Member account not found');
}
user.type = 'User';
this.decorateUserObject(user);
return user;
}
decorateUserObject (user) {
user.hasAuthorPermissions = user.permissions.canAuthorPages || user.permissions.canAuthorPosts;
user.hasPublishPermissions = user.permissions.canPublishPages || user.permissions.canPublishPosts;
user.hasAuthorDashboard = user.hasAuthorPermissions || user.hasPublishPermissions;
}
async getUserAccounts (pagination, username) {
let search = { };
if (username) {

@ -55,6 +55,12 @@ block content
label
input(id="can-author-posts", name="canAuthorPosts", type="checkbox", checked= userAccount.permissions.canAuthorPosts)
| Can Author Posts
label
input(id="can-publish-pages", name="canPublishPages", type="checkbox", checked= userAccount.permissions.canPublishPages)
| Can Publish Pages
label
input(id="can-publish-posts", name="canPublishPosts", type="checkbox", checked= userAccount.permissions.canPublishPosts)
| Can Publish Posts
.uk-margin
label.uk-form-label Opt-Ins

@ -61,6 +61,12 @@ block content
label
input(id="can-author-posts", name="canAuthorPosts", type="checkbox", checked= userAccount.permissions.canAuthorPosts)
| Can Author Posts
label
input(id="can-publish-pages", name="canPublishPages", type="checkbox", checked= userAccount.permissions.canPublishPages)
| Can Publish Pages
label
input(id="can-publish-posts", name="canPublishPosts", type="checkbox", checked= userAccount.permissions.canPublishPosts)
| Can Publish Posts
.uk-margin
label.uk-form-label Opt-Ins

@ -28,7 +28,7 @@ mixin renderMenuItem (iconClass, label)
if user
li.uk-nav-header Member Menu
if user.permissions.canAuthorPosts
if user.hasAuthorDashboard
li(class={ "uk-active": (currentView === 'author') })
a(href='/author').uk-display-block
div(uk-grid).uk-grid-collapse

@ -80,7 +80,7 @@ block viewjs
'bold italic backcolor',
'alignleft aligncenter alignright alignjustify',
'bullist numlist outdent indent removeformat',
'link image code',
'link image media code',
'help'
];
const pluginItems = [

@ -30,10 +30,13 @@ block content
div(uk-grid).uk-grid-small.uk-flex-top
.uk-width-expand
div #{moment(post.created).format('MMM DD, YYYY, hh:mm a')}, by #[a(href=`/user/${post.author._id}`)= post.author.displayName || post.author.username]
if user && user.permissions.canAuthorPages && post.author._id.equals(user._id)
.uk-width-auto
a(href=`/post/${post._id}/edit`)
+renderButtonIcon('fa-pen', 'edit')
if user && user.hasAuthorDashboard
.uk-width-auto= post.status
if post.author._id.equals(user._id)
.uk-width-auto
a(href=`/post/${post._id}/edit`)
+renderButtonIcon('fa-pen', 'edit')
.uk-width-auto
+renderButtonIcon('fa-eye', displayIntegerValue(post.stats.totalViewCount))
.uk-width-auto

Loading…
Cancel
Save