Merge pull request #17023 from overleaf/ii-token-access-null-values

[web] Fix projects token access values

GitOrigin-RevId: f0c6a4993e42320c06753cb65198138afe55b71a
This commit is contained in:
ilkin-overleaf
2024-02-26 09:03:13 +00:00
committed by Copybot
parent 4a1af0f057
commit b04247dd5a
6 changed files with 425 additions and 5 deletions
@@ -0,0 +1,174 @@
const { db, waitForDb } = require('../app/src/infrastructure/mongodb')
const { batchedUpdate } = require('./helpers/batchedUpdate')
const { ObjectId } = require('mongodb')
const minimist = require('minimist')
const CollaboratorsHandler = require('../app/src/Features/Collaborators/CollaboratorsHandler')
const argv = minimist(process.argv.slice(2), {
string: ['projects'],
boolean: ['dry-run', 'help'],
alias: {
projects: 'p',
},
default: {
'dry-run': true,
},
})
if (argv.help || argv._.length > 1) {
console.error(`Usage: node scripts/remove_deleted_users_from_token_access_refs.js [OPTS]
Finds or removes deleted user ids from token access fields
"tokenAccessReadOnly_refs" and "tokenAccessReadAndWrite_refs" in the "projects" collection.
If no projects are specified, all projects will be processed.
Options:
--dry-run finds projects and deleted users but does not do any updates
--projects list of projects ids to be fixed (comma separated)
`)
process.exit(1)
}
const DRY_RUN = argv['dry-run']
const PROJECTS_LIST = argv.projects
async function findUserIds() {
const userIds = new Set()
const cursor = db.users.find({}, { _id: 1 })
for await (const user of cursor) {
userIds.add(user._id.toString())
}
console.log(`=> User ids count: ${userIds.size}`)
return userIds
}
async function fixProjectsWithInvalidTokenAccessRefsIds(
DRY_RUN,
PROJECTS_LIST
) {
if (DRY_RUN) {
console.log('=> Doing dry run')
}
const DELETED_USER_COLLABORATOR_IDS = new Set()
const PROJECTS_WITH_DELETED_USER = new Set()
// get a set of all users ids as an in-memory cache
const userIds = await findUserIds()
// default query for finding all projects with non-empty token access fields
let query = {
$or: [
{ 'tokenAccessReadOnly_refs.0': { $exists: true } },
{ 'tokenAccessReadAndWrite_refs.0': { $exists: true } },
],
}
const projectIds = PROJECTS_LIST?.split(',').map(
projectId => new ObjectId(projectId)
)
// query for finding projects passed in as args
if (projectIds) {
query = { $and: [{ _id: { $in: projectIds } }] }
}
await batchedUpdate(
'projects',
query,
async projects => {
for (const project of projects) {
// find the set of user ids that are in the token access fields
// i.e. the set of collaborators
const collaboratorIds = new Set()
for (const roUserId of project.tokenAccessReadOnly_refs) {
collaboratorIds.add(roUserId.toString())
}
for (const rwUserId of project.tokenAccessReadAndWrite_refs) {
collaboratorIds.add(rwUserId.toString())
}
// determine which collaborator ids are not in the `users` collection
// i.e. the user has been deleted
const deletedUserIds = new Set()
for (const collaboratorId of collaboratorIds) {
if (!userIds.has(collaboratorId)) {
deletedUserIds.add(collaboratorId)
}
}
// double-check that users doesn't exist in the users collection
// we don't want to remove users that were added after the initial query
const existingUsersCursor = db.users.find(
{ _id: { $in: [...deletedUserIds].map(id => new ObjectId(id)) } },
{ _id: 1 }
)
for await (const user of existingUsersCursor) {
const id = user._id.toString()
deletedUserIds.delete(id)
// add the user id to the cache
userIds.add(id)
}
// remove the actual deleted users
for (const deletedUserId of deletedUserIds) {
DELETED_USER_COLLABORATOR_IDS.add(deletedUserId)
PROJECTS_WITH_DELETED_USER.add(project._id.toString())
console.log(
'=> Found deleted user id:',
deletedUserId,
'in project:',
project._id.toString()
)
if (DRY_RUN) {
console.log(
`=> DRY RUN - would remove deleted ${deletedUserId} from all projects (found in project ${project._id.toString()})`
)
continue
}
console.log(
`=> Removing deleted ${deletedUserId} from all projects (found in project ${project._id.toString()})`
)
await CollaboratorsHandler.promises.removeUserFromAllProjects(
new ObjectId(deletedUserId)
)
}
}
},
{ tokenAccessReadOnly_refs: 1, tokenAccessReadAndWrite_refs: 1 }
)
console.log(
`=> ${DRY_RUN ? 'DRY RUN - would delete' : 'Deleted'} user ids (${
DELETED_USER_COLLABORATOR_IDS.size
})`
)
if (DELETED_USER_COLLABORATOR_IDS.size) {
console.log(Array.from(DELETED_USER_COLLABORATOR_IDS).join('\n'))
}
console.log(
`=> Projects with deleted user ids (${PROJECTS_WITH_DELETED_USER.size})`
)
if (PROJECTS_WITH_DELETED_USER.size) {
console.log(Array.from(PROJECTS_WITH_DELETED_USER).join('\n'))
}
}
async function main(DRY_RUN, PROJECTS_LIST) {
await waitForDb()
await fixProjectsWithInvalidTokenAccessRefsIds(DRY_RUN, PROJECTS_LIST)
}
module.exports = main
if (require.main === module) {
main(DRY_RUN, PROJECTS_LIST)
.then(() => {
console.error('Done')
process.exit(0)
})
.catch(err => {
console.error(err)
process.exit(1)
})
}