Fixes of bugs caused by the public endpoint.

This commit is contained in:
Tomas Bures 2018-09-29 22:07:24 +02:00
parent efbfa2b366
commit 213039c141
10 changed files with 79 additions and 52 deletions

View file

@ -1,15 +1,15 @@
### Front page ### Front page
- Some dashboard - Some dashboard
### Deletion
- Check/delete dependencies
### Templates ### Templates
- Add MJML template editor - Add MJML template editor
- Include GrapeJS with MJML support - Include GrapeJS with MJML support
- CKEditor to sandbox - CKEditor to sandbox
- Add Files support to CKEditor - Add Files support to CKEditor
### Message delivery
- Better integration with ZoneMTA to allow multiple send configurations (with different DKIM) against one ZoneMTA instance via different HTTP configuration of ZoneMTA. This may need an extension of ZoneMTA to provide some header entry that identifies the campaign.
### Campaigns ### Campaigns
- Statistics for a sent campaign - Statistics for a sent campaign
- List of sent RSS campaigns (?) - List of sent RSS campaigns (?)

View file

@ -218,13 +218,12 @@ function createApp(appType) {
useWith404Fallback('/subscription', subscription); useWith404Fallback('/subscription', subscription);
useWith404Fallback('/links', links); useWith404Fallback('/links', links);
useWith404Fallback('/archive', archive); useWith404Fallback('/archive', archive);
useWith404Fallback('/files', files);
} }
if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) { useWith404Fallback('/mosaico', mosaico.getRouter(appType));
// Regular endpoints
useWith404Fallback('/files', files);
useWith404Fallback('/mosaico', mosaico.getRouter(appType));
if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) {
if (config.reports && config.reports.enabled === true) { if (config.reports && config.reports.enabled === true) {
useWith404Fallback('/reports', reports); useWith404Fallback('/reports', reports);
} }
@ -266,7 +265,7 @@ function createApp(appType) {
app.use('/', index.getRouter(appType)); app.use('/', index.getRouter(appType));
// Error handlers // Error handlers
if (app.get('env') === 'development') { if (app.get('env') === 'development' || app.get('env') === 'test') {
// development error handler // development error handler
// will print stacktrace // will print stacktrace
app.use((err, req, res, next) => { app.use((err, req, res, next) => {

View file

@ -8,6 +8,7 @@ import styles from "./mosaico.scss";
import {UntrustedContentHost, parentRPC} from './untrusted'; import {UntrustedContentHost, parentRPC} from './untrusted';
import {Icon} from "./bootstrap-components"; import {Icon} from "./bootstrap-components";
import { import {
getPublicUrl,
getSandboxUrl, getSandboxUrl,
getTrustedUrl getTrustedUrl
} from "./urls"; } from "./urls";
@ -105,12 +106,13 @@ export class MosaicoSandbox extends Component {
} }
async exportState(method, params) { async exportState(method, params) {
const sandboxUrlBase = getSandboxUrl();
const trustedUrlBase = getTrustedUrl(); const trustedUrlBase = getTrustedUrl();
const sandboxUrlBase = getSandboxUrl();
const publicUrlBase = getPublicUrl();
return { return {
html: unbase(this.viewModel.exportHTML(), trustedUrlBase, sandboxUrlBase, true), html: unbase(this.viewModel.exportHTML(), trustedUrlBase, sandboxUrlBase, publicUrlBase, true),
model: unbase(this.viewModel.exportJSON(), trustedUrlBase, sandboxUrlBase), model: unbase(this.viewModel.exportJSON(), trustedUrlBase, sandboxUrlBase, publicUrlBase),
metadata: unbase(this.viewModel.exportMetadata(), trustedUrlBase, sandboxUrlBase) metadata: unbase(this.viewModel.exportMetadata(), trustedUrlBase, sandboxUrlBase, publicUrlBase)
}; };
} }
@ -155,10 +157,11 @@ export class MosaicoSandbox extends Component {
strings: window.mosaicoLanguageStrings strings: window.mosaicoLanguageStrings
}; };
const sandboxUrlBase = getSandboxUrl();
const trustedUrlBase = getTrustedUrl(); const trustedUrlBase = getTrustedUrl();
const metadata = this.props.initialMetadata && JSON.parse(base(this.props.initialMetadata, trustedUrlBase, sandboxUrlBase)); const sandboxUrlBase = getSandboxUrl();
const model = this.props.initialModel && JSON.parse(base(this.props.initialModel, trustedUrlBase, sandboxUrlBase)); const publicUrlBase = getPublicUrl();
const metadata = this.props.initialMetadata && JSON.parse(base(this.props.initialMetadata, trustedUrlBase, sandboxUrlBase, publicUrlBase));
const model = this.props.initialModel && JSON.parse(base(this.props.initialModel, trustedUrlBase, sandboxUrlBase, publicUrlBase));
const template = this.props.templateId ? getSandboxUrl(`mosaico/templates/${this.props.templateId}/index.html`) : this.props.templatePath; const template = this.props.templateId ? getSandboxUrl(`mosaico/templates/${this.props.templateId}/index.html`) : this.props.templatePath;
const allPlugins = plugins.concat(window.mosaicoPlugins); const allPlugins = plugins.concat(window.mosaicoPlugins);

View file

@ -51,7 +51,7 @@ class CampaignSender {
} }
if (campaign.source === CampaignSource.TEMPLATE) { if (campaign.source === CampaignSource.TEMPLATE) {
this.template = templates.getByIdTx(tx, contextHelpers.getAdminContext(), this.campaign.data.sourceTemplate, false); this.template = await templates.getByIdTx(tx, contextHelpers.getAdminContext(), this.campaign.data.sourceTemplate, false);
} }
const attachments = await files.listTx(tx, contextHelpers.getAdminContext(), 'campaign', 'attachment', this.campaign.id); const attachments = await files.listTx(tx, contextHelpers.getAdminContext(), 'campaign', 'attachment', this.campaign.id);
@ -111,7 +111,7 @@ class CampaignSender {
if (replaceDataImgs) { if (replaceDataImgs) {
// replace data: images with embedded attachments // replace data: images with embedded attachments
html = html.replace(/(<img\b[^>]* src\s*=[\s"']*)(data:[^"'>\s]+)/gi, (match, prefix, dataUri) => { html = html.replace(/(<img\b[^>]* src\s*=[\s"']*)(data:[^"'>\s]+)/gi, (match, prefix, dataUri) => {
let cid = shortid.generate() + '-attachments@' + campaign.address.split('@').pop(); const cid = shortid.generate() + '-attachments@' + campaign.address.split('@').pop();
attachments.push({ attachments.push({
path: dataUri, path: dataUri,
cid cid

View file

@ -14,6 +14,8 @@ const { formatDate, formatBirthday, parseDate, parseBirthday } = require('../sha
const { getFieldColumn } = require('../shared/lists'); const { getFieldColumn } = require('../shared/lists');
const { cleanupFromPost } = require('../lib/helpers'); const { cleanupFromPost } = require('../lib/helpers');
const Handlebars = require('handlebars'); const Handlebars = require('handlebars');
const { getTrustedUrl, getSandboxUrl, getPublicUrl } = require('../lib/urls');
const { getMergeTagsForBases } = require('../shared/templates');
const allowedKeysCreate = new Set(['name', 'key', 'default_value', 'type', 'group', 'settings']); const allowedKeysCreate = new Set(['name', 'key', 'default_value', 'type', 'group', 'settings']);
@ -228,7 +230,7 @@ fieldTypes['date'] = {
getHbsType: field => 'typeDate' + field.settings.dateFormat.charAt(0).toUpperCase() + field.settings.dateFormat.slice(1), getHbsType: field => 'typeDate' + field.settings.dateFormat.charAt(0).toUpperCase() + field.settings.dateFormat.slice(1),
forHbs: (field, value) => formatDate(field.settings.dateFormat, value), forHbs: (field, value) => formatDate(field.settings.dateFormat, value),
parsePostValue: (field, value) => parseDate(field.settings.dateFormat, value), parsePostValue: (field, value) => parseDate(field.settings.dateFormat, value),
render: (field, value) => value !== null && value.trim() !== '' ? formatDate(field.settings.dateFormat, value) : '' render: (field, value) => value !== null ? formatDate(field.settings.dateFormat, value) : ''
}; };
fieldTypes['birthday'] = { fieldTypes['birthday'] = {
@ -243,7 +245,7 @@ fieldTypes['birthday'] = {
getHbsType: field => 'typeBirthday' + field.settings.dateFormat.charAt(0).toUpperCase() + field.settings.dateFormat.slice(1), getHbsType: field => 'typeBirthday' + field.settings.dateFormat.charAt(0).toUpperCase() + field.settings.dateFormat.slice(1),
forHbs: (field, value) => formatBirthday(field.settings.dateFormat, value), forHbs: (field, value) => formatBirthday(field.settings.dateFormat, value),
parsePostValue: (field, value) => parseBirthday(field.settings.dateFormat, value), parsePostValue: (field, value) => parseBirthday(field.settings.dateFormat, value),
render: (field, value) => value !== null && value.trim() !== '' ? formatBirthday(field.settings.dateFormat, value) : '' render: (field, value) => value !== null ? formatBirthday(field.settings.dateFormat, value) : ''
}; };
const groupedTypes = Object.keys(fieldTypes).filter(key => fieldTypes[key].grouped); const groupedTypes = Object.keys(fieldTypes).filter(key => fieldTypes[key].grouped);
@ -694,7 +696,8 @@ async function forHbs(context, listId, subscription) { // assumes grouped subscr
function getMergeTags(fieldsGrouped, subscription) { // assumes grouped subscription function getMergeTags(fieldsGrouped, subscription) { // assumes grouped subscription
const mergeTags = { const mergeTags = {
'EMAIL': subscription.email 'EMAIL': subscription.email,
...getMergeTagsForBases(getTrustedUrl(), getSandboxUrl(), getPublicUrl())
}; };
for (const fld of fieldsGrouped) { for (const fld of fieldsGrouped) {

View file

@ -8,7 +8,7 @@ const fs = require('fs-extra-promise');
const path = require('path'); const path = require('path');
const interoperableErrors = require('../shared/interoperable-errors'); const interoperableErrors = require('../shared/interoperable-errors');
const entitySettings = require('../lib/entity-settings'); const entitySettings = require('../lib/entity-settings');
const {getTrustedUrl} = require('../lib/urls'); const {getPublicUrl} = require('../lib/urls');
const crypto = require('crypto'); const crypto = require('crypto');
const bluebird = require('bluebird'); const bluebird = require('bluebird');
@ -29,7 +29,7 @@ function getFilePath(type, subType, entityId, filename) {
} }
function getFileUrl(context, type, subType, entityId, filename) { function getFileUrl(context, type, subType, entityId, filename) {
return getTrustedUrl(`files/${type}/${subType}/${entityId}/${filename}`, context) return getPublicUrl(`files/${type}/${subType}/${entityId}/${filename}`, context)
} }
function getFilesTable(type, subType) { function getFilesTable(type, subType) {
@ -109,7 +109,7 @@ async function getFileByFilename(context, type, subType, entityId, name) {
} }
async function getFileByUrl(context, url) { async function getFileByUrl(context, url) {
const urlPrefix = getTrustedUrl('files/', context); const urlPrefix = getPublicUrl('files/', context);
if (url.startsWith(urlPrefix)) { if (url.startsWith(urlPrefix)) {
const path = url.substring(urlPrefix.length); const path = url.substring(urlPrefix.length);
const pathElem = path.split('/'); const pathElem = path.split('/');

View file

@ -15,16 +15,14 @@ router.get('/:campaign/:list/:subscription', (req, res, next) => {
res.render('partials/tracking-scripts', { res.render('partials/tracking-scripts', {
layout: 'archive/layout-raw' layout: 'archive/layout-raw'
}, (err, scripts) => { }, (err, scripts) => {
console.log(scripts);
console.log(err);
if (err) { if (err) {
return next(err); return next(err);
} }
html = scripts ? html.replace(/<\/body\b/i, match => scripts + match) : html; const htmlWithScripts = scripts ? html.replace(/<\/body\b/i, match => scripts + match) : html;
res.render('archive/view', { res.render('archive/view', {
layout: 'archive/layout-raw', layout: 'archive/layout-raw',
message: html message: htmlWithScripts
}); });
}); });

View file

@ -23,7 +23,7 @@ const mosaicoTemplates = require('../models/mosaico-templates');
const contextHelpers = require('../lib/context-helpers'); const contextHelpers = require('../lib/context-helpers');
const interoperableErrors = require('../shared/interoperable-errors'); const interoperableErrors = require('../shared/interoperable-errors');
const { getTrustedUrl, getSandboxUrl } = require('../lib/urls'); const { getTrustedUrl, getSandboxUrl, getPublicUrl } = require('../lib/urls');
const { base } = require('../shared/templates'); const { base } = require('../shared/templates');
const { AppType } = require('../shared/app'); const { AppType } = require('../shared/app');
@ -134,7 +134,7 @@ function getRouter(appType) {
const tmpl = await mosaicoTemplates.getById(req.context, castToInteger(req.params.mosaicoTemplateId)); const tmpl = await mosaicoTemplates.getById(req.context, castToInteger(req.params.mosaicoTemplateId));
res.set('Content-Type', 'text/html'); res.set('Content-Type', 'text/html');
res.send(base(tmpl.data.html, getTrustedUrl(), getSandboxUrl('', req.context))); res.send(base(tmpl.data.html, getTrustedUrl(), getSandboxUrl('', req.context), getPublicUrl()));
}); });
// Mosaico looks for block thumbnails in edres folder relative to index.html of the template. We respond to such requests here. // Mosaico looks for block thumbnails in edres folder relative to index.html of the template. We respond to such requests here.
@ -153,7 +153,7 @@ function getRouter(appType) {
}); });
// This is a fallback to versafix-1 if the block thumbnail is not defined by the template // This is a fallback to versafix-1 if the block thumbnail is not defined by the template
router.use('/templates/:mosaicoTemplateId/edres', express.static(path.join(__dirname, '..', 'client', 'public', 'mosaico', 'templates', 'versafix-1', 'edres'))); router.use('/templates/:mosaicoTemplateId/edres', express.static(path.join(__dirname, '..', 'client', 'static', 'mosaico', 'templates', 'versafix-1', 'edres')));
fileHelpers.installUploadHandler(router, '/upload/:type/:entityId', files.ReplacementBehavior.RENAME, null, 'file'); fileHelpers.installUploadHandler(router, '/upload/:type/:entityId', files.ReplacementBehavior.RENAME, null, 'file');
@ -185,7 +185,7 @@ function getRouter(appType) {
if (config.language && config.language !== 'en') { if (config.language && config.language !== 'en') {
const lang = config.language.split('_')[0]; const lang = config.language.split('_')[0];
try { try {
const file = path.join(__dirname, '..', 'client', 'public', 'mosaico', 'lang', 'mosaico-' + lang + '.json'); const file = path.join(__dirname, '..', 'client', 'static', 'mosaico', 'lang', 'mosaico-' + lang + '.json');
languageStrings = await fsReadFile(file, 'utf8'); languageStrings = await fsReadFile(file, 'utf8');
} catch (err) { } catch (err) {
} }
@ -205,7 +205,8 @@ function getRouter(appType) {
}); });
}); });
} else { } else if (appType === AppType.TRUSTED || appType === AppType.PUBLIC) { // Mosaico editor loads the images from TRUSTED endpoint. This is hard to change because the index.html has to come from TRUSTED.
// So we serve /mosaico/img under both endpoints. There is no harm in it.
router.getAsync('/img', async (req, res) => { router.getAsync('/img', async (req, res) => {
const method = req.query.method; const method = req.query.method;
const params = req.query.params; const params = req.query.params;
@ -214,6 +215,7 @@ function getRouter(appType) {
// FIXME - cache the generated files !!! // FIXME - cache the generated files !!!
if (method === 'placeholder') { if (method === 'placeholder') {
width = sanitizeSize(width, 1, 2048, 600, false); width = sanitizeSize(width, 1, 2048, 600, false);
height = sanitizeSize(height, 1, 2048, 300, false); height = sanitizeSize(height, 1, 2048, 300, false);
@ -228,7 +230,7 @@ function getRouter(appType) {
const mosaicoLegacyUrlPrefix = getTrustedUrl(`mosaico/uploads/`); const mosaicoLegacyUrlPrefix = getTrustedUrl(`mosaico/uploads/`);
if (url.startsWith(mosaicoLegacyUrlPrefix)) { if (url.startsWith(mosaicoLegacyUrlPrefix)) {
filePath = path.join(__dirname, '..', 'client', 'public' , 'mosaico', 'uploads', url.substring(mosaicoLegacyUrlPrefix.length)); filePath = path.join(__dirname, '..', 'client', 'static' , 'mosaico', 'uploads', url.substring(mosaicoLegacyUrlPrefix.length));
} else { } else {
const file = await files.getFileByUrl(contextHelpers.getAdminContext(), url); const file = await files.getFileByUrl(contextHelpers.getAdminContext(), url);
filePath = file.path; filePath = file.path;

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
function base(text, trustedBaseUrl, sandboxBaseUrl) { function _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
if (trustedBaseUrl.endsWith('/')) { if (trustedBaseUrl.endsWith('/')) {
trustedBaseUrl = trustedBaseUrl.substring(0, trustedBaseUrl.length - 1); trustedBaseUrl = trustedBaseUrl.substring(0, trustedBaseUrl.length - 1);
} }
@ -9,32 +9,54 @@ function base(text, trustedBaseUrl, sandboxBaseUrl) {
sandboxBaseUrl = sandboxBaseUrl.substring(0, sandboxBaseUrl.length - 1); sandboxBaseUrl = sandboxBaseUrl.substring(0, sandboxBaseUrl.length - 1);
} }
text = text.split('[URL_BASE]').join(trustedBaseUrl); if (publicBaseUrl.endsWith('/')) {
text = text.split('[SANDBOX_URL_BASE]').join(sandboxBaseUrl); publicBaseUrl = publicBaseUrl.substring(0, publicBaseUrl.length - 1);
text = text.split('[ENCODED_URL_BASE]').join(encodeURIComponent(trustedBaseUrl)); }
text = text.split('[ENCODED_SANDBOX_URL_BASE]').join(encodeURIComponent(sandboxBaseUrl));
return {trustedBaseUrl, sandboxBaseUrl, publicBaseUrl};
}
function getMergeTagsForBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
const bases = _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl);
return {
URL_BASE: bases.publicBaseUrl,
TRUSTED_URL_BASE: bases.trustedBaseUrl,
SANDBOX_URL_BASE: bases.sandboxBaseUrl,
ENCODED_URL_BASE: encodeURIComponent(bases.publicBaseUrl),
ENCODED_TRUSTED_URL_BASE: encodeURIComponent(bases.trustedBaseUrl),
ENCODED_SANDBOX_URL_BASE: encodeURIComponent(bases.sandboxBaseUrl)
};
}
function base(text, trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
const bases = _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl);
text = text.split('[URL_BASE]').join(bases.publicBaseUrl);
text = text.split('[TRUSTED_URL_BASE]').join(bases.trustedBaseUrl);
text = text.split('[SANDBOX_URL_BASE]').join(bases.sandboxBaseUrl);
text = text.split('[ENCODED_URL_BASE]').join(encodeURIComponent(bases.publicBaseUrl));
text = text.split('[ENCODED_TRUSTED_URL_BASE]').join(encodeURIComponent(bases.trustedBaseUrl));
text = text.split('[ENCODED_SANDBOX_URL_BASE]').join(encodeURIComponent(bases.sandboxBaseUrl));
return text; return text;
} }
function unbase(text, trustedBaseUrl, sandboxBaseUrl, treatSandboxAsTrusted = false) { function unbase(text, trustedBaseUrl, sandboxBaseUrl, publicBaseUrl, treatAllAsPublic = false) {
if (trustedBaseUrl.endsWith('/')) { const bases = _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl);
trustedBaseUrl = trustedBaseUrl.substring(0, trustedBaseUrl.length - 1);
}
if (sandboxBaseUrl.endsWith('/')) { text = text.split(bases.publicBaseUrl).join('[URL_BASE]');
sandboxBaseUrl = sandboxBaseUrl.substring(0, sandboxBaseUrl.length - 1); text = text.split(bases.trustedBaseUrl).join(treatAllAsPublic ? '[URL_BASE]' : '[TRUSTED_URL_BASE]');
} text = text.split(bases.sandboxBaseUrl).join(treatAllAsPublic ? '[URL_BASE]' : '[SANDBOX_URL_BASE]');
text = text.split(encodeURIComponent(bases.publicBaseUrl)).join('[ENCODED_URL_BASE]');
text = text.split(trustedBaseUrl).join('[URL_BASE]'); text = text.split(encodeURIComponent(bases.trustedBaseUrl)).join(treatAllAsPublic ? '[ENCODED_URL_BASE]' : '[ENCODED_TRUSTED_URL_BASE]');
text = text.split(sandboxBaseUrl).join(treatSandboxAsTrusted ? '[URL_BASE]' : '[SANDBOX_URL_BASE]'); text = text.split(encodeURIComponent(bases.sandboxBaseUrl)).join(treatAllAsPublic ? '[ENCODED_URL_BASE]' : '[ENCODED_SANDBOX_URL_BASE]');
text = text.split(encodeURIComponent(trustedBaseUrl)).join('[ENCODED_URL_BASE]');
text = text.split(encodeURIComponent(sandboxBaseUrl)).join(treatSandboxAsTrusted ? '[ENCODED_URL_BASE]' : '[ENCODED_SANDBOX_URL_BASE]');
return text; return text;
} }
module.exports = { module.exports = {
base, base,
unbase unbase,
getMergeTagsForBases
}; };

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const anonymousRestrictedAccessToken = 'public'; const anonymousRestrictedAccessToken = 'anonymous';
module.exports = { module.exports = {
anonymousRestrictedAccessToken anonymousRestrictedAccessToken