re-encrypt-tokens.js 2.7 KB

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