Created
November 11, 2019 18:22
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 | |
})($); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.... */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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!'}); | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- 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