summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js5
-rw-r--r--app/assets/javascripts/dropzone_input.js9
-rw-r--r--app/assets/javascripts/lib/utils/csrf.js56
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js5
-rw-r--r--spec/javascripts/lib/utils/csrf_token_spec.js49
5 files changed, 112 insertions, 12 deletions
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 26d3419a162..ddd1fea3aca 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -3,6 +3,7 @@
import '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
+import csrf from '../lib/utils/csrf';
function toggleLoading($el, $icon, loading) {
if (loading) {
@@ -36,9 +37,7 @@ export default class BlobFileDropzone {
maxFiles: 1,
addRemoveLinks: true,
previewsContainer: '.dropzone-previews',
- headers: {
- 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'),
- },
+ headers: csrf.headers,
init: function () {
this.on('addedfile', function () {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 975903159be..1cba65d17cd 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -2,6 +2,7 @@
/* global Dropzone */
import _ from 'underscore';
import './preview_markdown';
+import csrf from './lib/utils/csrf';
window.DropzoneInput = (function() {
function DropzoneInput(form) {
@@ -50,9 +51,7 @@ window.DropzoneInput = (function() {
paramName: 'file',
maxFilesize: maxFileSize,
uploadMultiple: false,
- headers: {
- 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
- },
+ headers: csrf.headers,
previewContainer: false,
processing: function() {
return $('.div-dropzone-alert').alert('close');
@@ -260,9 +259,7 @@ window.DropzoneInput = (function() {
dataType: 'json',
processData: false,
contentType: false,
- headers: {
- 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
- },
+ headers: csrf.headers,
beforeSend: function() {
showSpinner();
return closeAlertMessage();
diff --git a/app/assets/javascripts/lib/utils/csrf.js b/app/assets/javascripts/lib/utils/csrf.js
new file mode 100644
index 00000000000..ae41cc5e8a8
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/csrf.js
@@ -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;
+
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
index 7f8e514fda1..b9693892f45 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import VueResource from 'vue-resource';
+import csrf from '../lib/utils/csrf';
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
// and polling.
Vue.http.interceptors.push((request, next) => {
- if ($.rails) {
- request.headers.set('X-CSRF-Token', $.rails.csrfToken());
- }
+ request.headers.set(csrf.headerKey, csrf.token);
next((response) => {
// Headers object has a `forEach` property that iterates through all values.
diff --git a/spec/javascripts/lib/utils/csrf_token_spec.js b/spec/javascripts/lib/utils/csrf_token_spec.js
new file mode 100644
index 00000000000..c484213df8e
--- /dev/null
+++ b/spec/javascripts/lib/utils/csrf_token_spec.js
@@ -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);
+ });
+ });
+});