Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(CORS): only allow connections from the designated host #4985

Merged
merged 6 commits into from Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
219 changes: 219 additions & 0 deletions packages/@vue/cli-ui/graphql-server.js
@@ -0,0 +1,219 @@
// modified from vue-cli-plugin-apollo/graphql-server
// added a return value for the server() call

const http = require('http')
const { chalk } = require('@vue/cli-shared-utils')
const express = require('express')
const { ApolloServer, gql } = require('apollo-server-express')
const { PubSub } = require('graphql-subscriptions')
const merge = require('deepmerge')

function defaultValue (provided, value) {
return provided == null ? value : provided
}

function autoCall (fn, ...context) {
if (typeof fn === 'function') {
return fn(...context)
}
return fn
}

module.exports = (options, cb = null) => {
// Default options
options = merge({
integratedEngine: false
}, options)

// Express app
const app = express()

// Customize those files
let typeDefs = load(options.paths.typeDefs)
const resolvers = load(options.paths.resolvers)
const context = load(options.paths.context)
const schemaDirectives = load(options.paths.directives)
let pubsub
try {
pubsub = load(options.paths.pubsub)
} catch (e) {
if (process.env.NODE_ENV !== 'production' && !options.quiet) {
console.log(chalk.yellow('Using default PubSub implementation for subscriptions.'))
console.log(chalk.grey('You should provide a different implementation in production (for example with Redis) by exporting it in \'apollo-server/pubsub.js\'.'))
}
}
let dataSources
try {
dataSources = load(options.paths.dataSources)
} catch (e) {}

// GraphQL API Server

// Realtime subscriptions
if (!pubsub) pubsub = new PubSub()

// Customize server
try {
const serverModule = load(options.paths.server)
serverModule(app)
} catch (e) {
// No file found
}

// Apollo server options

typeDefs = processSchema(typeDefs)

let apolloServerOptions = {
typeDefs,
resolvers,
schemaDirectives,
dataSources,
tracing: true,
cacheControl: true,
engine: !options.integratedEngine,
// Resolvers context from POST
context: async ({ req, connection }) => {
let contextData
try {
if (connection) {
contextData = await autoCall(context, { connection })
} else {
contextData = await autoCall(context, { req })
}
} catch (e) {
console.error(e)
throw e
}
contextData = Object.assign({}, contextData, { pubsub })
return contextData
},
// Resolvers context from WebSocket
subscriptions: {
path: options.subscriptionsPath,
onConnect: async (connection, websocket) => {
let contextData = {}
try {
contextData = await autoCall(context, {
connection,
websocket
})
contextData = Object.assign({}, contextData, { pubsub })
} catch (e) {
console.error(e)
throw e
}
return contextData
}
}
}

// Automatic mocking
if (options.enableMocks) {
// Customize this file
apolloServerOptions.mocks = load(options.paths.mocks)
apolloServerOptions.mockEntireSchema = false

if (!options.quiet) {
if (process.env.NODE_ENV === 'production') {
console.warn('Automatic mocking is enabled, consider disabling it with the \'enableMocks\' option.')
} else {
console.log('✔️ Automatic mocking is enabled')
}
}
}

// Apollo Engine
if (options.enableEngine && options.integratedEngine) {
if (options.engineKey) {
apolloServerOptions.engine = {
apiKey: options.engineKey,
schemaTag: options.schemaTag,
...options.engineOptions || {}
}
console.log('✔️ Apollo Engine is enabled')
} else if (!options.quiet) {
console.log(chalk.yellow('Apollo Engine key not found.') + `To enable Engine, set the ${chalk.cyan('VUE_APP_APOLLO_ENGINE_KEY')} env variable.`)
console.log('Create a key at https://engine.apollographql.com/')
console.log('You may see `Error: Must provide document` errors (query persisting tries).')
}
} else {
apolloServerOptions.engine = false
}

// Final options
apolloServerOptions = merge(apolloServerOptions, defaultValue(options.serverOptions, {}))

// Apollo Server
const server = new ApolloServer(apolloServerOptions)

// Express middleware
server.applyMiddleware({
app,
path: options.graphqlPath,
cors: options.cors
// gui: {
// endpoint: graphqlPath,
// subscriptionEndpoint: graphqlSubscriptionsPath,
// },
})

// Start server
const httpServer = http.createServer(app)
httpServer.setTimeout(options.timeout)
server.installSubscriptionHandlers(httpServer)

httpServer.listen({
host: options.host || 'localhost',
port: options.port
}, () => {
if (!options.quiet) {
console.log(`✔️ GraphQL Server is running on ${chalk.cyan(`http://localhost:${options.port}${options.graphqlPath}`)}`)
if (process.env.NODE_ENV !== 'production' && !process.env.VUE_CLI_API_MODE) {
console.log(`✔️ Type ${chalk.cyan('rs')} to restart the server`)
}
}

cb && cb()
})

// added in order to let vue cli to deal with the http upgrade request
return {
apolloServer: server,
httpServer
}
}

function load (file) {
const module = require(file)
if (module.default) {
return module.default
}
return module
}

function processSchema (typeDefs) {
if (Array.isArray(typeDefs)) {
return typeDefs.map(processSchema)
}

if (typeof typeDefs === 'string') {
// Convert schema to AST
typeDefs = gql(typeDefs)
}

// Remove upload scalar (it's already included in Apollo Server)
removeFromSchema(typeDefs, 'ScalarTypeDefinition', 'Upload')

return typeDefs
}

function removeFromSchema (document, kind, name) {
const definitions = document.definitions
const index = definitions.findIndex(
def => def.kind === kind && def.name.kind === 'Name' && def.name.value === name
)
if (index !== -1) {
definitions.splice(index, 1)
}
}
2 changes: 2 additions & 0 deletions packages/@vue/cli-ui/package.json
Expand Up @@ -35,8 +35,10 @@
"dependencies": {
"@akryum/winattr": "^3.0.0",
"@vue/cli-shared-utils": "^4.1.2",
"apollo-server-express": "^2.9.6",
"clone": "^2.1.1",
"deepmerge": "^4.2.2",
"express": "^4.17.1",
"express-history-api-fallback": "^2.2.1",
"fkill": "^6.1.0",
"fs-extra": "^7.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/@vue/cli-ui/server.js
@@ -1,2 +1,2 @@
exports.server = require('vue-cli-plugin-apollo/graphql-server')
exports.server = require('./graphql-server')
exports.portfinder = require('portfinder')
13 changes: 11 additions & 2 deletions packages/@vue/cli/lib/ui.js
Expand Up @@ -36,7 +36,9 @@ async function ui (options = {}, context = process.cwd()) {
subscriptionsPath: '/graphql',
enableMocks: false,
enableEngine: false,
cors: '*',
cors: {
origin: host
},
timeout: 1000000,
quiet: true,
paths: {
Expand All @@ -49,7 +51,7 @@ async function ui (options = {}, context = process.cwd()) {
}
}

server(opts, () => {
const { httpServer } = server(opts, () => {
// Reset for yarn/npm to work correctly
if (typeof nodeEnv === 'undefined') {
delete process.env.NODE_ENV
Expand All @@ -66,6 +68,13 @@ async function ui (options = {}, context = process.cwd()) {
openBrowser(url)
}
})

httpServer.on('upgrade', (req, socket) => {
const { origin } = req.headers
if (!origin || !(new RegExp(host)).test(origin)) {
socket.destroy()
}
})
}

module.exports = (...args) => {
Expand Down
37 changes: 6 additions & 31 deletions yarn.lock
Expand Up @@ -6734,7 +6734,7 @@ detect-indent@^6.0.0:
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd"
integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==

detect-libc@^1.0.2, detect-libc@^1.0.3:
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
Expand Down Expand Up @@ -9608,7 +9608,7 @@ hyperlinker@^1.0.0:
resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e"
integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==

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

needle@^2.2.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
dependencies:
debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"

negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
Expand Down Expand Up @@ -12856,22 +12847,6 @@ node-notifier@^6.0.0:
shellwords "^0.1.1"
which "^1.3.1"

node-pre-gyp@*:
version "0.14.0"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
needle "^2.2.1"
nopt "^4.0.1"
npm-packlist "^1.1.6"
npmlog "^4.0.2"
rc "^1.2.7"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^4.4.2"

node-releases@^1.1.47:
version "1.1.47"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.47.tgz#c59ef739a1fd7ecbd9f0b7cf5b7871e8a8b591e4"
Expand Down Expand Up @@ -13030,7 +13005,7 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1:
semver "^5.6.0"
validate-npm-package-name "^3.0.0"

npm-packlist@^1.1.6, npm-packlist@^1.4.4:
npm-packlist@^1.4.4:
version "1.4.8"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
Expand Down Expand Up @@ -13069,7 +13044,7 @@ npm-run-path@^4.0.0:
dependencies:
path-key "^3.0.0"

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

rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8:
rc@^1.0.1, rc@^1.1.6, rc@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
Expand Down Expand Up @@ -16578,7 +16553,7 @@ tar@4.4.2:
safe-buffer "^5.1.2"
yallist "^3.0.2"

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