Basic support for Mosaico-based email templates.
This commit is contained in:
parent
b5cdf57f72
commit
7b5642e911
38 changed files with 1271 additions and 751 deletions
|
|
@ -133,7 +133,8 @@ async function createFiles(context, type, entityId, files, dontReplace = false)
|
|||
originalName: originalName,
|
||||
size: file.size,
|
||||
type: file.mimetype,
|
||||
url: `/files/${type}/${entityId}/${file.filename}`
|
||||
url: `/files/${type}/${entityId}/${file.filename}`,
|
||||
thumbnailUrl: `/files/${type}/${entityId}/${file.filename}` // TODO - use smaller thumbnails
|
||||
});
|
||||
|
||||
if (existingNameMap.has(originalName)) {
|
||||
|
|
@ -145,10 +146,10 @@ async function createFiles(context, type, entityId, files, dontReplace = false)
|
|||
}
|
||||
|
||||
const originalNameArray = Array.from(originalNameSet);
|
||||
await knex(getFilesTable(type)).where('entity', entityId).whereIn('originalname', originalNameArray).del();
|
||||
await tx(getFilesTable(type)).where('entity', entityId).whereIn('originalname', originalNameArray).del();
|
||||
|
||||
if (fileEntities) {
|
||||
await knex(getFilesTable(type)).insert(fileEntities);
|
||||
await tx(getFilesTable(type)).insert(fileEntities);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const { enforce } = require('../lib/helpers');
|
|||
const dtHelpers = require('../lib/dt-helpers');
|
||||
const permissions = require('../lib/permissions');
|
||||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
const log = require('npmlog');
|
||||
|
||||
// TODO: This would really benefit from some permission cache connected to rebuildPermissions
|
||||
// A bit of the problem is that the cache would have to expunged as the result of other processes modifying entites/permissions
|
||||
|
|
@ -418,14 +419,26 @@ function checkGlobalPermission(context, requiredOperations) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (context.user.admin) { // This handles the getAdminContext() case
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof requiredOperations === 'string') {
|
||||
requiredOperations = [ requiredOperations ];
|
||||
}
|
||||
|
||||
if (context.user.restrictedAccessHandler) {
|
||||
log.verbose('check global permissions with restrictedAccessHandler -- requiredOperations: ' + requiredOperations);
|
||||
const allowedPerms = context.user.restrictedAccessHandler.globalPermissions;
|
||||
if (allowedPerms) {
|
||||
requiredOperations = requiredOperations.filter(perm => allowedPerms.has(perm));
|
||||
}
|
||||
}
|
||||
|
||||
if (requiredOperations.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.user.admin) { // This handles the getAdminContext() case
|
||||
return true;
|
||||
}
|
||||
|
||||
const roleSpec = config.roles.global[context.user.role];
|
||||
let success = false;
|
||||
if (roleSpec) {
|
||||
|
|
@ -453,6 +466,24 @@ async function _checkPermissionTx(tx, context, entityTypeId, entityId, requiredO
|
|||
|
||||
const entityType = permissions.getEntityType(entityTypeId);
|
||||
|
||||
if (typeof requiredOperations === 'string') {
|
||||
requiredOperations = [ requiredOperations ];
|
||||
}
|
||||
|
||||
if (context.user.restrictedAccessHandler) {
|
||||
log.verbose('check permissions with restrictedAccessHandler -- entityTypeId: ' + entityTypeId + ' entityId: ' + entityId + ' requiredOperations: ' + requiredOperations);
|
||||
if (context.user.restrictedAccessHandler.permissions && context.user.restrictedAccessHandler.permissions[entityTypeId]) {
|
||||
const allowedPerms = context.user.restrictedAccessHandler.permissions[entityTypeId][entityId];
|
||||
if (allowedPerms) {
|
||||
requiredOperations = requiredOperations.filter(perm => allowedPerms.has(perm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (requiredOperations.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.user.admin) { // This handles the getAdminContext() case. In this case we don't check the permission, but just the existence.
|
||||
const existsQuery = tx(entityType.entitiesTable);
|
||||
|
||||
|
|
@ -465,10 +496,6 @@ async function _checkPermissionTx(tx, context, entityTypeId, entityId, requiredO
|
|||
return !!exists;
|
||||
|
||||
} else {
|
||||
if (typeof requiredOperations === 'string') {
|
||||
requiredOperations = [ requiredOperations ];
|
||||
}
|
||||
|
||||
const permsQuery = tx(entityType.permissionsTable)
|
||||
.where('user', context.user.id)
|
||||
.whereIn('operation', requiredOperations);
|
||||
|
|
@ -564,7 +591,6 @@ async function getPermissionsTx(tx, context, entityTypeId, entityId) {
|
|||
return rows.map(x => x.operation);
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
listByEntityDTAjax,
|
||||
listByUserDTAjax,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ async function getById(context, id) {
|
|||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'view');
|
||||
const entity = await tx('templates').where('id', id).first();
|
||||
entity.data = JSON.parse(entity.data);
|
||||
|
||||
entity.permissions = await shares.getPermissionsTx(tx, context, 'template', id);
|
||||
return entity;
|
||||
});
|
||||
|
|
@ -34,9 +36,16 @@ async function listDTAjax(context, params) {
|
|||
);
|
||||
}
|
||||
|
||||
async function _validateAndPreprocess(tx, entity, isCreate) {
|
||||
entity.data = JSON.stringify(entity.data);
|
||||
}
|
||||
|
||||
async function create(context, entity) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createTemplate');
|
||||
|
||||
await _validateAndPreprocess(tx, entity, true);
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
|
||||
const ids = await tx('templates').insert(filterObject(entity, allowedKeys));
|
||||
|
|
@ -57,11 +66,15 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
existing.data = JSON.parse(existing.data);
|
||||
|
||||
const existingHash = hash(existing);
|
||||
if (existingHash !== entity.originalHash) {
|
||||
throw new interoperableErrors.ChangedError();
|
||||
}
|
||||
|
||||
await _validateAndPreprocess(tx, entity, false);
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'template', 'createTemplate', 'delete');
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const interoperableErrors = require('../shared/interoperable-errors');
|
|||
const passwordValidator = require('../shared/password-validator')();
|
||||
const dtHelpers = require('../lib/dt-helpers');
|
||||
const tools = require('../lib/tools-async');
|
||||
let crypto = require('crypto');
|
||||
const crypto = require('crypto');
|
||||
const settings = require('./settings');
|
||||
const urllib = require('url');
|
||||
const _ = require('../lib/translate')._;
|
||||
|
|
@ -43,11 +43,7 @@ async function _getBy(context, key, value, extraColumns = []) {
|
|||
const user = await knex('users').select(columns).where(key, value).first();
|
||||
|
||||
if (!user) {
|
||||
if (context) {
|
||||
shares.throwPermissionDenied();
|
||||
} else {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
shares.throwPermissionDenied();
|
||||
}
|
||||
|
||||
await shares.enforceEntityPermission(context, 'namespace', user.namespace, 'manageUsers');
|
||||
|
|
@ -367,6 +363,50 @@ async function resetPassword(username, resetToken, password) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
const restrictedAccessTokenMethods = {};
|
||||
const restrictedAccessTokens = new Map();
|
||||
|
||||
function registerRestrictedAccessTokenMethod(method, getHandlerFromParams) {
|
||||
restrictedAccessTokenMethods[method] = getHandlerFromParams;
|
||||
}
|
||||
|
||||
function getRestrictedAccessToken(context, method, params) {
|
||||
const token = crypto.randomBytes(24).toString('hex').toLowerCase();
|
||||
const tokenEntry = {
|
||||
token,
|
||||
userId: context.user.id,
|
||||
handler: restrictedAccessTokenMethods[method](params),
|
||||
expires: Date.now() + 120 * 1000
|
||||
};
|
||||
|
||||
restrictedAccessTokens.set(token, tokenEntry);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
async function getByRestrictedAccessToken(token) {
|
||||
const now = Date.now();
|
||||
for (const entry of restrictedAccessTokens.values()) {
|
||||
if (entry.expires < now) {
|
||||
restrictedAccessTokens.delete(entry.token);
|
||||
}
|
||||
}
|
||||
|
||||
const tokenEntry = restrictedAccessTokens.get(token);
|
||||
|
||||
if (tokenEntry) {
|
||||
const user = await getById(contextHelpers.getAdminContext(), tokenEntry.userId);
|
||||
user.restrictedAccessHandler = tokenEntry.handler;
|
||||
|
||||
return user;
|
||||
|
||||
} else {
|
||||
shares.throwPermissionDenied();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
listDTAjax,
|
||||
remove,
|
||||
|
|
@ -382,5 +422,8 @@ module.exports = {
|
|||
resetAccessToken,
|
||||
sendPasswordReset,
|
||||
isPasswordResetTokenValid,
|
||||
resetPassword
|
||||
resetPassword,
|
||||
getByRestrictedAccessToken,
|
||||
getRestrictedAccessToken,
|
||||
registerRestrictedAccessTokenMethod
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue