1
0
Fork 0

feat: further work on more narrow permissions

fix-1
Paul Makles 2023-07-30 21:40:19 +01:00
parent 70aa95577a
commit ea39b68703
No known key found for this signature in database
GPG Key ID: 5059F398521BB0F6
7 changed files with 210 additions and 65 deletions

View File

@ -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} />

View File

@ -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)
)
)

View File

@ -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} />

View File

@ -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}

View File

@ -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>

View File

@ -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
View File

@ -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");