OneTimeTokenHandler.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. const crypto = require('crypto')
  2. const { db } = require('../../infrastructure/mongodb')
  3. const Errors = require('../Errors/Errors')
  4. const { promisifyAll } = require('@overleaf/promise-utils')
  5. const ONE_HOUR_IN_S = 60 * 60
  6. const OneTimeTokenHandler = {
  7. MAX_PEEKS: 4,
  8. getNewToken(use, data, options, callback) {
  9. // options is optional
  10. if (!options) {
  11. options = {}
  12. }
  13. if (typeof options === 'function') {
  14. callback = options
  15. options = {}
  16. }
  17. const expiresIn = options.expiresIn || ONE_HOUR_IN_S
  18. const createdAt = new Date()
  19. const expiresAt = new Date(createdAt.getTime() + expiresIn * 1000)
  20. const token = crypto.randomBytes(32).toString('hex')
  21. db.tokens.insertOne(
  22. {
  23. use,
  24. token,
  25. data,
  26. createdAt,
  27. expiresAt,
  28. },
  29. function (error) {
  30. if (error) {
  31. return callback(error)
  32. }
  33. callback(null, token)
  34. }
  35. )
  36. },
  37. getValueFromTokenAndExpire(use, token, callback) {
  38. const now = new Date()
  39. db.tokens.findOneAndUpdate(
  40. {
  41. use,
  42. token,
  43. expiresAt: { $gt: now },
  44. usedAt: { $exists: false },
  45. peekCount: { $not: { $gte: OneTimeTokenHandler.MAX_PEEKS } },
  46. },
  47. {
  48. $set: {
  49. usedAt: now,
  50. },
  51. },
  52. function (error, result) {
  53. if (error) {
  54. return callback(error)
  55. }
  56. const token = result.value
  57. if (!token) {
  58. return callback(new Errors.NotFoundError('no token found'))
  59. }
  60. callback(null, token.data)
  61. }
  62. )
  63. },
  64. peekValueFromToken(use, token, callback) {
  65. db.tokens.findOneAndUpdate(
  66. {
  67. use,
  68. token,
  69. expiresAt: { $gt: new Date() },
  70. usedAt: { $exists: false },
  71. peekCount: { $not: { $gte: OneTimeTokenHandler.MAX_PEEKS } },
  72. },
  73. {
  74. $inc: { peekCount: 1 },
  75. },
  76. {
  77. returnDocument: 'after',
  78. },
  79. function (error, result) {
  80. if (error) {
  81. return callback(error)
  82. }
  83. const token = result.value
  84. if (!token) {
  85. return callback(new Errors.NotFoundError('no token found'))
  86. }
  87. const remainingPeeks = OneTimeTokenHandler.MAX_PEEKS - token.peekCount
  88. callback(null, token.data, remainingPeeks)
  89. }
  90. )
  91. },
  92. expireToken(use, token, callback) {
  93. const now = new Date()
  94. db.tokens.updateOne(
  95. {
  96. use,
  97. token,
  98. },
  99. {
  100. $set: {
  101. usedAt: now,
  102. },
  103. },
  104. error => {
  105. callback(error)
  106. }
  107. )
  108. },
  109. expireAllTokensForUser(userId, use, callback) {
  110. const now = new Date()
  111. db.tokens.updateMany(
  112. {
  113. use,
  114. 'data.user_id': userId.toString(),
  115. usedAt: { $exists: false },
  116. },
  117. {
  118. $set: {
  119. usedAt: now,
  120. },
  121. },
  122. error => {
  123. callback(error)
  124. }
  125. )
  126. },
  127. }
  128. OneTimeTokenHandler.promises = promisifyAll(OneTimeTokenHandler)
  129. module.exports = OneTimeTokenHandler