re-encrypt-tokens.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. const _ = require('lodash')
  2. const { formatTokenUsageStats } = require('./format-usage-stats')
  3. const LOG_EVERY_IN_S = parseInt(process.env.LOG_EVERY_IN_S || '5', 10)
  4. const DRY_RUN = !process.argv.includes('--dry-run=false')
  5. /**
  6. * @param {AccessTokenEncryptor} accessTokenEncryptor
  7. * @param {string} encryptedJson
  8. * @return {Promise<string>}
  9. */
  10. async function reEncryptTokens(accessTokenEncryptor, encryptedJson) {
  11. return await new Promise((resolve, reject) => {
  12. accessTokenEncryptor.decryptToJson(encryptedJson, (err, json) => {
  13. if (err) return reject(err)
  14. accessTokenEncryptor.encryptJson(json, (err, reEncryptedJson) => {
  15. if (err) return reject(err)
  16. resolve(reEncryptedJson)
  17. })
  18. })
  19. })
  20. }
  21. /**
  22. * @param {AccessTokenEncryptor} accessTokenEncryptor
  23. * @param {Collection} collection
  24. * @param {Object} paths
  25. * @param {Object} queryOptions
  26. * @return {Promise<{}>}
  27. */
  28. async function reEncryptTokensInCollection({
  29. accessTokenEncryptor,
  30. collection,
  31. paths,
  32. queryOptions,
  33. }) {
  34. const { collectionName } = collection
  35. const stats = {}
  36. let processed = 0
  37. let updatedNUsers = 0
  38. let lastLog = 0
  39. const logProgress = () => {
  40. if (DRY_RUN) {
  41. console.warn(
  42. `processed ${processed} | Would have updated ${updatedNUsers} users`
  43. )
  44. } else {
  45. console.warn(`processed ${processed} | Updated ${updatedNUsers} users`)
  46. }
  47. }
  48. const projection = { _id: 1 }
  49. for (const path of Object.values(paths)) {
  50. projection[path] = 1
  51. }
  52. const cursor = collection.find(
  53. {},
  54. {
  55. ...queryOptions,
  56. projection,
  57. }
  58. )
  59. for await (const doc of cursor) {
  60. processed++
  61. let update = null
  62. for (const [name, path] of Object.entries(paths)) {
  63. const blob = _.get(doc, path)
  64. if (!blob) continue
  65. // Schema: LABEL-VERSION:SALT:CIPHERTEXT:IV
  66. const [label] = blob.split(':')
  67. let [, version] = label.split('-')
  68. version = version || 'v2'
  69. const key = [name, version, collectionName, path, label].join(':')
  70. stats[key] = (stats[key] || 0) + 1
  71. if (version === 'v2') {
  72. update = update || {}
  73. update[path] = await reEncryptTokens(accessTokenEncryptor, blob)
  74. }
  75. }
  76. if (Date.now() - lastLog >= LOG_EVERY_IN_S * 1000) {
  77. logProgress()
  78. lastLog = Date.now()
  79. }
  80. if (update) {
  81. updatedNUsers++
  82. const { _id } = doc
  83. if (DRY_RUN) {
  84. console.log('Would upgrade tokens for user', _id, Object.keys(update))
  85. } else {
  86. console.log('Upgrading tokens for user', _id, Object.keys(update))
  87. await collection.updateOne({ _id }, { $set: update })
  88. }
  89. }
  90. }
  91. logProgress()
  92. formatTokenUsageStats(stats)
  93. }
  94. module.exports = {
  95. reEncryptTokensInCollection,
  96. }