Admin access to author Dashboard, social embedds, otp admin tokens

Made several changes:
  - added viewing OTP backup tokens for admins
- added admin access to author dashboard and editing posts not in
 	    admin area
- removed canAuthorPages and can canPublishPages from
				    hasAuthorDashboard
  - added admin editing and deletion of posts from the post page
  - redirect after post deletion to home page
- added author of post when viewing posts as publisher and admin on
 	    Author Dashboard and admin post view
master
Andrew Woodlee 2 years ago
parent e0197b69cb
commit 12f60e0631

@ -51,6 +51,7 @@ class AdminController extends SiteController {
router.use('/log', await this.loadChild(path.join(__dirname, 'admin', 'log')));
router.use('/newsletter', await this.loadChild(path.join(__dirname, 'admin', 'newsletter')));
router.use('/newsroom', await this.loadChild(path.join(__dirname, 'admin', 'newsroom')));
router.use('/otp', await this.loadChild(path.join(__dirname, 'admin', 'otp')));
router.use('/page', await this.loadChild(path.join(__dirname, 'admin', 'page')));
router.use('/post', await this.loadChild(path.join(__dirname, 'admin', 'post')));
router.use('/settings', await this.loadChild(path.join(__dirname, 'admin', 'settings')));

@ -0,0 +1,60 @@
// admin/otp.js
// Copyright (C) 2021 Digital Telepresence, LLC
// License: Apache-2.0
'use strict';
const express = require('express');
// const multer = require('multer');
const { SiteController, SiteError } = require('../../../lib/site-lib');
class OtpAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
dtp.log.debug("OTP controller started");
}
async start ( ) {
// const upload = multer({ dest: `/tmp/${this.dtp.config.site.domainKey}/uploads/${module.exports.slug}` });
const router = express.Router();
router.use(async (req, res, next) => {
res.locals.currentView = 'admin';
res.locals.adminView = 'otp';
return next();
});
// router.param('otp', this.populateOtp.bind(this));
router.get('/', this.getIndex.bind(this));
// router.delete('/:postId', this.deletePost.bind(this));
return router;
}
async getIndex (req, res, next) {
try {
const { otpAuth: otpAuthService } = this.dtp.services;
if (!req.user) {
throw new SiteError(402, "Error getting user");
}
res.locals.tokens = await otpAuthService.getBackupTokens(req.user, "Admin");
res.render('admin/otp/index');
} catch (error) {
this.log.error('failed to get tokens', { error });
return next(error);
}
}
}
module.exports = {
name: 'adminOtp',
slug: 'admin-opt',
create: async (dtp) => { return new OtpAdminController(dtp); },
};

@ -89,6 +89,10 @@ class PostController extends SiteController {
}
async getComposer (req, res) {
const { post: postService } = this.dtp.services;
if (!res.locals.post) {
res.locals.post = await postService.createPlaceholder(req.user);
}
res.render('post/editor');
}

@ -30,6 +30,9 @@ class PostController extends SiteController {
dtp.app.use('/post', router);
async function requireAuthorPrivileges (req, res, next) {
if (req.user && req.user.flags.isAdmin) {
return next();
}
if (!req.user || !req.user.permissions.canAuthorPages) {
return next(new SiteError(403, 'Author privileges are required'));
}
@ -190,9 +193,11 @@ class PostController extends SiteController {
async postUpdatePost (req, res, next) {
const { post: postService } = this.dtp.services;
try {
if (!req.user._id.equals(res.locals.post.author._id) &&
!req.user.permissions.canPublishPosts) {
throw new SiteError(403, 'This is not your post');
if(!req.user.flags.isAdmin){
if (!req.user._id.equals(res.locals.post.author._id) ||
!req.user.permissions.canPublishPosts) {
throw new SiteError(403, 'This is not your post');
}
}
await postService.update(req.user, res.locals.post, req.body);
res.redirect(`/post/${res.locals.post.slug}`);
@ -282,6 +287,10 @@ class PostController extends SiteController {
res.locals.pagination,
);
res.locals.pageTitle = `${res.locals.post.title} on ${this.dtp.config.site.name}`;
res.locals.pageDescription = `${res.locals.post.summary}`;
if (res.locals.post.image) {
res.locals.shareImage = `https://${this.dtp.config.site.domain}/image/${res.locals.post.image._id}`;
}
res.render('post/view');
} catch (error) {
this.log.error('failed to service post view', { postId: res.locals.post._id, error });
@ -318,15 +327,17 @@ class PostController extends SiteController {
async deletePost (req, res) {
const { post: postService } = this.dtp.services;
try {
if (!req.user._id.equals(res.locals.post.author._id) ||
!req.user.permissions.canPublishPosts) {
throw new SiteError(403, 'This is not your post');
// only give admins and the author permission to delete
if (!req.user.flags.isAdmin) {
if (!req.user._id.equals(res.locals.post.author._id)) {
throw new SiteError(403, 'This is not your post');
}
}
await postService.deletePost(res.locals.post);
const displayList = this.createDisplayList('add-recipient');
displayList.reload();
displayList.navigateTo('/');
res.status(200).json({ success: true, displayList });
} catch (error) {

@ -53,10 +53,9 @@ UserSchema.virtual('hasPublishPermissions').get( function ( ) {
});
UserSchema.virtual('hasAuthorDashboard').get( function ( ) {
return this.permissions.canAuthorPages ||
this.permissions.cahAuthorPosts ||
this.permissions.canPublishPages ||
this.permissions.canPublishPosts;
return this.permissions.cahAuthorPosts ||
this.permissions.canPublishPosts ||
this.flags.isAdmin;
});
module.exports = mongoose.model('User', UserSchema);

@ -220,6 +220,13 @@ class OtpAuthService extends SiteService {
async removeForUser (user) {
return await OtpAccount.deleteMany({ user: user });
}
async getBackupTokens (user, serviceName) {
const tokens = await OtpAccount.findOne({ user: user._id, service: serviceName })
.select('+backupTokens')
.lean();
return tokens.backupTokens;
}
}
module.exports = {

@ -49,6 +49,27 @@ class PageService extends SiteService {
}
}
async createPlaceholder (author) {
const NOW = new Date();
if (!author.permissions.canAuthorPages || !author.flags.isAdmin) {
throw new SiteError(403, 'You are not permitted to author pages');
}
let page = new Page();
page.created = NOW;
page.authorType = author.type;
page.author = author._id;
page.title = "New Draft page";
page.slug = `draft-page-${page._id}`;
await page.save();
page = page.toObject();
page.author = author; // self-populate instead of calling db
return page;
}
async create (author, pageDefinition) {
if (!author.permissions.canAuthorPages) {
throw new SiteError(403, 'You are not permitted to author pages');

@ -42,7 +42,7 @@ class PostService extends SiteService {
async createPlaceholder (author) {
const NOW = new Date();
if (!author.permissions.canAuthorPosts) {
if (!author.permissions.canAuthorPosts || !author.flags.isAdmin) {
throw new SiteError(403, 'You are not permitted to author posts');
}
@ -63,11 +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.permissions.canPublishPosts) {
throw new SiteError(403, 'You are not permitted to publish posts');
if (!author.flags.isAdmin){
if (!author.permissions.canAuthorPosts) {
throw new SiteError(403, 'You are not permitted to author posts');
}
if ((postDefinition.status === 'published') && !author.permissions.canPublishPosts) {
throw new SiteError(403, 'You are not permitted to publish posts');
}
}
const post = new Post();
@ -92,10 +94,11 @@ class PostService extends SiteService {
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');
if (!user.flags.isAdmin){
if (!user.permissions.canAuthorPosts) {
throw new SiteError(403, 'You are not permitted to author posts');
}
}
const NOW = new Date();
const updateOp = {
$setOnInsert: {

@ -13,6 +13,12 @@ ul(uk-nav).uk-nav-default
i.fas.fa-cog
span.uk-margin-small-left Settings
li(class={ 'uk-active': (adminView === 'otp') })
a(href="/admin/otp")
span.nav-item-icon
i.fas.fa-cog
span.uk-margin-small-left Otp Tokens
li.uk-nav-divider
li(class={ 'uk-active': (adminView === 'announcement') })

@ -0,0 +1,9 @@
extends ../layouts/main
block content
div(uk-grid)
.uk-width-expand
h1 Tokens
each token of tokens
p #{token.token}

@ -1,7 +1,7 @@
block facebook-card
meta(property='og:site_name', content= site.name)
meta(property='og:type', content='website')
meta(property='og:image', content= `https://${site.domain}/img/social-cards/${site.domainKey}.png?v=${pkg.version}`)
meta(property='og:image', content= shareImage || `https://${site.domain}/img/social-cards/${site.domainKey}.png?v=${pkg.version}`)
meta(property='og:url', content= `https://${site.domain}${request.url}`)
meta(property='og:title', content= pageTitle || site.name)
meta(property='og:description', content= pageDescription || site.description)

@ -1,5 +1,5 @@
block twitter-card
meta(name='twitter:card', content='summary_large_image')
meta(name='twitter:image' content= `https://${site.domain}/img/social-cards/${site.domainKey}.png?v=${pkg.version}`)
meta(name='twitter:image' content= shareImage || `https://${site.domain}/img/social-cards/${site.domainKey}.png?v=${pkg.version}`)
meta(name='twitter:title', content= pageTitle || site.name)
meta(name='twitter:description', content= pageDescription || site.description)

@ -33,7 +33,7 @@ block content
if user && user.hasAuthorDashboard
.uk-width-auto= post.status
if post.author._id.equals(user._id) || user.permissions.canPublishPosts
if post.author._id.equals(user._id) || user.hasAuthorDashboard
.uk-width-auto
a(href=`/post/${post._id}/edit`).uk-display-block
+renderButtonIcon('fa-pen', 'edit')

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Loading…
Cancel
Save