-
Notifications
You must be signed in to change notification settings - Fork 335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implement deleteUsers
mutation
#10498
Conversation
deleteUsers
mutations
deleteUsers
mutationsdeleteUsers
mutation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would work, but I'm a bit skeptical if it's the correct approach. If I'm a contractor at Contractors United and join Megacorp for a job, I don't want them to be able to delete my whole account just because they have admin rights there.
packages/server/graphql/public/typeDefs/DeleteUsersSuccess.graphql
Outdated
Show resolved
Hide resolved
I think there are 2 possible scenarios we could implement:
|
Hi @Dschoordsch , I'm coming back to this PR after vacation. Sorry for dragging it this long!
I would argue that if the contractor at Contractors United is a billing leader or an org admin, then they should have the rights to delete users within the org. How would he/she has become a billing leader or an org admin in the first place? Well, for billing leader someone has to promote them, and for org admin, we need to run the mutation for them.
Can you elaborate what you are thinking on "owning the email domain"? Are you talking about verified org domains? How would we test that in the code? Thanks for your time! |
I, Bill, contractor at Contractors United, am in 2 organizations: Contractors United and Magacorp for a gig. I don't want Evil Bob, COO of Megacorp, be able to delete my account, which I also use for my Contractors United internal holiday planning. |
const hasTeamPermission = teamMembers.some((userTeamMember) => | ||
viewerTeamLeadPermissions.has(userTeamMember.teamId) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 If I'm a grumpy employee I can create a team "Status Reports" and get the CEO to join, then I can delete his user?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I removed the team level permission check and only rely on org level permissions
// Pre-compute org-level permissions | ||
const viewerOrgPermissions = await Promise.all( | ||
viewerOrgUsers.map(async (orgUser) => ({ | ||
orgId: orgUser.orgId, | ||
isOrgAdmin: await isUserOrgAdmin(viewerId, orgUser.orgId, dataLoader), | ||
isBillingLeader: await isUserBillingLeader(viewerId, orgUser.orgId, dataLoader) | ||
})) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 I find it not very easy to read the whole delete operation. I think the mutation would benefit from not trying to do these kind of optimizations, but instead do a simple
const deletedUsers = await Promise.all(usersToDelete.map(...
// load objects and check permission
// delete
).filter(isNotNull)
The dataloader can still partially still do it's thing and it's much easier to ensure we don't have any index errors.
If you want to go even further, we put the permission in a separate function
canDeleteUser(actor: User, user: User)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to implement the "all-or-nothing" strategy: if the caller does not have permissions to delete all of the users he/she requested, we won't delete any one and as a friendly robot, we return with error message to indicate which set of the users he/she requested cannot be deleted.
With that in mind, it's not possible to merge in the deletion operation into the permission check. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I went back to the issue to see how this would look in practice. I'm not sure this would be the best user experience. What I expect a user to do in this case is searching the selected users to unselect the ones they can't delete and try again.
I'm also not sure deleting users is what is actually required for this issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a mutation like this deleteUser
one makes sense for enterprise contexts where the company basically owns the user accounts. In all other cases removing the org user should be sufficient, shouldn't it?
const organization = await dataLoader.get('organizations').loadNonNull(orgId) | ||
return organization.activeDomain === getDomainFromEmail(userToDelete.email) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 This is not enough for owning a domain. We can only allow to delete users fully for organizations with verified domains. This usually means just enterprise orgs with SAML, but we can manually verify domains for them without them using SAML.
// Pre-compute org-level permissions | ||
const viewerOrgPermissions = await Promise.all( | ||
viewerOrgUsers.map(async (orgUser) => ({ | ||
orgId: orgUser.orgId, | ||
isOrgAdmin: await isUserOrgAdmin(viewerId, orgUser.orgId, dataLoader), | ||
isBillingLeader: await isUserBillingLeader(viewerId, orgUser.orgId, dataLoader) | ||
})) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I went back to the issue to see how this would look in practice. I'm not sure this would be the best user experience. What I expect a user to do in this case is searching the selected users to unselect the ones they can't delete and try again.
I'm also not sure deleting users is what is actually required for this issue.
With Charles' comment here, I think the Safe-To-Try™️ version of next step is to be able to remove multiple users from the org, instead of deleting the accounts altogether. I'm therefore closing this PR. Thanks a ton @Dschoordsch and apologize for taking too much of your time! |
Description
Part 1 of #10186
Testing scenarios
Final checklist