Skip to content

Commit a412772

Browse files
authored
Invitation revocation sId refactor (#4562)
* Invitation revocation sId refactor * nit
1 parent c65cbe2 commit a412772

File tree

6 files changed

+98
-75
lines changed

6 files changed

+98
-75
lines changed

front/lib/api/invitation.ts

+61-36
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
UserType,
55
WorkspaceType,
66
} from "@dust-tt/types";
7-
import { sanitizeString } from "@dust-tt/types";
7+
import { Err, sanitizeString } from "@dust-tt/types";
88
import sgMail from "@sendgrid/mail";
99
import { sign } from "jsonwebtoken";
1010

@@ -19,13 +19,72 @@ function typeFromModel(
1919
invitation: MembershipInvitation
2020
): MembershipInvitationType {
2121
return {
22+
sId: invitation.sId,
2223
id: invitation.id,
2324
inviteEmail: invitation.inviteEmail,
2425
status: invitation.status,
2526
initialRole: invitation.initialRole,
2627
};
2728
}
2829

30+
export async function getInvitation(
31+
auth: Authenticator,
32+
{
33+
invitationId,
34+
}: {
35+
invitationId: string;
36+
}
37+
): Promise<MembershipInvitationType | null> {
38+
const owner = auth.workspace();
39+
if (!owner || !auth.isAdmin()) {
40+
return null;
41+
}
42+
43+
const invitation = await MembershipInvitation.findOne({
44+
where: {
45+
workspaceId: owner.id,
46+
sId: invitationId,
47+
},
48+
});
49+
50+
if (!invitation) {
51+
return null;
52+
}
53+
54+
return typeFromModel(invitation);
55+
}
56+
57+
export async function updateInvitationStatus(
58+
auth: Authenticator,
59+
{
60+
invitation,
61+
status,
62+
}: {
63+
invitation: MembershipInvitationType;
64+
status: "pending" | "consumed" | "revoked";
65+
}
66+
): Promise<MembershipInvitationType> {
67+
const owner = auth.workspace();
68+
if (!owner || !auth.isAdmin()) {
69+
throw new Error("Unauthorized attempt to update invitation status.");
70+
}
71+
72+
const existingInvitation = await MembershipInvitation.findOne({
73+
where: {
74+
workspaceId: owner.id,
75+
id: invitation.id,
76+
},
77+
});
78+
79+
if (!existingInvitation) {
80+
throw new Err("Invitaion unexpectedly not found.");
81+
}
82+
83+
await existingInvitation.update({ status });
84+
85+
return typeFromModel(existingInvitation);
86+
}
87+
2988
export async function updateOrCreateInvitation(
3089
owner: WorkspaceType,
3190
inviteEmail: string,
@@ -58,41 +117,6 @@ export async function updateOrCreateInvitation(
58117
);
59118
}
60119

61-
export async function updateInvitation(
62-
owner: WorkspaceType,
63-
id: number,
64-
status: "pending" | "consumed" | "revoked"
65-
): Promise<MembershipInvitationType> {
66-
const invitation = await MembershipInvitation.findOne({
67-
where: {
68-
id,
69-
workspaceId: owner.id,
70-
},
71-
});
72-
73-
if (!invitation) {
74-
throw new Error("Invitation not found");
75-
}
76-
77-
await invitation.update({
78-
status,
79-
});
80-
81-
return typeFromModel(invitation);
82-
}
83-
84-
export async function deleteInvitation(
85-
owner: WorkspaceType,
86-
id: number
87-
): Promise<void> {
88-
await MembershipInvitation.destroy({
89-
where: {
90-
id,
91-
workspaceId: owner.id,
92-
},
93-
});
94-
}
95-
96120
export async function sendWorkspaceInvitationEmail(
97121
owner: WorkspaceType,
98122
user: UserType,
@@ -146,6 +170,7 @@ export async function getPendingInvitations(
146170

147171
return invitations.map((i) => {
148172
return {
173+
sId: i.sId,
149174
id: i.id,
150175
status: i.status,
151176
inviteEmail: i.inviteEmail,

front/lib/models/user.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ User.init(
5050
},
5151
sId: {
5252
type: DataTypes.STRING,
53-
allowNull: true,
53+
allowNull: false,
5454
},
5555
provider: {
5656
type: DataTypes.STRING,

front/lib/models/workspace.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ MembershipInvitation.init(
162162
},
163163
sId: {
164164
type: DataTypes.STRING,
165-
allowNull: true,
165+
allowNull: false,
166166
},
167167
inviteEmail: {
168168
type: DataTypes.STRING,

front/pages/api/w/[wId]/invitations/[invitationId]/index.ts front/pages/api/w/[wId]/invitations/[iId]/index.ts

+33-36
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@ import type {
22
MembershipInvitationType,
33
WithAPIErrorReponse,
44
} from "@dust-tt/types";
5+
import { isLeft } from "fp-ts/lib/Either";
6+
import * as t from "io-ts";
7+
import * as reporter from "io-ts-reporters";
58
import type { NextApiRequest, NextApiResponse } from "next";
69

10+
import { getInvitation, updateInvitationStatus } from "@app/lib/api/invitation";
711
import { Authenticator, getSession } from "@app/lib/auth";
8-
import { MembershipInvitation } from "@app/lib/models";
912
import { apiError, withLogging } from "@app/logger/withlogging";
1013

11-
export type GetMemberInvitationsResponseBody = {
12-
invitations: MembershipInvitationType[];
14+
export type PostMemberInvitationsResponseBody = {
15+
invitation: MembershipInvitationType;
1316
};
1417

18+
export const PostMemberInvitationBodySchema = t.type({
19+
status: t.literal("revoked"),
20+
});
21+
1522
async function handler(
1623
req: NextApiRequest,
17-
res: NextApiResponse<WithAPIErrorReponse<GetMemberInvitationsResponseBody>>
24+
res: NextApiResponse<WithAPIErrorReponse<PostMemberInvitationsResponseBody>>
1825
): Promise<void> {
1926
const session = await getSession(req, res);
2027
const auth = await Authenticator.fromSession(
@@ -44,8 +51,19 @@ async function handler(
4451
});
4552
}
4653

47-
const invitationId = parseInt(req.query.invitationId as string);
48-
if (isNaN(invitationId)) {
54+
if (!(typeof req.query.iId === "string")) {
55+
return apiError(req, res, {
56+
status_code: 400,
57+
api_error: {
58+
type: "invalid_request_error",
59+
message: "Invalid query parameters, `iId` (string) is required.",
60+
},
61+
});
62+
}
63+
64+
const invitationId = req.query.iId;
65+
let invitation = await getInvitation(auth, { invitationId });
66+
if (!invitation) {
4967
return apiError(req, res, {
5068
status_code: 404,
5169
api_error: {
@@ -57,47 +75,26 @@ async function handler(
5775

5876
switch (req.method) {
5977
case "POST":
60-
if (
61-
!req.body ||
62-
!typeof (req.body.status === "string") ||
63-
// For now we only allow revoking invitations.
64-
req.body.status !== "revoked"
65-
) {
78+
const bodyValidation = PostMemberInvitationBodySchema.decode(req.body);
79+
if (isLeft(bodyValidation)) {
80+
const pathError = reporter.formatValidationErrors(bodyValidation.left);
6681
return apiError(req, res, {
6782
status_code: 400,
6883
api_error: {
6984
type: "invalid_request_error",
70-
message:
71-
'The request body is invalid, expects { status: "revoked" }.',
85+
message: `The request body is invalid: ${pathError}`,
7286
},
7387
});
7488
}
89+
const body = bodyValidation.right;
7590

76-
const invitation = await MembershipInvitation.findOne({
77-
where: { id: invitationId },
91+
invitation = await updateInvitationStatus(auth, {
92+
invitation,
93+
status: body.status,
7894
});
7995

80-
if (!invitation) {
81-
return apiError(req, res, {
82-
status_code: 404,
83-
api_error: {
84-
type: "invitation_not_found",
85-
message: "The invitation requested was not found.",
86-
},
87-
});
88-
}
89-
90-
invitation.status = req.body.status;
91-
await invitation.save();
9296
res.status(200).json({
93-
invitations: [
94-
{
95-
id: invitation.id,
96-
status: invitation.status,
97-
inviteEmail: invitation.inviteEmail,
98-
initialRole: invitation.initialRole,
99-
},
100-
],
97+
invitation,
10198
});
10299
return;
103100

front/pages/w/[wId]/members/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ function RevokeInvitationModal({
809809
invitation: MembershipInvitationType
810810
): Promise<void> {
811811
const res = await fetch(
812-
`/api/w/${owner.sId}/invitations/${invitation.id}`,
812+
`/api/w/${owner.sId}/invitations/${invitation.sId}`,
813813
{
814814
method: "POST",
815815
headers: {

types/src/front/membership_invitation.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ModelId } from "../shared/model_id";
22
import { ActiveRoleType } from "./user";
33

44
export type MembershipInvitationType = {
5+
sId: string;
56
id: ModelId;
67
status: "pending" | "consumed" | "revoked";
78
inviteEmail: string;

0 commit comments

Comments
 (0)