Skip to content

Commit eba11d8

Browse files
AustioAkryum
authored andcommitted
fix(ssr): Add serialize-javascript for removing xss from javascript delivered to client when exporting apollo states. Add jest for running unit tests. Add unit test for serialize functionality (#624)
1 parent db95e91 commit eba11d8

File tree

5 files changed

+1497
-32
lines changed

5 files changed

+1497
-32
lines changed

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"test:eslint": "eslint --ext .js {src,ssr,lib,build}/**/*.js",
1818
"test:types": "tsc -p types/test",
1919
"test:e2e": "cd tests/demo && yarn test",
20+
"test:unit": "yarn run jest",
2021
"docs:dev": "vuepress dev docs",
2122
"docs:build": "vuepress build docs"
2223
},
@@ -41,6 +42,7 @@
4142
},
4243
"dependencies": {
4344
"chalk": "^2.4.2",
45+
"serialize-javascript": "^1.7.0",
4446
"throttle-debounce": "^2.1.0"
4547
},
4648
"devDependencies": {
@@ -64,6 +66,7 @@
6466
"eslint-plugin-standard": "^4.0.0",
6567
"graphql": "^14.0.2",
6668
"graphql-tag": "^2.5.0",
69+
"jest": "^24.8.0",
6770
"nodemon": "^1.18.4",
6871
"rimraf": "^2.6.1",
6972
"rollup": "^0.66.6",
@@ -77,5 +80,8 @@
7780
"vue": "^2.5.16",
7881
"vue-property-decorator": "^7.0.0",
7982
"vuepress": "^0.14.2"
83+
},
84+
"jest": {
85+
"testRegex": "tests/unit/.*\\.test.js$"
8086
}
8187
}

ssr/index.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
exports.getStates = function (apolloProvider, options) {
1+
const serializeJs = require('serialize-javascript');
2+
3+
exports.serializeStates = function(apolloProvider, options = {}) {
4+
const state = exports.getStates(apolloProvider, options)
5+
6+
return options.useUnsafeSerializer
7+
? JSON.stringify(state)
8+
: serializeJs(state)
9+
}
10+
11+
exports.getStates = function (apolloProvider, options = {}) {
212
const finalOptions = Object.assign({}, {
313
exportNamespace: '',
414
}, options)
@@ -15,8 +25,8 @@ exports.exportStates = function (apolloProvider, options) {
1525
const finalOptions = Object.assign({}, {
1626
globalName: '__APOLLO_STATE__',
1727
attachTo: 'window',
18-
}, options)
19-
const states = exports.getStates(apolloProvider, finalOptions)
20-
const js = `${finalOptions.attachTo}.${finalOptions.globalName} = ${JSON.stringify(states)};`
21-
return js
28+
useUnsafeSerializer: false,
29+
}, options);
30+
31+
return `${finalOptions.attachTo}.${finalOptions.globalName} = ${exports.serializeStates(apolloProvider, options)};`
2232
}

tests/demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"test:e2e:dev:client": "vue-cli-service test:e2e --mode development",
1313
"test:e2e": "start-server-and-test apollo:run http://localhost:4000/.well-known/apollo/server-health test:e2e:client",
1414
"test:e2e:client": "vue-cli-service test:e2e --mode production --headless",
15-
"test": "yarn run lint --no-fix && yarn run test:e2e"
15+
"test": "yarn run lint --no-fix && yarn run test:unit && yarn run test:e2e"
1616
},
1717
"dependencies": {
1818
"graphql-type-json": "^0.2.1",

tests/unit/ssr.test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const ssr = require('../../ssr');
2+
3+
describe('ssr states', () => {
4+
function buildClient(cache) {
5+
return {
6+
cache: {
7+
extract() {
8+
return cache
9+
}
10+
}
11+
}
12+
}
13+
14+
const defaultClient = buildClient({
15+
"foo": "<alert>hiya!</alert>",
16+
});
17+
18+
const otherClient = buildClient({
19+
"foo": "bar",
20+
});
21+
22+
const apolloProvider = {
23+
clients: {
24+
defaultClient,
25+
profile: defaultClient,
26+
other: otherClient,
27+
}
28+
};
29+
30+
describe('serializeStates', () => {
31+
it('safely serializes by default', () => {
32+
const safe = '{"defaultClient":{"foo":"\u003Calert\u003Ehiya!\u003C\u002Falert\u003E"},"profile":{"foo":"\u003Calert\u003Ehiya!\u003C\u002Falert\u003E"},"other":{"foo":"bar"}}'
33+
34+
expect(ssr.serializeStates(apolloProvider)).not.toMatch("<alert>hiya!</alert>");
35+
});
36+
37+
it('allows option to use raw JSON stringify', () => {
38+
const unsafe = '{"defaultClient":{"foo":"<alert>hiya!</alert>"},"profile":{"foo":"<alert>hiya!</alert>"},"other":{"foo":"bar"}}';
39+
40+
expect(ssr.serializeStates(apolloProvider, { useUnsafeSerializer: true })).toMatch(unsafe);
41+
})
42+
});
43+
44+
describe('getStates', () => {
45+
it('exports provider clients to object', () => {
46+
expect(ssr.getStates(apolloProvider)).toMatchObject({
47+
defaultClient: { foo: '<alert>hiya!</alert>' },
48+
profile: { foo: '<alert>hiya!</alert>' },
49+
other: { foo: 'bar' }
50+
});
51+
});
52+
});
53+
54+
describe('exportStates', () => {
55+
it('sets attachTo and globalName equal to serializedstates', () => {
56+
const string = ssr.exportStates(apolloProvider, { globalName: "NUXT", attachTo: "global" })
57+
58+
expect(string).toMatch(/^global\.NUXT/);
59+
});
60+
});
61+
});

0 commit comments

Comments
 (0)