mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
fix: address org invite resend
This commit is contained in:
@@ -73,6 +73,40 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/signup/resend",
|
||||||
|
config: {
|
||||||
|
rateLimit: inviteUserRateLimit
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
membershipId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
signupToken: z
|
||||||
|
.object({
|
||||||
|
email: z.string(),
|
||||||
|
link: z.string()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
return server.services.org.resendOrgMemberInvitation({
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
membershipId: req.body.membershipId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/verify",
|
url: "/verify",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ import {
|
|||||||
TGetOrgMembershipDTO,
|
TGetOrgMembershipDTO,
|
||||||
TInviteUserToOrgDTO,
|
TInviteUserToOrgDTO,
|
||||||
TListProjectMembershipsByOrgMembershipIdDTO,
|
TListProjectMembershipsByOrgMembershipIdDTO,
|
||||||
|
TResendOrgMemberInvitationDTO,
|
||||||
TUpdateOrgDTO,
|
TUpdateOrgDTO,
|
||||||
TUpdateOrgMembershipDTO,
|
TUpdateOrgMembershipDTO,
|
||||||
TVerifyUserToOrgDTO
|
TVerifyUserToOrgDTO
|
||||||
@@ -584,6 +585,66 @@ export const orgServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return membership;
|
return membership;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resendOrgMemberInvitation = async ({
|
||||||
|
orgId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
membershipId
|
||||||
|
}: TResendOrgMemberInvitationDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
|
|
||||||
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
|
|
||||||
|
const [inviteeOrgMembership] = await orgDAL.findMembership({
|
||||||
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
||||||
|
[`${TableName.OrgMembership}.id` as "id"]: membershipId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (inviteeOrgMembership.status !== OrgMembershipStatus.Invited) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Organization invitation already accepted"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await tokenService.createTokenForUser({
|
||||||
|
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
||||||
|
userId: inviteeOrgMembership.userId,
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!appCfg.isSmtpConfigured) {
|
||||||
|
return {
|
||||||
|
signupToken: {
|
||||||
|
email: inviteeOrgMembership.email as string,
|
||||||
|
link: `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${inviteeOrgMembership.email}&organization_id=${org?.id}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await smtpService.sendMail({
|
||||||
|
template: SmtpTemplates.OrgInvite,
|
||||||
|
subjectLine: "Infisical organization invitation",
|
||||||
|
recipients: [inviteeOrgMembership.email as string],
|
||||||
|
substitutions: {
|
||||||
|
inviterFirstName: inviteeOrgMembership.firstName,
|
||||||
|
inviterUsername: inviteeOrgMembership.email,
|
||||||
|
organizationName: org?.name,
|
||||||
|
email: inviteeOrgMembership.email,
|
||||||
|
organizationId: org?.id.toString(),
|
||||||
|
token,
|
||||||
|
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { signupToken: undefined };
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Invite user to organization
|
* Invite user to organization
|
||||||
*/
|
*/
|
||||||
@@ -627,6 +688,7 @@ export const orgServiceFactory = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (projectsToInvite.length !== invitedProjects?.length) {
|
if (projectsToInvite.length !== invitedProjects?.length) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Access denied to one or more of the specified projects"
|
message: "Access denied to one or more of the specified projects"
|
||||||
@@ -1221,6 +1283,7 @@ export const orgServiceFactory = ({
|
|||||||
deleteIncidentContact,
|
deleteIncidentContact,
|
||||||
getOrgGroups,
|
getOrgGroups,
|
||||||
listProjectMembershipsByOrgMembershipId,
|
listProjectMembershipsByOrgMembershipId,
|
||||||
findOrgBySlug
|
findOrgBySlug,
|
||||||
|
resendOrgMemberInvitation
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ export type TInviteUserToOrgDTO = {
|
|||||||
}[];
|
}[];
|
||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
export type TResendOrgMemberInvitationDTO = {
|
||||||
|
membershipId: string;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TVerifyUserToOrgDTO = {
|
export type TVerifyUserToOrgDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
|
|||||||
@@ -156,3 +156,18 @@ export const useCreateNewTotpRecoveryCodes = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useResendOrgMemberInvitation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (dto: { membershipId: string }) => {
|
||||||
|
const { data } = await apiRequest.post<{
|
||||||
|
signupToken?: {
|
||||||
|
email: string;
|
||||||
|
link: string;
|
||||||
|
};
|
||||||
|
}>("/api/v1/invite-org/signup/resend", dto);
|
||||||
|
|
||||||
|
return data.signupToken;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ import {
|
|||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { usePagination, useResetPageHelper } from "@app/hooks";
|
import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useAddUsersToOrg,
|
|
||||||
useFetchServerStatus,
|
useFetchServerStatus,
|
||||||
useGetOrgRoles,
|
useGetOrgRoles,
|
||||||
useGetOrgUsers,
|
useGetOrgUsers,
|
||||||
useUpdateOrgMembership
|
useUpdateOrgMembership
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||||
|
import { useResendOrgMemberInvitation } from "@app/hooks/api/users/mutation";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -83,7 +83,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
|
|||||||
const { data: serverDetails } = useFetchServerStatus();
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
const { data: members = [], isPending: isMembersLoading } = useGetOrgUsers(orgId);
|
const { data: members = [], isPending: isMembersLoading } = useGetOrgUsers(orgId);
|
||||||
|
|
||||||
const { mutateAsync: addUsersMutateAsync } = useAddUsersToOrg();
|
const { mutateAsync: resendOrgMemberInvitation, isPending } = useResendOrgMemberInvitation();
|
||||||
const { mutateAsync: updateOrgMembership } = useUpdateOrgMembership();
|
const { mutateAsync: updateOrgMembership } = useUpdateOrgMembership();
|
||||||
|
|
||||||
const onRoleChange = async (membershipId: string, role: string) => {
|
const onRoleChange = async (membershipId: string, role: string) => {
|
||||||
@@ -119,26 +119,25 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onResendInvite = async (email: string) => {
|
const onResendInvite = async (membershipId: string) => {
|
||||||
try {
|
try {
|
||||||
const { data } = await addUsersMutateAsync({
|
const signupToken = await resendOrgMemberInvitation({
|
||||||
organizationId: orgId,
|
membershipId
|
||||||
inviteeEmails: [email],
|
|
||||||
organizationRoleSlug: "member"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setCompleteInviteLinks(data?.completeInviteLinks || null);
|
if (signupToken) {
|
||||||
|
setCompleteInviteLinks([signupToken]);
|
||||||
if (!data.completeInviteLinks) {
|
return;
|
||||||
createNotification({
|
|
||||||
text: `Successfully resent invite to ${email}`,
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully resent org invitation",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Failed to resend invite to ${email}`,
|
text: "Failed to resend org invitation",
|
||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -370,7 +369,8 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
|
|||||||
className="w-48"
|
className="w-48"
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
onClick={() => onResendInvite(email)}
|
isLoading={isPending}
|
||||||
|
onClick={() => onResendInvite(orgMembershipId)}
|
||||||
>
|
>
|
||||||
Resend invite
|
Resend invite
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -18,13 +18,9 @@ import {
|
|||||||
useUser
|
useUser
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { useTimedReset } from "@app/hooks";
|
import { useTimedReset } from "@app/hooks";
|
||||||
import {
|
import { useFetchServerStatus, useGetOrgMembership, useGetOrgRoles } from "@app/hooks/api";
|
||||||
useAddUsersToOrg,
|
|
||||||
useFetchServerStatus,
|
|
||||||
useGetOrgMembership,
|
|
||||||
useGetOrgRoles
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
import { OrgUser } from "@app/hooks/api/types";
|
import { OrgUser } from "@app/hooks/api/types";
|
||||||
|
import { useResendOrgMemberInvitation } from "@app/hooks/api/users/mutation";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -45,28 +41,27 @@ export const UserDetailsSection = ({ membershipId, handlePopUpOpen }: Props) =>
|
|||||||
const { data: roles } = useGetOrgRoles(orgId);
|
const { data: roles } = useGetOrgRoles(orgId);
|
||||||
const { data: serverDetails } = useFetchServerStatus();
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
const { data: membership } = useGetOrgMembership(orgId, membershipId);
|
const { data: membership } = useGetOrgMembership(orgId, membershipId);
|
||||||
const { mutateAsync: inviteUsers, isPending } = useAddUsersToOrg();
|
|
||||||
|
|
||||||
const onResendInvite = async (email: string) => {
|
const { mutateAsync: resendOrgMemberInvitation, isPending } = useResendOrgMemberInvitation();
|
||||||
|
|
||||||
|
const onResendInvite = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await inviteUsers({
|
const signupToken = await resendOrgMemberInvitation({
|
||||||
organizationId: orgId,
|
membershipId
|
||||||
inviteeEmails: [email],
|
|
||||||
organizationRoleSlug: "member"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// setCompleteInviteLink(data?.completeInviteLink || "");
|
if (signupToken) {
|
||||||
|
return;
|
||||||
if (!data.completeInviteLinks) {
|
|
||||||
createNotification({
|
|
||||||
text: `Successfully resent invite to ${email}`,
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully resent org invitation",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Failed to resend invite to ${email}`,
|
text: "Failed to resend org invitation",
|
||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -210,9 +205,7 @@ export const UserDetailsSection = ({ membershipId, handlePopUpOpen }: Props) =>
|
|||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
isLoading={isPending}
|
isLoading={isPending}
|
||||||
onClick={() => {
|
onClick={onResendInvite}
|
||||||
onResendInvite(membership.user.email as string);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Resend Invite
|
Resend Invite
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user