Standardize access to CSRF token in JavaScript

This commit is contained in:
Bryce Johnson 2017-09-21 17:53:28 +00:00 committed by Phil Hughes
parent 6c0473ef6f
commit cca06da2e4
5 changed files with 112 additions and 12 deletions

View file

@ -3,6 +3,7 @@
import '../lib/utils/url_utility'; import '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants'; import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf';
function toggleLoading($el, $icon, loading) { function toggleLoading($el, $icon, loading) {
if (loading) { if (loading) {
@ -36,9 +37,7 @@ export default class BlobFileDropzone {
maxFiles: 1, maxFiles: 1,
addRemoveLinks: true, addRemoveLinks: true,
previewsContainer: '.dropzone-previews', previewsContainer: '.dropzone-previews',
headers: { headers: csrf.headers,
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'),
},
init: function () { init: function () {
this.on('addedfile', function () { this.on('addedfile', function () {
toggleLoading(submitButton, submitButtonLoadingIcon, false); toggleLoading(submitButton, submitButtonLoadingIcon, false);

View file

@ -2,6 +2,7 @@
/* global Dropzone */ /* global Dropzone */
import _ from 'underscore'; import _ from 'underscore';
import './preview_markdown'; import './preview_markdown';
import csrf from './lib/utils/csrf';
window.DropzoneInput = (function() { window.DropzoneInput = (function() {
function DropzoneInput(form) { function DropzoneInput(form) {
@ -50,9 +51,7 @@ window.DropzoneInput = (function() {
paramName: 'file', paramName: 'file',
maxFilesize: maxFileSize, maxFilesize: maxFileSize,
uploadMultiple: false, uploadMultiple: false,
headers: { headers: csrf.headers,
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
previewContainer: false, previewContainer: false,
processing: function() { processing: function() {
return $('.div-dropzone-alert').alert('close'); return $('.div-dropzone-alert').alert('close');
@ -260,9 +259,7 @@ window.DropzoneInput = (function() {
dataType: 'json', dataType: 'json',
processData: false, processData: false,
contentType: false, contentType: false,
headers: { headers: csrf.headers,
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
beforeSend: function() { beforeSend: function() {
showSpinner(); showSpinner();
return closeAlertMessage(); return closeAlertMessage();

View file

@ -0,0 +1,56 @@
/*
This module provides easy access to the CSRF token and caches
it for re-use. It also exposes some values commonly used in relation
to the CSRF token (header key and headers object).
If you need to refresh the csrfToken for some reason, just call `init` and
then use the accessors as you would normally.
If you need to compose a headers object, use the spread operator:
```
headers: {
...csrf.headers,
someOtherHeader: '12345',
}
```
*/
const csrf = {
init() {
const tokenEl = document.querySelector('meta[name=csrf-token]');
if (tokenEl !== null) {
this.csrfToken = tokenEl.getAttribute('content');
} else {
this.csrfToken = null;
}
},
get token() {
return this.csrfToken;
},
get headerKey() {
return 'X-CSRF-Token';
},
get headers() {
if (this.csrfToken !== null) {
return {
[this.headerKey]: this.token,
};
}
return {};
},
};
csrf.init();
// use our cached token for any $.rails-generated AJAX requests
if ($.rails) {
$.rails.csrfToken = () => csrf.token;
}
export default csrf;

View file

@ -1,5 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
import csrf from '../lib/utils/csrf';
Vue.use(VueResource); Vue.use(VueResource);
@ -18,9 +19,7 @@ Vue.http.interceptors.push((request, next) => {
// New Vue Resource version uses Headers, we are expecting a plain object to render pagination // New Vue Resource version uses Headers, we are expecting a plain object to render pagination
// and polling. // and polling.
Vue.http.interceptors.push((request, next) => { Vue.http.interceptors.push((request, next) => {
if ($.rails) { request.headers.set(csrf.headerKey, csrf.token);
request.headers.set('X-CSRF-Token', $.rails.csrfToken());
}
next((response) => { next((response) => {
// Headers object has a `forEach` property that iterates through all values. // Headers object has a `forEach` property that iterates through all values.

View file

@ -0,0 +1,49 @@
import csrf from '~/lib/utils/csrf';
describe('csrf', () => {
beforeEach(() => {
this.tokenKey = 'X-CSRF-Token';
this.token = 'pH1cvjnP9grx2oKlhWEDvUZnJ8x2eXsIs1qzyHkF3DugSG5yTxR76CWeEZRhML2D1IeVB7NEW0t5l/axE4iJpQ==';
});
it('returns the correct headerKey', () => {
expect(csrf.headerKey).toBe(this.tokenKey);
});
describe('when csrf token is in the DOM', () => {
beforeEach(() => {
setFixtures(`
<meta name="csrf-token" content="${this.token}">
`);
csrf.init();
});
it('returns the csrf token', () => {
expect(csrf.token).toBe(this.token);
});
it('returns the csrf headers object', () => {
expect(csrf.headers[this.tokenKey]).toBe(this.token);
});
});
describe('when csrf token is not in the DOM', () => {
beforeEach(() => {
setFixtures(`
<meta name="some-other-token">
`);
csrf.init();
});
it('returns null for token', () => {
expect(csrf.token).toBeNull();
});
it('returns empty object for headers', () => {
expect(typeof csrf.headers).toBe('object');
expect(Object.keys(csrf.headers).length).toBe(0);
});
});
});