diff --git a/app/panel/inspect/page.tsx b/app/panel/inspect/page.tsx index 263a9d3..7ae0b0d 100644 --- a/app/panel/inspect/page.tsx +++ b/app/panel/inspect/page.tsx @@ -28,6 +28,13 @@ export default function Inspect() { > User + + Account + > = { @@ -93,7 +146,7 @@ const ACL: Record> = { }; 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); } diff --git a/lib/actions.ts b/lib/actions.ts index 2846bdc..b754364 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -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("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("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("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("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("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("accounts") diff --git a/lib/constants.ts b/lib/constants.ts index afbed6f..a89ba8b 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -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 +]; diff --git a/lib/db.ts b/lib/db.ts index 4c7f9ab..6911156 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -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) { - await checkPermission("channels/fetch"); + await checkPermission("channels/fetch", query); return await mongo() .db("revolt") @@ -173,7 +200,7 @@ export async function fetchChannels(query: Filter) { } 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) { - await checkPermission("servers/fetch"); + await checkPermission("servers/fetch", query); return await mongo() .db("revolt") @@ -238,7 +265,7 @@ export async function fetchServers(query: Filter) { } 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, 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, 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 = { 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") diff --git a/lib/redis.ts b/lib/redis.ts index efc77f5..bed29a2 100644 --- a/lib/redis.ts +++ b/lib/redis.ts @@ -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()