From 74aba49aa7132b6aec0a529680655588e66b5001 Mon Sep 17 00:00:00 2001 From: Jarrett Widman Date: Wed, 5 Aug 2015 17:43:46 -0500 Subject: [PATCH 1/3] s3 upload support in wysiwyg html --- admin/api/s3.js | 43 ++++++++++++++++++++++++++++++++++ fields/types/html/HtmlField.js | 24 +++++++++---------- lib/core/render.js | 23 +++++++++--------- lib/core/routes.js | 6 +++++ 4 files changed, 73 insertions(+), 23 deletions(-) create mode 100644 admin/api/s3.js diff --git a/admin/api/s3.js b/admin/api/s3.js new file mode 100644 index 0000000000..5f9d3eba74 --- /dev/null +++ b/admin/api/s3.js @@ -0,0 +1,43 @@ +var knox = require('knox'); +var keystone = require('../../'); + +exports = module.exports = { + + upload: function(req, res) { + if(req.files && req.files.file){ + + var s3Config = keystone.get('s3 config'); + + var file = req.files.file, + path = s3Config.s3path ? s3Config.s3path + '/' : ''; + originalname = file.originalname, + filetype = file.mimetype || file.type; + + var s3Client = knox.createClient(s3Config); + + s3Client.putFile(file.path, path + file.name, function(err, s3Response) { + var sendResult = function () { + if(err){ + return res.send({ error: { message: err.message } }); + } + + if (s3Response) { + if (s3Response.statusCode !== 200) { + return res.send({ error: { message:'Amazon returned Http Code: ' + s3Response.statusCode } }); + } else { + return res.send({ image: { url: "https://s3.amazonaws.com/" + s3Config.bucket + "/" + file.name } }); + } + } + }; + + res.format({ + html: sendResult, + json: sendResult + }); + }); + + } else { + res.json({ error: { message: 'No image selected' } }); + } + } +}; diff --git a/fields/types/html/HtmlField.js b/fields/types/html/HtmlField.js index 944fe1990b..c1703b824f 100644 --- a/fields/types/html/HtmlField.js +++ b/fields/types/html/HtmlField.js @@ -10,9 +10,9 @@ function getId() { } module.exports = Field.create({ - + displayName: 'HtmlField', - + getInitialState: function() { return { id: getId(), @@ -41,7 +41,7 @@ module.exports = Field.create({ if (prevState.isCollapsed && !this.state.isCollapsed) { this.initWysiwyg(); } - + if (_.isEqual(this.props.dependsOn, this.props.currentDependencies) && !_.isEqual(this.props.currentDependencies, prevProps.currentDependencies)) { var instance = tinymce.get(prevState.id); @@ -57,13 +57,13 @@ module.exports = Field.create({ componentDidMount: function() { this.initWysiwyg(); }, - + componentWillReceiveProps: function(nextProps) { if (this.editor && this._currentValue !== nextProps.value) { this.editor.setContent(nextProps.value); } }, - + focusChanged: function(focused) { this.setState({ isFocused: focused @@ -102,7 +102,7 @@ module.exports = Field.create({ toolbar += ' | image'; } - if (options.enableCloudinaryUploads) { + if (options.enableCloudinaryUploads || options.enableS3Uploads) { plugins.push('uploadimage'); toolbar += options.enableImages ? ' uploadimage' : ' | uploadimage'; } @@ -126,10 +126,10 @@ module.exports = Field.create({ importcss_append: true, importcss_merge_classes: true }; - + _.extend(options.additionalOptions, importcssOptions); } - + if (!options.overrideToolbar) { toolbar += ' | code'; } @@ -143,7 +143,7 @@ module.exports = Field.create({ }; if (this.shouldRenderField()) { - opts.uploadimage_form_url = '/keystone/api/cloudinary/upload'; + opts.uploadimage_form_url = options.enableS3Uploads ? '/keystone/api/s3/upload' : '/keystone/api/cloudinary/upload'; } else { _.extend(opts, { mode: 'textareas', @@ -165,7 +165,7 @@ module.exports = Field.create({ var className = this.props.wysiwyg ? 'wysiwyg' : 'code'; return className; }, - + renderEditor: function(readOnly) { var className = this.state.isFocused ? 'is-focused' : ''; var style = { @@ -181,9 +181,9 @@ module.exports = Field.create({ renderField: function() { return this.renderEditor(); }, - + renderValue: function() { return this.renderEditor(true); } - + }); diff --git a/lib/core/render.js b/lib/core/render.js index cd66f9c5a2..60f864b58d 100644 --- a/lib/core/render.js +++ b/lib/core/render.js @@ -16,9 +16,9 @@ var utils = require('keystone-utils'); var templateCache = {}; function render(req, res, view, ext) { - + var keystone = this; - + var templatePath = __dirname + '/../../templates/views/' + view + '.jade'; debug('rendering ' + templatePath); @@ -27,17 +27,17 @@ function render(req, res, view, ext) { filename: templatePath, pretty: keystone.get('env') !== 'production' }; - + // TODO: Allow custom basePath for extensions... like this or similar // if (keystone.get('extensions')) { // jadeOptions.basedir = keystone.getPath('extensions') + '/templates'; // } - + var compileTemplate = function() { debug('compiling'); return jade.compile(fs.readFileSync(templatePath, 'utf8'), jadeOptions); }; - + var template = keystone.get('viewCache') ? templateCache[view] || (templateCache[view] = compileTemplate()) : compileTemplate(); @@ -53,7 +53,7 @@ function render(req, res, view, ext) { error: res.req.flash('error'), hilight: res.req.flash('hilight') }; - + var locals = { _: _, moment: moment, @@ -83,6 +83,7 @@ function render(req, res, view, ext) { wysiwygOptions: { enableImages: keystone.get('wysiwyg images') ? true : false, enableCloudinaryUploads: keystone.get('wysiwyg cloudinary images') ? true : false, + enableS3Uploads: keystone.get('wysiwyg s3 images') ? true : false, additionalButtons: keystone.get('wysiwyg additional buttons') || '', additionalPlugins: keystone.get('wysiwyg additional plugins') || '', additionalOptions: keystone.get('wysiwyg additional options') || {}, @@ -92,10 +93,10 @@ function render(req, res, view, ext) { importcss: keystone.get('wysiwyg importcss') || '' } }; - + // optional extensions to the local scope _.extend(locals, ext); - + // add cloudinary locals if configured if (keystone.get('cloudinary config')) { try { @@ -120,12 +121,12 @@ function render(req, res, view, ext) { } } } - + // fieldLocals defines locals that are provided to each field's `render` method locals.fieldLocals = _.pick(locals, '_', 'moment', 'numeral', 'env', 'js', 'utils', 'user', 'cloudinary'); - + var html = template(_.extend(locals, ext)); - + debug('sending down html'); res.send(html); } diff --git a/lib/core/routes.js b/lib/core/routes.js index 530d062b35..3781a16118 100644 --- a/lib/core/routes.js +++ b/lib/core/routes.js @@ -83,6 +83,12 @@ function routes(app) { app.post('/keystone/api/cloudinary/upload', require('../../admin/api/cloudinary').upload); } + // S3 API for uploading a new image + if (keystone.get('s3 config')) { + debug('setting S3 api'); + app.post('/keystone/api/s3/upload', require('../../admin/api/s3').upload); + } + // Init API request helpers app.use('/keystone/api', function(req, res, next) { res.apiError = function(key, err) { From 0091ee2af7c9ad1f4c71ae88826a4292837e29b5 Mon Sep 17 00:00:00 2001 From: Jarrett Widman Date: Tue, 11 Aug 2015 13:58:53 -0500 Subject: [PATCH 2/3] use csrf with S3 wysiwyg uploads --- admin/api/s3.js | 9 ++++++++- templates/layout/base.jade | 9 +++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/admin/api/s3.js b/admin/api/s3.js index 5f9d3eba74..f6fb0a8a4a 100644 --- a/admin/api/s3.js +++ b/admin/api/s3.js @@ -1,9 +1,14 @@ var knox = require('knox'); var keystone = require('../../'); +var Types = keystone.Field.Types; exports = module.exports = { upload: function(req, res) { + if (!keystone.security.csrf.validate(req, req.body.authenticity_token)) { + return res.status(400).send({ error: { message:'invalid csrf' } }); + } + if(req.files && req.files.file){ var s3Config = keystone.get('s3 config'); @@ -13,9 +18,11 @@ exports = module.exports = { originalname = file.originalname, filetype = file.mimetype || file.type; + var headers = Types.S3File.prototype.generateHeaders.call({s3config: s3Config, options: {}}, null, file); + var s3Client = knox.createClient(s3Config); - s3Client.putFile(file.path, path + file.name, function(err, s3Response) { + s3Client.putFile(file.path, path + file.name, headers, function(err, s3Response) { var sendResult = function () { if(err){ return res.send({ error: { message: err.message } }); diff --git a/templates/layout/base.jade b/templates/layout/base.jade index 1caee5efd0..67efc99fe6 100644 --- a/templates/layout/base.jade +++ b/templates/layout/base.jade @@ -5,6 +5,7 @@ html head meta(charset="utf-8") meta(name="viewport", content="initial-scale=1.0,user-scalable=no,maximum-scale=1,width=device-width") + meta(name="csrf-token", content="#{csrf_token_value}") title= title @@ -63,7 +64,7 @@ html - path = navList.path else - path = '/keystone/' + navList.path - + li(class=navList.key == list.key ? 'active' : null): a(href=path)= navList.label #body: .container block intro @@ -71,10 +72,10 @@ html block content #footer: .container - p #{brand} #{appversion} + p #{brand} #{appversion} | Powered by KeystoneJS version #{version}. if User && user - | Signed in as + | Signed in as a(href='/keystone/' + User.path + '/' + user.id)= User.getDocumentName(user) | . @@ -128,7 +129,7 @@ html script(src="/keystone/js/common/ui.js") script(src="/keystone/js/common/ui-alt-text.js") script(src="/keystone/js/common/ui-sortable.js") - + //- Page Scripts block js From d12d03ac799dc7fd2855d397fc704c4f499bf988 Mon Sep 17 00:00:00 2001 From: Jarrett Widman Date: Tue, 11 Aug 2015 17:00:17 -0500 Subject: [PATCH 3/3] linting --- admin/api/s3.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/admin/api/s3.js b/admin/api/s3.js index f6fb0a8a4a..2eb5e172f8 100644 --- a/admin/api/s3.js +++ b/admin/api/s3.js @@ -15,11 +15,9 @@ exports = module.exports = { var file = req.files.file, path = s3Config.s3path ? s3Config.s3path + '/' : ''; - originalname = file.originalname, - filetype = file.mimetype || file.type; - var headers = Types.S3File.prototype.generateHeaders.call({s3config: s3Config, options: {}}, null, file); - + var headers = Types.S3File.prototype.generateHeaders.call({ s3config: s3Config, options: {} }, null, file); + var s3Client = knox.createClient(s3Config); s3Client.putFile(file.path, path + file.name, headers, function(err, s3Response) { @@ -32,7 +30,7 @@ exports = module.exports = { if (s3Response.statusCode !== 200) { return res.send({ error: { message:'Amazon returned Http Code: ' + s3Response.statusCode } }); } else { - return res.send({ image: { url: "https://s3.amazonaws.com/" + s3Config.bucket + "/" + file.name } }); + return res.send({ image: { url: 'https://s3.amazonaws.com/' + s3Config.bucket + '/' + file.name } }); } } };