config.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. const convict = require('convict');
  2. const convict_format_with_validator = require('convict-format-with-validator');
  3. const { tmpdir } = require('os');
  4. const path = require('path');
  5. const { randomBytes } = require('crypto');
  6. convict.addFormats(convict_format_with_validator);
  7. convict.addFormat({
  8. name: 'positive-int-array',
  9. coerce: ints => {
  10. // can take: int[] | string[] | string (csv), returns -> int[]
  11. const ints_arr = Array.isArray(ints) ? ints : ints.trim().split(',');
  12. return ints_arr.map(int =>
  13. typeof int === 'number'
  14. ? int
  15. : parseInt(int.replace(/['"]+/g, '').trim(), 10)
  16. );
  17. },
  18. validate: ints => {
  19. // takes: int[], errors if any NaNs, negatives, or floats present
  20. for (const int of ints) {
  21. if (typeof int !== 'number' || isNaN(int) || int < 0 || int % 1 > 0)
  22. throw new Error('must be a comma-separated list of positive integers');
  23. }
  24. }
  25. });
  26. const conf = convict({
  27. s3_bucket: {
  28. format: String,
  29. default: '',
  30. env: 'S3_BUCKET'
  31. },
  32. s3_endpoint: {
  33. format: String,
  34. default: '',
  35. env: 'S3_ENDPOINT'
  36. },
  37. s3_use_path_style_endpoint: {
  38. format: Boolean,
  39. default: false,
  40. env: 'S3_USE_PATH_STYLE_ENDPOINT'
  41. },
  42. gcs_bucket: {
  43. format: String,
  44. default: '',
  45. env: 'GCS_BUCKET'
  46. },
  47. expire_times_seconds: {
  48. format: 'positive-int-array',
  49. default: [300, 3600, 86400, 604800],
  50. env: 'EXPIRE_TIMES_SECONDS'
  51. },
  52. default_expire_seconds: {
  53. format: Number,
  54. default: 86400,
  55. env: 'DEFAULT_EXPIRE_SECONDS'
  56. },
  57. max_expire_seconds: {
  58. format: Number,
  59. default: 86400 * 7,
  60. env: 'MAX_EXPIRE_SECONDS'
  61. },
  62. download_counts: {
  63. format: 'positive-int-array',
  64. default: [1, 2, 3, 4, 5, 20, 50, 100],
  65. env: 'DOWNLOAD_COUNTS'
  66. },
  67. default_downloads: {
  68. format: Number,
  69. default: 1,
  70. env: 'DEFAULT_DOWNLOADS'
  71. },
  72. max_downloads: {
  73. format: Number,
  74. default: 100,
  75. env: 'MAX_DOWNLOADS'
  76. },
  77. max_files_per_archive: {
  78. format: Number,
  79. default: 64,
  80. env: 'MAX_FILES_PER_ARCHIVE'
  81. },
  82. max_archives_per_user: {
  83. format: Number,
  84. default: 16,
  85. env: 'MAX_ARCHIVES_PER_USER'
  86. },
  87. redis_host: {
  88. format: String,
  89. default: 'localhost',
  90. env: 'REDIS_HOST'
  91. },
  92. redis_port: {
  93. format: Number,
  94. default: 6379,
  95. env: 'REDIS_PORT'
  96. },
  97. redis_user: {
  98. format: String,
  99. default: '',
  100. env: 'REDIS_USER'
  101. },
  102. redis_password: {
  103. format: String,
  104. default: '',
  105. env: 'REDIS_PASSWORD'
  106. },
  107. redis_db: {
  108. format: String,
  109. default: '',
  110. env: 'REDIS_DB'
  111. },
  112. redis_event_expire: {
  113. format: Boolean,
  114. default: false,
  115. env: 'REDIS_EVENT_EXPIRE'
  116. },
  117. redis_retry_time: {
  118. format: Number,
  119. default: 10000,
  120. env: 'REDIS_RETRY_TIME'
  121. },
  122. redis_retry_delay: {
  123. format: Number,
  124. default: 500,
  125. env: 'REDIS_RETRY_DELAY'
  126. },
  127. listen_address: {
  128. format: 'ipaddress',
  129. default: '0.0.0.0',
  130. env: 'IP_ADDRESS'
  131. },
  132. listen_port: {
  133. format: 'port',
  134. default: 1443,
  135. arg: 'port',
  136. env: 'PORT'
  137. },
  138. sentry_id: {
  139. format: String,
  140. default: '',
  141. env: 'SENTRY_CLIENT'
  142. },
  143. sentry_dsn: {
  144. format: String,
  145. default: '',
  146. env: 'SENTRY_DSN'
  147. },
  148. env: {
  149. format: ['production', 'development', 'test'],
  150. default: 'development',
  151. env: 'NODE_ENV'
  152. },
  153. max_file_size: {
  154. format: Number,
  155. default: 1024 * 1024 * 1024 * 2.5,
  156. env: 'MAX_FILE_SIZE'
  157. },
  158. l10n_dev: {
  159. format: Boolean,
  160. default: false,
  161. env: 'L10N_DEV'
  162. },
  163. base_url: {
  164. format: 'url',
  165. default: 'https://send.example.com',
  166. env: 'BASE_URL'
  167. },
  168. custom_title: {
  169. format: String,
  170. default: 'Send',
  171. env: 'CUSTOM_TITLE'
  172. },
  173. custom_description: {
  174. format: String,
  175. default: 'Encrypt and send files with a link that automatically expires to ensure your important documents don’t stay online forever.',
  176. env: 'CUSTOM_DESCRIPTION'
  177. },
  178. detect_base_url: {
  179. format: Boolean,
  180. default: false,
  181. env: 'DETECT_BASE_URL'
  182. },
  183. file_dir: {
  184. format: 'String',
  185. default: `${tmpdir()}${path.sep}send-${randomBytes(4).toString('hex')}`,
  186. env: 'FILE_DIR'
  187. },
  188. fxa_url: {
  189. format: 'url',
  190. default: 'https://send-fxa.dev.lcip.org',
  191. env: 'FXA_URL'
  192. },
  193. fxa_client_id: {
  194. format: String,
  195. default: '', // disabled
  196. env: 'FXA_CLIENT_ID'
  197. },
  198. fxa_key_scope: {
  199. format: String,
  200. default: 'https://identity.mozilla.com/apps/send',
  201. env: 'FXA_KEY_SCOPE'
  202. },
  203. fxa_csp_oauth_url: {
  204. format: String,
  205. default: '',
  206. env: 'FXA_CSP_OAUTH_URL'
  207. },
  208. fxa_csp_content_url: {
  209. format: String,
  210. default: '',
  211. env: 'FXA_CSP_CONTENT_URL'
  212. },
  213. fxa_csp_profile_url: {
  214. format: String,
  215. default: '',
  216. env: 'FXA_CSP_PROFILE_URL'
  217. },
  218. fxa_csp_profileimage_url: {
  219. format: String,
  220. default: '',
  221. env: 'FXA_CSP_PROFILEIMAGE_URL'
  222. },
  223. survey_url: {
  224. format: String,
  225. default: '',
  226. env: 'SURVEY_URL'
  227. },
  228. ip_db: {
  229. format: String,
  230. default: '',
  231. env: 'IP_DB'
  232. },
  233. footer_donate_url: {
  234. format: String,
  235. default: '',
  236. env: 'SEND_FOOTER_DONATE_URL'
  237. },
  238. footer_cli_url: {
  239. format: String,
  240. default: 'https://github.com/timvisee/ffsend',
  241. env: 'SEND_FOOTER_CLI_URL'
  242. },
  243. footer_dmca_url: {
  244. format: String,
  245. default: '',
  246. env: 'SEND_FOOTER_DMCA_URL'
  247. },
  248. footer_source_url: {
  249. format: String,
  250. default: 'https://github.com/timvisee/send',
  251. env: 'SEND_FOOTER_SOURCE_URL'
  252. },
  253. custom_footer_text: {
  254. format: String,
  255. default: '',
  256. env: 'CUSTOM_FOOTER_TEXT'
  257. },
  258. custom_footer_url: {
  259. format: String,
  260. default: '',
  261. env: 'CUSTOM_FOOTER_URL'
  262. },
  263. ui_color_primary: {
  264. format: String,
  265. default: '#0a84ff',
  266. env: 'UI_COLOR_PRIMARY'
  267. },
  268. ui_color_accent: {
  269. format: String,
  270. default: '#003eaa',
  271. env: 'UI_COLOR_ACCENT'
  272. },
  273. custom_locale: {
  274. format: String,
  275. default: '',
  276. env: 'CUSTOM_LOCALE'
  277. },
  278. ui_custom_assets: {
  279. android_chrome_192px: {
  280. format: String,
  281. default: '',
  282. env: 'UI_CUSTOM_ASSETS_ANDROID_CHROME_192PX'
  283. },
  284. android_chrome_512px: {
  285. format: String,
  286. default: '',
  287. env: 'UI_CUSTOM_ASSETS_ANDROID_CHROME_512PX'
  288. },
  289. apple_touch_icon: {
  290. format: String,
  291. default: '',
  292. env: 'UI_CUSTOM_ASSETS_APPLE_TOUCH_ICON'
  293. },
  294. favicon_16px: {
  295. format: String,
  296. default: '',
  297. env: 'UI_CUSTOM_ASSETS_FAVICON_16PX'
  298. },
  299. favicon_32px: {
  300. format: String,
  301. default: '',
  302. env: 'UI_CUSTOM_ASSETS_FAVICON_32PX'
  303. },
  304. icon: {
  305. format: String,
  306. default: '',
  307. env: 'UI_CUSTOM_ASSETS_ICON'
  308. },
  309. safari_pinned_tab: {
  310. format: String,
  311. default: '',
  312. env: 'UI_CUSTOM_ASSETS_SAFARI_PINNED_TAB'
  313. },
  314. facebook: {
  315. format: String,
  316. default: '',
  317. env: 'UI_CUSTOM_ASSETS_FACEBOOK'
  318. },
  319. twitter: {
  320. format: String,
  321. default: '',
  322. env: 'UI_CUSTOM_ASSETS_TWITTER'
  323. },
  324. wordmark: {
  325. format: String,
  326. default: '',
  327. env: 'UI_CUSTOM_ASSETS_WORDMARK'
  328. },
  329. custom_css: {
  330. format: String,
  331. default: '',
  332. env: 'UI_CUSTOM_CSS'
  333. }
  334. }
  335. });
  336. // Perform validation
  337. conf.validate({ allowed: 'strict' });
  338. const props = conf.getProperties();
  339. const deriveBaseUrl = req => {
  340. if (!props.detect_base_url) {
  341. return props.base_url;
  342. }
  343. const protocol = req.secure ? 'https://' : 'http://';
  344. return `${protocol}${req.headers.host}`;
  345. };
  346. module.exports = {
  347. ...props,
  348. deriveBaseUrl
  349. };