Skip to content

Instantly share code, notes, and snippets.

@gregbown
Created November 11, 2019 18:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gregbown/040521b2ab6d549e8ab59184ef942a10 to your computer and use it in GitHub Desktop.
Save gregbown/040521b2ab6d549e8ab59184ef942a10 to your computer and use it in GitHub Desktop.
How to use csurf with multipart/form-data example with express, express-fileupload, csurf and jquery + a bit of bootstrap and ejs
/* Form AJAX JavaScript. Requires jQurery to be loaded */
window.DEMO = window.DEMO || {};
/**
* @method rws
* @description RESTful Web services (RWS)
* Wrapped in an IIFE (Immediately Invoked Function Expression)
* @paraam $ {object} jQuery object, used for ajax methods.
*/
// eslint-disable-next-line no-undef
DEMO.rws = (function($) {
if (typeof $ === 'undefined')
throw new Error('jQuery is not defined');
function multipart(serviceUrl, formData, token, cb) {
$.ajax({
type: 'POST',
enctype: 'multipart/form-data',
url: serviceUrl,
headers: {'CSRF-Token': token},
data: formData,
processData: false,
contentType: false,
cache: false,
timeout: 10000,
success: cb,
error(err) {
console.log('ERROR : ', err); // TODO
}
});
}
return {multipart};
// eslint-disable-next-line no-undef
})($);
/* eslint-disable no-undef */
/* Form JavaScript */
window.DEMO = window.DEMO || {};
/**
* @method account
* @description Form handler. Plain JavaScript. No jQuery
* Wrapped in an IIFE (Immediately Invoked Function Expression)
*/
DEMO.form = (function() {
let profilePicture;
let profilePictureLabel;
let file;
let action;
let preview;
let reader;
let svg;
let upload;
let cancel;
let tokens;
let formData;
let profilePictureForm;
function init() {
tokens = document.querySelectorAll('[name=\'_csrf\']');
if (typeof tokens === 'undefined' || tokens.length < 1)
throw new Error('No CSRF tokens available');
profilePictureForm = document.getElementById('uploadPicture');
profilePicture = document.getElementById('profilePicture');
profilePictureLabel = document.getElementById('profilePictureLabel');
svg = document.getElementById('svg');
preview = document.getElementById('preview');
upload = document.getElementById('upload');
cancel = document.getElementById('cancel');
action = profilePictureForm.action;
if (profilePicture) {
reader = new FileReader();
reader.addEventListener('load', function(event) {
preview.src = reader.result;
svg.setAttribute('style', 'display:none;');
preview.setAttribute('style', 'display:block;');
upload.removeAttribute('disabled');
cancel.removeAttribute('disabled');
cancel.addEventListener('click', cancelUpload, false);
}, false);
profilePicture.addEventListener('change', function(event) {
file = this.files;
// If there is (at least) one file selected
if (file.length > 0) {
if (file[0].size < 500 * 1024) { // Check the constraint, smaller than 500KB
console.log('File size ok');
this.blur();
formData = new FormData(profilePictureForm);
profilePictureLabel.innerText = file[0].name;
reader.readAsDataURL(file[0]);
} else
console.log('File is too large');
}
}, false);
/* Disable default form functionality because we need to add csurf header to request */
profilePictureForm.addEventListener('submit', function(event) {
event.preventDefault();
event.stopPropagation();
/* Obviously jQuery and rws need to be loaded prior to this. */
DEMO.rws.multipart(action, formData, tokens[0].value, profilePictureUploadHandler);
}, false);
}
}
function profilePictureUploadHandler(res) {
console.log('File upload with CSURF: ', res);
}
function cancelUpload(event) {
console.log('Cancel', event);
preview.src = '';
profilePicture.value = '';
preview.setAttribute('style', 'display:none;');
svg.setAttribute('style', 'display:block;');
file = null;
profilePictureLabel.innerText = '';
upload.setAttribute('disabled', '');
cancel.setAttribute('disabled', '');
}
return {init};
})();
// noinspection JSUnresolvedVariable
if (document.getElementById('profilePicture'))
DEMO.form.init();
require("csurf");
require("express-fileupload");
/** code.... */
/* before:routes
* Define form input token for non-api routes
* Could also be used in api request headers */
app.use(csurf({ cookie: false })); // May be deprecated if we cant get it to play nice with JWT
/* Proving a point here. CSURF works with multipart/form-data */
app.use(fileupload({
limits: { fileSize: 500 * 1024 },
useTempFiles: true,
tempFileDir: '/resources/static/img/'
}));
/** define routes code.... */
/**
* @method
* @name addPicture
* @description route handler for POST /upload.
* For authenticated routes under /account
* Requires express-fileupload ^1.1.6-alpha.6 or greater
*
* @param req {object} The Request object represents the HTTP <a href="https://expressjs.com/en/api.html#req">request</a>
* and has properties for the request query string, parameters, body, HTTP headers, and so on.
*
* @param res {object} The Response object represents the HTTP <a href="https://expressjs.com/en/api.html#res">response</a>
* that an Express app sends when it gets an HTTP request.
*/
function addPicture(req, res) {
if (!req.files || Object.keys(req.files).length === 0)
return res.status(400).send({error: 'No files were uploaded.'});
// The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file
const profilePicture = req.files.profilePicture;
// Use the mv() method to place the file somewhere on your server
profilePicture.mv('./resources/static/img/' + req.files.profilePicture.name, function(err) {
if (err)
return res.status(400).send({error: err});
res.status(200).send({result: 'File uploaded!'});
});
}
<!-- requires jQuery, bootstrap 4.3+ and ejs -->
<form id="uploadPicture" action="/upload" method="post" enctype="multipart/form-data">
<div class="form-group row justify-content-center">
<div class="col-sm-2 justify-content-center">
<label class="form-label">Profile Picture</label>
<div class="image-constrain d-flex align-items-center">
<img src="" class="image-preview img-fluid img-thumbnail" id="preview" style="display:none;">
<svg
width="160" height="160" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg"
id="svg" class="bd-placeholder-img img-thumbnail" preserveAspectRatio="xMidYMid slice">
<rect width="100%" height="100%" fill="#868e96"></rect>
<text x="15%" y="50%" fill="#dee2e6">Upload Picture</text>
</svg>
</div>
</div>
<div class="col-sm-6">
<div class="input-group custom-file">
<input type="file" class="form-control custom-file-input" id="profilePicture" name="profilePicture" accept="image/png, image/jpeg, image/gif">
<div class="input-group-append">
<button id="cancel" class="btn btn-outline-secondary" type="button" disabled>Cancel</button>
<button id="upload" class="btn btn-outline-secondary" type="submit" disabled>Upload</button>
</div>
<label id="profilePictureLabel" class="custom-file-label" style="right: 15px;left: 15px;" for="profilePicture">Choose File</label>
</div>
</div>
</div>
</form>
<!-- outside form because we only want the image file in the form data -->
<input type="hidden" value="<%= xsrfToken %>" name="_csrf">
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment