feat: Cleanup api keys and webhooks for suspended users (#3756)

This commit is contained in:
Tom Moor
2022-07-13 09:59:31 +02:00
committed by GitHub
parent d1b01d28e6
commit 47e73cee4e
11 changed files with 264 additions and 29 deletions

View File

@@ -0,0 +1,102 @@
import { ApiKey } from "@server/models";
import {
buildUser,
buildApiKey,
buildAdmin,
buildWebhookSubscription,
buildViewer,
} from "@server/test/factories";
import { flushdb } from "@server/test/support";
import CleanupDemotedUserTask from "./CleanupDemotedUserTask";
beforeEach(() => flushdb());
describe("CleanupDemotedUserTask", () => {
it("should delete api keys for suspended user", async () => {
const admin = await buildAdmin();
const user = await buildUser({
teamId: admin.teamId,
suspendedAt: new Date(),
suspendedById: admin.id,
});
const apiKey = await buildApiKey({
userId: user.id,
});
const task = new CleanupDemotedUserTask();
await task.perform({ userId: user.id });
expect(await ApiKey.findByPk(apiKey.id)).toBeNull();
});
it("should delete api keys for viewer", async () => {
const user = await buildViewer();
const apiKey = await buildApiKey({
userId: user.id,
});
const task = new CleanupDemotedUserTask();
await task.perform({ userId: user.id });
expect(await ApiKey.findByPk(apiKey.id)).toBeNull();
});
it("should retain api keys for member", async () => {
const user = await buildUser();
const apiKey = await buildApiKey({
userId: user.id,
});
const task = new CleanupDemotedUserTask();
await task.perform({ userId: user.id });
expect(await ApiKey.findByPk(apiKey.id)).toBeTruthy();
});
it("should disable webhooks for suspended user", async () => {
const admin = await buildAdmin();
const user = await buildUser({
teamId: admin.teamId,
suspendedAt: new Date(),
suspendedById: admin.id,
});
const webhook = await buildWebhookSubscription({
teamId: user.teamId,
createdById: user.id,
});
const task = new CleanupDemotedUserTask();
await task.perform({ userId: user.id });
await webhook.reload();
expect(webhook.enabled).toEqual(false);
});
it("should disable webhooks for member", async () => {
const admin = await buildAdmin();
const user = await buildUser({
teamId: admin.teamId,
});
const webhook = await buildWebhookSubscription({
teamId: user.teamId,
createdById: user.id,
});
const task = new CleanupDemotedUserTask();
await task.perform({ userId: user.id });
await webhook.reload();
expect(webhook.enabled).toEqual(false);
});
it("should retain webhooks for admin", async () => {
const user = await buildAdmin();
const webhook = await buildWebhookSubscription({
teamId: user.teamId,
createdById: user.id,
});
const task = new CleanupDemotedUserTask();
await task.perform({ userId: user.id });
await webhook.reload();
expect(webhook.enabled).toEqual(true);
});
});

View File

@@ -0,0 +1,57 @@
import { sequelize } from "@server/database/sequelize";
import Logger from "@server/logging/Logger";
import { WebhookSubscription, ApiKey, User } from "@server/models";
import BaseTask from "./BaseTask";
type Props = {
userId: string;
};
/**
* Task to disable mechanisms for exporting data from a suspended or demoted user,
* currently this is done by destroying associated Api Keys and disabling webhooks.
*/
export default class CleanupDemotedUserTask extends BaseTask<Props> {
public async perform(props: Props) {
await sequelize.transaction(async (transaction) => {
const user = await User.findByPk(props.userId, { rejectOnEmpty: true });
if (user.isSuspended || !user.isAdmin) {
const subscriptions = await WebhookSubscription.findAll({
where: {
createdById: props.userId,
enabled: true,
},
transaction,
lock: transaction.LOCK.UPDATE,
});
await Promise.all(
subscriptions.map((subscription) =>
subscription.disable({ transaction })
)
);
Logger.info(
"task",
`Disabled ${subscriptions.length} webhooks for user ${props.userId}`
);
}
if (user.isSuspended || user.isViewer) {
const apiKeys = await ApiKey.findAll({
where: {
userId: props.userId,
},
transaction,
lock: transaction.LOCK.UPDATE,
});
await Promise.all(
apiKeys.map((apiKey) => apiKey.destroy({ transaction }))
);
Logger.info(
"task",
`Destroyed ${apiKeys.length} api keys for user ${props.userId}`
);
}
});
}
}

View File

@@ -586,7 +586,7 @@ export default class DeliverWebhookTask extends BaseTask<Props> {
if (recentDeliveries.length === 25 && allFailed) {
// If the last 25 deliveries failed, disable the subscription
await subscription.update({ enabled: false });
await subscription.disable();
// Send an email to the creator of the webhook to let them know
const [createdBy, team] = await Promise.all([