Standardize access to CSRF token in JavaScript
This commit is contained in:
parent
6c0473ef6f
commit
cca06da2e4
5 changed files with 112 additions and 12 deletions
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
56
app/assets/javascripts/lib/utils/csrf.js
Normal file
56
app/assets/javascripts/lib/utils/csrf.js
Normal 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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
49
spec/javascripts/lib/utils/csrf_token_spec.js
Normal file
49
spec/javascripts/lib/utils/csrf_token_spec.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue