forked from administration/panel
feat: work on access control lists
parent
77bf9dfeed
commit
6d7681e2a1
|
@ -8,8 +8,8 @@ import { NavigationToolbar } from "@/components/common/NavigationToolbar";
|
||||||
import { ReportActions } from "@/components/pages/inspector/ReportActions";
|
import { ReportActions } from "@/components/pages/inspector/ReportActions";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import {
|
import {
|
||||||
|
fetchRelatedReports,
|
||||||
fetchReportById,
|
fetchReportById,
|
||||||
fetchReports,
|
|
||||||
fetchSnapshotsByReport,
|
fetchSnapshotsByReport,
|
||||||
fetchUserById,
|
fetchUserById,
|
||||||
} from "@/lib/db";
|
} from "@/lib/db";
|
||||||
|
@ -28,13 +28,9 @@ export default async function Reports({ params }: { params: { id: string } }) {
|
||||||
snapshots
|
snapshots
|
||||||
.map((snapshot) => snapshot._id)
|
.map((snapshot) => snapshot._id)
|
||||||
.map((contentId) =>
|
.map((contentId) =>
|
||||||
fetchReports({
|
fetchRelatedReports(contentId).then((reports) =>
|
||||||
_id: {
|
reports.filter((entry) => entry._id !== report._id)
|
||||||
$ne: report._id,
|
)
|
||||||
},
|
|
||||||
status: "Created",
|
|
||||||
"content.id": contentId,
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { ReportCard } from "@/components/cards/ReportCard";
|
import { ReportCard } from "@/components/cards/ReportCard";
|
||||||
import { CardLink } from "@/components/common/CardLink";
|
import { CardLink } from "@/components/common/CardLink";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { fetchReports } from "@/lib/db";
|
import { fetchOpenReports } from "@/lib/db";
|
||||||
|
|
||||||
export default async function Reports() {
|
export default async function Reports() {
|
||||||
const reports = (await fetchReports())
|
const reports = (await fetchOpenReports())
|
||||||
.reverse()
|
.reverse()
|
||||||
.sort((b, _) => (b.content.report_reason.includes("Illegal") ? -1 : 0));
|
.sort((b, _) => (b.content.report_reason.includes("Illegal") ? -1 : 0));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
|
||||||
|
type Permission =
|
||||||
|
| "authifier"
|
||||||
|
| `accounts${"" | `/fetch${"" | "/by-id"}`}`
|
||||||
|
| `bots${"" | `/fetch${"" | "/by-id" | "/by-user"}`}`
|
||||||
|
| `channels${"" | `/fetch${"" | "/by-id" | "/dm"}` | `/create${"" | "/dm"}`}`
|
||||||
|
| `messages${"" | `/fetch${"" | "/by-id"}`}`
|
||||||
|
| `reports${
|
||||||
|
| ""
|
||||||
|
| `/fetch${
|
||||||
|
| ""
|
||||||
|
| "/by-id"
|
||||||
|
| "/open"
|
||||||
|
| `/related${"" | "/by-content" | "/by-user"}`
|
||||||
|
| `/snapshots${"" | "/by-report" | "/by-user"}`}`}`
|
||||||
|
| `sessions${"" | `/fetch${"" | "/by-account-id"}`}`
|
||||||
|
| `servers${"" | `/fetch${"" | "/by-id"}`}`
|
||||||
|
| `users${"" | `/fetch${"" | "/by-id" | "/memberships"}`}`;
|
||||||
|
|
||||||
|
const ACL: Record<string, Set<Permission>> = {
|
||||||
|
"insert@revolt.chat": new Set([
|
||||||
|
"users/fetch/by-id",
|
||||||
|
"reports/fetch/open",
|
||||||
|
"reports/fetch/by-id",
|
||||||
|
"reports/fetch/related",
|
||||||
|
"reports/fetch/snapshots/by-report",
|
||||||
|
] as Permission[]),
|
||||||
|
};
|
||||||
|
|
||||||
|
function hasPermission(email: string, permission: Permission) {
|
||||||
|
const segments = permission.split("/");
|
||||||
|
while (segments.length) {
|
||||||
|
if (ACL[email].has(segments.join("/") as Permission)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
segments.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hasPermissionFromSession(permission: Permission) {
|
||||||
|
const session = await getServerSession();
|
||||||
|
if (!session?.user?.email) throw "Not authenticated.";
|
||||||
|
return hasPermission(session.user.email, permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkPermission(permission: Permission) {
|
||||||
|
if (!(await hasPermissionFromSession(permission)))
|
||||||
|
throw `Missing permission ${permission}`;
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ import mongo, {
|
||||||
fetchMessages,
|
fetchMessages,
|
||||||
fetchUserById,
|
fetchUserById,
|
||||||
findDM,
|
findDM,
|
||||||
updateLastMessageId,
|
|
||||||
} from "./db";
|
} from "./db";
|
||||||
import { publishMessage, sendChatMessage } from "./redis";
|
import { publishMessage, sendChatMessage } from "./redis";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
@ -34,7 +33,6 @@ export async function sendAlert(userId: string, content: string) {
|
||||||
|
|
||||||
let dm = await findDM(PLATFORM_MOD_ID, userId);
|
let dm = await findDM(PLATFORM_MOD_ID, userId);
|
||||||
if (!dm) dm = await createDM(PLATFORM_MOD_ID, userId, messageId);
|
if (!dm) dm = await createDM(PLATFORM_MOD_ID, userId, messageId);
|
||||||
else await updateLastMessageId(dm._id, messageId);
|
|
||||||
|
|
||||||
await sendChatMessage({
|
await sendChatMessage({
|
||||||
_id: messageId,
|
_id: messageId,
|
||||||
|
|
89
lib/db.ts
89
lib/db.ts
|
@ -14,6 +14,7 @@ import type {
|
||||||
} from "revolt-api";
|
} from "revolt-api";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
import { publishMessage } from "./redis";
|
import { publishMessage } from "./redis";
|
||||||
|
import { checkPermission } from "./accessPermissions";
|
||||||
|
|
||||||
let client: MongoClient;
|
let client: MongoClient;
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ function mongo() {
|
||||||
export default mongo;
|
export default mongo;
|
||||||
|
|
||||||
export async function fetchBotById(id: string) {
|
export async function fetchBotById(id: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("bots/fetch/by-id");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -78,7 +79,7 @@ export type Account = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function fetchAccountById(id: string) {
|
export async function fetchAccountById(id: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("accounts/fetch/by-id");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -95,7 +96,7 @@ export async function fetchAccountById(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchSessionsByAccount(accountId: string) {
|
export async function fetchSessionsByAccount(accountId: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("sessions/fetch/by-account-id");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -115,7 +116,7 @@ export async function fetchSessionsByAccount(accountId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUserById(id: string) {
|
export async function fetchUserById(id: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("users/fetch/by-id");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -124,7 +125,7 @@ export async function fetchUserById(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUsersById(ids: string[]) {
|
export async function fetchUsersById(ids: string[]) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("users/fetch/by-id");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -134,7 +135,7 @@ export async function fetchUsersById(ids: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchChannelById(id: string) {
|
export async function fetchChannelById(id: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("channels/fetch/by-id");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -143,7 +144,7 @@ export async function fetchChannelById(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchChannels(query: Filter<Channel>) {
|
export async function fetchChannels(query: Filter<Channel>) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("channels/fetch");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -152,20 +153,8 @@ export async function fetchChannels(query: Filter<Channel>) {
|
||||||
.toArray();
|
.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateLastMessageId(
|
|
||||||
channelId: string,
|
|
||||||
messageId: string
|
|
||||||
) {
|
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
|
||||||
|
|
||||||
return await mongo()
|
|
||||||
.db("revolt")
|
|
||||||
.collection<Channel>("channels")
|
|
||||||
.updateOne({ _id: channelId }, { $set: { last_message_id: messageId } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function findDM(user_a: string, user_b: string) {
|
export async function findDM(user_a: string, user_b: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("channels/fetch/dm");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -183,7 +172,7 @@ export async function createDM(
|
||||||
userB: string,
|
userB: string,
|
||||||
lastMessageId?: string
|
lastMessageId?: string
|
||||||
) {
|
) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("channels/create/dm");
|
||||||
|
|
||||||
const newChannel: Channel & { channel_type: "DirectMessage" } = {
|
const newChannel: Channel & { channel_type: "DirectMessage" } = {
|
||||||
_id: ulid(),
|
_id: ulid(),
|
||||||
|
@ -211,7 +200,7 @@ export async function createDM(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchServerById(id: string) {
|
export async function fetchServerById(id: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("servers/fetch/by-id");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -220,7 +209,7 @@ export async function fetchServerById(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchServers(query: Filter<Server>) {
|
export async function fetchServers(query: Filter<Server>) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("servers/fetch");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -230,7 +219,7 @@ export async function fetchServers(query: Filter<Server>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchMessageById(id: string) {
|
export async function fetchMessageById(id: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("messages/fetch/by-id");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -239,7 +228,7 @@ export async function fetchMessageById(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchMessages(query: Filter<Message>, limit = 50) {
|
export async function fetchMessages(query: Filter<Message>, limit = 50) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("messages/fetch");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -248,10 +237,44 @@ export async function fetchMessages(query: Filter<Message>, limit = 50) {
|
||||||
.toArray();
|
.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchOpenReports() {
|
||||||
|
await checkPermission("reports/fetch/open");
|
||||||
|
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Report>("safety_reports")
|
||||||
|
.find(
|
||||||
|
{ status: "Created" },
|
||||||
|
{
|
||||||
|
sort: {
|
||||||
|
_id: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchRelatedReports(contentId: string) {
|
||||||
|
await checkPermission("reports/fetch/related/by-content");
|
||||||
|
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Report>("safety_reports")
|
||||||
|
.find(
|
||||||
|
{ status: "Created", "content.id": contentId },
|
||||||
|
{
|
||||||
|
sort: {
|
||||||
|
_id: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchReports(
|
export async function fetchReports(
|
||||||
query: Filter<Report> = { status: "Created" }
|
query: Filter<Report> = { status: "Created" }
|
||||||
) {
|
) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("reports/fetch");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -265,7 +288,7 @@ export async function fetchReports(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchReportById(id: string) {
|
export async function fetchReportById(id: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("reports/fetch/by-id");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -274,7 +297,7 @@ export async function fetchReportById(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchMembershipsByUser(userId: string) {
|
export async function fetchMembershipsByUser(userId: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("users/fetch/memberships");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -286,7 +309,7 @@ export async function fetchMembershipsByUser(userId: string) {
|
||||||
export async function fetchSnapshots(
|
export async function fetchSnapshots(
|
||||||
query: Filter<{ _id: string; report_id: string; content: SnapshotContent }>
|
query: Filter<{ _id: string; report_id: string; content: SnapshotContent }>
|
||||||
) {
|
) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("reports/fetch/snapshots");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -298,7 +321,7 @@ export async function fetchSnapshots(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchSnapshotsByReport(reportId: string) {
|
export async function fetchSnapshotsByReport(reportId: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("reports/fetch/snapshots/by-report");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -309,7 +332,7 @@ export async function fetchSnapshotsByReport(reportId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchStrikesByUser(userId: string) {
|
export async function fetchStrikesByUser(userId: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("reports/fetch/snapshots/by-user");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -326,7 +349,7 @@ export async function fetchStrikesByUser(userId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchBotsByUser(userId: string) {
|
export async function fetchBotsByUser(userId: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("bots/fetch/by-user");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -341,7 +364,7 @@ export type EmailClassification = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function fetchAuthifierEmailClassification(provider: string) {
|
export async function fetchAuthifierEmailClassification(provider: string) {
|
||||||
if (Math.random() > -1) throw "TODO: ACL";
|
await checkPermission("authifier");
|
||||||
|
|
||||||
return await mongo()
|
return await mongo()
|
||||||
.db("authifier")
|
.db("authifier")
|
||||||
|
|
10
lib/redis.ts
10
lib/redis.ts
|
@ -1,7 +1,7 @@
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { createClient } from "redis";
|
import { createClient } from "redis";
|
||||||
import { Message } from "revolt-api";
|
import { Channel, Message } from "revolt-api";
|
||||||
import type { ProtocolV1 } from "revolt.js/lib/events/v1";
|
import type { ProtocolV1 } from "revolt.js/lib/events/v1";
|
||||||
import mongo from "./db";
|
import mongo from "./db";
|
||||||
|
|
||||||
|
@ -38,6 +38,14 @@ export async function sendChatMessage(message: Message, ephermal = false) {
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
.collection<Message>("messages")
|
.collection<Message>("messages")
|
||||||
.insertOne(message);
|
.insertOne(message);
|
||||||
|
|
||||||
|
await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Channel>("channels")
|
||||||
|
.updateOne(
|
||||||
|
{ _id: message.channel },
|
||||||
|
{ $set: { last_message_id: message._id } }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await publishMessage(message.channel, {
|
await publishMessage(message.channel, {
|
||||||
|
|
Loading…
Reference in New Issue