diff --git a/front/lib/api/invitation.ts b/front/lib/api/invitation.ts index 45f15be03967..53cce9f78bcf 100644 --- a/front/lib/api/invitation.ts +++ b/front/lib/api/invitation.ts @@ -7,6 +7,7 @@ import type { import { Err, sanitizeString } from "@dust-tt/types"; import sgMail from "@sendgrid/mail"; import { sign } from "jsonwebtoken"; +import { Op } from "sequelize"; import config from "@app/lib/api/config"; import type { Authenticator } from "@app/lib/auth"; @@ -178,3 +179,40 @@ export async function getPendingInvitations( }; }); } + +/** + * Returns the pending or revoked inviations that were created today + * associated with the authenticator's owner workspace. + * @param auth Authenticator + * @returns MenbershipInvitation[] members of the workspace + */ + +export async function getRecentPendingOrRevokedInvitations( + auth: Authenticator +): Promise { + const owner = auth.workspace(); + if (!owner) { + return []; + } + const oneDayAgo = new Date(); + oneDayAgo.setDate(oneDayAgo.getDate() - 1); + const invitations = await MembershipInvitation.findAll({ + where: { + workspaceId: owner.id, + status: ["pending", "revoked"], + createdAt: { + [Op.gt]: oneDayAgo, + }, + }, + }); + + return invitations.map((i) => { + return { + sId: i.sId, + id: i.id, + status: i.status, + inviteEmail: i.inviteEmail, + initialRole: i.initialRole, + }; + }); +} diff --git a/front/lib/invitations.ts b/front/lib/invitations.ts index 0b37df909698..2b72b9c39261 100644 --- a/front/lib/invitations.ts +++ b/front/lib/invitations.ts @@ -1,5 +1,2 @@ -// Maxmimum allowed number of unconsumed invitations per workspace. -export const MAX_UNCONSUMED_INVITATIONS = 50; -// If the user already received an invitation from this workspace and hasn't consumed it yet, we won't send another one -// before this cooldown period. -export const UNCONSUMED_INVITATION_COOLDOWN_PER_EMAIL_MS = 1000 * 60 * 60 * 24; // 1 day +// Maxmimum allowed number of unconsumed invitations per workspace per day. +export const MAX_UNCONSUMED_INVITATIONS_PER_WORKSPACE_PER_DAY = 50; diff --git a/front/pages/api/w/[wId]/invitations/index.ts b/front/pages/api/w/[wId]/invitations/index.ts index e523a3e7b591..dc07d75ab0bb 100644 --- a/front/pages/api/w/[wId]/invitations/index.ts +++ b/front/pages/api/w/[wId]/invitations/index.ts @@ -7,20 +7,16 @@ import { isLeft } from "fp-ts/lib/Either"; import * as t from "io-ts"; import * as reporter from "io-ts-reporters"; import type { NextApiRequest, NextApiResponse } from "next"; -import { Op } from "sequelize"; import { + getRecentPendingOrRevokedInvitations, sendWorkspaceInvitationEmail, updateOrCreateInvitation, } from "@app/lib/api/invitation"; import { getPendingInvitations } from "@app/lib/api/invitation"; import { getMembers } from "@app/lib/api/workspace"; import { Authenticator, getSession } from "@app/lib/auth"; -import { - MAX_UNCONSUMED_INVITATIONS, - UNCONSUMED_INVITATION_COOLDOWN_PER_EMAIL_MS, -} from "@app/lib/invitations"; -import { MembershipInvitation } from "@app/lib/models"; +import { MAX_UNCONSUMED_INVITATIONS_PER_WORKSPACE_PER_DAY } from "@app/lib/invitations"; import { MembershipResource } from "@app/lib/resources/membership_resource"; import { isEmailValid } from "@app/lib/utils"; import logger from "@app/logger/logger"; @@ -158,18 +154,13 @@ async function handler( }); } const existingMembers = await getMembers(auth); - const startOfDay = new Date(); - startOfDay.setHours(0, 0, 0, 0); - const unconsumedInvitations = await MembershipInvitation.findAll({ - where: { - workspaceId: owner.id, - status: ["pending", "revoked"], - createdAt: { - [Op.gte]: startOfDay, - }, - }, - }); - if (unconsumedInvitations.length > MAX_UNCONSUMED_INVITATIONS) { + const unconsumedInvitations = await getRecentPendingOrRevokedInvitations( + auth + ); + if ( + unconsumedInvitations.length > + MAX_UNCONSUMED_INVITATIONS_PER_WORKSPACE_PER_DAY + ) { return apiError(req, res, { status_code: 400, api_error: { @@ -179,13 +170,7 @@ async function handler( }); } const emailsWithRecentUnconsumedInvitations = new Set( - unconsumedInvitations - .filter( - (i) => - i.createdAt.getTime() > - new Date().getTime() - UNCONSUMED_INVITATION_COOLDOWN_PER_EMAIL_MS - ) - .map((i) => i.inviteEmail.toLowerCase().trim()) + unconsumedInvitations.map((i) => i.inviteEmail.toLowerCase().trim()) ); if ( invitationRequests.some((r) => diff --git a/front/pages/w/[wId]/members/index.tsx b/front/pages/w/[wId]/members/index.tsx index 29b314b231f2..77a07cebfe8e 100644 --- a/front/pages/w/[wId]/members/index.tsx +++ b/front/pages/w/[wId]/members/index.tsx @@ -53,7 +53,7 @@ import { PRO_PLAN_29_COST, } from "@app/lib/client/subscription"; import { withDefaultUserAuthRequirements } from "@app/lib/iam/session"; -import { MAX_UNCONSUMED_INVITATIONS } from "@app/lib/invitations"; +import { MAX_UNCONSUMED_INVITATIONS_PER_WORKSPACE_PER_DAY } from "@app/lib/invitations"; import { isUpgraded, PRO_PLAN_SEAT_29_CODE } from "@app/lib/plans/plan_codes"; import { useMembers, useWorkspaceInvitations } from "@app/lib/swr"; import { classNames, isEmailValid } from "@app/lib/utils"; @@ -485,11 +485,13 @@ function InviteEmailModal({ async function handleSendInvitations( inviteEmailsList: string[] ): Promise { - if (inviteEmailsList.length > MAX_UNCONSUMED_INVITATIONS) { + if ( + inviteEmailsList.length > MAX_UNCONSUMED_INVITATIONS_PER_WORKSPACE_PER_DAY + ) { sendNotification({ type: "error", title: "Too many invitations", - description: `Your cannot send more than ${MAX_UNCONSUMED_INVITATIONS} invitations.`, + description: `Your cannot send more than ${MAX_UNCONSUMED_INVITATIONS_PER_WORKSPACE_PER_DAY} invitations per day.`, }); return; }