You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					218 lines
				
				6.4 KiB
			
		
		
			
		
	
	
					218 lines
				
				6.4 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const fp = require('fastify-plugin')
							 | 
						||
| 
								 | 
							
								const vary = require('./vary')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const defaultOptions = {
							 | 
						||
| 
								 | 
							
								  origin: '*',
							 | 
						||
| 
								 | 
							
								  methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
							 | 
						||
| 
								 | 
							
								  preflightContinue: false,
							 | 
						||
| 
								 | 
							
								  optionsSuccessStatus: 204,
							 | 
						||
| 
								 | 
							
								  credentials: false,
							 | 
						||
| 
								 | 
							
								  exposedHeaders: null,
							 | 
						||
| 
								 | 
							
								  allowedHeaders: null,
							 | 
						||
| 
								 | 
							
								  maxAge: null,
							 | 
						||
| 
								 | 
							
								  preflight: true,
							 | 
						||
| 
								 | 
							
								  strictPreflight: true
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function fastifyCors (fastify, opts, next) {
							 | 
						||
| 
								 | 
							
								  fastify.decorateRequest('corsPreflightEnabled', false)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let hideOptionsRoute = true
							 | 
						||
| 
								 | 
							
								  if (typeof opts === 'function') {
							 | 
						||
| 
								 | 
							
								    handleCorsOptionsDelegator(opts, fastify)
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    hideOptionsRoute = opts.hideOptionsRoute
							 | 
						||
| 
								 | 
							
								    const corsOptions = Object.assign({}, defaultOptions, opts)
							 | 
						||
| 
								 | 
							
								    fastify.addHook('onRequest', (req, reply, next) => {
							 | 
						||
| 
								 | 
							
								      onRequest(fastify, corsOptions, req, reply, next)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // The preflight reply must occur in the hook. This allows fastify-cors to reply to
							 | 
						||
| 
								 | 
							
								  // preflight requests BEFORE possible authentication plugins. If the preflight reply
							 | 
						||
| 
								 | 
							
								  // occurred in this handler, other plugins may deny the request since the browser will
							 | 
						||
| 
								 | 
							
								  // remove most headers (such as the Authentication header).
							 | 
						||
| 
								 | 
							
								  //
							 | 
						||
| 
								 | 
							
								  // This route simply enables fastify to accept preflight requests.
							 | 
						||
| 
								 | 
							
								  fastify.options('*', { schema: { hide: hideOptionsRoute } }, (req, reply) => {
							 | 
						||
| 
								 | 
							
								    if (!req.corsPreflightEnabled) {
							 | 
						||
| 
								 | 
							
								      // Do not handle preflight requests if the origin option disabled CORS
							 | 
						||
| 
								 | 
							
								      reply.callNotFound()
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    reply.send()
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  next()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function handleCorsOptionsDelegator (optionsResolver, fastify) {
							 | 
						||
| 
								 | 
							
								  fastify.addHook('onRequest', (req, reply, next) => {
							 | 
						||
| 
								 | 
							
								    if (optionsResolver.length === 2) {
							 | 
						||
| 
								 | 
							
								      handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next)
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      // handle delegator based on Promise
							 | 
						||
| 
								 | 
							
								      const ret = optionsResolver(reply)
							 | 
						||
| 
								 | 
							
								      if (ret && typeof ret.then === 'function') {
							 | 
						||
| 
								 | 
							
								        ret.then(options => Object.assign({}, defaultOptions, options))
							 | 
						||
| 
								 | 
							
								          .then(corsOptions => onRequest(fastify, corsOptions, req, reply, next)).catch(next)
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    next(new Error('Invalid CORS origin option'))
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function handleCorsOptionsCallbackDelegator (optionsResolver, fastify, req, reply, next) {
							 | 
						||
| 
								 | 
							
								  optionsResolver(req, (err, options) => {
							 | 
						||
| 
								 | 
							
								    if (err) {
							 | 
						||
| 
								 | 
							
								      next(err)
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      const corsOptions = Object.assign({}, defaultOptions, options)
							 | 
						||
| 
								 | 
							
								      onRequest(fastify, corsOptions, req, reply, next)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function onRequest (fastify, options, req, reply, next) {
							 | 
						||
| 
								 | 
							
								  // Always set Vary header
							 | 
						||
| 
								 | 
							
								  // https://github.com/rs/cors/issues/10
							 | 
						||
| 
								 | 
							
								  vary(reply, 'Origin')
							 | 
						||
| 
								 | 
							
								  const resolveOriginOption = typeof options.origin === 'function' ? resolveOriginWrapper(fastify, options.origin) : (_, cb) => cb(null, options.origin)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  resolveOriginOption(req, (error, resolvedOriginOption) => {
							 | 
						||
| 
								 | 
							
								    if (error !== null) {
							 | 
						||
| 
								 | 
							
								      return next(error)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Disable CORS and preflight if false
							 | 
						||
| 
								 | 
							
								    if (resolvedOriginOption === false) {
							 | 
						||
| 
								 | 
							
								      return next()
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Falsy values are invalid
							 | 
						||
| 
								 | 
							
								    if (!resolvedOriginOption) {
							 | 
						||
| 
								 | 
							
								      return next(new Error('Invalid CORS origin option'))
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    addCorsHeaders(req, reply, resolvedOriginOption, options)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (req.raw.method === 'OPTIONS' && options.preflight === true) {
							 | 
						||
| 
								 | 
							
								      // Strict mode enforces the required headers for preflight
							 | 
						||
| 
								 | 
							
								      if (options.strictPreflight === true && (!req.headers.origin || !req.headers['access-control-request-method'])) {
							 | 
						||
| 
								 | 
							
								        reply.status(400).type('text/plain').send('Invalid Preflight Request')
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      req.corsPreflightEnabled = true
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      addPreflightHeaders(req, reply, options)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!options.preflightContinue) {
							 | 
						||
| 
								 | 
							
								        // Do not call the hook callback and terminate the request
							 | 
						||
| 
								 | 
							
								        // Safari (and potentially other browsers) need content-length 0,
							 | 
						||
| 
								 | 
							
								        // for 204 or they just hang waiting for a body
							 | 
						||
| 
								 | 
							
								        reply
							 | 
						||
| 
								 | 
							
								          .code(options.optionsSuccessStatus)
							 | 
						||
| 
								 | 
							
								          .header('Content-Length', '0')
							 | 
						||
| 
								 | 
							
								          .send()
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return next()
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function addCorsHeaders (req, reply, originOption, corsOptions) {
							 | 
						||
| 
								 | 
							
								  reply.header('Access-Control-Allow-Origin',
							 | 
						||
| 
								 | 
							
								    getAccessControlAllowOriginHeader(req.headers.origin, originOption))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (corsOptions.credentials) {
							 | 
						||
| 
								 | 
							
								    reply.header('Access-Control-Allow-Credentials', 'true')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (corsOptions.exposedHeaders !== null) {
							 | 
						||
| 
								 | 
							
								    reply.header(
							 | 
						||
| 
								 | 
							
								      'Access-Control-Expose-Headers',
							 | 
						||
| 
								 | 
							
								      Array.isArray(corsOptions.exposedHeaders) ? corsOptions.exposedHeaders.join(', ') : corsOptions.exposedHeaders
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function addPreflightHeaders (req, reply, corsOptions) {
							 | 
						||
| 
								 | 
							
								  reply.header(
							 | 
						||
| 
								 | 
							
								    'Access-Control-Allow-Methods',
							 | 
						||
| 
								 | 
							
								    Array.isArray(corsOptions.methods) ? corsOptions.methods.join(', ') : corsOptions.methods
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (corsOptions.allowedHeaders === null) {
							 | 
						||
| 
								 | 
							
								    vary(reply, 'Access-Control-Request-Headers')
							 | 
						||
| 
								 | 
							
								    const reqAllowedHeaders = req.headers['access-control-request-headers']
							 | 
						||
| 
								 | 
							
								    if (reqAllowedHeaders !== undefined) {
							 | 
						||
| 
								 | 
							
								      reply.header('Access-Control-Allow-Headers', reqAllowedHeaders)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    reply.header(
							 | 
						||
| 
								 | 
							
								      'Access-Control-Allow-Headers',
							 | 
						||
| 
								 | 
							
								      Array.isArray(corsOptions.allowedHeaders) ? corsOptions.allowedHeaders.join(', ') : corsOptions.allowedHeaders
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (corsOptions.maxAge !== null) {
							 | 
						||
| 
								 | 
							
								    reply.header('Access-Control-Max-Age', String(corsOptions.maxAge))
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function resolveOriginWrapper (fastify, origin) {
							 | 
						||
| 
								 | 
							
								  return function (req, cb) {
							 | 
						||
| 
								 | 
							
								    const result = origin.call(fastify, req.headers.origin, cb)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Allow for promises
							 | 
						||
| 
								 | 
							
								    if (result && typeof result.then === 'function') {
							 | 
						||
| 
								 | 
							
								      result.then(res => cb(null, res), cb)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function getAccessControlAllowOriginHeader (reqOrigin, originOption) {
							 | 
						||
| 
								 | 
							
								  if (originOption === '*') {
							 | 
						||
| 
								 | 
							
								    // allow any origin
							 | 
						||
| 
								 | 
							
								    return '*'
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof originOption === 'string') {
							 | 
						||
| 
								 | 
							
								    // fixed origin
							 | 
						||
| 
								 | 
							
								    return originOption
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // reflect origin
							 | 
						||
| 
								 | 
							
								  return isRequestOriginAllowed(reqOrigin, originOption) ? reqOrigin : false
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isRequestOriginAllowed (reqOrigin, allowedOrigin) {
							 | 
						||
| 
								 | 
							
								  if (Array.isArray(allowedOrigin)) {
							 | 
						||
| 
								 | 
							
								    for (let i = 0; i < allowedOrigin.length; ++i) {
							 | 
						||
| 
								 | 
							
								      if (isRequestOriginAllowed(reqOrigin, allowedOrigin[i])) {
							 | 
						||
| 
								 | 
							
								        return true
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return false
							 | 
						||
| 
								 | 
							
								  } else if (typeof allowedOrigin === 'string') {
							 | 
						||
| 
								 | 
							
								    return reqOrigin === allowedOrigin
							 | 
						||
| 
								 | 
							
								  } else if (allowedOrigin instanceof RegExp) {
							 | 
						||
| 
								 | 
							
								    return allowedOrigin.test(reqOrigin)
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    return !!allowedOrigin
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = fp(fastifyCors, {
							 | 
						||
| 
								 | 
							
								  fastify: '3.x',
							 | 
						||
| 
								 | 
							
								  name: 'fastify-cors'
							 | 
						||
| 
								 | 
							
								})
							 |