From a3a354904851d587b354f334efc59642400ab215 Mon Sep 17 00:00:00 2001 From: myftija Date: Thu, 25 Sep 2025 19:09:55 +0200 Subject: [PATCH 1/2] fix: use higher entropy invite tokens We currently use CUIDs for invite tokens, which are generated using a pattern and are not cryptographically secure. This PR switches to a higher entropy string generated with `nanoid`. --- apps/webapp/app/models/member.server.ts | 26 +++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/webapp/app/models/member.server.ts b/apps/webapp/app/models/member.server.ts index 86ae5d371d..3b787ac530 100644 --- a/apps/webapp/app/models/member.server.ts +++ b/apps/webapp/app/models/member.server.ts @@ -1,5 +1,9 @@ -import { prisma } from "~/db.server"; +import { type Prisma, prisma } from "~/db.server"; import { createEnvironment } from "./organization.server"; +import { customAlphabet } from "nanoid"; + +const tokenValueLength = 40; +const tokenGenerator = customAlphabet("123456789abcdefghijkmnopqrstuvwxyz", tokenValueLength); export async function getTeamMembersAndInvites({ userId, @@ -95,13 +99,19 @@ export async function inviteMembers({ throw new Error("User does not have access to this organization"); } - const created = await prisma.orgMemberInvite.createMany({ - data: emails.map((email) => ({ - email, - organizationId: org.id, - inviterId: userId, - role: "MEMBER", - })), + const invites = emails.map( + (email) => + ({ + email, + token: tokenGenerator(), + organizationId: org.id, + inviterId: userId, + role: "MEMBER", + } satisfies Prisma.OrgMemberInviteCreateManyInput) + ); + + await prisma.orgMemberInvite.createMany({ + data: invites, skipDuplicates: true, }); From 0d113915a708f3bd89fb493108f9540c615e9290 Mon Sep 17 00:00:00 2001 From: myftija Date: Thu, 25 Sep 2025 19:26:00 +0200 Subject: [PATCH 2/2] Dedupe the invite emails in the application --- apps/webapp/app/models/member.server.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/webapp/app/models/member.server.ts b/apps/webapp/app/models/member.server.ts index 3b787ac530..56d610af9b 100644 --- a/apps/webapp/app/models/member.server.ts +++ b/apps/webapp/app/models/member.server.ts @@ -99,7 +99,7 @@ export async function inviteMembers({ throw new Error("User does not have access to this organization"); } - const invites = emails.map( + const invites = [...new Set(emails)].map( (email) => ({ email, @@ -112,7 +112,6 @@ export async function inviteMembers({ await prisma.orgMemberInvite.createMany({ data: invites, - skipDuplicates: true, }); return await prisma.orgMemberInvite.findMany({