mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-06-16 17:49:09 +00:00
big overhaul (#474)
* miniflare init * feat(api): add tests * chore: more tests, almost 100% * add sessions/state spec * add majority of routes and datapaths, start on interactions * nevermind, no interactions * nevermind x2, tweetnacl is bad but SubtleCrypto has what we need apparently * simplify interactions verify * add brute force interactions tests * every primary path API route is refactored! * automatically import from legacy, or die trying. * check that we only fetch legacy once, ever * remove old-src, same some historic pieces * remove interactions & worker-utils package, update misc/types * update some packages we don't need specific pinning for anymore * update web references to API routes since they all changed * fix all linting issues, upgrade most packages * fix tests, divorce enzyme where-ever possible * update web, fix integration issues * pre-build api * fix tests * move api pretest to api package.json instead of CI * remove interactions from terraform, fix deploy side configs * update to tf 1.1.4 * prevent double writes to worker in GCS, port to newer GCP auth workflow * fix api.tf var refs, upgrade node action * change to curl-based script upload for worker script due to terraform provider limitations * oh no, cloudflare freaked out :(
This commit is contained in:
parent
b644a38aa7
commit
3291f9aacc
183 changed files with 9853 additions and 9924 deletions
252
packages/api/historic/audit-log.ts~
Normal file
252
packages/api/historic/audit-log.ts~
Normal file
|
@ -0,0 +1,252 @@
|
|||
import { uiPublicURI } from '@roleypoly/api/utils/config';
|
||||
import {
|
||||
Category,
|
||||
DiscordUser,
|
||||
Embed,
|
||||
GuildData,
|
||||
GuildDataUpdate,
|
||||
GuildSlug,
|
||||
WebhookValidationStatus,
|
||||
} from '@roleypoly/types';
|
||||
import { userAgent } from '@roleypoly/worker-utils';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { sortBy, uniq } from 'lodash';
|
||||
|
||||
type WebhookPayload = {
|
||||
username: string;
|
||||
avatar_url: string;
|
||||
embeds: Embed[];
|
||||
provider: {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
|
||||
type ChangeHandler = (
|
||||
oldValue: GuildDataUpdate[keyof GuildDataUpdate],
|
||||
newValue: GuildData[keyof GuildDataUpdate]
|
||||
) => Embed[];
|
||||
|
||||
const changeHandlers: Record<keyof GuildDataUpdate, ChangeHandler> = {
|
||||
message: (oldValue, newValue) => [
|
||||
{
|
||||
timestamp: new Date().toISOString(),
|
||||
color: 0x453e3d,
|
||||
fields: [
|
||||
{
|
||||
name: 'Old Message',
|
||||
value: oldValue as string,
|
||||
inline: false,
|
||||
},
|
||||
{
|
||||
name: 'New Message',
|
||||
value: newValue as string,
|
||||
inline: false,
|
||||
},
|
||||
],
|
||||
title: `Server message was updated...`,
|
||||
},
|
||||
],
|
||||
auditLogWebhook: (oldValue, newValue) => [
|
||||
{
|
||||
timestamp: new Date().toISOString(),
|
||||
color: 0x5d5352,
|
||||
fields: [
|
||||
{
|
||||
name: 'Old Webhook ID',
|
||||
value: !oldValue ? '*unset*' : (oldValue as string).split('/')[5],
|
||||
inline: false,
|
||||
},
|
||||
{
|
||||
name: 'New Webhook ID',
|
||||
value: !newValue ? '*unset*' : (newValue as string).split('/')[5],
|
||||
inline: false,
|
||||
},
|
||||
],
|
||||
title: `Audit Log webhook URL was changed...`,
|
||||
},
|
||||
],
|
||||
categories: (oldValue, newValue) => [
|
||||
{
|
||||
timestamp: new Date().toISOString(),
|
||||
color: 0xab9b9a,
|
||||
fields: [
|
||||
{
|
||||
name: 'Changed Categories',
|
||||
value: getChangedCategories(
|
||||
oldValue as Category[],
|
||||
newValue as Category[]
|
||||
).join('\n'),
|
||||
inline: false,
|
||||
},
|
||||
],
|
||||
title: `Categories were changed...`,
|
||||
},
|
||||
],
|
||||
accessControl: (oldValue, newValue) => [
|
||||
{
|
||||
timestamp: new Date().toISOString(),
|
||||
color: 0xab9b9a,
|
||||
fields: [
|
||||
{
|
||||
name: 'Changed Access Control',
|
||||
value: getChangedAccessControl(
|
||||
oldValue as GuildDataUpdate['accessControl'],
|
||||
newValue as GuildDataUpdate['accessControl']
|
||||
).join('\n'),
|
||||
inline: false,
|
||||
},
|
||||
],
|
||||
title: `Access Control was changed...`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sendAuditLog = async (
|
||||
guild: GuildData,
|
||||
guildUpdate: GuildDataUpdate,
|
||||
user: DiscordUser
|
||||
) => {
|
||||
const auditLogWebhooks = uniq([
|
||||
guild.auditLogWebhook || '',
|
||||
guildUpdate.auditLogWebhook || '',
|
||||
]).filter((webhook) => webhook !== '');
|
||||
|
||||
if (auditLogWebhooks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = Object.keys(guildUpdate) as (keyof GuildDataUpdate)[];
|
||||
const webhookPayload: WebhookPayload = {
|
||||
username: 'Roleypoly (Audit Log)',
|
||||
avatar_url: `https://next.roleypoly.com/logo192.png`, //TODO: change to roleypoly.com when swapped.
|
||||
embeds: [
|
||||
{
|
||||
fields: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
title: `${user.username}#${user.discriminator} has edited Roleypoly settings`,
|
||||
color: 0x332d2d,
|
||||
author: {
|
||||
name: user.username,
|
||||
icon_url: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`,
|
||||
},
|
||||
},
|
||||
],
|
||||
provider: {
|
||||
name: 'Roleypoly',
|
||||
url: uiPublicURI,
|
||||
},
|
||||
};
|
||||
|
||||
for (let key of keys) {
|
||||
if (deepEqual(guildUpdate[key], guild[key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const handler = changeHandlers[key];
|
||||
if (!handler) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const changeFields = handler(guild[key], guildUpdate[key]);
|
||||
webhookPayload.embeds.push(...changeFields);
|
||||
}
|
||||
|
||||
if (webhookPayload.embeds.length === 1) {
|
||||
// No changes, don't bother sending
|
||||
return;
|
||||
}
|
||||
|
||||
// Colors are in order already, so use them to order the embeds.
|
||||
webhookPayload.embeds = sortBy(webhookPayload.embeds, 'color');
|
||||
|
||||
const doWebhook = (webhook: string) =>
|
||||
fetch(webhook, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(webhookPayload),
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'user-agent': userAgent,
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all(auditLogWebhooks.map((webhook) => doWebhook(webhook)));
|
||||
};
|
||||
|
||||
export const validateAuditLogWebhook = async (
|
||||
guild: GuildSlug,
|
||||
webhook: string | null
|
||||
): Promise<WebhookValidationStatus> => {
|
||||
if (!webhook) {
|
||||
return WebhookValidationStatus.NoneSet;
|
||||
}
|
||||
|
||||
const url = new URL(webhook);
|
||||
|
||||
if (
|
||||
url.hostname !== 'discord.com' ||
|
||||
url.protocol !== 'https:' ||
|
||||
url.pathname.startsWith('api/webhooks/')
|
||||
) {
|
||||
return WebhookValidationStatus.NotDiscordURL;
|
||||
}
|
||||
|
||||
const response = await fetch(webhook, { method: 'GET' });
|
||||
if (response.status !== 200) {
|
||||
return WebhookValidationStatus.DoesNotExist;
|
||||
}
|
||||
|
||||
const webhookData = await response.json();
|
||||
|
||||
if (webhookData.guild_id !== guild.id) {
|
||||
return WebhookValidationStatus.NotSameGuild;
|
||||
}
|
||||
|
||||
return WebhookValidationStatus.Ok;
|
||||
};
|
||||
|
||||
const getChangedCategories = (oldCategories: Category[], newCategories: Category[]) => {
|
||||
const addedCategories = newCategories.filter(
|
||||
(c) => !oldCategories.find((o) => o.id === c.id)
|
||||
);
|
||||
const removedCategories = oldCategories.filter(
|
||||
(c) => !newCategories.find((o) => o.id === c.id)
|
||||
);
|
||||
const changedCategories = newCategories.filter(
|
||||
(c) =>
|
||||
oldCategories.find((o) => o.id === c.id) &&
|
||||
!deepEqual(
|
||||
oldCategories.find((o) => o.id === c.id),
|
||||
newCategories.find((o) => o.id === c.id)
|
||||
)
|
||||
);
|
||||
|
||||
return [
|
||||
...addedCategories.map((c) => `➕ **Added** ${c.name}`),
|
||||
...removedCategories.map((c) => `➖ **Removed** ${c.name}`),
|
||||
...changedCategories.map((c) => `🔧 **Changed** ${c.name}`),
|
||||
];
|
||||
};
|
||||
|
||||
const getChangedAccessControl = (
|
||||
oldAccessControl: GuildDataUpdate['accessControl'],
|
||||
newAccessControl: GuildDataUpdate['accessControl']
|
||||
) => {
|
||||
const pendingChanged = newAccessControl.blockPending !== oldAccessControl.blockPending;
|
||||
|
||||
return [
|
||||
`✅ Allowed roles: ${
|
||||
newAccessControl.allowList.map((role) => `<@&${role}>`).join(', ') || `*all roles*`
|
||||
}`,
|
||||
`❌ Blocked roles: ${
|
||||
newAccessControl.blockList.map((role) => `<@&${role}>`).join(', ') || `*no roles*`
|
||||
}`,
|
||||
...(pendingChanged
|
||||
? [
|
||||
`🔧 Pending/Welcome Screening users are ${
|
||||
newAccessControl.blockPending ? 'blocked ❌' : 'allowed ✔'
|
||||
}`,
|
||||
]
|
||||
: []),
|
||||
];
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue