1
0
Fork 0

chore: finish implementing ACLs

fix-1
Paul Makles 2023-08-05 20:58:08 +01:00
parent 095ea105db
commit 05a33172fd
No known key found for this signature in database
GPG Key ID: 5059F398521BB0F6
6 changed files with 180 additions and 57 deletions

View File

@ -28,6 +28,13 @@ export default function Inspect() {
>
User
</Button>
<Button
className="flex-1"
variant="outline"
onClick={createHandler("account")}
>
Account
</Button>
<Button
className="flex-1"
variant="outline"

View File

@ -1,8 +1,10 @@
import { getServerSession } from "next-auth";
import { insertAuditLog } from "./db";
type Permission =
| "TEMP"
| "authifier"
| "publish_message"
| "chat_message"
| `accounts${
| ""
| `/fetch${"" | "/by-id"}`
@ -49,6 +51,21 @@ type Permission =
| `/action${"" | "/unsuspend" | "/suspend" | "/wipe" | "/ban"}`}`;
const PermissionSets = {
// Admin
admin: [
"authifier",
"publish_message",
"chat_message",
"accounts",
"bots",
"channels",
"messages",
"reports",
"sessions",
"servers",
"users",
] as Permission[],
// View open reports
"view-open-reports": [
"users/fetch/by-id",
@ -66,17 +83,50 @@ const PermissionSets = {
"reports/update/reopen",
] as Permission[],
// Moderate users
"moderate-users": [
// Revolt Discover
"revolt-discover": [
"servers/fetch/by-id",
"servers/update/flags",
"servers/update/discoverability",
"bots/fetch/by-id",
"bots/update/discoverability",
] as Permission[],
// User support
"user-support": [
"accounts/fetch/by-id",
"users/fetch/by-id",
"users/fetch/strikes",
"users/fetch/notices",
"accounts/disable",
"accounts/restore",
"accounts/deletion/queue",
"accounts/deletion/cancel",
] as Permission[],
// Moderate users
"moderate-users": [
// "bots/fetch/by-user",
// "messages/fetch/by-user",
// "users/fetch/memberships",
// "servers/fetch",
"reports/fetch/related/by-user",
"reports/fetch/related/by-content",
"reports/fetch/related/against-user",
"users/create/alert",
"users/create/strike",
"users/action/suspend",
"users/action/wipe",
"users/action/ban",
"users/action/unsuspend",
"accounts/disable",
"accounts/restore",
"publish_message",
"chat_message",
] as Permission[],
};
@ -86,6 +136,9 @@ const Roles = {
...PermissionSets["edit-reports"],
...PermissionSets["moderate-users"],
],
"user-support": [...PermissionSets["user-support"]],
"revolt-discover": [...PermissionSets["revolt-discover"]],
admin: [...PermissionSets["admin"]],
};
const ACL: Record<string, Set<Permission>> = {
@ -93,7 +146,7 @@ const ACL: Record<string, Set<Permission>> = {
};
function hasPermission(email: string, permission: Permission) {
if (email === "insert@revolt.chat") return true;
// if (email === "insert@revolt.chat") return true;
const segments = permission.split("/");
while (segments.length) {
@ -113,7 +166,13 @@ export async function hasPermissionFromSession(permission: Permission) {
return hasPermission(session.user.email, permission);
}
export async function checkPermission(permission: Permission) {
export async function checkPermission(
permission: Permission,
context: any,
args?: any
) {
if (!(await hasPermissionFromSession(permission)))
throw `Missing permission ${permission}`;
await insertAuditLog(permission, context, args);
}

View File

@ -1,7 +1,7 @@
"use server";
import { writeFile } from "fs/promises";
import { PLATFORM_MOD_ID } from "./constants";
import { PLATFORM_MOD_ID, RESTRICT_ACCESS_LIST } from "./constants";
import mongo, {
Account,
createDM,
@ -28,7 +28,7 @@ import {
import { checkPermission } from "./accessPermissions";
export async function sendAlert(userId: string, content: string) {
await checkPermission("users/create/alert");
await checkPermission("users/create/alert", userId, { content });
const messageId = ulid();
@ -48,7 +48,12 @@ export async function createStrike(
givenReason: string,
additionalContext: string
) {
await checkPermission("users/create/strike");
if (RESTRICT_ACCESS_LIST.includes(userId)) throw "restricted access";
await checkPermission("users/create/strike", userId, {
givenReason,
additionalContext,
});
const strike: AccountStrike & { moderator_id: string } = {
_id: ulid(),
@ -75,7 +80,7 @@ Further violations will result in suspension or a permanent ban depending on sev
}
export async function updateReportNotes(reportId: string, notes: string) {
await checkPermission("reports/update/notes");
await checkPermission("reports/update/notes", reportId, { notes });
return await mongo()
.db("revolt")
@ -91,7 +96,7 @@ export async function updateReportNotes(reportId: string, notes: string) {
}
export async function resolveReport(reportId: string) {
await checkPermission("reports/update/resolve");
await checkPermission("reports/update/resolve", reportId);
const $set = {
status: "Resolved",
@ -109,7 +114,7 @@ export async function resolveReport(reportId: string) {
}
export async function rejectReport(reportId: string, reason: string) {
await checkPermission("reports/update/reject");
await checkPermission("reports/update/reject", reportId, { reason });
const $set = {
status: "Rejected",
@ -128,7 +133,7 @@ export async function rejectReport(reportId: string, reason: string) {
}
export async function reopenReport(reportId: string) {
await checkPermission("reports/update/reopen");
await checkPermission("reports/update/reopen", reportId);
const $set = {
status: "Created",
@ -151,7 +156,7 @@ export async function reopenReport(reportId: string) {
}
export async function closeReportsByUser(userId: string) {
await checkPermission("TEMP");
await checkPermission("reports/update/bulk-close/by-user", userId);
return await mongo()
.db("revolt")
@ -173,7 +178,9 @@ export async function closeReportsByUser(userId: string) {
}
export async function disableAccount(userId: string) {
await checkPermission("accounts/disable");
if (RESTRICT_ACCESS_LIST.includes(userId)) throw "restricted access";
await checkPermission("accounts/disable", userId);
await mongo()
.db("revolt")
@ -186,7 +193,9 @@ export async function disableAccount(userId: string) {
}
export async function suspendUser(userId: string) {
await checkPermission("users/action/suspend");
if (RESTRICT_ACCESS_LIST.includes(userId)) throw "restricted access";
await checkPermission("users/action/suspend", userId);
await disableAccount(userId);
await mongo()
@ -217,7 +226,7 @@ export async function suspendUser(userId: string) {
}
export async function updateUserBadges(userId: string, badges: number) {
await checkPermission("users/update/badges");
await checkPermission("users/update/badges", userId, { badges });
await mongo().db("revolt").collection<User>("users").updateOne(
{
_id: userId,
@ -243,7 +252,9 @@ export async function updateUserBadges(userId: string, badges: number) {
}
export async function wipeUser(userId: string, flags = 4) {
await checkPermission("users/action/wipe");
if (RESTRICT_ACCESS_LIST.includes(userId)) throw "restricted access";
await checkPermission("users/action/wipe", userId, { flags });
// retrieve messages, dm channels, relationships, server memberships
const backup = {
@ -340,12 +351,14 @@ export async function wipeUser(userId: string, flags = 4) {
}
export async function banUser(userId: string) {
await checkPermission("users/action/ban");
if (RESTRICT_ACCESS_LIST.includes(userId)) throw "restricted access";
await checkPermission("users/action/ban", userId);
return await wipeUser(userId, 4);
}
export async function unsuspendUser(userId: string) {
await checkPermission("users/action/unsuspend");
if (RESTRICT_ACCESS_LIST.includes(userId)) throw "restricted access";
await checkPermission("users/action/unsuspend", userId);
await restoreAccount(userId);
await mongo()
@ -364,7 +377,7 @@ export async function unsuspendUser(userId: string) {
}
export async function updateServerFlags(serverId: string, flags: number) {
await checkPermission("servers/update/flags");
await checkPermission("servers/update/flags", serverId, { flags });
await mongo().db("revolt").collection<Server>("servers").updateOne(
{
_id: serverId,
@ -390,7 +403,7 @@ export async function updateServerDiscoverability(
serverId: string,
state: boolean
) {
await checkPermission("servers/update/discoverability");
await checkPermission("servers/update/discoverability", serverId, { state });
await mongo()
.db("revolt")
.collection<Server>("servers")
@ -408,7 +421,7 @@ export async function updateServerDiscoverability(
}
export async function updateBotDiscoverability(botId: string, state: boolean) {
await checkPermission("bots/update/discoverability");
await checkPermission("bots/update/discoverability", botId, { state });
await mongo()
.db("revolt")
.collection<Bot>("bots")
@ -426,7 +439,8 @@ export async function updateBotDiscoverability(botId: string, state: boolean) {
}
export async function restoreAccount(accountId: string) {
await checkPermission("accounts/restore");
if (RESTRICT_ACCESS_LIST.includes(accountId)) throw "restricted access";
await checkPermission("accounts/restore", accountId);
await mongo()
.db("revolt")
.collection<Account>("accounts")
@ -443,7 +457,8 @@ export async function restoreAccount(accountId: string) {
}
export async function queueAccountDeletion(accountId: string) {
await checkPermission("accounts/deletion/queue");
if (RESTRICT_ACCESS_LIST.includes(accountId)) throw "restricted access";
await checkPermission("accounts/deletion/queue", accountId);
const twoWeeksFuture = new Date();
twoWeeksFuture.setDate(twoWeeksFuture.getDate() + 14);
@ -469,7 +484,8 @@ export async function queueAccountDeletion(accountId: string) {
}
export async function cancelAccountDeletion(accountId: string) {
await checkPermission("accounts/deletion/cancel");
if (RESTRICT_ACCESS_LIST.includes(accountId)) throw "restricted access";
await checkPermission("accounts/deletion/cancel", accountId);
await mongo()
.db("revolt")
.collection<Account>("accounts")

View File

@ -1,3 +1,17 @@
export const PLATFORM_MOD_ID = process.env.PLATFORM_MOD_ID || "01FC17E1WTM2BGE4F3ARN3FDAF";
export const AUTUMN_URL = process.env.AUTUMN_URL || "https://autumn.revolt.chat";
export const PLATFORM_MOD_ID =
process.env.PLATFORM_MOD_ID || "01FC17E1WTM2BGE4F3ARN3FDAF";
export const AUTUMN_URL =
process.env.AUTUMN_URL || "https://autumn.revolt.chat";
export const API_URL = process.env.API_URL || "https://api.revolt.chat";
export const BLOCK_ACCESS_LIST = [];
export const RESTRICT_ACCESS_LIST = [
"01EX2NCWQ0CHS3QJF0FEQS1GR4",
"01EXAME8DPWQH72RCBPX490ZMQ", //- fatal
"01EXAF3KX65608AJ4NG27YG1HM", //- lea
"01FD58YK5W7QRV5H3D64KTQYX3", //- zoma
"01F1WKM5TK2V6KCZWR6DGBJDTZ", //- infi
"01EXAHMSGNDCAZTJXDJZ0BK8N3", //- wait what
"01GRGF2J5BRW9MVN6RS4V3X2DR", //- temp
"01FEEFJCKY5C4DMMJYZ20ACWWC", //- rexo
];

View File

@ -16,6 +16,7 @@ import { ulid } from "ulid";
import { publishMessage } from "./redis";
import { checkPermission, hasPermissionFromSession } from "./accessPermissions";
import { PLATFORM_MOD_ID } from "./constants";
import { getServerSession } from "next-auth";
let client: MongoClient;
@ -29,8 +30,34 @@ function mongo() {
export default mongo;
export async function insertAuditLog(
permission: string,
context: any,
args?: any
) {
const session = await getServerSession();
if (!session!.user!.email!) throw "invalid procedure call";
await mongo()
.db("revolt")
.collection<{
_id: string;
moderator: string;
permission: string;
context: any;
args: any;
}>("safety_audit")
.insertOne({
_id: ulid(),
moderator: session!.user!.email!,
permission,
context,
args,
});
}
export async function fetchBotById(id: string) {
await checkPermission("bots/fetch/by-id");
await checkPermission("bots/fetch/by-id", id);
return await mongo()
.db("revolt")
@ -80,7 +107,7 @@ export type Account = {
};
export async function fetchAccountById(id: string) {
await checkPermission("accounts/fetch/by-id");
await checkPermission("accounts/fetch/by-id", id);
return await mongo()
.db("revolt")
@ -97,7 +124,7 @@ export async function fetchAccountById(id: string) {
}
export async function fetchSessionsByAccount(accountId: string) {
await checkPermission("sessions/fetch/by-account-id");
await checkPermission("sessions/fetch/by-account-id", accountId);
return await mongo()
.db("revolt")
@ -117,7 +144,7 @@ export async function fetchSessionsByAccount(accountId: string) {
}
export async function fetchUserById(id: string) {
await checkPermission("users/fetch/by-id");
await checkPermission("users/fetch/by-id", id);
return await mongo()
.db("revolt")
@ -135,7 +162,7 @@ export async function fetchUserById(id: string) {
}
export async function fetchUsersById(ids: string[]) {
await checkPermission("users/fetch/by-id");
await checkPermission("users/fetch/by-id", ids);
return await mongo()
.db("revolt")
@ -154,7 +181,7 @@ export async function fetchUsersById(ids: string[]) {
}
export async function fetchChannelById(id: string) {
await checkPermission("channels/fetch/by-id");
await checkPermission("channels/fetch/by-id", id);
return await mongo()
.db("revolt")
@ -163,7 +190,7 @@ export async function fetchChannelById(id: string) {
}
export async function fetchChannels(query: Filter<Channel>) {
await checkPermission("channels/fetch");
await checkPermission("channels/fetch", query);
return await mongo()
.db("revolt")
@ -173,7 +200,7 @@ export async function fetchChannels(query: Filter<Channel>) {
}
export async function findDM(user_a: string, user_b: string) {
await checkPermission("channels/fetch/dm");
await checkPermission("channels/fetch/dm", { user_a, user_b });
return await mongo()
.db("revolt")
@ -191,7 +218,7 @@ export async function createDM(
userB: string,
lastMessageId?: string
) {
await checkPermission("channels/create/dm");
await checkPermission("channels/create/dm", { userA, userB }, lastMessageId);
const newChannel: Channel & { channel_type: "DirectMessage" } = {
_id: ulid(),
@ -219,7 +246,7 @@ export async function createDM(
}
export async function fetchServerById(id: string) {
await checkPermission("servers/fetch/by-id");
await checkPermission("servers/fetch/by-id", id);
return await mongo()
.db("revolt")
@ -228,7 +255,7 @@ export async function fetchServerById(id: string) {
}
export async function fetchServers(query: Filter<Server>) {
await checkPermission("servers/fetch");
await checkPermission("servers/fetch", query);
return await mongo()
.db("revolt")
@ -238,7 +265,7 @@ export async function fetchServers(query: Filter<Server>) {
}
export async function fetchMessageById(id: string) {
await checkPermission("messages/fetch/by-id");
await checkPermission("messages/fetch/by-id", id);
return await mongo()
.db("revolt")
@ -247,7 +274,7 @@ export async function fetchMessageById(id: string) {
}
export async function fetchMessagesByUser(userId: string) {
await checkPermission("messages/fetch/by-user");
await checkPermission("messages/fetch/by-user", userId);
return await mongo()
.db("revolt")
@ -257,7 +284,7 @@ export async function fetchMessagesByUser(userId: string) {
}
export async function fetchMessagesByChannel(channelId: string) {
await checkPermission("messages/fetch/by-user");
await checkPermission("messages/fetch/by-user", channelId);
return await mongo()
.db("revolt")
@ -267,7 +294,7 @@ export async function fetchMessagesByChannel(channelId: string) {
}
export async function fetchMessages(query: Filter<Message>, limit = 50) {
await checkPermission("messages/fetch");
await checkPermission("messages/fetch", query, { limit });
return await mongo()
.db("revolt")
@ -277,7 +304,7 @@ export async function fetchMessages(query: Filter<Message>, limit = 50) {
}
export async function fetchOpenReports() {
await checkPermission("reports/fetch/open");
await checkPermission("reports/fetch/open", "all");
return await mongo()
.db("revolt")
@ -294,7 +321,7 @@ export async function fetchOpenReports() {
}
export async function fetchRelatedReportsByContent(contentId: string) {
await checkPermission("reports/fetch/related/by-content");
await checkPermission("reports/fetch/related/by-content", contentId);
return await mongo()
.db("revolt")
@ -311,7 +338,7 @@ export async function fetchRelatedReportsByContent(contentId: string) {
}
export async function fetchReportsByUser(userId: string) {
await checkPermission("reports/fetch/related/by-user");
await checkPermission("reports/fetch/related/by-user", userId);
return await mongo()
.db("revolt")
@ -328,7 +355,7 @@ export async function fetchReportsByUser(userId: string) {
}
export async function fetchReportsAgainstUser(userId: string) {
await checkPermission("reports/fetch/related/against-user");
await checkPermission("reports/fetch/related/against-user", userId);
const reportIdsInvolvingUser = await mongo()
.db("revolt")
@ -364,7 +391,7 @@ export async function fetchReportsAgainstUser(userId: string) {
export async function fetchReports(
query: Filter<Report> = { status: "Created" }
) {
await checkPermission("reports/fetch");
await checkPermission("reports/fetch", query);
return await mongo()
.db("revolt")
@ -378,7 +405,7 @@ export async function fetchReports(
}
export async function fetchReportById(id: string) {
await checkPermission("reports/fetch/by-id");
await checkPermission("reports/fetch/by-id", id);
return await mongo()
.db("revolt")
@ -387,7 +414,7 @@ export async function fetchReportById(id: string) {
}
export async function fetchMembershipsByUser(userId: string) {
await checkPermission("users/fetch/memberships");
await checkPermission("users/fetch/memberships", userId);
return await mongo()
.db("revolt")
@ -399,7 +426,7 @@ export async function fetchMembershipsByUser(userId: string) {
export async function fetchSnapshots(
query: Filter<{ _id: string; report_id: string; content: SnapshotContent }>
) {
await checkPermission("reports/fetch/snapshots");
await checkPermission("reports/fetch/snapshots", query);
return await mongo()
.db("revolt")
@ -411,7 +438,7 @@ export async function fetchSnapshots(
}
export async function fetchSnapshotsByReport(reportId: string) {
await checkPermission("reports/fetch/snapshots/by-report");
await checkPermission("reports/fetch/snapshots/by-report", reportId);
return await mongo()
.db("revolt")
@ -422,7 +449,7 @@ export async function fetchSnapshotsByReport(reportId: string) {
}
export async function fetchStrikesByUser(userId: string) {
await checkPermission("users/fetch/strikes");
await checkPermission("users/fetch/strikes", userId);
return await mongo()
.db("revolt")
@ -439,7 +466,7 @@ export async function fetchStrikesByUser(userId: string) {
}
export async function fetchNoticesByUser(userId: string) {
await checkPermission("users/fetch/notices");
await checkPermission("users/fetch/notices", userId);
const dm = await mongo()
.db("revolt")
@ -461,7 +488,7 @@ export async function fetchNoticesByUser(userId: string) {
}
export async function fetchBotsByUser(userId: string) {
await checkPermission("bots/fetch/by-user");
await checkPermission("bots/fetch/by-user", userId);
return await mongo()
.db("revolt")
@ -476,7 +503,7 @@ export type EmailClassification = {
};
export async function fetchAuthifierEmailClassification(provider: string) {
await checkPermission("authifier");
await checkPermission("authifier", provider);
return await mongo()
.db("authifier")

View File

@ -26,12 +26,12 @@ export async function publishMessage(
topic: string,
message: ProtocolV1["server"]
) {
await checkPermission("TEMP");
await checkPermission("publish_message", topic, { message });
return await (await redis()).publish(topic, JSON.stringify(message));
}
export async function sendChatMessage(message: Message, ephermal = false) {
await checkPermission("TEMP");
await checkPermission("chat_message", message.channel, { message, ephermal });
if (!ephermal) {
await mongo()