From 4589bbf67925531a04834245b44df28a07a74c7b Mon Sep 17 00:00:00 2001
From: Christian Fehmer
Date: Mon, 20 May 2024 12:21:14 +0200
Subject: [PATCH] feat: maintain hashed blocklist of banned usernames, emails
and discordids (fehmer) (#5371)
* feat: maintain blocklist of banned usernames and email (fehmer)
* update privacy policy
---------
Co-authored-by: Miodec
---
.../__tests__/api/controllers/user.spec.ts | 514 +++++++++++++--
backend/__tests__/dal/blocklist.spec.ts | 339 ++++++++++
backend/__tests__/global-setup.ts | 14 +
backend/__tests__/setup-tests.ts | 122 ++--
backend/__tests__/utils/misc.spec.ts | 7 +-
backend/package-lock.json | 618 ++++++++++--------
backend/package.json | 3 +
backend/src/api/controllers/user.ts | 83 ++-
backend/src/api/routes/admin.ts | 1 -
backend/src/dal/blocklist.ts | 116 ++++
backend/src/server.ts | 4 +
backend/src/types/types.d.ts | 10 +
backend/src/utils/discord.ts | 29 +-
backend/vitest.config.js | 1 +
frontend/src/privacy-policy.html | 14 +-
15 files changed, 1453 insertions(+), 422 deletions(-)
create mode 100644 backend/__tests__/dal/blocklist.spec.ts
create mode 100644 backend/__tests__/global-setup.ts
create mode 100644 backend/src/dal/blocklist.ts
diff --git a/backend/__tests__/api/controllers/user.spec.ts b/backend/__tests__/api/controllers/user.spec.ts
index c26a70725..45c607473 100644
--- a/backend/__tests__/api/controllers/user.spec.ts
+++ b/backend/__tests__/api/controllers/user.spec.ts
@@ -4,11 +4,35 @@ import * as Configuration from "../../../src/init/configuration";
import { getCurrentTestActivity } from "../../../src/api/controllers/user";
import * as UserDal from "../../../src/dal/user";
import _ from "lodash";
+import { DecodedIdToken } from "firebase-admin/lib/auth/token-verifier";
+import * as AuthUtils from "../../../src/utils/auth";
+import * as BlocklistDal from "../../../src/dal/blocklist";
+import * as ApeKeys from "../../../src/dal/ape-keys";
+import * as PresetDal from "../../../src/dal/preset";
+import * as ConfigDal from "../../../src/dal/config";
+import * as ResultDal from "../../../src/dal/result";
+import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards";
+import GeorgeQueue from "../../../src/queues/george-queue";
+import * as AdminUuids from "../../../src/dal/admin-uids";
+import * as DiscordUtils from "../../../src/utils/discord";
const mockApp = request(app);
+const configuration = Configuration.getCachedConfiguration();
+
+const mockDecodedToken: DecodedIdToken = {
+ uid: "123456789",
+ email: "newuser@mail.com",
+ iat: Date.now(),
+} as DecodedIdToken;
describe("user controller test", () => {
+ beforeEach(() => {
+ vi.spyOn(AuthUtils, "verifyIdToken").mockResolvedValue(mockDecodedToken);
+ });
describe("user creation flow", () => {
+ beforeEach(async () => {
+ await enableSignup(true);
+ });
it("should be able to check name, sign up, and get user data", async () => {
await mockApp
.get("/users/checkName/NewUser")
@@ -24,42 +48,6 @@ describe("user controller test", () => {
captcha: "captcha",
};
- vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue({
- //if stuff breaks this might be the reason
- users: {
- signUp: true,
- discordIntegration: {
- enabled: false,
- },
- autoBan: {
- enabled: false,
- maxCount: 5,
- maxHours: 1,
- },
- profiles: {
- enabled: false,
- },
- xp: {
- enabled: false,
- gainMultiplier: 0,
- maxDailyBonus: 0,
- minDailyBonus: 0,
- streak: {
- enabled: false,
- maxStreakDays: 0,
- maxStreakMultiplier: 0,
- },
- },
- inbox: {
- enabled: false,
- maxMail: 0,
- },
- premium: {
- enabled: true,
- },
- },
- } as any);
-
await mockApp
.post("/users/signup")
.set("authorization", "Uid 123456789|newuser@mail.com")
@@ -92,20 +80,133 @@ describe("user controller test", () => {
Accept: "application/json",
})
.expect(409);
-
- vi.restoreAllMocks();
});
});
+ describe("user signup", () => {
+ const blocklistContainsMock = vi.spyOn(BlocklistDal, "contains");
+ const firebaseDeleteUserMock = vi.spyOn(AuthUtils, "deleteUser");
+ const usernameAvailableMock = vi.spyOn(UserDal, "isNameAvailable");
+ beforeEach(async () => {
+ await enableSignup(true);
+ usernameAvailableMock.mockResolvedValue(true);
+ });
+ afterEach(() => {
+ [
+ blocklistContainsMock,
+ firebaseDeleteUserMock,
+ usernameAvailableMock,
+ ].forEach((it) => it.mockReset());
+ });
+ it("should not create user if blocklisted", async () => {
+ //GIVEN
+ blocklistContainsMock.mockResolvedValue(true);
+ firebaseDeleteUserMock.mockResolvedValue();
+
+ const newUser = {
+ name: "NewUser",
+ uid: "123456789",
+ email: "newuser@mail.com",
+ captcha: "captcha",
+ };
+
+ //WHEN
+ const result = await mockApp
+ .post("/users/signup")
+ .set("authorization", "Uid 123456789|newuser@mail.com")
+ .send(newUser)
+ .set({
+ Accept: "application/json",
+ })
+ .expect(409);
+
+ //THEN
+ expect(result.body.message).toEqual("Username or email blocked");
+ expect(blocklistContainsMock).toHaveBeenCalledWith({
+ name: "NewUser",
+ email: "newuser@mail.com",
+ });
+
+ //user will be created in firebase from the frontend, make sure we remove it
+ expect(firebaseDeleteUserMock).toHaveBeenCalledWith("123456789");
+ });
+
+ it("should not create user domain is blacklisted", async () => {
+ ["tidal.lol", "selfbot.cc"].forEach(async (domain) => {
+ //GIVEN
+ firebaseDeleteUserMock.mockResolvedValue();
+
+ const newUser = {
+ name: "NewUser",
+ uid: "123456789",
+ email: `newuser@${domain}`,
+ captcha: "captcha",
+ };
+
+ //WHEN
+ const result = await mockApp
+ .post("/users/signup")
+ .set("authorization", `Uid 123456789|newuser@${domain}`)
+ .send(newUser)
+ .set({
+ Accept: "application/json",
+ })
+ .expect(400);
+
+ //THEN
+ expect(result.body.message).toEqual("Invalid domain");
+
+ //user will be created in firebase from the frontend, make sure we remove it
+ expect(firebaseDeleteUserMock).toHaveBeenCalledWith("123456789");
+ });
+ });
+
+ it("should not create user if username is taken", async () => {
+ //GIVEN
+ usernameAvailableMock.mockResolvedValue(false);
+ firebaseDeleteUserMock.mockResolvedValue();
+
+ const newUser = {
+ name: "NewUser",
+ uid: "123456789",
+ email: "newuser@mail.com",
+ captcha: "captcha",
+ };
+
+ //WHEN
+ const result = await mockApp
+ .post("/users/signup")
+ .set("authorization", "Uid 123456789|newuser@mail.com")
+ .send(newUser)
+ .set({
+ Accept: "application/json",
+ })
+ .expect(409);
+
+ //THEN
+ expect(result.body.message).toEqual("Username unavailable");
+ expect(usernameAvailableMock).toHaveBeenCalledWith(
+ "NewUser",
+ "123456789"
+ );
+
+ //user will be created in firebase from the frontend, make sure we remove it
+ expect(firebaseDeleteUserMock).toHaveBeenCalledWith("123456789");
+ });
+ });
describe("getTestActivity", () => {
+ const getUserMock = vi.spyOn(UserDal, "getUser");
+ afterAll(() => {
+ getUserMock.mockReset();
+ });
it("should return 503 for non premium users", async () => {
//given
- vi.spyOn(UserDal, "getUser").mockResolvedValue({
+ getUserMock.mockResolvedValue({
testActivity: { "2023": [1, 2, 3], "2024": [4, 5, 6] },
} as unknown as MonkeyTypes.DBUser);
//when
- const response = await mockApp
+ await mockApp
.get("/users/testActivity")
.set("authorization", "Uid 123456789")
.send()
@@ -113,7 +214,7 @@ describe("user controller test", () => {
});
it("should send data for premium users", async () => {
//given
- vi.spyOn(UserDal, "getUser").mockResolvedValue({
+ getUserMock.mockResolvedValue({
testActivity: { "2023": [1, 2, 3], "2024": [4, 5, 6] },
} as unknown as MonkeyTypes.DBUser);
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
@@ -200,6 +301,305 @@ describe("user controller test", () => {
expect(testsByDays[365]).toEqual(2024094); //2024-01
});
});
+
+ describe("toggle ban", () => {
+ const getUserMock = vi.spyOn(UserDal, "getUser");
+ const setBannedMock = vi.spyOn(UserDal, "setBanned");
+ const georgeUserBannedMock = vi.spyOn(GeorgeQueue, "userBanned");
+ const isAdminMock = vi.spyOn(AdminUuids, "isAdmin");
+ beforeEach(async () => {
+ await enableAdminFeatures(true);
+
+ isAdminMock.mockResolvedValue(true);
+ });
+ afterEach(() => {
+ [getUserMock, setBannedMock, georgeUserBannedMock, isAdminMock].forEach(
+ (it) => it.mockReset()
+ );
+ });
+
+ it("bans user with discord", async () => {
+ //GIVEN
+ const uid = "myUid";
+ const user = {
+ uid,
+ name: "name",
+ email: "email",
+ discordId: "discordId",
+ } as unknown as MonkeyTypes.DBUser;
+ getUserMock.mockResolvedValue(user);
+
+ //WHEN
+ await mockApp
+ .post("/admin/toggleBan")
+ .set("Authorization", "Bearer 123456789")
+ .send({ uid })
+ .set({
+ Accept: "application/json",
+ })
+ .expect(200);
+
+ //THEN
+ expect(getUserMock).toHaveBeenLastCalledWith(uid, "toggle ban");
+ expect(setBannedMock).toHaveBeenCalledWith(uid, true);
+ expect(georgeUserBannedMock).toHaveBeenCalledWith("discordId", true);
+ });
+ it("bans user without discord", async () => {
+ //GIVEN
+ const uid = "myUid";
+ const user = {
+ uid,
+ name: "name",
+ email: "email",
+ discordId: "",
+ } as unknown as MonkeyTypes.DBUser;
+ getUserMock.mockResolvedValue(user);
+
+ //WHEN
+ await mockApp
+ .post("/admin/toggleBan")
+ .set("Authorization", "Bearer 123456789")
+ .send({ uid })
+ .set({
+ Accept: "application/json",
+ })
+ .expect(200);
+
+ //THEN
+ expect(georgeUserBannedMock).not.toHaveBeenCalled();
+ });
+ it("unbans user with discord", async () => {
+ //GIVEN
+ const uid = "myUid";
+
+ const user = {
+ uid,
+ name: "name",
+ email: "email",
+ discordId: "discordId",
+ banned: true,
+ } as unknown as MonkeyTypes.DBUser;
+ getUserMock.mockResolvedValue(user);
+
+ //WHEN
+ await mockApp
+ .post("/admin/toggleBan")
+ .set("Authorization", "Bearer 123456789")
+ .send({ uid })
+ .set({
+ Accept: "application/json",
+ })
+ .expect(200);
+
+ //THEN
+ expect(getUserMock).toHaveBeenLastCalledWith(uid, "toggle ban");
+ expect(setBannedMock).toHaveBeenCalledWith(uid, false);
+ expect(georgeUserBannedMock).toHaveBeenCalledWith("discordId", false);
+ });
+ it("unbans user without discord", async () => {
+ //GIVEN
+ const uid = "myUid";
+
+ const user = {
+ uid,
+ name: "name",
+ email: "email",
+ discordId: "",
+ banned: true,
+ } as unknown as MonkeyTypes.DBUser;
+ getUserMock.mockResolvedValue(user);
+
+ //WHEN
+ await mockApp
+ .post("/admin/toggleBan")
+ .set("Authorization", "Bearer 123456789")
+ .send({ uid })
+ .set({
+ Accept: "application/json",
+ })
+ .expect(200);
+
+ //THEN
+ expect(georgeUserBannedMock).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("delete user", () => {
+ const getUserMock = vi.spyOn(UserDal, "getUser");
+ const deleteUserMock = vi.spyOn(UserDal, "deleteUser");
+ const firebaseDeleteUserMock = vi.spyOn(AuthUtils, "deleteUser");
+ const deleteAllApeKeysMock = vi.spyOn(ApeKeys, "deleteAllApeKeys");
+ const deleteAllPresetsMock = vi.spyOn(PresetDal, "deleteAllPresets");
+ const deleteConfigMock = vi.spyOn(ConfigDal, "deleteConfig");
+ const deleteAllResultMock = vi.spyOn(ResultDal, "deleteAll");
+ const purgeUserFromDailyLeaderboardsMock = vi.spyOn(
+ DailyLeaderboards,
+ "purgeUserFromDailyLeaderboards"
+ );
+ const blocklistAddMock = vi.spyOn(BlocklistDal, "add");
+
+ beforeEach(() => {
+ [
+ firebaseDeleteUserMock,
+ deleteUserMock,
+ blocklistAddMock,
+ deleteAllApeKeysMock,
+ deleteAllPresetsMock,
+ deleteConfigMock,
+ purgeUserFromDailyLeaderboardsMock,
+ ].forEach((it) => it.mockResolvedValue(undefined));
+
+ deleteAllResultMock.mockResolvedValue({} as any);
+ });
+
+ afterEach(() => {
+ [
+ getUserMock,
+ deleteUserMock,
+ blocklistAddMock,
+ firebaseDeleteUserMock,
+ deleteConfigMock,
+ deleteAllResultMock,
+ deleteAllApeKeysMock,
+ deleteAllPresetsMock,
+ purgeUserFromDailyLeaderboardsMock,
+ ].forEach((it) => it.mockReset());
+ });
+
+ it("should add user to blocklist if banned", async () => {
+ //GIVEN
+ const uid = mockDecodedToken.uid;
+ const user = {
+ uid,
+ name: "name",
+ email: "email",
+ discordId: "discordId",
+ banned: true,
+ } as unknown as MonkeyTypes.DBUser;
+ await getUserMock.mockResolvedValue(user);
+
+ //WHEN
+ await mockApp
+ .delete("/users/")
+ .set("Authorization", "Bearer 123456789")
+ .set({
+ Accept: "application/json",
+ })
+ .expect(200);
+
+ //THEN
+ expect(blocklistAddMock).toHaveBeenCalledWith(user);
+
+ expect(deleteUserMock).toHaveBeenCalledWith(uid);
+ expect(firebaseDeleteUserMock).toHaveBeenCalledWith(uid);
+ expect(deleteAllApeKeysMock).toHaveBeenCalledWith(uid);
+ expect(deleteAllPresetsMock).toHaveBeenCalledWith(uid);
+ expect(deleteConfigMock).toHaveBeenCalledWith(uid);
+ expect(deleteAllResultMock).toHaveBeenCalledWith(uid);
+ expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
+ uid,
+ (await configuration).dailyLeaderboards
+ );
+ });
+ it("should delete user without adding to blocklist if not banned", async () => {
+ //GIVEN
+ const uid = mockDecodedToken.uid;
+ const user = {
+ uid,
+ name: "name",
+ email: "email",
+ discordId: "discordId",
+ } as unknown as MonkeyTypes.DBUser;
+ getUserMock.mockResolvedValue(user);
+
+ //WHEN
+ await mockApp
+ .delete("/users/")
+ .set("Authorization", "Bearer 123456789")
+ .set({
+ Accept: "application/json",
+ })
+ .expect(200);
+
+ //THEN
+ expect(blocklistAddMock).not.toHaveBeenCalled();
+
+ expect(deleteUserMock).toHaveBeenCalledWith(uid);
+ expect(firebaseDeleteUserMock).toHaveBeenCalledWith(uid);
+ expect(deleteAllApeKeysMock).toHaveBeenCalledWith(uid);
+ expect(deleteAllPresetsMock).toHaveBeenCalledWith(uid);
+ expect(deleteConfigMock).toHaveBeenCalledWith(uid);
+ expect(deleteAllResultMock).toHaveBeenCalledWith(uid);
+ expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
+ uid,
+ (await configuration).dailyLeaderboards
+ );
+ });
+ });
+ describe("link discord", () => {
+ const getUserMock = vi.spyOn(UserDal, "getUser");
+ const isDiscordIdAvailableMock = vi.spyOn(UserDal, "isDiscordIdAvailable");
+ const isStateValidForUserMock = vi.spyOn(
+ DiscordUtils,
+ "iStateValidForUser"
+ );
+ const getDiscordUserMock = vi.spyOn(DiscordUtils, "getDiscordUser");
+ const blocklistContainsMock = vi.spyOn(BlocklistDal, "contains");
+
+ beforeEach(async () => {
+ isStateValidForUserMock.mockResolvedValue(true);
+ getDiscordUserMock.mockResolvedValue({
+ id: "discordUserId",
+ avatar: "discorUserAvatar",
+ username: "discordUserName",
+ discriminator: "discordUserDiscriminator",
+ });
+ isDiscordIdAvailableMock.mockResolvedValue(true);
+ blocklistContainsMock.mockResolvedValue(false);
+ await enableDiscordIntegration(true);
+ });
+ afterEach(() => {
+ [
+ getUserMock,
+ isStateValidForUserMock,
+ isDiscordIdAvailableMock,
+ getDiscordUserMock,
+ ].forEach((it) => it.mockReset());
+ });
+
+ it("should not link if discordId is blocked", async () => {
+ //GIVEN
+ const uid = mockDecodedToken.uid;
+ const user = {
+ uid,
+ name: "name",
+ email: "email",
+ } as unknown as MonkeyTypes.DBUser;
+ getUserMock.mockResolvedValue(user);
+ blocklistContainsMock.mockResolvedValue(true);
+
+ //WHEN
+ const result = await mockApp
+ .post("/users/discord/link")
+ .set("Authorization", "Bearer 123456789")
+ .set({
+ Accept: "application/json",
+ })
+ .send({
+ tokenType: "tokenType",
+ accessToken: "accessToken",
+ state: "statestatestatestate",
+ })
+ .expect(409);
+
+ //THEN
+ expect(result.body.message).toEqual("The Discord account is blocked");
+
+ expect(blocklistContainsMock).toBeCalledWith({
+ discordId: "discordUserId",
+ });
+ });
+ });
});
function fillYearWithDay(days: number): number[] {
@@ -210,8 +610,6 @@ function fillYearWithDay(days: number): number[] {
return result;
}
-const configuration = Configuration.getCachedConfiguration();
-
async function enablePremiumFeatures(premium: boolean): Promise {
const mockConfig = _.merge(await configuration, {
users: { premium: { enabled: premium } },
@@ -221,3 +619,33 @@ async function enablePremiumFeatures(premium: boolean): Promise {
mockConfig
);
}
+
+async function enableAdminFeatures(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ admin: { endpointsEnabled: enabled },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
+
+async function enableSignup(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ users: { signUp: enabled },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
+
+async function enableDiscordIntegration(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ users: { discordIntegration: { enabled } },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
diff --git a/backend/__tests__/dal/blocklist.spec.ts b/backend/__tests__/dal/blocklist.spec.ts
new file mode 100644
index 000000000..44deb3eed
--- /dev/null
+++ b/backend/__tests__/dal/blocklist.spec.ts
@@ -0,0 +1,339 @@
+import { ObjectId } from "mongodb";
+import * as BlacklistDal from "../../src/dal/blocklist";
+
+describe("BlocklistDal", () => {
+ describe("add", () => {
+ beforeEach(() => {
+ vitest.useFakeTimers();
+ });
+ afterEach(() => {
+ vitest.useRealTimers();
+ });
+ it("adds user", async () => {
+ //GIVEN
+ const now = 1715082588;
+ vitest.setSystemTime(now);
+
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+
+ //WHEN
+ await BlacklistDal.add({ name, email });
+
+ //THEN
+ expect(
+ BlacklistDal.getCollection().findOne({
+ emailHash: BlacklistDal.hash(email),
+ })
+ ).resolves.toMatchObject({
+ emailHash: BlacklistDal.hash(email),
+ timestamp: now,
+ });
+
+ expect(
+ BlacklistDal.getCollection().findOne({
+ usernameHash: BlacklistDal.hash(name),
+ })
+ ).resolves.toMatchObject({
+ usernameHash: BlacklistDal.hash(name),
+ timestamp: now,
+ });
+ });
+ it("adds user with discordId", async () => {
+ //GIVEN
+ const now = 1715082588;
+ vitest.setSystemTime(now);
+
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ const discordId = `${name}DiscordId`;
+
+ //WHEN
+ await BlacklistDal.add({ name, email, discordId });
+
+ //THEN
+ expect(
+ BlacklistDal.getCollection().findOne({
+ discordIdHash: BlacklistDal.hash(discordId),
+ })
+ ).resolves.toMatchObject({
+ discordIdHash: BlacklistDal.hash(discordId),
+ timestamp: now,
+ });
+ });
+ it("adds user should not create duplicate name", async () => {
+ //GIVEN
+ const now = 1715082588;
+ vitest.setSystemTime(now);
+
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ const email2 = `${name}@otherdomain.com`;
+ await BlacklistDal.add({ name, email });
+
+ //WHEN
+ await BlacklistDal.add({ name, email: email2 });
+
+ //THEN
+ expect(
+ BlacklistDal.getCollection()
+ .find({
+ usernameHash: BlacklistDal.hash(name),
+ })
+ .toArray()
+ ).resolves.toHaveLength(1);
+ expect(
+ BlacklistDal.getCollection()
+ .find({
+ emailHash: BlacklistDal.hash(email),
+ })
+ .toArray()
+ ).resolves.toHaveLength(1);
+ expect(
+ BlacklistDal.getCollection()
+ .find({
+ emailHash: BlacklistDal.hash(email2),
+ })
+ .toArray()
+ ).resolves.toHaveLength(1);
+ });
+ it("adds user should not create duplicate email", async () => {
+ //GIVEN
+ const now = 1715082588;
+ vitest.setSystemTime(now);
+
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ const name2 = "user" + new ObjectId().toHexString();
+ await BlacklistDal.add({ name, email });
+
+ //WHEN
+ await BlacklistDal.add({ name: name2, email });
+
+ //THEN
+ expect(
+ BlacklistDal.getCollection()
+ .find({
+ emailHash: BlacklistDal.hash(email),
+ })
+ .toArray()
+ ).resolves.toHaveLength(1);
+ });
+ it("adds user should not create duplicate discordId", async () => {
+ //GIVEN
+ const now = 1715082588;
+ vitest.setSystemTime(now);
+
+ const name = "user" + new ObjectId().toHexString();
+ const name2 = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ const discordId = `${name}DiscordId`;
+
+ await BlacklistDal.add({ name, email, discordId });
+
+ //WHEN
+ await BlacklistDal.add({ name: name2, email, discordId });
+
+ //THEN
+
+ expect(
+ BlacklistDal.getCollection()
+ .find({
+ discordIdHash: BlacklistDal.hash(discordId),
+ })
+ .toArray()
+ ).resolves.toHaveLength(1);
+ });
+ });
+ describe("contains", () => {
+ it("contains user", async () => {
+ //GIVEN
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ const discordId = `${name}DiscordId`;
+ await BlacklistDal.add({ name, email, discordId });
+ await BlacklistDal.add({ name: "test", email: "test@example.com" });
+
+ //WHEN / THEN
+ //by name
+ expect(BlacklistDal.contains({ name })).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ name: name.toUpperCase() })
+ ).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ name, email: "unknown", discordId: "unknown" })
+ ).resolves.toBeTruthy();
+
+ //by email
+ expect(BlacklistDal.contains({ email })).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ email: email.toUpperCase() })
+ ).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ name: "unknown", email, discordId: "unknown" })
+ ).resolves.toBeTruthy();
+
+ //by discordId
+ expect(BlacklistDal.contains({ discordId })).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ discordId: discordId.toUpperCase() })
+ ).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ name: "unknown", email: "unknown", discordId })
+ ).resolves.toBeTruthy();
+
+ //by name and email and discordId
+ expect(
+ BlacklistDal.contains({ name, email, discordId })
+ ).resolves.toBeTruthy();
+ });
+ it("does not contain user", async () => {
+ //GIVEN
+ await BlacklistDal.add({ name: "test", email: "test@example.com" });
+ await BlacklistDal.add({ name: "test2", email: "test2@example.com" });
+
+ //WHEN / THEN
+ expect(BlacklistDal.contains({ name: "unknown" })).resolves.toBeFalsy();
+ expect(BlacklistDal.contains({ email: "unknown" })).resolves.toBeFalsy();
+ expect(
+ BlacklistDal.contains({ discordId: "unknown" })
+ ).resolves.toBeFalsy();
+ expect(
+ BlacklistDal.contains({
+ name: "unknown",
+ email: "unknown",
+ discordId: "unknown",
+ })
+ ).resolves.toBeFalsy();
+
+ expect(BlacklistDal.contains({})).resolves.toBeFalsy();
+ });
+ });
+
+ describe("remove", () => {
+ it("removes existing username", async () => {
+ //GIVEN
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ await BlacklistDal.add({ name, email });
+ await BlacklistDal.add({ name: "test", email: "test@example.com" });
+
+ //WHEN
+ await BlacklistDal.remove({ name });
+
+ //THEN
+ expect(BlacklistDal.contains({ name })).resolves.toBeFalsy();
+ expect(BlacklistDal.contains({ email })).resolves.toBeTruthy();
+
+ //decoy still exists
+ expect(BlacklistDal.contains({ name: "test" })).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ email: "test@example.com" })
+ ).resolves.toBeTruthy();
+ });
+ it("removes existing email", async () => {
+ //GIVEN
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ await BlacklistDal.add({ name, email });
+ await BlacklistDal.add({ name: "test", email: "test@example.com" });
+
+ //WHEN
+ await BlacklistDal.remove({ email });
+
+ //THEN
+ expect(BlacklistDal.contains({ email })).resolves.toBeFalsy();
+ expect(BlacklistDal.contains({ name })).resolves.toBeTruthy();
+
+ //decoy still exists
+ expect(BlacklistDal.contains({ name: "test" })).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ email: "test@example.com" })
+ ).resolves.toBeTruthy();
+ });
+ it("removes existing discordId", async () => {
+ //GIVEN
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ const discordId = `${name}DiscordId`;
+ await BlacklistDal.add({ name, email, discordId });
+ await BlacklistDal.add({
+ name: "test",
+ email: "test@example.com",
+ discordId: "testDiscordId",
+ });
+
+ //WHEN
+ await BlacklistDal.remove({ discordId });
+
+ //THEN
+ expect(BlacklistDal.contains({ discordId })).resolves.toBeFalsy();
+ expect(BlacklistDal.contains({ name })).resolves.toBeTruthy();
+ expect(BlacklistDal.contains({ email })).resolves.toBeTruthy();
+
+ //decoy still exists
+ expect(BlacklistDal.contains({ name: "test" })).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ email: "test@example.com" })
+ ).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ discordId: "testDiscordId" })
+ ).resolves.toBeTruthy();
+ });
+ it("removes existing username,email and discordId", async () => {
+ //GIVEN
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ const discordId = `${name}DiscordId`;
+ await BlacklistDal.add({ name, email, discordId });
+ await BlacklistDal.add({
+ name: "test",
+ email: "test@example.com",
+ discordId: "testDiscordId",
+ });
+
+ //WHEN
+ await BlacklistDal.remove({ name, email, discordId });
+
+ //THEN
+ expect(BlacklistDal.contains({ email })).resolves.toBeFalsy();
+ expect(BlacklistDal.contains({ name })).resolves.toBeFalsy();
+ expect(BlacklistDal.contains({ discordId })).resolves.toBeFalsy();
+
+ //decoy still exists
+ expect(BlacklistDal.contains({ name: "test" })).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ email: "test@example.com" })
+ ).resolves.toBeTruthy();
+ expect(
+ BlacklistDal.contains({ discordId: "testDiscordId" })
+ ).resolves.toBeTruthy();
+ });
+
+ it("does not remove for empty user", async () => {
+ //GIVEN
+ const name = "user" + new ObjectId().toHexString();
+ const email = `${name}@example.com`;
+ const discordId = `${name}DiscordId`;
+ await BlacklistDal.add({ name, email, discordId });
+ await BlacklistDal.add({ name: "test", email: "test@example.com" });
+
+ //WHEN
+ await BlacklistDal.remove({});
+
+ //THEN
+ expect(BlacklistDal.contains({ email })).resolves.toBeTruthy();
+ expect(BlacklistDal.contains({ name })).resolves.toBeTruthy();
+ expect(BlacklistDal.contains({ discordId })).resolves.toBeTruthy();
+ });
+ });
+ describe("hash", () => {
+ it("hashes case insensitive", () => {
+ ["test", "TEST", "tESt"].forEach((value) =>
+ expect(BlacklistDal.hash(value)).toEqual(
+ "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
+ )
+ );
+ });
+ });
+});
diff --git a/backend/__tests__/global-setup.ts b/backend/__tests__/global-setup.ts
new file mode 100644
index 000000000..461ccb2f4
--- /dev/null
+++ b/backend/__tests__/global-setup.ts
@@ -0,0 +1,14 @@
+import * as MongoDbMock from "vitest-mongodb";
+export async function setup({ provide }): Promise {
+ await MongoDbMock.setup({
+ serverOptions: {
+ binary: {
+ version: "6.0.12",
+ },
+ },
+ });
+}
+
+export async function teardown(): Promise {
+ await MongoDbMock.teardown();
+}
diff --git a/backend/__tests__/setup-tests.ts b/backend/__tests__/setup-tests.ts
index ccf048ef9..5d267700c 100644
--- a/backend/__tests__/setup-tests.ts
+++ b/backend/__tests__/setup-tests.ts
@@ -1,78 +1,77 @@
import { Collection, Db, MongoClient, WithId } from "mongodb";
-import { afterAll, beforeAll, beforeEach, afterEach } from "vitest";
+import { afterAll, beforeAll, afterEach } from "vitest";
import * as MongoDbMock from "vitest-mongodb";
process.env["MODE"] = "dev";
-
-vi.mock("../src/init/db", () => ({
- __esModule: true,
- getDb: (): Db => db,
- collection: (name: string): Collection> =>
- db.collection>(name),
- close: () => client?.close(),
-}));
-
-vi.mock("../src/utils/logger", () => ({
- __esModule: true,
- default: {
- error: console.error,
- warning: console.warn,
- info: console.info,
- success: console.info,
- logToDb: console.info,
- },
-}));
-
-vi.mock("swagger-stats", () => ({
- getMiddleware:
- () =>
- (_: unknown, __: unknown, next: () => unknown): void => {
- next();
- },
-}));
+//process.env["MONGOMS_DISTRO"] = "ubuntu-22.04";
if (!process.env["REDIS_URI"]) {
// use mock if not set
process.env["REDIS_URI"] = "redis://mock";
}
-// TODO: better approach for this when needed
-// https://firebase.google.com/docs/rules/unit-tests#run_local_unit_tests_with_the_version_9_javascript_sdk
-vi.mock("firebase-admin", () => ({
- __esModule: true,
- default: {
- auth: (): unknown => ({
- verifyIdToken: (
- _token: string,
- _checkRevoked: boolean
- ): unknown /* Promise */ =>
- Promise.resolve({
- aud: "mockFirebaseProjectId",
- auth_time: 123,
- exp: 1000,
- uid: "mockUid",
- }),
- }),
- },
-}));
-
-const collectionsForCleanUp = ["users"];
-
let db: Db;
let client: MongoClient;
+const collectionsForCleanUp = ["users"];
+
beforeAll(async () => {
await MongoDbMock.setup({
- serverOptions: {
- binary: {
- version: "6.0.12",
- },
- },
+ //don't add any configuration here, add to global-setup.ts instead.
});
+
client = new MongoClient(globalThis.__MONGO_URI__);
+ await client.connect();
db = client.db();
+
+ vi.mock("../src/init/db", () => ({
+ __esModule: true,
+ getDb: (): Db => db,
+ collection: (name: string): Collection> =>
+ db.collection>(name),
+ close: () => {},
+ }));
+
+ vi.mock("../src/utils/logger", () => ({
+ __esModule: true,
+ default: {
+ error: console.error,
+ warning: console.warn,
+ info: console.info,
+ success: console.info,
+ logToDb: console.info,
+ },
+ }));
+
+ vi.mock("swagger-stats", () => ({
+ getMiddleware:
+ () =>
+ (_: unknown, __: unknown, next: () => unknown): void => {
+ next();
+ },
+ }));
+
+ // TODO: better approach for this when needed
+ // https://firebase.google.com/docs/rules/unit-tests#run_local_unit_tests_with_the_version_9_javascript_sdk
+ vi.mock("firebase-admin", () => ({
+ __esModule: true,
+ default: {
+ auth: (): unknown => ({
+ verifyIdToken: (
+ _token: string,
+ _checkRevoked: boolean
+ ): unknown /* Promise */ =>
+ Promise.resolve({
+ aud: "mockFirebaseProjectId",
+ auth_time: 123,
+ exp: 1000,
+ uid: "mockUid",
+ }),
+ }),
+ },
+ }));
});
-beforeEach(async () => {
+afterEach(async () => {
if (globalThis.__MONGO_URI__) {
await Promise.all(
collectionsForCleanUp.map((collection) =>
@@ -82,13 +81,12 @@ beforeEach(async () => {
}
});
-const realDateNow = Date.now;
-
-afterEach(() => {
- Date.now = realDateNow;
-});
-
afterAll(async () => {
await client?.close();
await MongoDbMock.teardown();
+ // @ts-ignore
+ db = undefined;
+ //@ts-ignore
+ client = undefined;
+ vi.resetAllMocks();
});
diff --git a/backend/__tests__/utils/misc.spec.ts b/backend/__tests__/utils/misc.spec.ts
index a02995af2..f8c2b0065 100644
--- a/backend/__tests__/utils/misc.spec.ts
+++ b/backend/__tests__/utils/misc.spec.ts
@@ -2,8 +2,13 @@ import _ from "lodash";
import * as misc from "../../src/utils/misc";
describe("Misc Utils", () => {
+ afterAll(() => {
+ vi.useRealTimers();
+ });
+
it("getCurrentDayTimestamp", () => {
- Date.now = vi.fn(() => 1652743381);
+ vi.useFakeTimers();
+ vi.setSystemTime(1652743381);
const currentDay = misc.getCurrentDayTimestamp();
expect(currentDay).toBe(1641600000);
diff --git a/backend/package-lock.json b/backend/package-lock.json
index c214593d8..45b9c3e81 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -62,13 +62,13 @@
"@types/swagger-ui-express": "4.1.3",
"@types/ua-parser-js": "0.7.36",
"@types/uuid": "8.3.4",
- "@vitest/coverage-v8": "^1.6.0",
+ "@vitest/coverage-v8": "1.6.0",
"ioredis-mock": "7.4.0",
"readline-sync": "1.4.10",
"supertest": "6.2.3",
"ts-node-dev": "2.0.0",
"typescript": "5.3.3",
- "vitest": "^1.6.0",
+ "vitest": "1.6.0",
"vitest-mongodb": "0.0.5"
},
"engines": {
@@ -104,6 +104,7 @@
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz",
"integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-crypto/util": "^3.0.0",
"@aws-sdk/types": "^3.222.0",
@@ -114,13 +115,15 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true
+ "optional": true,
+ "peer": true
},
"node_modules/@aws-crypto/ie11-detection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz",
"integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^1.11.1"
}
@@ -129,13 +132,15 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true
+ "optional": true,
+ "peer": true
},
"node_modules/@aws-crypto/sha256-browser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz",
"integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-crypto/ie11-detection": "^3.0.0",
"@aws-crypto/sha256-js": "^3.0.0",
@@ -151,13 +156,15 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true
+ "optional": true,
+ "peer": true
},
"node_modules/@aws-crypto/sha256-js": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz",
"integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-crypto/util": "^3.0.0",
"@aws-sdk/types": "^3.222.0",
@@ -168,13 +175,15 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true
+ "optional": true,
+ "peer": true
},
"node_modules/@aws-crypto/supports-web-crypto": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz",
"integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^1.11.1"
}
@@ -183,13 +192,15 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true
+ "optional": true,
+ "peer": true
},
"node_modules/@aws-crypto/util": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz",
"integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "^3.222.0",
"@aws-sdk/util-utf8-browser": "^3.0.0",
@@ -200,13 +211,15 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true
+ "optional": true,
+ "peer": true
},
"node_modules/@aws-sdk/client-cognito-identity": {
"version": "3.451.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.451.0.tgz",
"integrity": "sha512-xoImUiGoaXJZpOCgbWcdrU4vHJ8HG5KluaCkc32kuFobM277sjQimaUIHOGHL24M5vyo4QxcJD9CT/IhX63Vlg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
@@ -257,6 +270,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.451.0.tgz",
"integrity": "sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
@@ -304,6 +318,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.451.0.tgz",
"integrity": "sha512-48NcIRxWBdP1fom6RSjwn2R2u7SE7eeV3p+c4s7ukEOfrHhBxJfn3EpqBVQMGzdiU55qFImy+Fe81iA2lXq3Jw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
@@ -355,6 +370,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.451.0.tgz",
"integrity": "sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/smithy-client": "^2.1.15",
"tslib": "^2.5.0"
@@ -368,6 +384,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.451.0.tgz",
"integrity": "sha512-g1ZT46NuYfou00d94rJZ59N4TLI1T+v46lbHTtF9jwohiUsi7/vHkPIOdrgtrThGzGUVl01w62N0a2mpMydaBA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/client-cognito-identity": "3.451.0",
"@aws-sdk/types": "3.451.0",
@@ -384,6 +401,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.451.0.tgz",
"integrity": "sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/property-provider": "^2.0.0",
@@ -399,6 +417,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.451.0.tgz",
"integrity": "sha512-q82kEzymqimkJ2dHmuN2RGpi9HTFSxwwoXALnzPRaRcvR/v+YY8FMgSTfwXzPkHUDf/q8J+aDz6lPcYlnsP3sQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/fetch-http-handler": "^2.2.6",
@@ -419,6 +438,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.451.0.tgz",
"integrity": "sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/credential-provider-env": "3.451.0",
"@aws-sdk/credential-provider-process": "3.451.0",
@@ -440,6 +460,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.451.0.tgz",
"integrity": "sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/credential-provider-env": "3.451.0",
"@aws-sdk/credential-provider-ini": "3.451.0",
@@ -462,6 +483,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.451.0.tgz",
"integrity": "sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/property-provider": "^2.0.0",
@@ -478,6 +500,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.451.0.tgz",
"integrity": "sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/client-sso": "3.451.0",
"@aws-sdk/token-providers": "3.451.0",
@@ -496,6 +519,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.451.0.tgz",
"integrity": "sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/property-provider": "^2.0.0",
@@ -511,6 +535,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.451.0.tgz",
"integrity": "sha512-ihbYZrI/tSVsZFDGLfJoCx3sg1s9EQqWA+xbLoquK+RjMqTnaeshYntFJmQA5yqCIbcAkyw63OwOIBRrVb7tMA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/client-cognito-identity": "3.451.0",
"@aws-sdk/client-sso": "3.451.0",
@@ -538,6 +563,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.451.0.tgz",
"integrity": "sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/protocol-http": "^3.0.9",
@@ -553,6 +579,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.451.0.tgz",
"integrity": "sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/types": "^2.5.0",
@@ -567,6 +594,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.451.0.tgz",
"integrity": "sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/protocol-http": "^3.0.9",
@@ -582,6 +610,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.451.0.tgz",
"integrity": "sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/middleware-signing": "3.451.0",
"@aws-sdk/types": "3.451.0",
@@ -597,6 +626,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.451.0.tgz",
"integrity": "sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/property-provider": "^2.0.0",
@@ -615,6 +645,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.451.0.tgz",
"integrity": "sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@aws-sdk/util-endpoints": "3.451.0",
@@ -631,6 +662,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.451.0.tgz",
"integrity": "sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/node-config-provider": "^2.1.5",
"@smithy/types": "^2.5.0",
@@ -647,6 +679,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.451.0.tgz",
"integrity": "sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
@@ -695,6 +728,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.451.0.tgz",
"integrity": "sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -708,6 +742,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.451.0.tgz",
"integrity": "sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/util-endpoints": "^1.0.4",
@@ -722,6 +757,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz",
"integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.5.0"
},
@@ -734,6 +770,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.451.0.tgz",
"integrity": "sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/types": "^2.5.0",
@@ -746,6 +783,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.451.0.tgz",
"integrity": "sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-sdk/types": "3.451.0",
"@smithy/node-config-provider": "^2.1.5",
@@ -769,6 +807,7 @@
"resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz",
"integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.3.1"
}
@@ -2127,6 +2166,7 @@
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.13.tgz",
"integrity": "sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -2140,6 +2180,7 @@
"resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.18.tgz",
"integrity": "sha512-761sJSgNbvsqcsKW6/WZbrZr4H+0Vp/QKKqwyrxCPwD8BsiPEXNHyYnqNgaeK9xRWYswjon0Uxbpe3DWQo0j/g==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/node-config-provider": "^2.1.5",
"@smithy/types": "^2.5.0",
@@ -2156,6 +2197,7 @@
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.1.tgz",
"integrity": "sha512-gw5G3FjWC6sNz8zpOJgPpH5HGKrpoVFQpToNAwLwJVyI/LJ2jDJRjSKEsM6XI25aRpYjMSE/Qptxx305gN1vHw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/node-config-provider": "^2.1.5",
"@smithy/property-provider": "^2.0.14",
@@ -2172,6 +2214,7 @@
"resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.13.tgz",
"integrity": "sha512-CExbelIYp+DxAHG8RIs0l9QL7ElqhG4ym9BNoSpkPa4ptBQfzJdep3LbOSVJIE2VUdBAeObdeL6EDB3Jo85n3g==",
"optional": true,
+ "peer": true,
"dependencies": {
"@aws-crypto/crc32": "3.0.0",
"@smithy/types": "^2.5.0",
@@ -2184,6 +2227,7 @@
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz",
"integrity": "sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/protocol-http": "^3.0.9",
"@smithy/querystring-builder": "^2.0.13",
@@ -2197,6 +2241,7 @@
"resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.15.tgz",
"integrity": "sha512-t/qjEJZu/G46A22PAk1k/IiJZT4ncRkG5GOCNWN9HPPy5rCcSZUbh7gwp7CGKgJJ7ATMMg+0Td7i9o1lQTwOfQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"@smithy/util-buffer-from": "^2.0.0",
@@ -2212,6 +2257,7 @@
"resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.13.tgz",
"integrity": "sha512-XsGYhVhvEikX1Yz0kyIoLssJf2Rs6E0U2w2YuKdT4jSra5A/g8V2oLROC1s56NldbgnpesTYB2z55KCHHbKyjw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -2222,6 +2268,7 @@
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz",
"integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.5.0"
},
@@ -2234,6 +2281,7 @@
"resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.15.tgz",
"integrity": "sha512-xH4kRBw01gJgWiU+/mNTrnyFXeozpZHw39gLb3JKGsFDVmSrJZ8/tRqu27tU/ki1gKkxr2wApu+dEYjI3QwV1Q==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/protocol-http": "^3.0.9",
"@smithy/types": "^2.5.0",
@@ -2248,6 +2296,7 @@
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.0.tgz",
"integrity": "sha512-tddRmaig5URk2106PVMiNX6mc5BnKIKajHHDxb7K0J5MLdcuQluHMGnjkv18iY9s9O0tF+gAcPd/pDXA5L9DZw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/middleware-serde": "^2.0.13",
"@smithy/node-config-provider": "^2.1.5",
@@ -2266,6 +2315,7 @@
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.20.tgz",
"integrity": "sha512-X2yrF/SHDk2WDd8LflRNS955rlzQ9daz9UWSp15wW8KtzoTXg3bhHM78HbK1cjr48/FWERSJKh9AvRUUGlIawg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/node-config-provider": "^2.1.5",
"@smithy/protocol-http": "^3.0.9",
@@ -2285,6 +2335,7 @@
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"optional": true,
+ "peer": true,
"bin": {
"uuid": "dist/bin/uuid"
}
@@ -2294,6 +2345,7 @@
"resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.13.tgz",
"integrity": "sha512-tBGbeXw+XsE6pPr4UaXOh+UIcXARZeiA8bKJWxk2IjJcD1icVLhBSUQH9myCIZLNNzJIH36SDjUX8Wqk4xJCJg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -2307,6 +2359,7 @@
"resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz",
"integrity": "sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -2320,6 +2373,7 @@
"resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz",
"integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/property-provider": "^2.0.14",
"@smithy/shared-ini-file-loader": "^2.2.4",
@@ -2335,6 +2389,7 @@
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz",
"integrity": "sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/abort-controller": "^2.0.13",
"@smithy/protocol-http": "^3.0.9",
@@ -2351,6 +2406,7 @@
"resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz",
"integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -2364,6 +2420,7 @@
"resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.9.tgz",
"integrity": "sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -2377,6 +2434,7 @@
"resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz",
"integrity": "sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"@smithy/util-uri-escape": "^2.0.0",
@@ -2391,6 +2449,7 @@
"resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.13.tgz",
"integrity": "sha512-TEiT6o8CPZVxJ44Rly/rrsATTQsE+b/nyBVzsYn2sa75xAaZcurNxsFd8z1haoUysONiyex24JMHoJY6iCfLdA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -2404,6 +2463,7 @@
"resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.6.tgz",
"integrity": "sha512-fCQ36frtYra2fqY2/DV8+3/z2d0VB/1D1hXbjRcM5wkxTToxq6xHbIY/NGGY6v4carskMyG8FHACxgxturJ9Pg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0"
},
@@ -2416,6 +2476,7 @@
"resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz",
"integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -2429,6 +2490,7 @@
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.15.tgz",
"integrity": "sha512-SRTEJSEhQYVlBKIIdZ9SZpqW+KFqxqcNnEcBX+8xkDdWx+DItme9VcCDkdN32yTIrICC+irUufnUdV7mmHPjoA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/eventstream-codec": "^2.0.13",
"@smithy/is-array-buffer": "^2.0.0",
@@ -2448,6 +2510,7 @@
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.15.tgz",
"integrity": "sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/middleware-stack": "^2.0.7",
"@smithy/types": "^2.5.0",
@@ -2463,6 +2526,7 @@
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz",
"integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.5.0"
},
@@ -2475,6 +2539,7 @@
"resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.13.tgz",
"integrity": "sha512-okWx2P/d9jcTsZWTVNnRMpFOE7fMkzloSFyM53fA7nLKJQObxM2T4JlZ5KitKKuXq7pxon9J6SF2kCwtdflIrA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/querystring-parser": "^2.0.13",
"@smithy/types": "^2.5.0",
@@ -2486,6 +2551,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz",
"integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/util-buffer-from": "^2.0.0",
"tslib": "^2.5.0"
@@ -2499,6 +2565,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz",
"integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.5.0"
}
@@ -2508,6 +2575,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz",
"integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.5.0"
},
@@ -2520,6 +2588,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz",
"integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/is-array-buffer": "^2.0.0",
"tslib": "^2.5.0"
@@ -2533,6 +2602,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz",
"integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.5.0"
},
@@ -2545,6 +2615,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.19.tgz",
"integrity": "sha512-VHP8xdFR7/orpiABJwgoTB0t8Zhhwpf93gXhNfUBiwAE9O0rvsv7LwpQYjgvbOUDDO8JfIYQB2GYJNkqqGWsXw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/property-provider": "^2.0.14",
"@smithy/smithy-client": "^2.1.15",
@@ -2561,6 +2632,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.25.tgz",
"integrity": "sha512-jkmep6/JyWmn2ADw9VULDeGbugR4N/FJCKOt+gYyVswmN1BJOfzF2umaYxQ1HhQDvna3kzm1Dbo1qIfBW4iuHA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/config-resolver": "^2.0.18",
"@smithy/credential-provider-imds": "^2.1.1",
@@ -2579,6 +2651,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.4.tgz",
"integrity": "sha512-FPry8j1xye5yzrdnf4xKUXVnkQErxdN7bUIaqC0OFoGsv2NfD9b2UUMuZSSt+pr9a8XWAqj0HoyVNUfPiZ/PvQ==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/node-config-provider": "^2.1.5",
"@smithy/types": "^2.5.0",
@@ -2593,6 +2666,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz",
"integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.5.0"
},
@@ -2605,6 +2679,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.6.tgz",
"integrity": "sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/types": "^2.5.0",
"tslib": "^2.5.0"
@@ -2618,6 +2693,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.6.tgz",
"integrity": "sha512-PSO41FofOBmyhPQJwBQJ6mVlaD7Sp9Uff9aBbnfBJ9eqXOE/obrqQjn0PNdkfdvViiPXl49BINfnGcFtSP4kYw==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/service-error-classification": "^2.0.6",
"@smithy/types": "^2.5.0",
@@ -2632,6 +2708,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.20.tgz",
"integrity": "sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/fetch-http-handler": "^2.2.6",
"@smithy/node-http-handler": "^2.1.9",
@@ -2651,6 +2728,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz",
"integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==",
"optional": true,
+ "peer": true,
"dependencies": {
"tslib": "^2.5.0"
},
@@ -2663,6 +2741,7 @@
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz",
"integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==",
"optional": true,
+ "peer": true,
"dependencies": {
"@smithy/util-buffer-from": "^2.0.0",
"tslib": "^2.5.0"
@@ -3370,6 +3449,15 @@
"version": "3.2.3",
"license": "MIT"
},
+ "node_modules/async-mutex": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz",
+ "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/async-retry": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
@@ -3446,13 +3534,25 @@
"node": ">= 6"
}
},
+ "node_modules/b4a": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz",
+ "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==",
+ "dev": true
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"license": "MIT"
},
+ "node_modules/bare-events": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz",
+ "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==",
+ "dev": true,
+ "optional": true
+ },
"node_modules/base64-js": {
"version": "1.5.1",
- "devOptional": true,
"funding": [
{
"type": "github",
@@ -3467,7 +3567,8 @@
"url": "https://feross.org/support"
}
],
- "license": "MIT"
+ "license": "MIT",
+ "optional": true
},
"node_modules/basic-auth": {
"version": "2.0.1",
@@ -3511,23 +3612,6 @@
"node_modules/bintrees": {
"version": "1.0.1"
},
- "node_modules/bl": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
- "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
- "dev": true,
- "dependencies": {
- "buffer": "^5.5.0",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
- }
- },
- "node_modules/bl/node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
"node_modules/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz",
@@ -3557,7 +3641,8 @@
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
"integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==",
- "optional": true
+ "optional": true,
+ "peer": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
@@ -3585,30 +3670,6 @@
"node": ">=16.20.1"
}
},
- "node_modules/buffer": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
- "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.1.13"
- }
- },
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -4469,8 +4530,8 @@
},
"node_modules/end-of-stream": {
"version": "1.4.4",
- "devOptional": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
"once": "^1.4.0"
}
@@ -4662,6 +4723,12 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+ "dev": true
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"dev": true,
@@ -4709,6 +4776,7 @@
}
],
"optional": true,
+ "peer": true,
"dependencies": {
"strnum": "^1.0.5"
},
@@ -4762,15 +4830,6 @@
"node": ">=0.8.0"
}
},
- "node_modules/fd-slicer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
- "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
- "dev": true,
- "dependencies": {
- "pend": "~1.2.0"
- }
- },
"node_modules/fecha": {
"version": "4.2.1",
"license": "MIT"
@@ -4922,9 +4981,9 @@
"license": "MIT"
},
"node_modules/follow-redirects": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
- "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
@@ -5019,12 +5078,6 @@
"node": ">= 0.6"
}
},
- "node_modules/fs-constants": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
- "dev": true
- },
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
@@ -5255,18 +5308,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-port": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz",
- "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/glob": {
"version": "7.2.0",
"license": "ISC",
@@ -5563,26 +5604,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
"node_modules/ignore-by-default": {
"version": "1.0.1",
"license": "ISC"
@@ -6369,18 +6390,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/md5-file": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz",
- "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==",
- "dev": true,
- "bin": {
- "md5-file": "cli.js"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -7046,6 +7055,195 @@
"node": ">=16"
}
},
+ "node_modules/mongodb-memory-server": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-9.2.0.tgz",
+ "integrity": "sha512-w/usKdYtby5EALERxmA0+et+D0brP0InH3a26shNDgGefXA61hgl6U0P3IfwqZlEGRZdkbZig3n57AHZgDiwvg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "mongodb-memory-server-core": "9.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.20.1"
+ }
+ },
+ "node_modules/mongodb-memory-server-core": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-9.2.0.tgz",
+ "integrity": "sha512-9SWZEy+dGj5Fvm5RY/mtqHZKS64o4heDwReD4SsfR7+uNgtYo+JN41kPCcJeIH3aJf04j25i5Dia2s52KmsMPA==",
+ "dev": true,
+ "dependencies": {
+ "async-mutex": "^0.4.0",
+ "camelcase": "^6.3.0",
+ "debug": "^4.3.4",
+ "find-cache-dir": "^3.3.2",
+ "follow-redirects": "^1.15.6",
+ "https-proxy-agent": "^7.0.4",
+ "mongodb": "^5.9.1",
+ "new-find-package-json": "^2.0.0",
+ "semver": "^7.6.0",
+ "tar-stream": "^3.1.7",
+ "tslib": "^2.6.2",
+ "yauzl": "^3.1.3"
+ },
+ "engines": {
+ "node": ">=14.20.1"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
+ "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "@types/webidl-conversions": "*"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/agent-base": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+ "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/bson": {
+ "version": "5.5.1",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz",
+ "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.20.1"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/https-proxy-agent": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
+ "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^7.0.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/mongodb": {
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz",
+ "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==",
+ "dev": true,
+ "dependencies": {
+ "bson": "^5.5.0",
+ "mongodb-connection-string-url": "^2.6.0",
+ "socks": "^2.7.1"
+ },
+ "engines": {
+ "node": ">=14.20.1"
+ },
+ "optionalDependencies": {
+ "@mongodb-js/saslprep": "^1.1.0"
+ },
+ "peerDependencies": {
+ "@aws-sdk/credential-providers": "^3.188.0",
+ "@mongodb-js/zstd": "^1.0.0",
+ "kerberos": "^1.0.0 || ^2.0.0",
+ "mongodb-client-encryption": ">=2.3.0 <3",
+ "snappy": "^7.2.2"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-providers": {
+ "optional": true
+ },
+ "@mongodb-js/zstd": {
+ "optional": true
+ },
+ "kerberos": {
+ "optional": true
+ },
+ "mongodb-client-encryption": {
+ "optional": true
+ },
+ "snappy": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
+ "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/whatwg-url": "^8.2.1",
+ "whatwg-url": "^11.0.0"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/mongodb-memory-server-core/node_modules/whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/ms": {
"version": "2.0.0",
"license": "MIT"
@@ -7760,6 +7958,12 @@
],
"license": "MIT"
},
+ "node_modules/queue-tick": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
+ "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
+ "dev": true
+ },
"node_modules/quick-format-unescaped": {
"version": "4.0.4",
"dev": true,
@@ -8315,6 +8519,19 @@
"integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
"optional": true
},
+ "node_modules/streamx": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz",
+ "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==",
+ "dev": true,
+ "dependencies": {
+ "fast-fifo": "^1.1.0",
+ "queue-tick": "^1.0.1"
+ },
+ "optionalDependencies": {
+ "bare-events": "^2.2.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"license": "MIT",
@@ -8732,6 +8949,17 @@
"node": ">=10"
}
},
+ "node_modules/tar-stream": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
+ "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
+ "dev": true,
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
"node_modules/tdigest": {
"version": "0.1.1",
"license": "MIT",
@@ -9326,37 +9554,6 @@
"mongodb-memory-server": "^8.12.0"
}
},
- "node_modules/vitest-mongodb/node_modules/@types/whatwg-url": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
- "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
- "dev": true,
- "dependencies": {
- "@types/node": "*",
- "@types/webidl-conversions": "*"
- }
- },
- "node_modules/vitest-mongodb/node_modules/async-mutex": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz",
- "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==",
- "dev": true,
- "dependencies": {
- "tslib": "^2.3.1"
- }
- },
- "node_modules/vitest-mongodb/node_modules/bson": {
- "version": "4.7.2",
- "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz",
- "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==",
- "dev": true,
- "dependencies": {
- "buffer": "^5.6.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/vitest-mongodb/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -9374,130 +9571,12 @@
}
}
},
- "node_modules/vitest-mongodb/node_modules/mongodb": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz",
- "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==",
- "dev": true,
- "dependencies": {
- "bson": "^4.7.2",
- "mongodb-connection-string-url": "^2.6.0",
- "socks": "^2.7.1"
- },
- "engines": {
- "node": ">=12.9.0"
- },
- "optionalDependencies": {
- "@aws-sdk/credential-providers": "^3.186.0",
- "@mongodb-js/saslprep": "^1.1.0"
- }
- },
- "node_modules/vitest-mongodb/node_modules/mongodb-connection-string-url": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
- "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
- "dev": true,
- "dependencies": {
- "@types/whatwg-url": "^8.2.1",
- "whatwg-url": "^11.0.0"
- }
- },
- "node_modules/vitest-mongodb/node_modules/mongodb-memory-server": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-8.16.0.tgz",
- "integrity": "sha512-oaeu2GZWycIysTj18b1gZ6d+CqWeQQZe5f8ml8Z1buaGAn3GcrGdbG5+0fseEO5ANQzcjA92qHhbsImgXeEmIQ==",
- "dev": true,
- "hasInstallScript": true,
- "dependencies": {
- "mongodb-memory-server-core": "8.16.0",
- "tslib": "^2.6.1"
- },
- "engines": {
- "node": ">=12.22.0"
- }
- },
- "node_modules/vitest-mongodb/node_modules/mongodb-memory-server-core": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-8.16.0.tgz",
- "integrity": "sha512-wyNo8yj6se7KH49hQmRtiwide7DnGINUGa1m84RyX1NU9DkCrTwbOV2VbPgd3+55DZfRup/DebU1M1zEv+3Rng==",
- "dev": true,
- "dependencies": {
- "async-mutex": "^0.3.2",
- "camelcase": "^6.3.0",
- "debug": "^4.3.4",
- "find-cache-dir": "^3.3.2",
- "follow-redirects": "^1.15.2",
- "get-port": "^5.1.1",
- "https-proxy-agent": "^5.0.1",
- "md5-file": "^5.0.0",
- "mongodb": "^4.16.0",
- "new-find-package-json": "^2.0.0",
- "semver": "^7.5.4",
- "tar-stream": "^2.1.4",
- "tslib": "^2.6.1",
- "uuid": "^9.0.0",
- "yauzl": "^2.10.0"
- },
- "engines": {
- "node": ">=12.22.0"
- }
- },
"node_modules/vitest-mongodb/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
- "node_modules/vitest-mongodb/node_modules/tar-stream": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
- "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
- "dev": true,
- "dependencies": {
- "bl": "^4.0.3",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/vitest-mongodb/node_modules/tr46": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
- "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
- "dev": true,
- "dependencies": {
- "punycode": "^2.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest-mongodb/node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
- "dev": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest-mongodb/node_modules/whatwg-url": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
- "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
- "dev": true,
- "dependencies": {
- "tr46": "^3.0.0",
- "webidl-conversions": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/vitest/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -9956,13 +10035,16 @@
}
},
"node_modules/yauzl": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
- "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.1.3.tgz",
+ "integrity": "sha512-JCCdmlJJWv7L0q/KylOekyRaUrdEoUxWkWVcgorosTROCFWiS9p2NNPE9Yb91ak7b1N5SxAZEliWpspbZccivw==",
"dev": true,
"dependencies": {
"buffer-crc32": "~0.2.3",
- "fd-slicer": "~1.1.0"
+ "pend": "~1.2.0"
+ },
+ "engines": {
+ "node": ">=12"
}
},
"node_modules/yn": {
diff --git a/backend/package.json b/backend/package.json
index 93785b499..4dba879b0 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -81,5 +81,8 @@
"typescript": "5.3.3",
"vitest": "1.6.0",
"vitest-mongodb": "0.0.5"
+ },
+ "overrides": {
+ "mongodb-memory-server": "9.2.0"
}
}
diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts
index 568d5186b..021848696 100644
--- a/backend/src/api/controllers/user.ts
+++ b/backend/src/api/controllers/user.ts
@@ -19,8 +19,6 @@ import { deleteConfig } from "../../dal/config";
import { verify } from "../../utils/captcha";
import * as LeaderboardsDAL from "../../dal/leaderboards";
import { purgeUserFromDailyLeaderboards } from "../../utils/daily-leaderboards";
-import { randomBytes } from "crypto";
-import * as RedisClient from "../../init/redis";
import { v4 as uuidv4 } from "uuid";
import { ObjectId } from "mongodb";
import * as ReportDAL from "../../dal/report";
@@ -32,6 +30,7 @@ import {
} from "../../utils/auth";
import * as Dates from "date-fns";
import { UTCDateMini } from "@date-fns/utc";
+import * as BlocklistDal from "../../dal/blocklist";
async function verifyCaptcha(captcha: string): Promise {
if (!(await verify(captcha))) {
@@ -47,28 +46,30 @@ export async function createNewUser(
try {
await verifyCaptcha(captcha);
- } catch (e) {
- try {
- await firebaseDeleteUser(uid);
- } catch (e) {
- // user might be deleted on the frontend
+
+ if (email.endsWith("@tidal.lol") || email.endsWith("@selfbot.cc")) {
+ throw new MonkeyError(400, "Invalid domain");
}
+
+ const available = await UserDAL.isNameAvailable(name, uid);
+ if (!available) {
+ throw new MonkeyError(409, "Username unavailable");
+ }
+
+ const blocklisted = await BlocklistDal.contains({ name, email });
+ if (blocklisted) {
+ throw new MonkeyError(409, "Username or email blocked");
+ }
+
+ await UserDAL.addUser(name, email, uid);
+ void Logger.logToDb("user_created", `${name} ${email}`, uid);
+
+ return new MonkeyResponse("User created");
+ } catch (e) {
+ //user was created in firebase from the frontend, remove it
+ await firebaseDeleteUserIgnoreError(uid);
throw e;
}
-
- if (email.endsWith("@tidal.lol") || email.endsWith("@selfbot.cc")) {
- throw new MonkeyError(400, "Invalid domain");
- }
-
- const available = await UserDAL.isNameAvailable(name, uid);
- if (!available) {
- throw new MonkeyError(409, "Username unavailable");
- }
-
- await UserDAL.addUser(name, email, uid);
- void Logger.logToDb("user_created", `${name} ${email}`, uid);
-
- return new MonkeyResponse("User created");
}
export async function sendVerificationEmail(
@@ -182,10 +183,9 @@ export async function deleteUser(
const userInfo = await UserDAL.getUser(uid, "delete user");
- // gdpr goes brr, find a different way
- // if (userInfo.banned) {
- // throw new MonkeyError(403, "Banned users cannot delete their account");
- // }
+ if (userInfo.banned === true) {
+ await BlocklistDal.add(userInfo);
+ }
//cleanup database
await Promise.all([
@@ -435,39 +435,24 @@ export async function getUser(
export async function getOauthLink(
req: MonkeyTypes.Request
): Promise {
- const connection = RedisClient.getConnection();
- if (!connection) {
- throw new MonkeyError(500, "Redis connection not found");
- }
-
const { uid } = req.ctx.decodedToken;
- const token = randomBytes(10).toString("hex");
-
- //add the token uid pair to reids
- await connection.setex(`discordoauth:${uid}`, 60, token);
//build the url
- const url = DiscordUtils.getOauthLink();
+ const url = await DiscordUtils.getOauthLink(uid);
//return
return new MonkeyResponse("Discord oauth link generated", {
- url: `${url}&state=${token}`,
+ url: url,
});
}
export async function linkDiscord(
req: MonkeyTypes.Request
): Promise {
- const connection = RedisClient.getConnection();
- if (!connection) {
- throw new MonkeyError(500, "Redis connection not found");
- }
const { uid } = req.ctx.decodedToken;
const { tokenType, accessToken, state } = req.body;
- const redisToken = await connection.getdel(`discordoauth:${uid}`);
-
- if (!(redisToken ?? "") || redisToken !== state) {
+ if (!(await DiscordUtils.iStateValidForUser(state, uid))) {
throw new MonkeyError(403, "Invalid user token");
}
@@ -503,6 +488,10 @@ export async function linkDiscord(
);
}
+ if (await BlocklistDal.contains({ discordId })) {
+ throw new MonkeyError(409, "The Discord account is blocked");
+ }
+
await UserDAL.linkDiscord(uid, discordId, discordAvatar);
await GeorgeQueue.linkDiscord(discordId, uid);
@@ -1033,3 +1022,11 @@ export async function getTestActivity(
return new MonkeyResponse("Test activity data retrieved", user.testActivity);
}
+
+async function firebaseDeleteUserIgnoreError(uid: string): Promise {
+ try {
+ await firebaseDeleteUser(uid);
+ } catch (e) {
+ //ignore
+ }
+}
diff --git a/backend/src/api/routes/admin.ts b/backend/src/api/routes/admin.ts
index 2abf72e77..058f2d420 100644
--- a/backend/src/api/routes/admin.ts
+++ b/backend/src/api/routes/admin.ts
@@ -22,7 +22,6 @@ router.use(
invalidMessage: "Admin endpoints are currently disabled.",
})
);
-
router.get(
"/",
adminLimit,
diff --git a/backend/src/dal/blocklist.ts b/backend/src/dal/blocklist.ts
new file mode 100644
index 000000000..78e53daef
--- /dev/null
+++ b/backend/src/dal/blocklist.ts
@@ -0,0 +1,116 @@
+import { Collection } from "mongodb";
+import * as db from "../init/db";
+import { createHash } from "crypto";
+
+type BlocklistEntryProperties = Pick<
+ SharedTypes.User,
+ "name" | "email" | "discordId"
+>;
+// Export for use in tests
+export const getCollection = (): Collection =>
+ db.collection("blocklist");
+
+export async function add(user: BlocklistEntryProperties): Promise {
+ const timestamp = Date.now();
+ const inserts: Promise[] = [];
+
+ const usernameHash = hash(user.name);
+ const emailHash = hash(user.email);
+ inserts.push(
+ getCollection().replaceOne(
+ { usernameHash },
+ {
+ usernameHash,
+ timestamp,
+ },
+ { upsert: true }
+ ),
+ getCollection().replaceOne(
+ { emailHash },
+ {
+ emailHash,
+ timestamp,
+ },
+ { upsert: true }
+ )
+ );
+
+ if (user.discordId !== undefined && user.discordId !== "") {
+ const discordIdHash = hash(user.discordId);
+ inserts.push(
+ getCollection().replaceOne(
+ { discordIdHash },
+ {
+ discordIdHash,
+ timestamp,
+ },
+ { upsert: true }
+ )
+ );
+ }
+ await Promise.all(inserts);
+}
+
+export async function remove(
+ user: Partial
+): Promise {
+ const filter = getFilter(user);
+ if (filter.length === 0) return;
+ await getCollection().deleteMany({ $or: filter });
+}
+
+export async function contains(
+ user: Partial
+): Promise {
+ const filter = getFilter(user);
+ if (filter.length === 0) return false;
+
+ return (
+ (await getCollection().countDocuments({
+ $or: filter,
+ })) !== 0
+ );
+}
+export function hash(value: string): string {
+ return createHash("sha256").update(value.toLocaleLowerCase()).digest("hex");
+}
+
+function getFilter(
+ user: Partial
+): Partial[] {
+ const filter: Partial[] = [];
+ if (user.email !== undefined) {
+ filter.push({ emailHash: hash(user.email) });
+ }
+ if (user.name !== undefined) {
+ filter.push({ usernameHash: hash(user.name) });
+ }
+ if (user.discordId !== undefined) {
+ filter.push({ discordIdHash: hash(user.discordId) });
+ }
+ return filter;
+}
+
+export async function createIndicies(): Promise {
+ await getCollection().createIndex(
+ { usernameHash: 1 },
+ {
+ unique: true,
+ partialFilterExpression: { usernameHash: { $exists: true } },
+ }
+ );
+ await getCollection().createIndex(
+ { emailHash: 1 },
+ {
+ unique: true,
+ partialFilterExpression: { emailHash: { $exists: true } },
+ }
+ );
+ await getCollection().createIndex(
+ { discordIdHash: 1 },
+ {
+ unique: true,
+ partialFilterExpression: { discordIdHash: { $exists: true } },
+ }
+ );
+}
diff --git a/backend/src/server.ts b/backend/src/server.ts
index 9f71f48de..0cbf9802a 100644
--- a/backend/src/server.ts
+++ b/backend/src/server.ts
@@ -14,6 +14,7 @@ import * as EmailClient from "./init/email-client";
import { init as initFirebaseAdmin } from "./init/firebase-admin";
import { createIndicies as leaderboardDbSetup } from "./dal/leaderboards";
+import { createIndicies as blocklistDbSetup } from "./dal/blocklist";
async function bootServer(port: number): Promise {
try {
@@ -68,6 +69,9 @@ async function bootServer(port: number): Promise {
Logger.info("Setting up leaderboard indicies...");
await leaderboardDbSetup();
+ Logger.info("Setting up blocklist indicies...");
+ await blocklistDbSetup();
+
recordServerVersion(version);
} catch (error) {
Logger.error("Failed to boot server");
diff --git a/backend/src/types/types.d.ts b/backend/src/types/types.d.ts
index f767247bc..97a519669 100644
--- a/backend/src/types/types.d.ts
+++ b/backend/src/types/types.d.ts
@@ -110,4 +110,14 @@ declare namespace MonkeyTypes {
type DBResult = MonkeyTypes.WithObjectId<
SharedTypes.DBResult
>;
+
+ type BlocklistEntry = {
+ _id: string;
+ usernameHash?: string;
+ emailHash?: string;
+ discordIdHash?: string;
+ timestamp: number;
+ };
+
+ type DBBlocklistEntry = WithObjectId;
}
diff --git a/backend/src/utils/discord.ts b/backend/src/utils/discord.ts
index 74971bbf0..b3b74381f 100644
--- a/backend/src/utils/discord.ts
+++ b/backend/src/utils/discord.ts
@@ -1,5 +1,8 @@
import fetch from "node-fetch";
import { isDevEnvironment } from "./misc";
+import * as RedisClient from "../init/redis";
+import { randomBytes } from "crypto";
+import MonkeyError from "./error";
const BASE_URL = "https://discord.com/api";
@@ -34,10 +37,32 @@ export async function getDiscordUser(
return (await response.json()) as DiscordUser;
}
-export function getOauthLink(): string {
+export async function getOauthLink(uid: string): Promise {
+ const connection = RedisClient.getConnection();
+ if (!connection) {
+ throw new MonkeyError(500, "Redis connection not found");
+ }
+ const token = randomBytes(10).toString("hex");
+
+ //add the token uid pair to reids
+ await connection.setex(`discordoauth:${uid}`, 60, token);
+
return `${BASE_URL}/oauth2/authorize?client_id=798272335035498557&redirect_uri=${
isDevEnvironment()
? `http%3A%2F%2Flocalhost%3A3000%2Fverify`
: `https%3A%2F%2Fmonkeytype.com%2Fverify`
- }&response_type=token&scope=identify`;
+ }&response_type=token&scope=identify&state=${token}`;
+}
+
+export async function iStateValidForUser(
+ state: string,
+ uid: string
+): Promise {
+ const connection = RedisClient.getConnection();
+ if (!connection) {
+ throw new MonkeyError(500, "Redis connection not found");
+ }
+ const redisToken = await connection.getdel(`discordoauth:${uid}`);
+
+ return redisToken === state;
}
diff --git a/backend/vitest.config.js b/backend/vitest.config.js
index 8a431a4b0..aacbb1a01 100644
--- a/backend/vitest.config.js
+++ b/backend/vitest.config.js
@@ -4,6 +4,7 @@ export default defineConfig({
test: {
globals: true,
environment: "node",
+ globalSetup: "__tests__/global-setup.ts",
setupFiles: ["__tests__/setup-tests.ts"],
pool: "forks",
diff --git a/frontend/src/privacy-policy.html b/frontend/src/privacy-policy.html
index 3860a1c7e..ec1f3c3db 100644
--- a/frontend/src/privacy-policy.html
+++ b/frontend/src/privacy-policy.html
@@ -112,7 +112,7 @@
Effective date: September 8, 2021
- Last updated: Oct 24, 2023
+ Last updated: May 20, 2024
Thanks for trusting Monkeytype ('Monkeytype', 'we', 'us', 'our') with
your personal information! We take our responsibility to you very
@@ -177,6 +177,7 @@
- Email
- Username
+ - Discord id and discord avatar id (if you provide it)
- Information about each typing test
- Your currently active settings
- How many typing tests you've started and completed
@@ -221,6 +222,12 @@
- Display leaderboards
+
+ If you are found to be cheating or exploiting the website, we may
+ store hashed versions of your username, email and/or discord id to
+ prevent you from creating new accounts.
+
+
How do we store your data?
Monkeytype securely stores your data using MongoDB.
@@ -245,7 +252,10 @@
The right to erasure – You have the right to request that Monkeytype
- erase your personal data, under certain conditions.
+ erase your personal data, under certain conditions. (Hashed data
+ mentioned in the "How will we use your data?" section will not be
+ deleted, as it is essential in preventing the exploitation of the
+ website)
The right to restrict processing – You have the right to request