Skip to content

Commit a3cea13

Browse files
authored
fix(webapp): org invite scoping (#2554)
* fix: org invite scoping Fixes some scoping issues with team invites. * Fix invite flow changes
1 parent a3bdd3c commit a3cea13

File tree

5 files changed

+54
-37
lines changed

5 files changed

+54
-37
lines changed

apps/webapp/app/models/member.server.ts

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,19 @@ export async function getUsersInvites({ email }: { email: string }) {
147147
});
148148
}
149149

150-
export async function acceptInvite({ userId, inviteId }: { userId: string; inviteId: string }) {
150+
export async function acceptInvite({
151+
user,
152+
inviteId,
153+
}: {
154+
user: { id: string; email: string };
155+
inviteId: string;
156+
}) {
151157
return await prisma.$transaction(async (tx) => {
152158
// 1. Delete the invite and get the invite details
153159
const invite = await tx.orgMemberInvite.delete({
154160
where: {
155161
id: inviteId,
162+
email: user.email,
156163
},
157164
include: {
158165
organization: {
@@ -167,7 +174,7 @@ export async function acceptInvite({ userId, inviteId }: { userId: string; invit
167174
const member = await tx.orgMember.create({
168175
data: {
169176
organizationId: invite.organizationId,
170-
userId,
177+
userId: user.id,
171178
role: invite.role,
172179
},
173180
});
@@ -187,47 +194,49 @@ export async function acceptInvite({ userId, inviteId }: { userId: string; invit
187194
// 4. Check for other invites
188195
const remainingInvites = await tx.orgMemberInvite.findMany({
189196
where: {
190-
email: invite.email,
197+
email: user.email,
191198
},
192199
});
193200

194201
return { remainingInvites, organization: invite.organization };
195202
});
196203
}
197204

198-
export async function declineInvite({ userId, inviteId }: { userId: string; inviteId: string }) {
205+
export async function declineInvite({
206+
user,
207+
inviteId,
208+
}: {
209+
user: { id: string; email: string };
210+
inviteId: string;
211+
}) {
199212
return await prisma.$transaction(async (tx) => {
200213
//1. delete invite
201214
const declinedInvite = await prisma.orgMemberInvite.delete({
202215
where: {
203216
id: inviteId,
217+
email: user.email,
204218
},
205219
include: {
206220
organization: true,
207221
},
208222
});
209223

210-
//2. get email
211-
const user = await prisma.user.findUnique({
212-
where: { id: userId },
213-
select: { email: true },
214-
});
215-
216-
//3. check for other invites
224+
//2. check for other invites
217225
const remainingInvites = await prisma.orgMemberInvite.findMany({
218226
where: {
219-
email: user!.email,
227+
email: user.email,
220228
},
221229
});
222230

223231
return { remainingInvites, organization: declinedInvite.organization };
224232
});
225233
}
226234

227-
export async function resendInvite({ inviteId }: { inviteId: string }) {
235+
export async function resendInvite({ inviteId, userId }: { inviteId: string; userId: string }) {
228236
return await prisma.orgMemberInvite.update({
229237
where: {
230238
id: inviteId,
239+
inviterId: userId,
231240
},
232241
data: {
233242
updatedAt: new Date(),
@@ -241,26 +250,27 @@ export async function resendInvite({ inviteId }: { inviteId: string }) {
241250

242251
export async function revokeInvite({
243252
userId,
244-
slug,
253+
orgSlug,
245254
inviteId,
246255
}: {
247256
userId: string;
248-
slug: string;
257+
orgSlug: string;
249258
inviteId: string;
250259
}) {
251-
const org = await prisma.organization.findFirst({
252-
where: { slug, members: { some: { userId } } },
253-
});
254-
255-
if (!org) {
256-
throw new Error("User does not have access to this organization");
257-
}
258-
const invite = await prisma.orgMemberInvite.delete({
260+
const invite = await prisma.orgMemberInvite.findFirst({
259261
where: {
260262
id: inviteId,
261-
organizationId: org.id,
263+
organization: {
264+
slug: orgSlug,
265+
members: {
266+
some: {
267+
userId,
268+
},
269+
},
270+
},
262271
},
263272
select: {
273+
id: true,
264274
email: true,
265275
organization: true,
266276
},
@@ -270,5 +280,11 @@ export async function revokeInvite({
270280
throw new Error("Invite not found");
271281
}
272282

283+
await prisma.orgMemberInvite.delete({
284+
where: {
285+
id: invite.id,
286+
},
287+
});
288+
273289
return { email: invite.email, organization: invite.organization };
274290
}

apps/webapp/app/routes/invite-accept.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export async function loader({ request }: LoaderFunctionArgs) {
1818
);
1919
}
2020

21+
if (!user) {
22+
return redirectWithSuccessMessage("/", request, "Please log in to accept the invite.", {
23+
ephemeral: false,
24+
});
25+
}
26+
2127
const invite = await getInviteFromToken({ token });
2228
if (!invite) {
2329
return redirectWithErrorMessage(
@@ -28,12 +34,6 @@ export async function loader({ request }: LoaderFunctionArgs) {
2834
);
2935
}
3036

31-
if (!user) {
32-
return redirectWithSuccessMessage("/", request, "Please log in to accept the invite.", {
33-
ephemeral: false,
34-
});
35-
}
36-
3737
if (invite.email !== user.email) {
3838
return redirectWithErrorMessage(
3939
"/",

apps/webapp/app/routes/invite-resend.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { parse } from "@conform-to/zod";
2-
import { ActionFunction, json } from "@remix-run/server-runtime";
2+
import { type ActionFunction, json } from "@remix-run/server-runtime";
33
import { env } from "process";
44
import { z } from "zod";
55
import { resendInvite } from "~/models/member.server";
@@ -25,6 +25,7 @@ export const action: ActionFunction = async ({ request }) => {
2525
try {
2626
const invite = await resendInvite({
2727
inviteId: submission.value.inviteId,
28+
userId,
2829
});
2930

3031
try {

apps/webapp/app/routes/invite-revoke.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { parse } from "@conform-to/zod";
2-
import { ActionFunction, json } from "@remix-run/server-runtime";
2+
import { type ActionFunction, json } from "@remix-run/server-runtime";
33
import { z } from "zod";
44
import { revokeInvite } from "~/models/member.server";
55
import { redirectWithSuccessMessage } from "~/models/message.server";
@@ -24,7 +24,7 @@ export const action: ActionFunction = async ({ request }) => {
2424
try {
2525
const { email, organization } = await revokeInvite({
2626
userId,
27-
slug: submission.value.slug,
27+
orgSlug: submission.value.slug,
2828
inviteId: submission.value.inviteId,
2929
});
3030

apps/webapp/app/routes/invites.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { conform, useForm } from "@conform-to/react";
22
import { parse } from "@conform-to/zod";
3-
import { ActionFunction, LoaderFunctionArgs, json, redirect } from "@remix-run/node";
3+
import { type ActionFunction, type LoaderFunctionArgs, json, redirect } from "@remix-run/node";
44
import { Form, useActionData } from "@remix-run/react";
55
import { typedjson, useTypedLoaderData } from "remix-typedjson";
66
import { z } from "zod";
@@ -36,7 +36,7 @@ const schema = z.object({
3636
});
3737

3838
export const action: ActionFunction = async ({ request }) => {
39-
const userId = await requireUserId(request);
39+
const user = await requireUser(request);
4040

4141
const formData = await request.formData();
4242
const submission = parse(formData, { schema });
@@ -49,7 +49,7 @@ export const action: ActionFunction = async ({ request }) => {
4949
if (submission.intent === "accept") {
5050
const { remainingInvites, organization } = await acceptInvite({
5151
inviteId: submission.value.inviteId,
52-
userId,
52+
user: { id: user.id, email: user.email },
5353
});
5454

5555
if (remainingInvites.length === 0) {
@@ -64,7 +64,7 @@ export const action: ActionFunction = async ({ request }) => {
6464
} else if (submission.intent === "decline") {
6565
const { remainingInvites, organization } = await declineInvite({
6666
inviteId: submission.value.inviteId,
67-
userId,
67+
user: { id: user.id, email: user.email },
6868
});
6969
if (remainingInvites.length === 0) {
7070
return redirectWithSuccessMessage(

0 commit comments

Comments
 (0)