index.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. const express = require('express')
  2. const qs = require('querystring')
  3. const uppy = require('../companion')
  4. const helmet = require('helmet')
  5. const morgan = require('morgan')
  6. const bodyParser = require('body-parser')
  7. // @ts-ignore
  8. const promBundle = require('express-prom-bundle')
  9. const session = require('express-session')
  10. const addRequestId = require('express-request-id')()
  11. const helper = require('./helper')
  12. // @ts-ignore
  13. const { version } = require('../../package.json')
  14. const app = express()
  15. // for server metrics tracking.
  16. const metricsMiddleware = promBundle({ includeMethod: true })
  17. const promClient = metricsMiddleware.promClient
  18. const collectDefaultMetrics = promClient.collectDefaultMetrics
  19. const promInterval = collectDefaultMetrics({ register: promClient.register, timeout: 5000 })
  20. // Add version as a prometheus gauge
  21. const versionGauge = new promClient.Gauge({ name: 'companion_version', help: 'npm version as an integer' })
  22. // @ts-ignore
  23. const numberVersion = version.replace(/\D/g, '') * 1
  24. versionGauge.set(numberVersion)
  25. if (app.get('env') !== 'test') {
  26. clearInterval(promInterval)
  27. }
  28. app.use(addRequestId)
  29. // log server requests.
  30. app.use(morgan('combined'))
  31. morgan.token('url', (req, res) => {
  32. const mask = (key) => {
  33. // don't log access_tokens in urls
  34. const query = Object.assign({}, req.query)
  35. // replace logged access token with xxxx character
  36. query[key] = 'x'.repeat(req.query[key].length)
  37. return `${req.path}?${qs.stringify(query)}`
  38. }
  39. if (req.query && req.query.access_token) {
  40. return mask('access_token')
  41. } else if (req.query && req.query.uppyAuthToken) {
  42. return mask('uppyAuthToken')
  43. }
  44. return req.originalUrl || req.url
  45. })
  46. // make app metrics available at '/metrics'.
  47. app.use(metricsMiddleware)
  48. app.use(bodyParser.json())
  49. app.use(bodyParser.urlencoded({ extended: false }))
  50. // Use helmet to secure Express headers
  51. app.use(helmet.frameguard())
  52. app.use(helmet.xssFilter())
  53. app.use(helmet.noSniff())
  54. app.use(helmet.ieNoOpen())
  55. app.disable('x-powered-by')
  56. const uppyOptions = helper.getUppyOptions()
  57. const sessionOptions = {
  58. secret: uppyOptions.secret,
  59. resave: true,
  60. saveUninitialized: true
  61. }
  62. if (process.env.COMPANION_REDIS_URL) {
  63. const RedisStore = require('connect-redis')(session)
  64. sessionOptions.store = new RedisStore({
  65. url: process.env.COMPANION_REDIS_URL
  66. })
  67. }
  68. if (process.env.COMPANION_COOKIE_DOMAIN) {
  69. sessionOptions.cookie = {
  70. domain: process.env.COMPANION_COOKIE_DOMAIN,
  71. maxAge: 24 * 60 * 60 * 1000 // 1 day
  72. }
  73. }
  74. app.use(session(sessionOptions))
  75. app.use((req, res, next) => {
  76. const protocol = process.env.COMPANION_PROTOCOL || 'http'
  77. // if endpoint urls are specified, then we only allow those endpoints
  78. // otherwise, we allow any client url to access companion.
  79. // here we also enforce that only the protocol allowed by companion is used.
  80. if (process.env.COMPANION_CLIENT_ORIGINS) {
  81. const whitelist = process.env.COMPANION_CLIENT_ORIGINS
  82. .split(',')
  83. .map((url) => helper.hasProtocol(url) ? url : `${protocol}://${url}`)
  84. // @ts-ignore
  85. if (req.headers.origin && whitelist.indexOf(req.headers.origin) > -1) {
  86. res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
  87. // only allow credentials when origin is whitelisted
  88. res.setHeader('Access-Control-Allow-Credentials', 'true')
  89. }
  90. } else {
  91. res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*')
  92. }
  93. res.setHeader(
  94. 'Access-Control-Allow-Methods',
  95. 'GET, POST, OPTIONS, PUT, PATCH, DELETE'
  96. )
  97. res.setHeader(
  98. 'Access-Control-Allow-Headers',
  99. 'Authorization, Origin, Content-Type, Accept'
  100. )
  101. next()
  102. })
  103. // Routes
  104. app.get('/', (req, res) => {
  105. res.setHeader('Content-Type', 'text/plain')
  106. res.send(helper.buildHelpfulStartupMessage(uppyOptions))
  107. })
  108. // initialize uppy
  109. helper.validateConfig(uppyOptions)
  110. if (process.env.COMPANION_PATH) {
  111. app.use(process.env.COMPANION_PATH, uppy.app(uppyOptions))
  112. } else {
  113. app.use(uppy.app(uppyOptions))
  114. }
  115. app.use((req, res, next) => {
  116. return res.status(404).json({ message: 'Not Found' })
  117. })
  118. if (app.get('env') === 'production') {
  119. // @ts-ignore
  120. app.use((err, req, res, next) => {
  121. console.error('\x1b[31m', req.id, err, '\x1b[0m')
  122. res.status(err.status || 500).json({ message: 'Something went wrong', requestId: req.id })
  123. })
  124. } else {
  125. // @ts-ignore
  126. app.use((err, req, res, next) => {
  127. console.error('\x1b[31m', req.id, err, '\x1b[0m')
  128. res.status(err.status || 500).json({ message: err.message, error: err, requestId: req.id })
  129. })
  130. }
  131. module.exports = { app, uppyOptions }