1
0
Fork 0

feat: wipe user messages

main
Paul Makles 2023-11-26 13:14:44 +00:00
parent eb0bd7a7c9
commit 8113a86db9
No known key found for this signature in database
GPG Key ID: 5059F398521BB0F6
2 changed files with 167 additions and 92 deletions

View File

@ -30,6 +30,7 @@ import {
unsuspendUser,
updateBotDiscoverability,
updateUserBadges,
wipeUser,
wipeUserProfile,
} from "@/lib/actions";
import { useRef, useState } from "react";
@ -264,6 +265,51 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button className="flex-1 bg-pink-600" disabled={userInaccessible}>
Wipe Messages
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want to wipe this user&apos;s messages?
</AlertDialogTitle>
<AlertDialogDescription>
All messages sent by this user will be deleted immediately.
<br className="text-base/8" />
<span className="text-red-700">
This action is irreversible and{" "}
<b className="font-bold">will not publish any events</b>!
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
className="hover:bg-red-700 transition-all"
onClick={() =>
wipeUser(user._id, 0, true)
.then(() => {
setUserDraft((user) => ({ ...user, flags: 4 }));
toast({ title: "Wiped user's messages" });
})
.catch((err) =>
toast({
title: "Failed to wipe user's messages!",
description: String(err),
variant: "destructive",
})
)
}
>
Ban
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button className="flex-1 bg-yellow-600">Bees</Button>
@ -424,29 +470,37 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" disabled={!user.bot?.owner}>Reset bot token</Button>
<Button variant="ghost" disabled={!user.bot?.owner}>
Reset bot token
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Reset token</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2">
<span>
Re-roll this bot&apos;s authentication token. This will not disconnect active connections.
Re-roll this bot&apos;s authentication token. This will
not disconnect active connections.
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => resetBotToken(user._id)
.then(() => toast({
title: "Reset bot token",
}))
.catch((e) => toast({
title: "Failed to reset token",
description: String(e),
variant: "destructive",
}))
onClick={() =>
resetBotToken(user._id)
.then(() =>
toast({
title: "Reset bot token",
})
)
.catch((e) =>
toast({
title: "Failed to reset token",
description: String(e),
variant: "destructive",
})
)
}
>
Reset
@ -457,18 +511,16 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" disabled={!user.bot?.owner}>Transfer bot</Button>
<Button variant="ghost" disabled={!user.bot?.owner}>
Transfer bot
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Transfer bot</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2">
<span>
Transfer this bot to a new owner.
</span>
<UserSelector
onChange={setTransferTarget}
/>
<span>Transfer this bot to a new owner.</span>
<UserSelector onChange={setTransferTarget} />
<Checkbox
checked={transferResetToken}
onChange={(e) => setTransferResetToken(!!e)}
@ -481,19 +533,28 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
disabled={!transferTarget}
onClick={() => transferBot(user._id, transferTarget!._id, transferResetToken)
.then(() => toast({
title: "Reset bot token",
}))
.catch((e) => toast({
title: "Failed to reset token",
description: String(e),
variant: "destructive",
}))
.finally(() => {
setTransferResetToken(true);
setTransferTarget(null);
})
onClick={() =>
transferBot(
user._id,
transferTarget!._id,
transferResetToken
)
.then(() =>
toast({
title: "Reset bot token",
})
)
.catch((e) =>
toast({
title: "Failed to reset token",
description: String(e),
variant: "destructive",
})
)
.finally(() => {
setTransferResetToken(true);
setTransferTarget(null);
})
}
>
Transfer

View File

@ -446,15 +446,21 @@ export async function updateUserBadges(userId: string, badges: number) {
}
}
export async function wipeUser(userId: string, flags = 4) {
export async function wipeUser(
userId: string,
flags = 4,
onlyMessages = false
) {
if (RESTRICT_ACCESS_LIST.includes(userId)) throw "restricted access";
await checkPermission("users/action/wipe", userId, { flags });
const user = await mongo()
.db("revolt")
.collection<User>("users")
.findOne({ _id: userId });
const user = onlyMessages
? null
: await mongo()
.db("revolt")
.collection<User>("users")
.findOne({ _id: userId });
const messages = await mongo()
.db("revolt")
@ -462,24 +468,28 @@ export async function wipeUser(userId: string, flags = 4) {
.find({ author: userId }, { sort: { _id: -1 } })
.toArray();
const dms = await mongo()
.db("revolt")
.collection<Channel>("channels")
.find({
channel_type: "DirectMessage",
recipients: userId,
})
.toArray();
const dms = onlyMessages
? null
: await mongo()
.db("revolt")
.collection<Channel>("channels")
.find({
channel_type: "DirectMessage",
recipients: userId,
})
.toArray();
const memberships = await mongo()
.db("revolt")
.collection<{ _id: { user: string; server: string } }>("server_members")
.find({ "_id.user": userId })
.toArray();
const memberships = onlyMessages
? null
: await mongo()
.db("revolt")
.collection<{ _id: { user: string; server: string } }>("server_members")
.find({ "_id.user": userId })
.toArray();
// retrieve messages, dm channels, relationships, server memberships
const backup = {
_event: "wipe",
_event: onlyMessages ? "messages" : "wipe",
user,
messages,
dms,
@ -499,12 +509,14 @@ export async function wipeUser(userId: string, flags = 4) {
.filter((attachment) => attachment)
.map((attachment) => attachment!._id);
if (backup.user?.avatar) {
attachmentIds.push(backup.user.avatar._id);
}
if (!onlyMessages) {
if (backup.user?.avatar) {
attachmentIds.push(backup.user.avatar._id);
}
if (backup.user?.profile?.background) {
attachmentIds.push(backup.user.profile.background._id);
if (backup.user?.profile?.background) {
attachmentIds.push(backup.user.profile.background._id);
}
}
if (attachmentIds.length) {
@ -527,44 +539,46 @@ export async function wipeUser(userId: string, flags = 4) {
author: userId,
});
// delete server memberships
await mongo().db("revolt").collection<Member>("server_members").deleteMany({
"_id.user": userId,
});
// disable account
await disableAccount(userId);
// clear user profile
await mongo()
.db("revolt")
.collection<User>("users")
.updateOne(
{
_id: userId,
},
{
$set: {
flags,
},
$unset: {
avatar: 1,
profile: 1,
status: 1,
},
}
);
// broadcast wipe event
for (const topic of [
...backup.dms.map((x) => x._id),
...backup.memberships.map((x) => x._id.server),
]) {
await publishMessage(topic, {
type: "UserPlatformWipe",
user_id: userId,
flags,
if (!onlyMessages) {
// delete server memberships
await mongo().db("revolt").collection<Member>("server_members").deleteMany({
"_id.user": userId,
});
// disable account
await disableAccount(userId);
// clear user profile
await mongo()
.db("revolt")
.collection<User>("users")
.updateOne(
{
_id: userId,
},
{
$set: {
flags,
},
$unset: {
avatar: 1,
profile: 1,
status: 1,
},
}
);
// broadcast wipe event
for (const topic of [
...backup.dms!.map((x) => x._id),
...backup.memberships!.map((x) => x._id.server),
]) {
await publishMessage(topic, {
type: "UserPlatformWipe",
user_id: userId,
flags,
});
}
}
}