Skip to content

Commit da43343

Browse files
Akryumhaoqunjiang
andauthored
fix(CORS): only allow connections from the designated host (#4985)
* fix(cors): only allow localhost * fix: use host so it's configurable * fix: use cors options object * feat: use a custom graphql-server instead of the one from apollo plugin exports the httpServer instance * fix: add CORS validation in the http upgrade request Co-authored-by: Haoqun Jiang <haoqunjiang@gmail.com>
1 parent 8028d9f commit da43343

File tree

5 files changed

+239
-34
lines changed

5 files changed

+239
-34
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// modified from vue-cli-plugin-apollo/graphql-server
2+
// added a return value for the server() call
3+
4+
const http = require('http')
5+
const { chalk } = require('@vue/cli-shared-utils')
6+
const express = require('express')
7+
const { ApolloServer, gql } = require('apollo-server-express')
8+
const { PubSub } = require('graphql-subscriptions')
9+
const merge = require('deepmerge')
10+
11+
function defaultValue (provided, value) {
12+
return provided == null ? value : provided
13+
}
14+
15+
function autoCall (fn, ...context) {
16+
if (typeof fn === 'function') {
17+
return fn(...context)
18+
}
19+
return fn
20+
}
21+
22+
module.exports = (options, cb = null) => {
23+
// Default options
24+
options = merge({
25+
integratedEngine: false
26+
}, options)
27+
28+
// Express app
29+
const app = express()
30+
31+
// Customize those files
32+
let typeDefs = load(options.paths.typeDefs)
33+
const resolvers = load(options.paths.resolvers)
34+
const context = load(options.paths.context)
35+
const schemaDirectives = load(options.paths.directives)
36+
let pubsub
37+
try {
38+
pubsub = load(options.paths.pubsub)
39+
} catch (e) {
40+
if (process.env.NODE_ENV !== 'production' && !options.quiet) {
41+
console.log(chalk.yellow('Using default PubSub implementation for subscriptions.'))
42+
console.log(chalk.grey('You should provide a different implementation in production (for example with Redis) by exporting it in \'apollo-server/pubsub.js\'.'))
43+
}
44+
}
45+
let dataSources
46+
try {
47+
dataSources = load(options.paths.dataSources)
48+
} catch (e) {}
49+
50+
// GraphQL API Server
51+
52+
// Realtime subscriptions
53+
if (!pubsub) pubsub = new PubSub()
54+
55+
// Customize server
56+
try {
57+
const serverModule = load(options.paths.server)
58+
serverModule(app)
59+
} catch (e) {
60+
// No file found
61+
}
62+
63+
// Apollo server options
64+
65+
typeDefs = processSchema(typeDefs)
66+
67+
let apolloServerOptions = {
68+
typeDefs,
69+
resolvers,
70+
schemaDirectives,
71+
dataSources,
72+
tracing: true,
73+
cacheControl: true,
74+
engine: !options.integratedEngine,
75+
// Resolvers context from POST
76+
context: async ({ req, connection }) => {
77+
let contextData
78+
try {
79+
if (connection) {
80+
contextData = await autoCall(context, { connection })
81+
} else {
82+
contextData = await autoCall(context, { req })
83+
}
84+
} catch (e) {
85+
console.error(e)
86+
throw e
87+
}
88+
contextData = Object.assign({}, contextData, { pubsub })
89+
return contextData
90+
},
91+
// Resolvers context from WebSocket
92+
subscriptions: {
93+
path: options.subscriptionsPath,
94+
onConnect: async (connection, websocket) => {
95+
let contextData = {}
96+
try {
97+
contextData = await autoCall(context, {
98+
connection,
99+
websocket
100+
})
101+
contextData = Object.assign({}, contextData, { pubsub })
102+
} catch (e) {
103+
console.error(e)
104+
throw e
105+
}
106+
return contextData
107+
}
108+
}
109+
}
110+
111+
// Automatic mocking
112+
if (options.enableMocks) {
113+
// Customize this file
114+
apolloServerOptions.mocks = load(options.paths.mocks)
115+
apolloServerOptions.mockEntireSchema = false
116+
117+
if (!options.quiet) {
118+
if (process.env.NODE_ENV === 'production') {
119+
console.warn('Automatic mocking is enabled, consider disabling it with the \'enableMocks\' option.')
120+
} else {
121+
console.log('✔️ Automatic mocking is enabled')
122+
}
123+
}
124+
}
125+
126+
// Apollo Engine
127+
if (options.enableEngine && options.integratedEngine) {
128+
if (options.engineKey) {
129+
apolloServerOptions.engine = {
130+
apiKey: options.engineKey,
131+
schemaTag: options.schemaTag,
132+
...options.engineOptions || {}
133+
}
134+
console.log('✔️ Apollo Engine is enabled')
135+
} else if (!options.quiet) {
136+
console.log(chalk.yellow('Apollo Engine key not found.') + `To enable Engine, set the ${chalk.cyan('VUE_APP_APOLLO_ENGINE_KEY')} env variable.`)
137+
console.log('Create a key at https://engine.apollographql.com/')
138+
console.log('You may see `Error: Must provide document` errors (query persisting tries).')
139+
}
140+
} else {
141+
apolloServerOptions.engine = false
142+
}
143+
144+
// Final options
145+
apolloServerOptions = merge(apolloServerOptions, defaultValue(options.serverOptions, {}))
146+
147+
// Apollo Server
148+
const server = new ApolloServer(apolloServerOptions)
149+
150+
// Express middleware
151+
server.applyMiddleware({
152+
app,
153+
path: options.graphqlPath,
154+
cors: options.cors
155+
// gui: {
156+
// endpoint: graphqlPath,
157+
// subscriptionEndpoint: graphqlSubscriptionsPath,
158+
// },
159+
})
160+
161+
// Start server
162+
const httpServer = http.createServer(app)
163+
httpServer.setTimeout(options.timeout)
164+
server.installSubscriptionHandlers(httpServer)
165+
166+
httpServer.listen({
167+
host: options.host || 'localhost',
168+
port: options.port
169+
}, () => {
170+
if (!options.quiet) {
171+
console.log(`✔️ GraphQL Server is running on ${chalk.cyan(`http://localhost:${options.port}${options.graphqlPath}`)}`)
172+
if (process.env.NODE_ENV !== 'production' && !process.env.VUE_CLI_API_MODE) {
173+
console.log(`✔️ Type ${chalk.cyan('rs')} to restart the server`)
174+
}
175+
}
176+
177+
cb && cb()
178+
})
179+
180+
// added in order to let vue cli to deal with the http upgrade request
181+
return {
182+
apolloServer: server,
183+
httpServer
184+
}
185+
}
186+
187+
function load (file) {
188+
const module = require(file)
189+
if (module.default) {
190+
return module.default
191+
}
192+
return module
193+
}
194+
195+
function processSchema (typeDefs) {
196+
if (Array.isArray(typeDefs)) {
197+
return typeDefs.map(processSchema)
198+
}
199+
200+
if (typeof typeDefs === 'string') {
201+
// Convert schema to AST
202+
typeDefs = gql(typeDefs)
203+
}
204+
205+
// Remove upload scalar (it's already included in Apollo Server)
206+
removeFromSchema(typeDefs, 'ScalarTypeDefinition', 'Upload')
207+
208+
return typeDefs
209+
}
210+
211+
function removeFromSchema (document, kind, name) {
212+
const definitions = document.definitions
213+
const index = definitions.findIndex(
214+
def => def.kind === kind && def.name.kind === 'Name' && def.name.value === name
215+
)
216+
if (index !== -1) {
217+
definitions.splice(index, 1)
218+
}
219+
}

packages/@vue/cli-ui/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
"dependencies": {
3636
"@akryum/winattr": "^3.0.0",
3737
"@vue/cli-shared-utils": "^4.1.2",
38+
"apollo-server-express": "^2.9.6",
3839
"clone": "^2.1.1",
3940
"deepmerge": "^4.2.2",
41+
"express": "^4.17.1",
4042
"express-history-api-fallback": "^2.2.1",
4143
"fkill": "^6.1.0",
4244
"fs-extra": "^7.0.1",

packages/@vue/cli-ui/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
exports.server = require('vue-cli-plugin-apollo/graphql-server')
1+
exports.server = require('./graphql-server')
22
exports.portfinder = require('portfinder')

packages/@vue/cli/lib/ui.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ async function ui (options = {}, context = process.cwd()) {
3636
subscriptionsPath: '/graphql',
3737
enableMocks: false,
3838
enableEngine: false,
39-
cors: '*',
39+
cors: {
40+
origin: host
41+
},
4042
timeout: 1000000,
4143
quiet: true,
4244
paths: {
@@ -49,7 +51,7 @@ async function ui (options = {}, context = process.cwd()) {
4951
}
5052
}
5153

52-
server(opts, () => {
54+
const { httpServer } = server(opts, () => {
5355
// Reset for yarn/npm to work correctly
5456
if (typeof nodeEnv === 'undefined') {
5557
delete process.env.NODE_ENV
@@ -66,6 +68,13 @@ async function ui (options = {}, context = process.cwd()) {
6668
openBrowser(url)
6769
}
6870
})
71+
72+
httpServer.on('upgrade', (req, socket) => {
73+
const { origin } = req.headers
74+
if (!origin || !(new RegExp(host)).test(origin)) {
75+
socket.destroy()
76+
}
77+
})
6978
}
7079

7180
module.exports = (...args) => {

yarn.lock

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6734,7 +6734,7 @@ detect-indent@^6.0.0:
67346734
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd"
67356735
integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==
67366736

6737-
detect-libc@^1.0.2, detect-libc@^1.0.3:
6737+
detect-libc@^1.0.3:
67386738
version "1.0.3"
67396739
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
67406740
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
@@ -9608,7 +9608,7 @@ hyperlinker@^1.0.0:
96089608
resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e"
96099609
integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==
96109610

9611-
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
9611+
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
96129612
version "0.4.24"
96139613
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
96149614
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -12662,15 +12662,6 @@ neat-csv@^2.1.0:
1266212662
get-stream "^2.1.0"
1266312663
into-stream "^2.0.0"
1266412664

12665-
needle@^2.2.1:
12666-
version "2.4.0"
12667-
resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
12668-
integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
12669-
dependencies:
12670-
debug "^3.2.6"
12671-
iconv-lite "^0.4.4"
12672-
sax "^1.2.4"
12673-
1267412665
negotiator@0.6.2:
1267512666
version "0.6.2"
1267612667
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -12856,22 +12847,6 @@ node-notifier@^6.0.0:
1285612847
shellwords "^0.1.1"
1285712848
which "^1.3.1"
1285812849

12859-
node-pre-gyp@*:
12860-
version "0.14.0"
12861-
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
12862-
integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==
12863-
dependencies:
12864-
detect-libc "^1.0.2"
12865-
mkdirp "^0.5.1"
12866-
needle "^2.2.1"
12867-
nopt "^4.0.1"
12868-
npm-packlist "^1.1.6"
12869-
npmlog "^4.0.2"
12870-
rc "^1.2.7"
12871-
rimraf "^2.6.1"
12872-
semver "^5.3.0"
12873-
tar "^4.4.2"
12874-
1287512850
node-releases@^1.1.47:
1287612851
version "1.1.47"
1287712852
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.47.tgz#c59ef739a1fd7ecbd9f0b7cf5b7871e8a8b591e4"
@@ -13030,7 +13005,7 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1:
1303013005
semver "^5.6.0"
1303113006
validate-npm-package-name "^3.0.0"
1303213007

13033-
npm-packlist@^1.1.6, npm-packlist@^1.4.4:
13008+
npm-packlist@^1.4.4:
1303413009
version "1.4.8"
1303513010
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
1303613011
integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
@@ -13069,7 +13044,7 @@ npm-run-path@^4.0.0:
1306913044
dependencies:
1307013045
path-key "^3.0.0"
1307113046

13072-
npmlog@^4.0.2, npmlog@^4.1.2:
13047+
npmlog@^4.1.2:
1307313048
version "4.1.2"
1307413049
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
1307513050
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -14758,7 +14733,7 @@ raw-body@^2.2.0:
1475814733
iconv-lite "0.4.24"
1475914734
unpipe "1.0.0"
1476014735

14761-
rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8:
14736+
rc@^1.0.1, rc@^1.1.6, rc@^1.2.8:
1476214737
version "1.2.8"
1476314738
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
1476414739
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@@ -16578,7 +16553,7 @@ tar@4.4.2:
1657816553
safe-buffer "^5.1.2"
1657916554
yallist "^3.0.2"
1658016555

16581-
tar@^4.4.10, tar@^4.4.12, tar@^4.4.2, tar@^4.4.8:
16556+
tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
1658216557
version "4.4.13"
1658316558
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
1658416559
integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==

0 commit comments

Comments
 (0)