2023-01-31 06:03:50 -05:00
|
|
|
const _ = require('lodash')
|
|
|
|
const { formatTokenUsageStats } = require('./format-usage-stats')
|
|
|
|
|
|
|
|
const LOG_EVERY_IN_S = parseInt(process.env.LOG_EVERY_IN_S || '5', 10)
|
|
|
|
const DRY_RUN = !process.argv.includes('--dry-run=false')
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AccessTokenEncryptor} accessTokenEncryptor
|
|
|
|
* @param {string} encryptedJson
|
|
|
|
* @return {Promise<string>}
|
|
|
|
*/
|
|
|
|
async function reEncryptTokens(accessTokenEncryptor, encryptedJson) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
accessTokenEncryptor.decryptToJson(encryptedJson, (err, json) => {
|
|
|
|
if (err) return reject(err)
|
|
|
|
accessTokenEncryptor.encryptJson(json, (err, reEncryptedJson) => {
|
|
|
|
if (err) return reject(err)
|
|
|
|
resolve(reEncryptedJson)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AccessTokenEncryptor} accessTokenEncryptor
|
|
|
|
* @param {Collection} collection
|
|
|
|
* @param {Object} paths
|
2024-02-13 03:52:02 -05:00
|
|
|
* @param {Object} queryOptions
|
2023-01-31 06:03:50 -05:00
|
|
|
* @return {Promise<{}>}
|
|
|
|
*/
|
|
|
|
async function reEncryptTokensInCollection({
|
|
|
|
accessTokenEncryptor,
|
|
|
|
collection,
|
|
|
|
paths,
|
2024-02-13 03:52:02 -05:00
|
|
|
queryOptions,
|
2023-01-31 06:03:50 -05:00
|
|
|
}) {
|
|
|
|
const { collectionName } = collection
|
|
|
|
const stats = {}
|
|
|
|
|
|
|
|
let processed = 0
|
|
|
|
let updatedNUsers = 0
|
|
|
|
let lastLog = 0
|
|
|
|
const logProgress = () => {
|
|
|
|
if (DRY_RUN) {
|
|
|
|
console.warn(
|
|
|
|
`processed ${processed} | Would have updated ${updatedNUsers} users`
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
console.warn(`processed ${processed} | Updated ${updatedNUsers} users`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const projection = { _id: 1 }
|
|
|
|
for (const path of Object.values(paths)) {
|
|
|
|
projection[path] = 1
|
|
|
|
}
|
|
|
|
const cursor = collection.find(
|
|
|
|
{},
|
|
|
|
{
|
2024-02-13 03:52:02 -05:00
|
|
|
...queryOptions,
|
2023-01-31 06:03:50 -05:00
|
|
|
projection,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
for await (const doc of cursor) {
|
|
|
|
processed++
|
|
|
|
|
|
|
|
let update = null
|
|
|
|
for (const [name, path] of Object.entries(paths)) {
|
|
|
|
const blob = _.get(doc, path)
|
|
|
|
if (!blob) continue
|
2023-02-15 08:13:36 -05:00
|
|
|
// Schema: LABEL-VERSION:SALT:CIPHERTEXT:IV
|
|
|
|
const [label] = blob.split(':')
|
2023-02-15 08:55:33 -05:00
|
|
|
let [, version] = label.split('-')
|
|
|
|
version = version || 'v2'
|
2023-01-31 06:03:50 -05:00
|
|
|
|
|
|
|
const key = [name, version, collectionName, path, label].join(':')
|
|
|
|
stats[key] = (stats[key] || 0) + 1
|
|
|
|
|
2023-02-15 08:13:36 -05:00
|
|
|
if (version === 'v2') {
|
2023-01-31 06:03:50 -05:00
|
|
|
update = update || {}
|
|
|
|
update[path] = await reEncryptTokens(accessTokenEncryptor, blob)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Date.now() - lastLog >= LOG_EVERY_IN_S * 1000) {
|
|
|
|
logProgress()
|
|
|
|
lastLog = Date.now()
|
|
|
|
}
|
|
|
|
if (update) {
|
|
|
|
updatedNUsers++
|
|
|
|
|
|
|
|
const { _id } = doc
|
|
|
|
if (DRY_RUN) {
|
|
|
|
console.log('Would upgrade tokens for user', _id, Object.keys(update))
|
|
|
|
} else {
|
|
|
|
console.log('Upgrading tokens for user', _id, Object.keys(update))
|
|
|
|
await collection.updateOne({ _id }, { $set: update })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
logProgress()
|
|
|
|
formatTokenUsageStats(stats)
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
reEncryptTokensInCollection,
|
|
|
|
}
|