companion.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. const fs = require('node:fs')
  2. const { isURL } = require('validator')
  3. const logger = require('../server/logger')
  4. const { defaultGetKey } = require('../server/helpers/utils')
  5. const defaultOptions = {
  6. server: {
  7. protocol: 'http',
  8. path: '',
  9. },
  10. providerOptions: {},
  11. s3: {
  12. endpoint: 'https://{service}.{region}.amazonaws.com',
  13. conditions: [],
  14. useAccelerateEndpoint: false,
  15. getKey: defaultGetKey,
  16. expires: 800, // seconds
  17. },
  18. enableUrlEndpoint: false,
  19. allowLocalUrls: false,
  20. periodicPingUrls: [],
  21. streamingUpload: false,
  22. clientSocketConnectTimeout: 60000,
  23. metrics: true,
  24. }
  25. /**
  26. * @param {object} companionOptions
  27. */
  28. function getMaskableSecrets (companionOptions) {
  29. const secrets = []
  30. const { providerOptions, customProviders, s3 } = companionOptions
  31. Object.keys(providerOptions).forEach((provider) => {
  32. if (providerOptions[provider].secret) {
  33. secrets.push(providerOptions[provider].secret)
  34. }
  35. })
  36. if (customProviders) {
  37. Object.keys(customProviders).forEach((provider) => {
  38. if (customProviders[provider].config && customProviders[provider].config.secret) {
  39. secrets.push(customProviders[provider].config.secret)
  40. }
  41. })
  42. }
  43. if (s3?.secret) {
  44. secrets.push(s3.secret)
  45. }
  46. return secrets
  47. }
  48. /**
  49. * validates that the mandatory companion options are set.
  50. * If it is invalid, it will console an error of unset options and exits the process.
  51. * If it is valid, nothing happens.
  52. *
  53. * @param {object} companionOptions
  54. */
  55. const validateConfig = (companionOptions) => {
  56. const mandatoryOptions = ['secret', 'filePath', 'server.host']
  57. /** @type {string[]} */
  58. const unspecified = []
  59. mandatoryOptions.forEach((i) => {
  60. const value = i.split('.').reduce((prev, curr) => (prev ? prev[curr] : undefined), companionOptions)
  61. if (!value) unspecified.push(`"${i}"`)
  62. })
  63. // vaidate that all required config is specified
  64. if (unspecified.length) {
  65. const messagePrefix = 'Please specify the following options to use companion:'
  66. throw new Error(`${messagePrefix}\n${unspecified.join(',\n')}`)
  67. }
  68. // validate that specified filePath is writeable/readable.
  69. try {
  70. // @ts-ignore
  71. fs.accessSync(`${companionOptions.filePath}`, fs.R_OK | fs.W_OK) // eslint-disable-line no-bitwise
  72. } catch (err) {
  73. throw new Error(
  74. `No access to "${companionOptions.filePath}". Please ensure the directory exists and with read/write permissions.`,
  75. )
  76. }
  77. const { providerOptions, periodicPingUrls, server } = companionOptions
  78. if (server && server.path) {
  79. // see https://github.com/transloadit/uppy/issues/4271
  80. // todo fix the code so we can allow `/`
  81. if (server.path === '/') throw new Error('If you want to use \'/\' as server.path, leave the \'path\' variable unset')
  82. }
  83. if (providerOptions) {
  84. const deprecatedOptions = { microsoft: 'providerOptions.onedrive', google: 'providerOptions.drive', s3: 's3' }
  85. Object.keys(deprecatedOptions).forEach((deprecated) => {
  86. if (Object.prototype.hasOwnProperty.call(providerOptions, deprecated)) {
  87. throw new Error(`The Provider option "providerOptions.${deprecated}" is no longer supported. Please use the option "${deprecatedOptions[deprecated]}" instead.`)
  88. }
  89. })
  90. }
  91. if (companionOptions.uploadUrls == null || companionOptions.uploadUrls.length === 0) {
  92. if (process.env.NODE_ENV === 'production') throw new Error('uploadUrls is required')
  93. logger.error('Running without uploadUrls is a security risk and Companion will refuse to start up when running in production (NODE_ENV=production)', 'startup.uploadUrls')
  94. }
  95. if (companionOptions.corsOrigins == null) {
  96. throw new TypeError('Option corsOrigins is required. To disable security, pass true')
  97. }
  98. if (periodicPingUrls != null && (
  99. !Array.isArray(periodicPingUrls)
  100. || periodicPingUrls.some((url2) => !isURL(url2, { protocols: ['http', 'https'], require_protocol: true, require_tld: false }))
  101. )) {
  102. throw new TypeError('Invalid periodicPingUrls')
  103. }
  104. }
  105. module.exports = {
  106. defaultOptions,
  107. getMaskableSecrets,
  108. validateConfig,
  109. }