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, unsuspendUser,
updateBotDiscoverability, updateBotDiscoverability,
updateUserBadges, updateUserBadges,
wipeUser,
wipeUserProfile, wipeUserProfile,
} from "@/lib/actions"; } from "@/lib/actions";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
@ -264,6 +265,51 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </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> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button className="flex-1 bg-yellow-600">Bees</Button> <Button className="flex-1 bg-yellow-600">Bees</Button>
@ -424,29 +470,37 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <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> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Reset token</AlertDialogTitle> <AlertDialogTitle>Reset token</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2"> <AlertDialogDescription className="flex flex-col gap-2">
<span> <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> </span>
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={() => resetBotToken(user._id) onClick={() =>
.then(() => toast({ resetBotToken(user._id)
title: "Reset bot token", .then(() =>
})) toast({
.catch((e) => toast({ title: "Reset bot token",
title: "Failed to reset token", })
description: String(e), )
variant: "destructive", .catch((e) =>
})) toast({
title: "Failed to reset token",
description: String(e),
variant: "destructive",
})
)
} }
> >
Reset Reset
@ -457,18 +511,16 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="ghost" disabled={!user.bot?.owner}>Transfer bot</Button> <Button variant="ghost" disabled={!user.bot?.owner}>
Transfer bot
</Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Transfer bot</AlertDialogTitle> <AlertDialogTitle>Transfer bot</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2"> <AlertDialogDescription className="flex flex-col gap-2">
<span> <span>Transfer this bot to a new owner.</span>
Transfer this bot to a new owner. <UserSelector onChange={setTransferTarget} />
</span>
<UserSelector
onChange={setTransferTarget}
/>
<Checkbox <Checkbox
checked={transferResetToken} checked={transferResetToken}
onChange={(e) => setTransferResetToken(!!e)} onChange={(e) => setTransferResetToken(!!e)}
@ -481,19 +533,28 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
disabled={!transferTarget} disabled={!transferTarget}
onClick={() => transferBot(user._id, transferTarget!._id, transferResetToken) onClick={() =>
.then(() => toast({ transferBot(
title: "Reset bot token", user._id,
})) transferTarget!._id,
.catch((e) => toast({ transferResetToken
title: "Failed to reset token", )
description: String(e), .then(() =>
variant: "destructive", toast({
})) title: "Reset bot token",
.finally(() => { })
setTransferResetToken(true); )
setTransferTarget(null); .catch((e) =>
}) toast({
title: "Failed to reset token",
description: String(e),
variant: "destructive",
})
)
.finally(() => {
setTransferResetToken(true);
setTransferTarget(null);
})
} }
> >
Transfer 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"; if (RESTRICT_ACCESS_LIST.includes(userId)) throw "restricted access";
await checkPermission("users/action/wipe", userId, { flags }); await checkPermission("users/action/wipe", userId, { flags });
const user = await mongo() const user = onlyMessages
.db("revolt") ? null
.collection<User>("users") : await mongo()
.findOne({ _id: userId }); .db("revolt")
.collection<User>("users")
.findOne({ _id: userId });
const messages = await mongo() const messages = await mongo()
.db("revolt") .db("revolt")
@ -462,24 +468,28 @@ export async function wipeUser(userId: string, flags = 4) {
.find({ author: userId }, { sort: { _id: -1 } }) .find({ author: userId }, { sort: { _id: -1 } })
.toArray(); .toArray();
const dms = await mongo() const dms = onlyMessages
.db("revolt") ? null
.collection<Channel>("channels") : await mongo()
.find({ .db("revolt")
channel_type: "DirectMessage", .collection<Channel>("channels")
recipients: userId, .find({
}) channel_type: "DirectMessage",
.toArray(); recipients: userId,
})
.toArray();
const memberships = await mongo() const memberships = onlyMessages
.db("revolt") ? null
.collection<{ _id: { user: string; server: string } }>("server_members") : await mongo()
.find({ "_id.user": userId }) .db("revolt")
.toArray(); .collection<{ _id: { user: string; server: string } }>("server_members")
.find({ "_id.user": userId })
.toArray();
// retrieve messages, dm channels, relationships, server memberships // retrieve messages, dm channels, relationships, server memberships
const backup = { const backup = {
_event: "wipe", _event: onlyMessages ? "messages" : "wipe",
user, user,
messages, messages,
dms, dms,
@ -499,12 +509,14 @@ export async function wipeUser(userId: string, flags = 4) {
.filter((attachment) => attachment) .filter((attachment) => attachment)
.map((attachment) => attachment!._id); .map((attachment) => attachment!._id);
if (backup.user?.avatar) { if (!onlyMessages) {
attachmentIds.push(backup.user.avatar._id); if (backup.user?.avatar) {
} attachmentIds.push(backup.user.avatar._id);
}
if (backup.user?.profile?.background) { if (backup.user?.profile?.background) {
attachmentIds.push(backup.user.profile.background._id); attachmentIds.push(backup.user.profile.background._id);
}
} }
if (attachmentIds.length) { if (attachmentIds.length) {
@ -527,44 +539,46 @@ export async function wipeUser(userId: string, flags = 4) {
author: userId, author: userId,
}); });
// delete server memberships if (!onlyMessages) {
await mongo().db("revolt").collection<Member>("server_members").deleteMany({ // delete server memberships
"_id.user": userId, 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,
}); });
// 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,
});
}
} }
} }