HttpPermissionsPolicy.js 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. // @ts-check
  2. const Settings = require('@overleaf/settings')
  3. /**
  4. * @import { HttpPermissionsPolicy } from './types'
  5. */
  6. class HttpPermissionsPolicyMiddleware {
  7. /**
  8. * Initialise the middleware with a Permissions Policy config
  9. * @param {HttpPermissionsPolicy} policy
  10. */
  11. constructor(policy) {
  12. this.middleware = this.middleware.bind(this)
  13. if (policy) {
  14. this.policy = this.buildPermissionsPolicy(policy)
  15. }
  16. }
  17. /**
  18. * Checks the provided policy is valid
  19. * @param {HttpPermissionsPolicy} policy
  20. * @returns {boolean}
  21. */
  22. validatePermissionsPolicy(policy) {
  23. let policyIsValid = true
  24. if (!policy.allowed) {
  25. return true
  26. }
  27. for (const [directive, origins] of Object.entries(policy.allowed)) {
  28. // Do any directives in the allowlist clash with the denylist?
  29. if (policy.blocked && policy.blocked.includes(directive)) {
  30. policyIsValid = false
  31. }
  32. if (!origins) {
  33. policyIsValid = false
  34. }
  35. }
  36. return policyIsValid
  37. }
  38. /**
  39. * Constructs a Permissions-Policy header string from the given policy configuration
  40. * @param {HttpPermissionsPolicy} policy
  41. * @returns {string}
  42. */
  43. buildPermissionsPolicy(policy) {
  44. if (!this.validatePermissionsPolicy(policy)) {
  45. throw new Error('Invalid Permissions-Policy header configuration')
  46. }
  47. const policyElements = []
  48. if (policy.blocked && policy.blocked.length > 0) {
  49. policyElements.push(
  50. policy.blocked.map(policyElement => `${policyElement}=()`).join(', ')
  51. )
  52. }
  53. if (policy.allowed && Object.entries(policy.allowed).length > 0) {
  54. policyElements.push(
  55. Object.keys(policy.allowed)
  56. .map(allowKey => `${allowKey}=(${policy.allowed[allowKey]})`)
  57. .join(', ')
  58. )
  59. }
  60. return policyElements.join(', ')
  61. }
  62. middleware(req, res, next) {
  63. if (this.policy && Settings.useHttpPermissionsPolicy) {
  64. const originalRender = res.render
  65. res.render = (...args) => {
  66. res.setHeader('Permissions-Policy', this.policy)
  67. originalRender.apply(res, args)
  68. }
  69. }
  70. next()
  71. }
  72. }
  73. module.exports = HttpPermissionsPolicyMiddleware