security.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. 'use strict'
  2. const basicAuth = require('basic-auth')
  3. const config = require('config')
  4. const HTTPStatus = require('http-status')
  5. const jwt = require('jsonwebtoken')
  6. const tsscmp = require('tsscmp')
  7. function setupBasicHttpAuthForSwaggerDocs(app) {
  8. app.use('/docs', function (req, res, next) {
  9. if (hasValidBasicAuthCredentials(req)) {
  10. return next()
  11. }
  12. res.header('WWW-Authenticate', 'Basic realm="Application"')
  13. res.status(HTTPStatus.UNAUTHORIZED).end()
  14. })
  15. }
  16. exports.setupBasicHttpAuthForSwaggerDocs = setupBasicHttpAuthForSwaggerDocs
  17. function hasValidBasicAuthCredentials(req) {
  18. const credentials = basicAuth(req)
  19. if (!credentials) return false
  20. // No security in the name, so just use straight comparison.
  21. if (credentials.name !== 'staging') return false
  22. const password = config.get('basicHttpAuth.password')
  23. if (password && tsscmp(credentials.pass, password)) return true
  24. // Support an old password so we can change the password without downtime.
  25. if (config.has('basicHttpAuth.oldPassword')) {
  26. const oldPassword = config.get('basicHttpAuth.oldPassword')
  27. if (oldPassword && tsscmp(credentials.pass, oldPassword)) return true
  28. }
  29. return false
  30. }
  31. function setupSSL(app) {
  32. const httpsOnly = config.get('httpsOnly') === 'true'
  33. if (!httpsOnly) {
  34. return
  35. }
  36. app.enable('trust proxy')
  37. app.use(function (req, res, next) {
  38. if (req.protocol === 'https') {
  39. next()
  40. return
  41. }
  42. if (req.method === 'GET' || req.method === 'HEAD') {
  43. res.redirect('https://' + req.headers.host + req.url)
  44. } else {
  45. res
  46. .status(HTTPStatus.FORBIDDEN)
  47. .send('Please use HTTPS when submitting data to this server.')
  48. }
  49. })
  50. }
  51. exports.setupSSL = setupSSL
  52. function handleJWTAuth(req, authOrSecDef, scopesOrApiKey, next) {
  53. // as a temporary solution, to make the OT demo still work
  54. // this handler will also check for basic authorization
  55. if (hasValidBasicAuthCredentials(req)) {
  56. return next()
  57. }
  58. let token, err
  59. if (authOrSecDef.name === 'token') {
  60. token = req.query.token
  61. } else if (
  62. req.headers.authorization &&
  63. req.headers.authorization.split(' ')[0] === 'Bearer'
  64. ) {
  65. token = req.headers.authorization.split(' ')[1]
  66. }
  67. if (!token) {
  68. err = new Error('jwt missing')
  69. err.statusCode = HTTPStatus.UNAUTHORIZED
  70. err.headers = { 'WWW-Authenticate': 'Bearer' }
  71. return next(err)
  72. }
  73. let decoded
  74. try {
  75. decoded = decodeJWT(token)
  76. } catch (error) {
  77. if (
  78. error instanceof jwt.JsonWebTokenError ||
  79. error instanceof jwt.TokenExpiredError
  80. ) {
  81. err = new Error(error.message)
  82. err.statusCode = HTTPStatus.UNAUTHORIZED
  83. err.headers = { 'WWW-Authenticate': 'Bearer error="invalid_token"' }
  84. return next(err)
  85. }
  86. throw error
  87. }
  88. if (decoded.project_id.toString() !== req.swagger.params.project_id.value) {
  89. err = new Error('Wrong project_id')
  90. err.statusCode = HTTPStatus.FORBIDDEN
  91. return next(err)
  92. }
  93. next()
  94. }
  95. exports.hasValidBasicAuthCredentials = hasValidBasicAuthCredentials
  96. /**
  97. * Verify and decode the given JSON Web Token
  98. */
  99. function decodeJWT(token) {
  100. const key = config.get('jwtAuth.key')
  101. const algorithm = config.get('jwtAuth.algorithm')
  102. try {
  103. return jwt.verify(token, key, { algorithms: [algorithm] })
  104. } catch (err) {
  105. // Support an old key so we can change the key without downtime.
  106. if (config.has('jwtAuth.oldKey')) {
  107. const oldKey = config.get('jwtAuth.oldKey')
  108. return jwt.verify(token, oldKey, { algorithms: [algorithm] })
  109. } else {
  110. throw err
  111. }
  112. }
  113. }
  114. function handleBasicAuth(req, authOrSecDef, scopesOrApiKey, next) {
  115. if (hasValidBasicAuthCredentials(req)) {
  116. return next()
  117. }
  118. const error = new Error()
  119. error.statusCode = HTTPStatus.UNAUTHORIZED
  120. error.headers = { 'WWW-Authenticate': 'Basic realm="Application"' }
  121. return next(error)
  122. }
  123. function getSwaggerHandlers() {
  124. const handlers = {}
  125. if (!config.has('jwtAuth.key') || !config.has('basicHttpAuth.password')) {
  126. throw new Error('missing authentication env vars')
  127. }
  128. handlers.jwt = handleJWTAuth
  129. handlers.basic = handleBasicAuth
  130. handlers.token = handleJWTAuth
  131. return handlers
  132. }
  133. exports.getSwaggerHandlers = getSwaggerHandlers