From 42d699fabee04e027ce9d297a36f4d0f3e14a98f Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 22 Sep 2025 13:26:28 +0200 Subject: [PATCH] fix: Add additional checks to userInviter (#10226) --- server/commands/userInviter.test.ts | 55 ++++++++++++++++++++++++++++- server/commands/userInviter.ts | 8 +++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/server/commands/userInviter.test.ts b/server/commands/userInviter.test.ts index 358b394587..25f75af5e4 100644 --- a/server/commands/userInviter.test.ts +++ b/server/commands/userInviter.test.ts @@ -1,8 +1,9 @@ import { faker } from "@faker-js/faker"; import { UserRole } from "@shared/types"; -import { buildUser } from "@server/test/factories"; +import { buildTeam, buildUser } from "@server/test/factories"; import userInviter from "./userInviter"; import { withAPIContext } from "@server/test/support"; +import { TeamDomain } from "@server/models"; describe("userInviter", () => { it("should return sent invites", async () => { @@ -37,6 +38,58 @@ describe("userInviter", () => { expect(response.sent.length).toEqual(0); }); + it("should error on non allowed domains", async () => { + const team = await buildTeam(); + const user = await buildUser({ teamId: team.id }); + + await TeamDomain.create({ + teamId: team.id, + name: faker.internet.domainName(), + createdById: user.id, + }); + + await withAPIContext(user, (ctx) => + expect( + userInviter(ctx, { + invites: [ + { + role: UserRole.Member, + email: "test@example.com", + name: "Test", + }, + ], + }) + ).rejects.toThrow("The domain is not allowed for this workspace") + ); + }); + + it("should allow invites for allowed domains", async () => { + const team = await buildTeam(); + const user = await buildUser({ teamId: team.id }); + const allowedDomain = "google.com"; + + await TeamDomain.create({ + teamId: team.id, + name: allowedDomain, + createdById: user.id, + }); + + const response = await withAPIContext(user, (ctx) => + userInviter(ctx, { + invites: [ + { + role: UserRole.Member, + email: `test@${allowedDomain}`, + name: "Test User", + }, + ], + }) + ); + + expect(response.sent.length).toEqual(1); + expect(response.sent[0].email).toEqual(`test@${allowedDomain}`); + }); + it("should filter obviously bunk emails", async () => { const user = await buildUser(); const response = await withAPIContext(user, (ctx) => diff --git a/server/commands/userInviter.ts b/server/commands/userInviter.ts index 3c8150f65f..5cd74556c5 100644 --- a/server/commands/userInviter.ts +++ b/server/commands/userInviter.ts @@ -6,6 +6,7 @@ import Logger from "@server/logging/Logger"; import { User, Team } from "@server/models"; import { UserFlag } from "@server/models/User"; import { APIContext } from "@server/types"; +import { DomainNotAllowedError } from "@server/errors"; export type Invite = { name: string; @@ -41,6 +42,13 @@ export default async function userInviter( ); // filter out any existing users in the system const emails = normalizedInvites.map((invite) => invite.email); + + for (const email of emails) { + if (!(await team.isDomainAllowed(email))) { + throw DomainNotAllowedError(); + } + } + const existingUsers = await User.findAll({ where: { teamId: user.teamId,