index.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. const crypto = require('crypto');
  2. const bodyParser = require('body-parser');
  3. const helmet = require('helmet');
  4. const uaparser = require('ua-parser-js');
  5. const storage = require('../storage');
  6. const config = require('../config');
  7. const auth = require('../middleware/auth');
  8. const language = require('../middleware/language');
  9. const pages = require('./pages');
  10. const filelist = require('./filelist');
  11. const clientConstants = require('../clientConstants');
  12. const IS_DEV = config.env === 'development';
  13. const ID_REGEX = '([0-9a-fA-F]{10,16})';
  14. module.exports = function(app) {
  15. app.set('trust proxy', true);
  16. app.use(helmet());
  17. app.use(
  18. helmet.hsts({
  19. maxAge: 31536000,
  20. force: !IS_DEV
  21. })
  22. );
  23. app.use(function(req, res, next) {
  24. req.ua = uaparser(req.header('user-agent'));
  25. next();
  26. });
  27. app.use(function(req, res, next) {
  28. req.cspNonce = crypto.randomBytes(16).toString('hex');
  29. next();
  30. });
  31. if (!IS_DEV) {
  32. let csp = {
  33. directives: {
  34. defaultSrc: ["'self'"],
  35. connectSrc: [
  36. "'self'",
  37. config.base_url.replace(/^https:\/\//, 'wss://')
  38. ],
  39. imgSrc: ["'self'"],
  40. scriptSrc: [
  41. "'self'",
  42. function(req) {
  43. return `'nonce-${req.cspNonce}'`;
  44. }
  45. ],
  46. formAction: ["'none'"],
  47. frameAncestors: ["'none'"],
  48. objectSrc: ["'none'"],
  49. reportUri: '/__cspreport__'
  50. }
  51. };
  52. if (config.fxa_client_id) {
  53. csp.directives.connectSrc.push('https://accounts.firefox.com');
  54. csp.directives.connectSrc.push('https://*.accounts.firefox.com');
  55. csp.directives.imgSrc.push('https://firefoxusercontent.com');
  56. csp.directives.imgSrc.push('https://secure.gravatar.com');
  57. }
  58. if (config.sentry_id) {
  59. csp.directives.connectSrc.push(config.sentry_host);
  60. }
  61. if (
  62. /^https:\/\/.*\.dev\.lcip\.org$/.test(config.base_url) ||
  63. /^https:\/\/.*\.send\.nonprod\.cloudops\.mozgcp\.net$/.test(
  64. config.base_url
  65. )
  66. ) {
  67. csp.directives.connectSrc.push('https://*.dev.lcip.org');
  68. csp.directives.imgSrc.push('https://*.dev.lcip.org');
  69. }
  70. if (config.fxa_csp_oauth_url != '') {
  71. csp.directives.connectSrc.push(config.fxa_csp_oauth_url);
  72. }
  73. if (config.fxa_csp_content_url != '') {
  74. csp.directives.connectSrc.push(config.fxa_csp_content_url);
  75. }
  76. if (config.fxa_csp_profile_url != '') {
  77. csp.directives.connectSrc.push(config.fxa_csp_profile_url);
  78. }
  79. if (config.fxa_csp_profileimage_url != '') {
  80. csp.directives.imgSrc.push(config.fxa_csp_profileimage_url);
  81. }
  82. app.use(helmet.contentSecurityPolicy(csp));
  83. }
  84. app.use(function(req, res, next) {
  85. res.set('Pragma', 'no-cache');
  86. res.set(
  87. 'Cache-Control',
  88. 'private, no-cache, no-store, must-revalidate, max-age=0'
  89. );
  90. next();
  91. });
  92. app.use(function(req, res, next) {
  93. try {
  94. // set by the load balancer
  95. const [country, state] = req.header('X-Client-Geo-Location').split(',');
  96. req.geo = {
  97. country,
  98. state
  99. };
  100. } catch (e) {
  101. req.geo = {};
  102. }
  103. next();
  104. });
  105. app.use(bodyParser.json());
  106. app.use(bodyParser.text());
  107. app.get('/', language, pages.index);
  108. app.get('/config', function(req, res) {
  109. res.json(clientConstants);
  110. });
  111. app.get('/error', language, pages.blank);
  112. app.get('/oauth', language, pages.blank);
  113. app.get('/legal', language, pages.legal);
  114. app.get('/login', language, pages.index);
  115. app.get('/report', language, pages.blank);
  116. app.get('/app.webmanifest', language, require('./webmanifest'));
  117. app.get(`/download/:id${ID_REGEX}`, language, pages.download);
  118. app.get('/unsupported/:reason', language, pages.unsupported);
  119. app.get(`/api/download/token/:id${ID_REGEX}`, auth.hmac, require('./token'));
  120. app.get(`/api/download/:id${ID_REGEX}`, auth.dlToken, require('./download'));
  121. app.get(
  122. `/api/download/blob/:id${ID_REGEX}`,
  123. auth.dlToken,
  124. require('./download')
  125. );
  126. app.post(
  127. `/api/download/done/:id${ID_REGEX}`,
  128. auth.dlToken,
  129. require('./done.js')
  130. );
  131. app.get(`/api/exists/:id${ID_REGEX}`, require('./exists'));
  132. app.get(`/api/metadata/:id${ID_REGEX}`, auth.hmac, require('./metadata'));
  133. app.get('/api/filelist/:id([\\w-]{16})', auth.fxa, filelist.get);
  134. app.post('/api/filelist/:id([\\w-]{16})', auth.fxa, filelist.post);
  135. // app.post('/api/upload', auth.fxa, require('./upload'));
  136. app.post(`/api/delete/:id${ID_REGEX}`, auth.owner, require('./delete'));
  137. app.post(`/api/password/:id${ID_REGEX}`, auth.owner, require('./password'));
  138. app.post(`/api/params/:id${ID_REGEX}`, auth.owner, require('./params'));
  139. app.post(`/api/info/:id${ID_REGEX}`, auth.owner, require('./info'));
  140. app.post(`/api/report/:id${ID_REGEX}`, auth.hmac, require('./report'));
  141. app.post('/api/metrics', require('./metrics'));
  142. app.get('/__version__', function(req, res) {
  143. // eslint-disable-next-line node/no-missing-require
  144. res.sendFile(require.resolve('../../dist/version.json'));
  145. });
  146. app.get('/__lbheartbeat__', function(req, res) {
  147. res.sendStatus(200);
  148. });
  149. app.get('/__heartbeat__', async (req, res) => {
  150. try {
  151. await storage.ping();
  152. res.sendStatus(200);
  153. } catch (e) {
  154. res.sendStatus(500);
  155. }
  156. });
  157. };