index.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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. function(req) {
  38. const baseUrl = config.deriveBaseUrl(req);
  39. const r = baseUrl.replace(/^http(s?):\/\//, 'ws$1://');
  40. console.log([baseUrl, r]);
  41. return r;
  42. }
  43. ],
  44. imgSrc: ["'self'", 'data:'],
  45. scriptSrc: [
  46. "'self'",
  47. function(req) {
  48. return `'nonce-${req.cspNonce}'`;
  49. }
  50. ],
  51. styleSrc: [
  52. "'self'",
  53. function(req) {
  54. return `'nonce-${req.cspNonce}'`;
  55. }
  56. ],
  57. formAction: ["'none'"],
  58. frameAncestors: ["'none'"],
  59. objectSrc: ["'none'"],
  60. reportUri: '/__cspreport__'
  61. }
  62. };
  63. app.use(helmet.contentSecurityPolicy(csp));
  64. }
  65. app.use(function(req, res, next) {
  66. res.set('Pragma', 'no-cache');
  67. res.set(
  68. 'Cache-Control',
  69. 'private, no-cache, no-store, must-revalidate, max-age=0'
  70. );
  71. next();
  72. });
  73. app.use(function(req, res, next) {
  74. try {
  75. // set by the load balancer
  76. const [country, state] = req.header('X-Client-Geo-Location').split(',');
  77. req.geo = {
  78. country,
  79. state
  80. };
  81. } catch (e) {
  82. req.geo = {};
  83. }
  84. next();
  85. });
  86. app.use(bodyParser.json());
  87. app.use(bodyParser.text());
  88. app.get('/', language, pages.index);
  89. app.get('/config', function(req, res) {
  90. res.json(clientConstants);
  91. });
  92. app.get('/error', language, pages.blank);
  93. app.get('/oauth', language, pages.blank);
  94. app.get('/login', language, pages.index);
  95. app.get('/app.webmanifest', language, require('./webmanifest'));
  96. app.get(`/download/:id${ID_REGEX}`, language, pages.download);
  97. app.get('/unsupported/:reason', language, pages.unsupported);
  98. app.get(`/api/download/:id${ID_REGEX}`, auth.hmac, require('./download'));
  99. app.get(
  100. `/api/download/blob/:id${ID_REGEX}`,
  101. auth.hmac,
  102. require('./download')
  103. );
  104. app.get(`/api/exists/:id${ID_REGEX}`, require('./exists'));
  105. app.get(`/api/metadata/:id${ID_REGEX}`, auth.hmac, require('./metadata'));
  106. app.get('/api/filelist/:id([\\w-]{16})', auth.fxa, filelist.get);
  107. app.post('/api/filelist/:id([\\w-]{16})', auth.fxa, filelist.post);
  108. app.post('/api/upload', auth.fxa, require('./upload'));
  109. app.post(`/api/delete/:id${ID_REGEX}`, auth.owner, require('./delete'));
  110. app.post(`/api/password/:id${ID_REGEX}`, auth.owner, require('./password'));
  111. app.post(
  112. `/api/params/:id${ID_REGEX}`,
  113. auth.owner,
  114. auth.fxa,
  115. require('./params')
  116. );
  117. app.post(`/api/info/:id${ID_REGEX}`, auth.owner, require('./info'));
  118. app.get('/__version__', function(req, res) {
  119. // eslint-disable-next-line node/no-missing-require
  120. res.sendFile(require.resolve('../../dist/version.json'));
  121. });
  122. app.get('/__lbheartbeat__', function(req, res) {
  123. res.sendStatus(200);
  124. });
  125. app.get('/__heartbeat__', async (req, res) => {
  126. try {
  127. await storage.ping();
  128. res.sendStatus(200);
  129. } catch (e) {
  130. res.sendStatus(500);
  131. }
  132. });
  133. };