mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 16:28:40 +00:00
feat: added change in user invitation mail for suborg and updated list operation for sub org
This commit is contained in:
@@ -93,10 +93,10 @@ export const subOrgServiceFactory = ({
|
|||||||
await permissionService.getOrgPermission({
|
await permissionService.getOrgPermission({
|
||||||
actorId: permissionActor.id,
|
actorId: permissionActor.id,
|
||||||
actor: permissionActor.type,
|
actor: permissionActor.type,
|
||||||
orgId: permissionActor.parentOrgId,
|
orgId: permissionActor.rootOrgId,
|
||||||
actorOrgId: permissionActor.rootOrgId,
|
actorOrgId: permissionActor.rootOrgId,
|
||||||
actorAuthMethod: permissionActor.authMethod,
|
actorAuthMethod: permissionActor.authMethod,
|
||||||
scope: OrganizationActionScope.ParentOrganization
|
scope: OrganizationActionScope.Any
|
||||||
});
|
});
|
||||||
|
|
||||||
const organizations = await orgDAL.listSubOrganizations({
|
const organizations = await orgDAL.listSubOrganizations({
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export const newOrgMembershipUserFactory = ({
|
|||||||
message: "Failed to invite user due to org-level auth enforced for organization"
|
message: "Failed to invite user due to org-level auth enforced for organization"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (org.rootOrgId) {
|
if (org.rootOrgId) {
|
||||||
const rootOrgMembership = await membershipUserDAL.find({
|
const rootOrgMembership = await membershipUserDAL.find({
|
||||||
scope: AccessScope.Organization,
|
scope: AccessScope.Organization,
|
||||||
@@ -120,7 +121,18 @@ export const newOrgMembershipUserFactory = ({
|
|||||||
|
|
||||||
const signUpTokens: { email: string; link: string }[] = [];
|
const signUpTokens: { email: string; link: string }[] = [];
|
||||||
const orgDetails = await orgDAL.findById(dto.permission.orgId);
|
const orgDetails = await orgDAL.findById(dto.permission.orgId);
|
||||||
|
if (orgDetails.rootOrgId) {
|
||||||
|
const emails = newUsers.map((el) => el.email).filter(Boolean);
|
||||||
|
await smtpService.sendMail({
|
||||||
|
template: SmtpTemplates.SubOrgInvite,
|
||||||
|
subjectLine: "Infisical sub-organization invitation",
|
||||||
|
recipients: emails as string[],
|
||||||
|
substitutions: {
|
||||||
|
subOrganizationName: orgDetails.slug,
|
||||||
|
callback_url: `${appCfg.SITE_URL}/organization/projects?${orgDetails.slug}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
newUsers.map(async (el) => {
|
newUsers.map(async (el) => {
|
||||||
const token = await tokenService.createTokenForUser({
|
const token = await tokenService.createTokenForUser({
|
||||||
@@ -154,6 +166,7 @@ export const newOrgMembershipUserFactory = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return { signUpTokens };
|
return { signUpTokens };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ export const orgServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
scope: OrganizationActionScope.Any
|
scope: OrganizationActionScope.ParentOrganization
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasRole(OrgMembershipRole.Admin)) {
|
if (!hasRole(OrgMembershipRole.Admin)) {
|
||||||
@@ -418,7 +418,7 @@ export const orgServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
scope: OrganizationActionScope.Any
|
scope: OrganizationActionScope.ParentOrganization
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
@@ -878,7 +878,7 @@ export const orgServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
scope: OrganizationActionScope.Any
|
scope: OrganizationActionScope.ParentOrganization
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
@@ -1172,7 +1172,7 @@ export const orgServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
scope: OrganizationActionScope.Any
|
scope: OrganizationActionScope.ParentOrganization
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
|
||||||
const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email });
|
const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email });
|
||||||
@@ -1200,7 +1200,7 @@ export const orgServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
scope: OrganizationActionScope.Any
|
scope: OrganizationActionScope.ParentOrganization
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseButton } from "./BaseButton";
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface SubOrganizationInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||||
|
callback_url: string;
|
||||||
|
subOrganizationName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SubOrganizationInvitationTemplate = ({
|
||||||
|
callback_url,
|
||||||
|
subOrganizationName,
|
||||||
|
siteUrl
|
||||||
|
}: SubOrganizationInvitationTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Sub-Organization Invitation"
|
||||||
|
preview="You've been invited to join a suborganization on Infisical."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
You've been invited to join a suborganization on Infisical
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
You've been invited to join the suborganization <strong>{subOrganizationName}</strong>.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center">
|
||||||
|
<BaseButton href={callback_url}>Join Suborganization</BaseButton>
|
||||||
|
</Section>
|
||||||
|
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
||||||
|
<Text className="mb-[0px]">
|
||||||
|
<strong>About Infisical:</strong> Infisical is an all-in-one platform to securely manage application secrets,
|
||||||
|
certificates, SSH keys, and configurations across your team and infrastructure.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubOrganizationInvitationTemplate;
|
||||||
|
|
||||||
|
SubOrganizationInvitationTemplate.PreviewProps = {
|
||||||
|
subOrganizationName: "Example Project",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
callback_url: "https://app.infisical.com"
|
||||||
|
} as SubOrganizationInvitationTemplateProps;
|
||||||
@@ -32,3 +32,4 @@ export * from "./SecretSyncFailedTemplate";
|
|||||||
export * from "./ServiceTokenExpiryNoticeTemplate";
|
export * from "./ServiceTokenExpiryNoticeTemplate";
|
||||||
export * from "./SignupEmailVerificationTemplate";
|
export * from "./SignupEmailVerificationTemplate";
|
||||||
export * from "./UnlockAccountTemplate";
|
export * from "./UnlockAccountTemplate";
|
||||||
|
export * from "./SubOrganizationInvitationTemplate";
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ import {
|
|||||||
SecretSyncFailedTemplate,
|
SecretSyncFailedTemplate,
|
||||||
ServiceTokenExpiryNoticeTemplate,
|
ServiceTokenExpiryNoticeTemplate,
|
||||||
SignupEmailVerificationTemplate,
|
SignupEmailVerificationTemplate,
|
||||||
UnlockAccountTemplate
|
UnlockAccountTemplate,
|
||||||
|
SubOrganizationInvitationTemplate
|
||||||
} from "./emails";
|
} from "./emails";
|
||||||
|
|
||||||
export type TSmtpConfig = SMTPTransport.Options;
|
export type TSmtpConfig = SMTPTransport.Options;
|
||||||
@@ -65,6 +66,7 @@ export enum SmtpTemplates {
|
|||||||
// HistoricalSecretList = "historicalSecretLeakIncident", not used anymore?
|
// HistoricalSecretList = "historicalSecretLeakIncident", not used anymore?
|
||||||
NewDeviceJoin = "newDevice",
|
NewDeviceJoin = "newDevice",
|
||||||
OrgInvite = "organizationInvitation",
|
OrgInvite = "organizationInvitation",
|
||||||
|
SubOrgInvite = "subOrganizationInvitation",
|
||||||
OrgAssignment = "organizationAssignment",
|
OrgAssignment = "organizationAssignment",
|
||||||
OAuthPasswordReset = "oAuthPasswordReset",
|
OAuthPasswordReset = "oAuthPasswordReset",
|
||||||
ResetPassword = "passwordReset",
|
ResetPassword = "passwordReset",
|
||||||
@@ -102,6 +104,7 @@ export enum SmtpHost {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const EmailTemplateMap: Record<SmtpTemplates, React.FC<any>> = {
|
const EmailTemplateMap: Record<SmtpTemplates, React.FC<any>> = {
|
||||||
[SmtpTemplates.OrgInvite]: OrganizationInvitationTemplate,
|
[SmtpTemplates.OrgInvite]: OrganizationInvitationTemplate,
|
||||||
|
[SmtpTemplates.SubOrgInvite]: SubOrganizationInvitationTemplate,
|
||||||
[SmtpTemplates.OrgAssignment]: OrganizationAssignmentTemplate,
|
[SmtpTemplates.OrgAssignment]: OrganizationAssignmentTemplate,
|
||||||
[SmtpTemplates.NewDeviceJoin]: NewDeviceLoginTemplate,
|
[SmtpTemplates.NewDeviceJoin]: NewDeviceLoginTemplate,
|
||||||
[SmtpTemplates.SignupEmailVerification]: SignupEmailVerificationTemplate,
|
[SmtpTemplates.SignupEmailVerification]: SignupEmailVerificationTemplate,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useSuspenseQuery } from "@tanstack/react-query";
|
|||||||
import { useRouteContext, useSearch } from "@tanstack/react-router";
|
import { useRouteContext, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
|
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export const useOrganization = () => {
|
export const useOrganization = () => {
|
||||||
const organizationId = useRouteContext({
|
const organizationId = useRouteContext({
|
||||||
@@ -20,7 +21,8 @@ export const useOrganization = () => {
|
|||||||
staleTime: Infinity
|
staleTime: Infinity
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
const org = useMemo(
|
||||||
|
() => ({
|
||||||
currentOrg: {
|
currentOrg: {
|
||||||
...currentOrg,
|
...currentOrg,
|
||||||
id: currentOrg?.subOrganization?.id || currentOrg?.id,
|
id: currentOrg?.subOrganization?.id || currentOrg?.id,
|
||||||
@@ -28,5 +30,9 @@ export const useOrganization = () => {
|
|||||||
},
|
},
|
||||||
isSubOrganization: Boolean(currentOrg.subOrganization),
|
isSubOrganization: Boolean(currentOrg.subOrganization),
|
||||||
isRootOrganization: !currentOrg.subOrganization
|
isRootOrganization: !currentOrg.subOrganization
|
||||||
};
|
}),
|
||||||
|
[currentOrg]
|
||||||
|
);
|
||||||
|
|
||||||
|
return org;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -131,14 +131,14 @@ export const INFISICAL_SUPPORT_OPTIONS = [
|
|||||||
export const Navbar = () => {
|
export const Navbar = () => {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const { currentOrg, isSubOrganization } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
const [showAdminsModal, setShowAdminsModal] = useState(false);
|
const [showAdminsModal, setShowAdminsModal] = useState(false);
|
||||||
const [showSubOrgForm, setShowSubOrgForm] = useState(false);
|
const [showSubOrgForm, setShowSubOrgForm] = useState(false);
|
||||||
const [showCardDeclinedModal, setShowCardDeclinedModal] = useState(false);
|
const [showCardDeclinedModal, setShowCardDeclinedModal] = useState(false);
|
||||||
const { data: subOrganizations = [] } = useQuery({
|
const { data: subOrganizations = [] } = useQuery({
|
||||||
...subOrganizationsQuery.list({ limit: 500 }),
|
...subOrganizationsQuery.list({ limit: 500 }),
|
||||||
enabled: Boolean(subscription.subOrganization) && !isSubOrganization
|
enabled: Boolean(subscription.subOrganization)
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
+1
-3
@@ -41,7 +41,6 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
|||||||
const [isFormInitialized, setIsFormInitialized] = useState(false);
|
const [isFormInitialized, setIsFormInitialized] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentOrg) {
|
|
||||||
reset({
|
reset({
|
||||||
name: currentOrg.name,
|
name: currentOrg.name,
|
||||||
slug: currentOrg.slug,
|
slug: currentOrg.slug,
|
||||||
@@ -54,8 +53,7 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
setIsFormInitialized(true);
|
setIsFormInitialized(true);
|
||||||
}
|
}, [roles]);
|
||||||
}, [currentOrg, roles]);
|
|
||||||
|
|
||||||
const onFormSubmit = async ({ name, slug, defaultMembershipRole }: FormData) => {
|
const onFormSubmit = async ({ name, slug, defaultMembershipRole }: FormData) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ export const OrgProductSelectSection = () => {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [currentOrg]);
|
}, [currentOrg?.id]);
|
||||||
|
|
||||||
const onProductToggle = async (value: boolean, key: string) => {
|
const onProductToggle = async (value: boolean, key: string) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user