forked from administration/panel
feat: further work on more narrow permissions
parent
70aa95577a
commit
ea39b68703
|
@ -8,13 +8,14 @@ import { RelevantReports } from "@/components/pages/inspector/RelevantReports";
|
|||
import { UserActions } from "@/components/pages/inspector/UserActions";
|
||||
import { Card, CardHeader } from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { PLATFORM_MOD_ID } from "@/lib/constants";
|
||||
import {
|
||||
fetchBotById,
|
||||
fetchBotsByUser,
|
||||
fetchMembershipsByUser,
|
||||
fetchMessages,
|
||||
fetchNoticesByUser,
|
||||
fetchReports,
|
||||
fetchReportsAgainstUser,
|
||||
fetchReportsByUser,
|
||||
fetchServers,
|
||||
fetchSnapshots,
|
||||
fetchStrikesByUser,
|
||||
|
@ -37,17 +38,14 @@ export default async function User({
|
|||
const bot = user.bot ? await fetchBotById(user._id) : undefined;
|
||||
const botOwner = user.bot ? await fetchUserById(user.bot.owner) : undefined;
|
||||
|
||||
// Fetch strikes
|
||||
// Fetch strikes and moderation alerts
|
||||
const strikes = await fetchStrikesByUser(user._id);
|
||||
|
||||
// Fetch moderation alerts
|
||||
const dm = await findDM(PLATFORM_MOD_ID, user._id);
|
||||
const notices = dm ? await fetchMessages({ channel: dm._id }) : [];
|
||||
const notices = await fetchNoticesByUser(user._id);
|
||||
|
||||
// Fetch friends and bots
|
||||
const botIds = await fetchBotsByUser(user._id).then((bots) =>
|
||||
bots.map((bot) => bot._id)
|
||||
);
|
||||
const botIds = await fetchBotsByUser(user._id)
|
||||
.then((bots) => bots.map((bot) => bot._id))
|
||||
.catch(() => []);
|
||||
|
||||
const relevantUsers = await fetchUsersById([
|
||||
...botIds,
|
||||
|
@ -59,30 +57,18 @@ export default async function User({
|
|||
relevantUsers.sort((a) => (a.bot ? -1 : 0));
|
||||
|
||||
// Fetch server memberships
|
||||
const serverMemberships = await fetchMembershipsByUser(user._id);
|
||||
const serverMemberships = await fetchMembershipsByUser(user._id).catch(
|
||||
() => []
|
||||
);
|
||||
const servers = await fetchServers({
|
||||
_id: {
|
||||
$in: serverMemberships.map((member) => member._id.server),
|
||||
},
|
||||
});
|
||||
}).catch(() => []);
|
||||
|
||||
// Fetch reports
|
||||
const reportsByUser = await fetchReports({
|
||||
author_id: user._id,
|
||||
});
|
||||
|
||||
const reportIdsInvolvingUser = await fetchSnapshots({
|
||||
// TODO: slow query
|
||||
$or: [{ "content._id": user._id }, { "content.author": user._id }],
|
||||
}).then((snapshots) => [
|
||||
...new Set(snapshots.map((snapshot) => snapshot.report_id)),
|
||||
]);
|
||||
|
||||
const reportsAgainstUser = await fetchReports({
|
||||
_id: {
|
||||
$in: reportIdsInvolvingUser,
|
||||
},
|
||||
});
|
||||
const reportsByUser = await fetchReportsByUser(user._id);
|
||||
const reportsAgainstUser = await fetchReportsAgainstUser(user._id);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
|
@ -123,7 +109,7 @@ export default async function User({
|
|||
<RelevantReports byUser={reportsByUser} forUser={reportsAgainstUser} />
|
||||
|
||||
<Separator />
|
||||
<RecentMessages query={{ author: user._id }} />
|
||||
<RecentMessages userId={user._id} />
|
||||
|
||||
<Separator />
|
||||
<JsonCard obj={user} />
|
||||
|
|
|
@ -8,7 +8,7 @@ import { NavigationToolbar } from "@/components/common/NavigationToolbar";
|
|||
import { ReportActions } from "@/components/pages/inspector/ReportActions";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
fetchRelatedReports,
|
||||
fetchRelatedReportsByContent,
|
||||
fetchReportById,
|
||||
fetchSnapshotsByReport,
|
||||
fetchUserById,
|
||||
|
@ -28,7 +28,7 @@ export default async function Reports({ params }: { params: { id: string } }) {
|
|||
snapshots
|
||||
.map((snapshot) => snapshot._id)
|
||||
.map((contentId) =>
|
||||
fetchRelatedReports(contentId).then((reports) =>
|
||||
fetchRelatedReportsByContent(contentId).then((reports) =>
|
||||
reports.filter((entry) => entry._id !== report._id)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -15,7 +15,12 @@ export function ListCompactor<T>({
|
|||
return (
|
||||
<>
|
||||
{data.length === 0 && (
|
||||
<h2 className="text-sm text-center pb-2 text-gray-400">Empty List</h2>
|
||||
<>
|
||||
<h2 className="text-sm text-center pb-2 text-gray-400">Empty List</h2>
|
||||
<h3 className="text-xs text-center pb-2 text-gray-400">
|
||||
You may be lacking permissions.
|
||||
</h3>
|
||||
</>
|
||||
)}
|
||||
{data.slice(0, limit).map((item, index) => (
|
||||
<Component key={index} item={item} />
|
||||
|
|
|
@ -7,23 +7,32 @@ import {
|
|||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../ui/card";
|
||||
import { fetchMessages, fetchUsersById } from "@/lib/db";
|
||||
import { fetchMessages, fetchMessagesByUser, fetchUsersById } from "@/lib/db";
|
||||
import { CompactMessage } from "../../cards/CompactMessage";
|
||||
|
||||
export async function RecentMessages({
|
||||
query,
|
||||
users,
|
||||
}: {
|
||||
query: Filter<Message>;
|
||||
users?: boolean | User[];
|
||||
}) {
|
||||
const recentMessages = (await fetchMessages(query)).reverse();
|
||||
type Props = { users?: boolean | User[] } & (
|
||||
| {
|
||||
query: Filter<Message>;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
}
|
||||
);
|
||||
|
||||
export async function RecentMessages(props: Props) {
|
||||
const recentMessages = (
|
||||
(props as { query: {} }).query
|
||||
? await fetchMessages((props as { query: Filter<Message> }).query)
|
||||
: await fetchMessagesByUser((props as { userId: string }).userId).catch(
|
||||
() => []
|
||||
)
|
||||
).reverse();
|
||||
|
||||
const userList = (
|
||||
users === true
|
||||
props.users === true
|
||||
? await fetchUsersById([...new Set(recentMessages.map((x) => x.author))])
|
||||
: Array.isArray(users)
|
||||
? users
|
||||
: Array.isArray(props.users)
|
||||
? props.users
|
||||
: []
|
||||
).reduce((prev, next) => {
|
||||
prev[next._id] = next;
|
||||
|
@ -38,6 +47,16 @@ export async function RecentMessages({
|
|||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* enter reason for fetching */}
|
||||
{!recentMessages.length && (
|
||||
<>
|
||||
<h2 className="text-sm text-center pb-2 text-gray-400">
|
||||
No messages found
|
||||
</h2>
|
||||
<h3 className="text-xs text-center pb-2 text-gray-400">
|
||||
You may be lacking permissions.
|
||||
</h3>
|
||||
</>
|
||||
)}
|
||||
{recentMessages.map((message) => (
|
||||
<CompactMessage
|
||||
key={message._id}
|
||||
|
|
|
@ -29,14 +29,8 @@ import {
|
|||
import dayjs from "dayjs";
|
||||
import { decodeTime } from "ulid";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param param0 You have received an account strike, for one or more reasons:
|
||||
- REASON_HERE
|
||||
|
||||
Further violations will result in suspension or a permanent ban depending on severity, please abide by the [Acceptable Usage Policy](https://revolt.chat/aup).
|
||||
* @returns
|
||||
*/
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export function RelevantModerationNotices({
|
||||
userId,
|
||||
|
@ -68,9 +62,7 @@ export function RelevantModerationNotices({
|
|||
{strikesDraft.map((strike) => (
|
||||
<TableRow key={strike._id}>
|
||||
<TableCell>{strike.reason}</TableCell>
|
||||
<TableCell>
|
||||
{dayjs(decodeTime(strike._id))?.fromNow()}
|
||||
</TableCell>
|
||||
<TableCell>{dayjs(decodeTime(strike._id)).fromNow()}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
|
|
@ -13,14 +13,14 @@ type Permission =
|
|||
| `/fetch${"" | "/by-id" | "/by-user"}`
|
||||
| `/update${"" | "/discoverability"}`}`
|
||||
| `channels${"" | `/fetch${"" | "/by-id" | "/dm"}` | `/create${"" | "/dm"}`}`
|
||||
| `messages${"" | `/fetch${"" | "/by-id"}`}`
|
||||
| `messages${"" | `/fetch${"" | "/by-id" | "/by-user"}`}`
|
||||
| `reports${
|
||||
| ""
|
||||
| `/fetch${
|
||||
| ""
|
||||
| "/by-id"
|
||||
| "/open"
|
||||
| `/related${"" | "/by-content" | "/by-user"}`
|
||||
| `/related${"" | "/by-content" | "/by-user" | "/against-user"}`
|
||||
| `/snapshots${"" | "/by-report" | "/by-user"}`}`
|
||||
| `/update${
|
||||
| ""
|
||||
|
@ -36,24 +36,55 @@ type Permission =
|
|||
| `/update${"" | "/flags" | "/discoverability"}`}`
|
||||
| `users${
|
||||
| ""
|
||||
| `/fetch${"" | "/by-id" | "/memberships"}`
|
||||
| `/fetch${
|
||||
| ""
|
||||
| "/by-id"
|
||||
| "/memberships"
|
||||
| "/strikes"
|
||||
| "/notices"
|
||||
| "/relations"}`
|
||||
| `/create${"" | "/alert" | "/strike"}`
|
||||
| `/update${"" | "/badges"}`
|
||||
| `/action${"" | "/unsuspend" | "/suspend" | "/wipe" | "/ban"}`}`;
|
||||
|
||||
const PermissionSets = {
|
||||
// View open reports
|
||||
"view-open-reports": [
|
||||
// Required for viewing open reports
|
||||
"users/fetch/by-id",
|
||||
"reports/fetch/open",
|
||||
"reports/fetch/by-id",
|
||||
"reports/fetch/related",
|
||||
"reports/fetch/snapshots/by-report",
|
||||
] as Permission[],
|
||||
|
||||
// Edit reports
|
||||
"edit-reports": [
|
||||
"reports/update/notes",
|
||||
"reports/update/resolve",
|
||||
"reports/update/reject",
|
||||
"reports/update/reopen",
|
||||
] as Permission[],
|
||||
|
||||
// Moderate users
|
||||
"moderate-users": [
|
||||
"users/fetch/by-id",
|
||||
"users/fetch/strikes",
|
||||
"users/fetch/notices",
|
||||
// "bots/fetch/by-user",
|
||||
// "messages/fetch/by-user",
|
||||
// "users/fetch/memberships",
|
||||
// "servers/fetch",
|
||||
"reports/fetch/related/by-user",
|
||||
"reports/fetch/related/against-user",
|
||||
] as Permission[],
|
||||
};
|
||||
|
||||
const Roles = {
|
||||
moderator: [...PermissionSets["view-open-reports"]],
|
||||
moderator: [
|
||||
...PermissionSets["view-open-reports"],
|
||||
...PermissionSets["edit-reports"],
|
||||
...PermissionSets["moderate-users"],
|
||||
],
|
||||
};
|
||||
|
||||
const ACL: Record<string, Set<Permission>> = {
|
||||
|
|
122
lib/db.ts
122
lib/db.ts
|
@ -14,7 +14,8 @@ import type {
|
|||
} from "revolt-api";
|
||||
import { ulid } from "ulid";
|
||||
import { publishMessage } from "./redis";
|
||||
import { checkPermission } from "./accessPermissions";
|
||||
import { checkPermission, hasPermissionFromSession } from "./accessPermissions";
|
||||
import { PLATFORM_MOD_ID } from "./constants";
|
||||
|
||||
let client: MongoClient;
|
||||
|
||||
|
@ -121,7 +122,16 @@ export async function fetchUserById(id: string) {
|
|||
return await mongo()
|
||||
.db("revolt")
|
||||
.collection<User>("users")
|
||||
.findOne({ _id: id });
|
||||
.findOne(
|
||||
{ _id: id },
|
||||
{
|
||||
projection: {
|
||||
relations: (await hasPermissionFromSession("users/fetch/relations"))
|
||||
? 1
|
||||
: 0,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchUsersById(ids: string[]) {
|
||||
|
@ -130,7 +140,16 @@ export async function fetchUsersById(ids: string[]) {
|
|||
return await mongo()
|
||||
.db("revolt")
|
||||
.collection<User>("users")
|
||||
.find({ _id: { $in: ids } })
|
||||
.find(
|
||||
{ _id: { $in: ids } },
|
||||
{
|
||||
projection: {
|
||||
relations: (await hasPermissionFromSession("users/fetch/relations"))
|
||||
? 1
|
||||
: 0,
|
||||
},
|
||||
}
|
||||
)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
|
@ -227,6 +246,26 @@ export async function fetchMessageById(id: string) {
|
|||
.findOne({ _id: id });
|
||||
}
|
||||
|
||||
export async function fetchMessagesByUser(userId: string) {
|
||||
await checkPermission("messages/fetch/by-user");
|
||||
|
||||
return await mongo()
|
||||
.db("revolt")
|
||||
.collection<Message>("messages")
|
||||
.find({ author: userId }, { sort: { _id: -1 }, limit: 50 })
|
||||
.toArray();
|
||||
}
|
||||
|
||||
export async function fetchMessagesByChannel(channelId: string) {
|
||||
await checkPermission("messages/fetch/by-user");
|
||||
|
||||
return await mongo()
|
||||
.db("revolt")
|
||||
.collection<Message>("messages")
|
||||
.find({ channel: channelId }, { sort: { _id: -1 }, limit: 50 })
|
||||
.toArray();
|
||||
}
|
||||
|
||||
export async function fetchMessages(query: Filter<Message>, limit = 50) {
|
||||
await checkPermission("messages/fetch");
|
||||
|
||||
|
@ -254,7 +293,7 @@ export async function fetchOpenReports() {
|
|||
.toArray();
|
||||
}
|
||||
|
||||
export async function fetchRelatedReports(contentId: string) {
|
||||
export async function fetchRelatedReportsByContent(contentId: string) {
|
||||
await checkPermission("reports/fetch/related/by-content");
|
||||
|
||||
return await mongo()
|
||||
|
@ -271,6 +310,57 @@ export async function fetchRelatedReports(contentId: string) {
|
|||
.toArray();
|
||||
}
|
||||
|
||||
export async function fetchReportsByUser(userId: string) {
|
||||
await checkPermission("reports/fetch/related/by-user");
|
||||
|
||||
return await mongo()
|
||||
.db("revolt")
|
||||
.collection<Report>("safety_reports")
|
||||
.find(
|
||||
{ status: "Created", authorId: userId },
|
||||
{
|
||||
sort: {
|
||||
_id: -1,
|
||||
},
|
||||
}
|
||||
)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
export async function fetchReportsAgainstUser(userId: string) {
|
||||
await checkPermission("reports/fetch/related/against-user");
|
||||
|
||||
const reportIdsInvolvingUser = await mongo()
|
||||
.db("revolt")
|
||||
.collection<{ _id: string; report_id: string; content: SnapshotContent }>(
|
||||
"safety_snapshots"
|
||||
)
|
||||
.find({
|
||||
$or: [{ "content._id": userId }, { "content.author": userId }],
|
||||
})
|
||||
.toArray()
|
||||
.then((snapshots) => [
|
||||
...new Set(snapshots.map((snapshot) => snapshot.report_id)),
|
||||
]);
|
||||
|
||||
return await mongo()
|
||||
.db("revolt")
|
||||
.collection<Report>("safety_reports")
|
||||
.find(
|
||||
{
|
||||
_id: {
|
||||
$in: reportIdsInvolvingUser,
|
||||
},
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
_id: -1,
|
||||
},
|
||||
}
|
||||
)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
export async function fetchReports(
|
||||
query: Filter<Report> = { status: "Created" }
|
||||
) {
|
||||
|
@ -332,7 +422,7 @@ export async function fetchSnapshotsByReport(reportId: string) {
|
|||
}
|
||||
|
||||
export async function fetchStrikesByUser(userId: string) {
|
||||
await checkPermission("reports/fetch/snapshots/by-user");
|
||||
await checkPermission("users/fetch/strikes");
|
||||
|
||||
return await mongo()
|
||||
.db("revolt")
|
||||
|
@ -348,6 +438,28 @@ export async function fetchStrikesByUser(userId: string) {
|
|||
.toArray();
|
||||
}
|
||||
|
||||
export async function fetchNoticesByUser(userId: string) {
|
||||
await checkPermission("users/fetch/notices");
|
||||
|
||||
const dm = await mongo()
|
||||
.db("revolt")
|
||||
.collection<Channel & { channel_type: "DirectMessage" }>("channels")
|
||||
.findOne({
|
||||
channel_type: "DirectMessage",
|
||||
recipients: {
|
||||
$all: [userId, PLATFORM_MOD_ID],
|
||||
},
|
||||
});
|
||||
|
||||
if (!dm) return [];
|
||||
|
||||
return await mongo()
|
||||
.db("revolt")
|
||||
.collection<Message>("messages")
|
||||
.find({ channel: dm!._id }, { sort: { _id: -1 } })
|
||||
.toArray();
|
||||
}
|
||||
|
||||
export async function fetchBotsByUser(userId: string) {
|
||||
await checkPermission("bots/fetch/by-user");
|
||||
|
||||
|
|
Loading…
Reference in New Issue