mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-15 09:42:14 +00:00
Compare commits
1 Commits
commit-ui-
...
revamp-ema
Author | SHA1 | Date | |
---|---|---|---|
7263a43c0c |
@ -69,6 +69,15 @@ module.exports = {
|
|||||||
["^\\."]
|
["^\\."]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"import/extensions": [
|
||||||
|
"error",
|
||||||
|
"ignorePackages",
|
||||||
|
{
|
||||||
|
"": "never", // this is required to get the .tsx to work...
|
||||||
|
ts: "never",
|
||||||
|
tsx: "never"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
2546
backend/package-lock.json
generated
2546
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -72,7 +72,8 @@
|
|||||||
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
||||||
"seed": "knex --knexfile ./dist/db/knexfile.ts --client pg seed:run",
|
"seed": "knex --knexfile ./dist/db/knexfile.ts --client pg seed:run",
|
||||||
"seed-dev": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
"seed-dev": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
||||||
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest"
|
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest",
|
||||||
|
"email:dev": "email dev --dir src/services/smtp/emails"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@ -96,6 +97,7 @@
|
|||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
"@types/pkcs11js": "^1.0.4",
|
"@types/pkcs11js": "^1.0.4",
|
||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
|
"@types/react": "^19.1.2",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
"@types/safe-regex": "^1.1.6",
|
"@types/safe-regex": "^1.1.6",
|
||||||
"@types/sjcl": "^1.0.34",
|
"@types/sjcl": "^1.0.34",
|
||||||
@ -115,6 +117,7 @@
|
|||||||
"nodemon": "^3.0.2",
|
"nodemon": "^3.0.2",
|
||||||
"pino-pretty": "^10.2.3",
|
"pino-pretty": "^10.2.3",
|
||||||
"prompt-sync": "^4.2.0",
|
"prompt-sync": "^4.2.0",
|
||||||
|
"react-email": "4.0.7",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsc-alias": "^1.8.8",
|
"tsc-alias": "^1.8.8",
|
||||||
@ -164,6 +167,7 @@
|
|||||||
"@opentelemetry/semantic-conventions": "^1.27.0",
|
"@opentelemetry/semantic-conventions": "^1.27.0",
|
||||||
"@peculiar/asn1-schema": "^2.3.8",
|
"@peculiar/asn1-schema": "^2.3.8",
|
||||||
"@peculiar/x509": "^1.12.1",
|
"@peculiar/x509": "^1.12.1",
|
||||||
|
"@react-email/components": "0.0.36",
|
||||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||||
"@sindresorhus/slugify": "1.1.0",
|
"@sindresorhus/slugify": "1.1.0",
|
||||||
"@slack/oauth": "^3.0.2",
|
"@slack/oauth": "^3.0.2",
|
||||||
@ -222,6 +226,8 @@
|
|||||||
"posthog-node": "^3.6.2",
|
"posthog-node": "^3.6.2",
|
||||||
"probot": "^13.3.8",
|
"probot": "^13.3.8",
|
||||||
"re2": "^1.21.4",
|
"re2": "^1.21.4",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-dom": "19.1.0",
|
||||||
"safe-regex": "^2.1.1",
|
"safe-regex": "^2.1.1",
|
||||||
"scim-patch": "^0.8.3",
|
"scim-patch": "^0.8.3",
|
||||||
"scim2-parse-filter": "^0.2.10",
|
"scim2-parse-filter": "^0.2.10",
|
||||||
|
@ -219,7 +219,7 @@ export const parseRotationErrorMessage = (err: unknown): string => {
|
|||||||
if (err instanceof AxiosError) {
|
if (err instanceof AxiosError) {
|
||||||
errorMessage += err?.response?.data
|
errorMessage += err?.response?.data
|
||||||
? JSON.stringify(err?.response?.data)
|
? JSON.stringify(err?.response?.data)
|
||||||
: err?.message ?? "An unknown error occurred.";
|
: (err?.message ?? "An unknown error occurred.");
|
||||||
} else {
|
} else {
|
||||||
errorMessage += (err as Error)?.message || "An unknown error occurred.";
|
errorMessage += (err as Error)?.message || "An unknown error occurred.";
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
// set [keyId] depending on if [allowCustomKeyIds] is true or false
|
// set [keyId] depending on if [allowCustomKeyIds] is true or false
|
||||||
const keyId = sshCertificateTemplate.allowCustomKeyIds
|
const keyId = sshCertificateTemplate.allowCustomKeyIds
|
||||||
? requestedKeyId ?? `${actor}-${actorId}`
|
? (requestedKeyId ?? `${actor}-${actorId}`)
|
||||||
: `${actor}-${actorId}`;
|
: `${actor}-${actorId}`;
|
||||||
|
|
||||||
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
|
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
|
||||||
@ -404,7 +404,7 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
// set [keyId] depending on if [allowCustomKeyIds] is true or false
|
// set [keyId] depending on if [allowCustomKeyIds] is true or false
|
||||||
const keyId = sshCertificateTemplate.allowCustomKeyIds
|
const keyId = sshCertificateTemplate.allowCustomKeyIds
|
||||||
? requestedKeyId ?? `${actor}-${actorId}`
|
? (requestedKeyId ?? `${actor}-${actorId}`)
|
||||||
: `${actor}-${actorId}`;
|
: `${actor}-${actorId}`;
|
||||||
|
|
||||||
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
|
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
|
||||||
|
@ -397,8 +397,8 @@ export const authLoginServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shouldCheckMfa = selectedOrg.enforceMfa || user.isMfaEnabled;
|
const shouldCheckMfa = selectedOrg.enforceMfa || user.isMfaEnabled;
|
||||||
const orgMfaMethod = selectedOrg.enforceMfa ? selectedOrg.selectedMfaMethod ?? MfaMethod.EMAIL : undefined;
|
const orgMfaMethod = selectedOrg.enforceMfa ? (selectedOrg.selectedMfaMethod ?? MfaMethod.EMAIL) : undefined;
|
||||||
const userMfaMethod = user.isMfaEnabled ? user.selectedMfaMethod ?? MfaMethod.EMAIL : undefined;
|
const userMfaMethod = user.isMfaEnabled ? (user.selectedMfaMethod ?? MfaMethod.EMAIL) : undefined;
|
||||||
const mfaMethod = orgMfaMethod ?? userMfaMethod;
|
const mfaMethod = orgMfaMethod ?? userMfaMethod;
|
||||||
|
|
||||||
if (shouldCheckMfa && (!decodedToken.isMfaVerified || decodedToken.mfaMethod !== mfaMethod)) {
|
if (shouldCheckMfa && (!decodedToken.isMfaVerified || decodedToken.mfaMethod !== mfaMethod)) {
|
||||||
@ -569,9 +569,9 @@ export const authLoginServiceFactory = ({
|
|||||||
}: TVerifyMfaTokenDTO) => {
|
}: TVerifyMfaTokenDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const user = await userDAL.findById(userId);
|
const user = await userDAL.findById(userId);
|
||||||
enforceUserLockStatus(Boolean(user.isLocked), user.temporaryLockDateEnd);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
enforceUserLockStatus(Boolean(user.isLocked), user.temporaryLockDateEnd);
|
||||||
if (mfaMethod === MfaMethod.EMAIL) {
|
if (mfaMethod === MfaMethod.EMAIL) {
|
||||||
await tokenService.validateTokenForUser({
|
await tokenService.validateTokenForUser({
|
||||||
type: TokenType.TOKEN_EMAIL_MFA,
|
type: TokenType.TOKEN_EMAIL_MFA,
|
||||||
|
@ -698,6 +698,8 @@ export const orgServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
|
|
||||||
|
const invitingUser = await userDAL.findOne({ id: actorId });
|
||||||
|
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
|
|
||||||
const [inviteeOrgMembership] = await orgDAL.findMembership({
|
const [inviteeOrgMembership] = await orgDAL.findMembership({
|
||||||
@ -731,8 +733,8 @@ export const orgServiceFactory = ({
|
|||||||
subjectLine: "Infisical organization invitation",
|
subjectLine: "Infisical organization invitation",
|
||||||
recipients: [inviteeOrgMembership.email as string],
|
recipients: [inviteeOrgMembership.email as string],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
inviterFirstName: inviteeOrgMembership.firstName,
|
inviterFirstName: invitingUser.firstName,
|
||||||
inviterUsername: inviteeOrgMembership.email,
|
inviterUsername: invitingUser.email,
|
||||||
organizationName: org?.name,
|
organizationName: org?.name,
|
||||||
email: inviteeOrgMembership.email,
|
email: inviteeOrgMembership.email,
|
||||||
organizationId: org?.id.toString(),
|
organizationId: org?.id.toString(),
|
||||||
@ -761,6 +763,8 @@ export const orgServiceFactory = ({
|
|||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
|
||||||
|
const invitingUser = await userDAL.findOne({ id: actorId });
|
||||||
|
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
|
|
||||||
const isEmailInvalid = await isDisposableEmail(inviteeEmails);
|
const isEmailInvalid = await isDisposableEmail(inviteeEmails);
|
||||||
@ -1179,8 +1183,8 @@ export const orgServiceFactory = ({
|
|||||||
subjectLine: "Infisical organization invitation",
|
subjectLine: "Infisical organization invitation",
|
||||||
recipients: [el.email],
|
recipients: [el.email],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
inviterFirstName: el.firstName,
|
inviterFirstName: invitingUser.firstName,
|
||||||
inviterUsername: el.email,
|
inviterUsername: invitingUser.email,
|
||||||
organizationName: org?.name,
|
organizationName: org?.name,
|
||||||
email: el.email,
|
email: el.email,
|
||||||
organizationId: org?.id.toString(),
|
organizationId: org?.id.toString(),
|
||||||
|
@ -282,7 +282,7 @@ export const parseSyncErrorMessage = (err: unknown): string => {
|
|||||||
} else if (err instanceof AxiosError) {
|
} else if (err instanceof AxiosError) {
|
||||||
errorMessage = err?.response?.data
|
errorMessage = err?.response?.data
|
||||||
? JSON.stringify(err?.response?.data)
|
? JSON.stringify(err?.response?.data)
|
||||||
: err?.message ?? "An unknown error occurred.";
|
: (err?.message ?? "An unknown error occurred.");
|
||||||
} else {
|
} else {
|
||||||
errorMessage = (err as Error)?.message || "An unknown error occurred.";
|
errorMessage = (err as Error)?.message || "An unknown error occurred.";
|
||||||
}
|
}
|
||||||
|
@ -834,7 +834,7 @@ export const secretSyncQueueFactory = ({
|
|||||||
secretPath: folder?.path,
|
secretPath: folder?.path,
|
||||||
environment: environment?.name,
|
environment: environment?.name,
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
syncUrl: `${appCfg.SITE_URL}/integrations/secret-syncs/${destination}/${secretSync.id}`
|
syncUrl: `${appCfg.SITE_URL}/secret-manager/${projectId}/integrations/secret-syncs/${destination}/${secretSync.id}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ import { ProjectType, SecretsV2Schema, SecretType, TableName, TSecretsV2, TSecre
|
|||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { generateCacheKeyFromData } from "@app/lib/crypto/cache";
|
import { generateCacheKeyFromData } from "@app/lib/crypto/cache";
|
||||||
|
import { applyJitter } from "@app/lib/dates";
|
||||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||||
import {
|
import {
|
||||||
buildFindFilter,
|
buildFindFilter,
|
||||||
@ -22,7 +23,6 @@ import type {
|
|||||||
TFindSecretsByFolderIdsFilter,
|
TFindSecretsByFolderIdsFilter,
|
||||||
TGetSecretsDTO
|
TGetSecretsDTO
|
||||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||||
import { applyJitter } from "@app/lib/dates";
|
|
||||||
|
|
||||||
export const SecretServiceCacheKeys = {
|
export const SecretServiceCacheKeys = {
|
||||||
get productKey() {
|
get productKey() {
|
||||||
|
@ -740,7 +740,7 @@ export const secretQueueFactory = ({
|
|||||||
environment: jobPayload.environmentName,
|
environment: jobPayload.environmentName,
|
||||||
count: jobPayload.count,
|
count: jobPayload.count,
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
integrationUrl: `${appCfg.SITE_URL}/integrations/${project.id}`
|
integrationUrl: `${appCfg.SITE_URL}/secret-manager/${project.id}/integrations?selectedTab=native-integrations`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface AccessApprovalRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
projectName: string;
|
||||||
|
requesterFullName: string;
|
||||||
|
requesterEmail: string;
|
||||||
|
isTemporary: boolean;
|
||||||
|
secretPath: string;
|
||||||
|
environment: string;
|
||||||
|
expiresIn: string;
|
||||||
|
permissions: string[];
|
||||||
|
note?: string;
|
||||||
|
approvalUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AccessApprovalRequestTemplate = ({
|
||||||
|
projectName,
|
||||||
|
siteUrl,
|
||||||
|
requesterFullName,
|
||||||
|
requesterEmail,
|
||||||
|
isTemporary,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
expiresIn,
|
||||||
|
permissions,
|
||||||
|
note,
|
||||||
|
approvalUrl
|
||||||
|
}: AccessApprovalRequestTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Access Approval Request"
|
||||||
|
preview="A new access approval request is pending your review."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
You have a new access approval request pending review for the project <strong>{projectName}</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
<strong>{requesterFullName}</strong> (
|
||||||
|
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
|
||||||
|
{requesterEmail}
|
||||||
|
</Link>
|
||||||
|
) has requested {isTemporary ? "temporary" : "permanent"} access to <strong>{secretPath}</strong> in the{" "}
|
||||||
|
<strong>{environment}</strong> environment.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{isTemporary && (
|
||||||
|
<Text className="text-[14px] text-red-500 leading-[24px]">
|
||||||
|
<strong>This access will expire {expiresIn} after approval.</strong>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text className="text-[14px] leading-[24px] mb-[4px]">
|
||||||
|
<strong>The following permissions are requested:</strong>
|
||||||
|
</Text>
|
||||||
|
{permissions.map((permission) => (
|
||||||
|
<Text key={permission} className="text-[14px] my-[2px] leading-[24px]">
|
||||||
|
- {permission}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
{note && (
|
||||||
|
<Text className="text-[14px] text-slate-700 leading-[24px]">
|
||||||
|
<strong className="text-black">User Note:</strong> "{note}"
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={approvalUrl}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Review Request
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessApprovalRequestTemplate;
|
||||||
|
|
||||||
|
AccessApprovalRequestTemplate.PreviewProps = {
|
||||||
|
requesterFullName: "Abigail Williams",
|
||||||
|
requesterEmail: "abigail@infisical.com",
|
||||||
|
isTemporary: true,
|
||||||
|
secretPath: "/api/secrets",
|
||||||
|
environment: "Production",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
projectName: "Example Project",
|
||||||
|
expiresIn: "1 day",
|
||||||
|
permissions: ["Read Secret", "Delete Project", "Create Dynamic Secret"],
|
||||||
|
note: "I need access to these permissions for the new initiative for HR."
|
||||||
|
} as AccessApprovalRequestTemplateProps;
|
45
backend/src/services/smtp/emails/BaseEmailWrapper.tsx
Normal file
45
backend/src/services/smtp/emails/BaseEmailWrapper.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Tailwind, Text } from "@react-email/components";
|
||||||
|
import React, { ReactNode } from "react";
|
||||||
|
|
||||||
|
export interface BaseEmailWrapperProps {
|
||||||
|
title: string;
|
||||||
|
preview: string;
|
||||||
|
siteUrl: string;
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BaseEmailWrapper = ({ title, preview, children, siteUrl }: BaseEmailWrapperProps) => {
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head title={title} />
|
||||||
|
<Tailwind>
|
||||||
|
<Body className="bg-gray-300 my-auto mx-auto font-sans px-[8px]">
|
||||||
|
<Preview>{preview}</Preview>
|
||||||
|
<Container className="bg-white rounded-xl my-[40px] mx-auto pb-[0px] max-w-[500px]">
|
||||||
|
<Section className="border-0 border-b border-[#d1e309] border-solid bg-[#EBF852] mb-[44px] h-[10px] rounded-t-xl" />
|
||||||
|
<Section className="px-[32px] mb-[18px]">
|
||||||
|
<Section className="w-[48px] h-[48px] border border-solid border-gray-300 rounded-full bg-gray-100 mx-auto">
|
||||||
|
<Img
|
||||||
|
src={`https://infisical.com/_next/image?url=%2Fimages%2Flogo-black.png&w=64&q=75`}
|
||||||
|
width="32"
|
||||||
|
alt="Infisical Logo"
|
||||||
|
className="mx-auto"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
</Section>
|
||||||
|
<Section className="px-[28px]">{children}</Section>
|
||||||
|
<Hr className=" mt-[32px] mb-[0px] h-[1px]" />
|
||||||
|
<Section className="px-[24px] text-center">
|
||||||
|
<Text className="text-gray-500 text-[12px]">
|
||||||
|
Email sent via{" "}
|
||||||
|
<Link href={siteUrl} className="text-slate-700 no-underline">
|
||||||
|
Infisical
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
};
|
50
backend/src/services/smtp/emails/EmailMfaTemplate.tsx
Normal file
50
backend/src/services/smtp/emails/EmailMfaTemplate.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface EmailMfaTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
code: string;
|
||||||
|
isCloud: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmailMfaTemplate = ({ code, siteUrl, isCloud }: EmailMfaTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper title="MFA Code" preview="Sign-in attempt requires further verification." siteUrl={siteUrl}>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>MFA required</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[8px] text-center pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text>Enter the MFA code below in the browser where you started sign-in.</Text>
|
||||||
|
<Text className="text-[24px] mt-[16px]">
|
||||||
|
<strong>{code}</strong>
|
||||||
|
</Text>
|
||||||
|
</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>Not you?</strong>{" "}
|
||||||
|
{isCloud ? (
|
||||||
|
<>
|
||||||
|
Contact us at{" "}
|
||||||
|
<Link href={`mailto:support@infisical.com`} className="text-slate-700 no-underline">
|
||||||
|
support@infisical.com
|
||||||
|
</Link>{" "}
|
||||||
|
immediately
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Contact your administrator immediately"
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmailMfaTemplate;
|
||||||
|
|
||||||
|
EmailMfaTemplate.PreviewProps = {
|
||||||
|
code: "124356",
|
||||||
|
isCloud: true,
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as EmailMfaTemplateProps;
|
@ -0,0 +1,53 @@
|
|||||||
|
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface EmailVerificationTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
code: string;
|
||||||
|
isCloud: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmailVerificationTemplate = ({ code, siteUrl, isCloud }: EmailVerificationTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Confirm Your Email Address"
|
||||||
|
preview="Verify your email address to get continue with Infisical."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>Confirm your email address</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[8px] text-center pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text>Enter the confirmation code below in the browser window requiring confirmation.</Text>
|
||||||
|
<Text className="text-[24px] mt-[16px]">
|
||||||
|
<strong>{code}</strong>
|
||||||
|
</Text>
|
||||||
|
</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>Questions about Infisical?</strong>{" "}
|
||||||
|
{isCloud ? (
|
||||||
|
<>
|
||||||
|
Email us at{" "}
|
||||||
|
<Link href={`mailto:support@infisical.com`} className="text-slate-700 no-underline">
|
||||||
|
support@infisical.com
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Contact your administrator"
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmailVerificationTemplate;
|
||||||
|
|
||||||
|
EmailVerificationTemplate.PreviewProps = {
|
||||||
|
code: "124356",
|
||||||
|
isCloud: true,
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as EmailVerificationTemplateProps;
|
@ -0,0 +1,43 @@
|
|||||||
|
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface ExternalImportFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
error: string;
|
||||||
|
provider: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExternalImportFailedTemplate = ({ error, siteUrl, provider }: ExternalImportFailedTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper title="Import Failed" preview={`An import from ${provider} has failed.`} siteUrl={siteUrl}>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
An import from <strong>{provider}</strong> to Infisical has failed
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
An import from <strong>{provider}</strong> to Infisical has failed due to unforeseen circumstances. Please
|
||||||
|
re-try your import.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
If your issue persists, you can contact the Infisical team at{" "}
|
||||||
|
<Link href={`mailto:support@infisical.com`} className="text-slate-700 no-underline">
|
||||||
|
support@infisical.com
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[14px] text-red-500 leading-[24px]">
|
||||||
|
<strong>Error:</strong> "{error}"
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExternalImportFailedTemplate;
|
||||||
|
|
||||||
|
ExternalImportFailedTemplate.PreviewProps = {
|
||||||
|
provider: "EnvKey",
|
||||||
|
error: "Something went wrong. Please try again.",
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as ExternalImportFailedTemplateProps;
|
@ -0,0 +1,30 @@
|
|||||||
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface ExternalImportStartedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
provider: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExternalImportStartedTemplate = ({ siteUrl, provider }: ExternalImportStartedTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper title="Import in Progress" preview={`An import from ${provider} has started.`} siteUrl={siteUrl}>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
An import from <strong>{provider}</strong> to Infisical has been started
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
An import from <strong>{provider}</strong> to Infisical is in progress. The import process may take up to 30
|
||||||
|
minutes. You will receive an email once the import has completed.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExternalImportStartedTemplate;
|
||||||
|
|
||||||
|
ExternalImportStartedTemplate.PreviewProps = {
|
||||||
|
provider: "EnvKey"
|
||||||
|
} as ExternalImportStartedTemplateProps;
|
@ -0,0 +1,31 @@
|
|||||||
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface ExternalImportSucceededTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
provider: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExternalImportSucceededTemplate = ({ siteUrl, provider }: ExternalImportSucceededTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper title="Import Complete" preview={`An import from ${provider} has completed.`} siteUrl={siteUrl}>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
An import from <strong>{provider}</strong> to Infisical has completed
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
An import from <strong>{provider}</strong> to Infisical was successful. Your data is now available in
|
||||||
|
Infisical.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExternalImportSucceededTemplate;
|
||||||
|
|
||||||
|
ExternalImportSucceededTemplate.PreviewProps = {
|
||||||
|
provider: "EnvKey",
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as ExternalImportSucceededTemplateProps;
|
@ -0,0 +1,65 @@
|
|||||||
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface IntegrationSyncFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
count: number;
|
||||||
|
projectName: string;
|
||||||
|
secretPath: string;
|
||||||
|
environment: string;
|
||||||
|
syncMessage: string;
|
||||||
|
integrationUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IntegrationSyncFailedTemplate = ({
|
||||||
|
count,
|
||||||
|
siteUrl,
|
||||||
|
projectName,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
syncMessage,
|
||||||
|
integrationUrl
|
||||||
|
}: IntegrationSyncFailedTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Integration Sync Failed"
|
||||||
|
preview="An integration sync has error has occurred."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>{count}</strong> integration(s) failed to sync
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<strong>Project</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{projectName}</Text>
|
||||||
|
<strong>Environment</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{environment}</Text>
|
||||||
|
<strong>Secret Path</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{secretPath}</Text>
|
||||||
|
<strong className="text-black">Failure Reason:</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px] text-red-500 leading-[24px]">"{syncMessage}"</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={integrationUrl}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
View Integrations
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IntegrationSyncFailedTemplate;
|
||||||
|
|
||||||
|
IntegrationSyncFailedTemplate.PreviewProps = {
|
||||||
|
projectName: "Example Project",
|
||||||
|
secretPath: "/api/secrets",
|
||||||
|
environment: "Production",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
integrationUrl: "https://infisical.com",
|
||||||
|
count: 2,
|
||||||
|
syncMessage: "Secret key cannot contain a colon (:)"
|
||||||
|
} as IntegrationSyncFailedTemplateProps;
|
68
backend/src/services/smtp/emails/NewDeviceLoginTemplate.tsx
Normal file
68
backend/src/services/smtp/emails/NewDeviceLoginTemplate.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface NewDeviceLoginTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
email: string;
|
||||||
|
timestamp: string;
|
||||||
|
ip: string;
|
||||||
|
userAgent: string;
|
||||||
|
isCloud: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NewDeviceLoginTemplate = ({
|
||||||
|
email,
|
||||||
|
timestamp,
|
||||||
|
ip,
|
||||||
|
userAgent,
|
||||||
|
siteUrl,
|
||||||
|
isCloud
|
||||||
|
}: NewDeviceLoginTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Successful Login from New Device"
|
||||||
|
preview="New device login from Infisical."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
We're verifying a recent login for
|
||||||
|
<br />
|
||||||
|
<strong>{email}</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<strong>Timestamp</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{timestamp}</Text>
|
||||||
|
<strong>IP Address</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{ip}</Text>
|
||||||
|
<strong>User Agent</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{userAgent}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="mt-[24px] bg-gray-50 px-[24px] pt-[2px] pb-[16px] border border-solid border-gray-200 rounded-md text-gray-800">
|
||||||
|
<Text className="mb-[0px]">
|
||||||
|
If you believe that this login is suspicious, please contact{" "}
|
||||||
|
{isCloud ? (
|
||||||
|
<Link href={`mailto:support@infisical.com`} className="text-slate-700 no-underline">
|
||||||
|
support@infisical.com
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
"your administrator"
|
||||||
|
)}{" "}
|
||||||
|
or reset your password immediately.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewDeviceLoginTemplate;
|
||||||
|
|
||||||
|
NewDeviceLoginTemplate.PreviewProps = {
|
||||||
|
email: "john@infisical.com",
|
||||||
|
ip: "127.0.0.1",
|
||||||
|
userAgent:
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15",
|
||||||
|
timestamp: "Tue Apr 29 2025 23:03:27 GMT+0000 (Coordinated Universal Time)",
|
||||||
|
isCloud: true,
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as NewDeviceLoginTemplateProps;
|
@ -0,0 +1,57 @@
|
|||||||
|
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface OrgAdminBreakglassAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
email: string;
|
||||||
|
timestamp: string;
|
||||||
|
ip: string;
|
||||||
|
userAgent: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrgAdminBreakglassAccessTemplate = ({
|
||||||
|
email,
|
||||||
|
siteUrl,
|
||||||
|
timestamp,
|
||||||
|
ip,
|
||||||
|
userAgent
|
||||||
|
}: OrgAdminBreakglassAccessTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Organization Admin has Bypassed SSO"
|
||||||
|
preview="An organization admin has bypassed SSO."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
The organization admin <strong>{email}</strong> has bypassed enforced SSO login.
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[24px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<strong className="text-[14px]">Timestamp</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{timestamp}</Text>
|
||||||
|
<strong className="text-[14px]">IP Address</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{ip}</Text>
|
||||||
|
<strong className="text-[14px]">User Agent</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{userAgent}</Text>
|
||||||
|
<Text className="text-[14px]">
|
||||||
|
If you'd like to disable Admin SSO Bypass, please visit{" "}
|
||||||
|
<Link href={`${siteUrl}/organization/settings`} className="text-slate-700 no-underline">
|
||||||
|
Organization Security Settings
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrgAdminBreakglassAccessTemplate;
|
||||||
|
|
||||||
|
OrgAdminBreakglassAccessTemplate.PreviewProps = {
|
||||||
|
ip: "127.0.0.1",
|
||||||
|
userAgent:
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15",
|
||||||
|
timestamp: "Tue Apr 29 2025 23:03:27 GMT+0000 (Coordinated Universal Time)",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
email: "august@infiscal.com"
|
||||||
|
} as OrgAdminBreakglassAccessTemplateProps;
|
@ -0,0 +1,40 @@
|
|||||||
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface OrgAdminProjectGrantAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview"> {
|
||||||
|
email: string;
|
||||||
|
projectName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrgAdminProjectGrantAccessTemplate = ({
|
||||||
|
email,
|
||||||
|
siteUrl,
|
||||||
|
projectName
|
||||||
|
}: OrgAdminProjectGrantAccessTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Project Access Granted to Organization Admin"
|
||||||
|
preview="An organization admin has self-issued direct access to a project in Infisical."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
An organization admin has joined the project <strong>{projectName}</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[24px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-[14px] mt-[4px]">
|
||||||
|
The organization admin <strong>{email}</strong> has self-issued direct access to the project{" "}
|
||||||
|
<strong>{projectName}</strong>.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrgAdminProjectGrantAccessTemplate;
|
||||||
|
|
||||||
|
OrgAdminProjectGrantAccessTemplate.PreviewProps = {
|
||||||
|
email: "kevin@infisical.com",
|
||||||
|
projectName: "Example Project"
|
||||||
|
} as OrgAdminProjectGrantAccessTemplateProps;
|
@ -0,0 +1,75 @@
|
|||||||
|
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface OrganizationInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||||
|
metadata?: string;
|
||||||
|
inviterFirstName: string;
|
||||||
|
inviterUsername: string;
|
||||||
|
organizationName: string;
|
||||||
|
email: string;
|
||||||
|
organizationId: string;
|
||||||
|
token: string;
|
||||||
|
callback_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrganizationInvitationTemplate = ({
|
||||||
|
organizationName,
|
||||||
|
inviterFirstName,
|
||||||
|
inviterUsername,
|
||||||
|
token,
|
||||||
|
callback_url,
|
||||||
|
metadata,
|
||||||
|
email,
|
||||||
|
organizationId,
|
||||||
|
siteUrl
|
||||||
|
}: OrganizationInvitationTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Organization Invitation"
|
||||||
|
preview="You've been invited to join an organization 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
|
||||||
|
<br />
|
||||||
|
<strong>{organizationName}</strong> on <strong>Infisical</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] 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]">
|
||||||
|
<strong>{inviterFirstName}</strong> (
|
||||||
|
<Link href={`mailto:${inviterUsername}`} className="text-slate-700 no-underline">
|
||||||
|
{inviterUsername}
|
||||||
|
</Link>
|
||||||
|
) has invited you to collaborate on <strong>{organizationName}</strong>.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={`${callback_url}?token=${token}${metadata ? `&metadata=${metadata}` : ""}&to=${email}&organization_id=${organizationId}`}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Accept Invite
|
||||||
|
</Button>
|
||||||
|
</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 OrganizationInvitationTemplate;
|
||||||
|
|
||||||
|
OrganizationInvitationTemplate.PreviewProps = {
|
||||||
|
organizationName: "Example Organization",
|
||||||
|
inviterFirstName: "Jane",
|
||||||
|
inviterUsername: "jane@infisical.com",
|
||||||
|
email: "john@infisical.com",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
callback_url: "https://app.infisical.com"
|
||||||
|
} as OrganizationInvitationTemplateProps;
|
58
backend/src/services/smtp/emails/PasswordResetTemplate.tsx
Normal file
58
backend/src/services/smtp/emails/PasswordResetTemplate.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface PasswordResetTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
email: string;
|
||||||
|
callback_url: string;
|
||||||
|
token: string;
|
||||||
|
isCloud: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PasswordResetTemplate = ({ email, isCloud, siteUrl, callback_url, token }: PasswordResetTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Account Recovery"
|
||||||
|
preview="A password reset was requested for your Infisical account."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>Account Recovery</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-[14px]">A password reset was requested for your Infisical account.</Text>
|
||||||
|
<Text className="text-[14px]">
|
||||||
|
If you did not initiate this request, please contact{" "}
|
||||||
|
{isCloud ? (
|
||||||
|
<>
|
||||||
|
us immediately at{" "}
|
||||||
|
<Link href={`mailto:support@infisical.com`} className="text-slate-700 no-underline">
|
||||||
|
support@infisical.com
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"your administrator immediately"
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={`${callback_url}?token=${token}&to=${email}`}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Reset Password
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PasswordResetTemplate;
|
||||||
|
|
||||||
|
PasswordResetTemplate.PreviewProps = {
|
||||||
|
email: "kevin@infisical.com",
|
||||||
|
callback_url: "https://app.infisical.com",
|
||||||
|
isCloud: true
|
||||||
|
} as PasswordResetTemplateProps;
|
57
backend/src/services/smtp/emails/PasswordSetupTemplate.tsx
Normal file
57
backend/src/services/smtp/emails/PasswordSetupTemplate.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface PasswordSetupTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
email: string;
|
||||||
|
callback_url: string;
|
||||||
|
token: string;
|
||||||
|
isCloud: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PasswordSetupTemplate = ({ email, isCloud, siteUrl, callback_url, token }: PasswordSetupTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper title="Password Setup" preview="Setup your password for Infisical." siteUrl={siteUrl}>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>Password Setup</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-[14px]">Someone requested to set up a password for your Infisical account.</Text>
|
||||||
|
<Text className="text-[14px] text-red-500">
|
||||||
|
Make sure you are already logged in to Infisical in the current browser before clicking the link below.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[14px]">
|
||||||
|
If you did not initiate this request, please contact{" "}
|
||||||
|
{isCloud ? (
|
||||||
|
<>
|
||||||
|
us immediately at{" "}
|
||||||
|
<Link href={`mailto:support@infisical.com`} className="text-slate-700 no-underline">
|
||||||
|
support@infisical.com
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"your administrator immediately"
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={`${callback_url}?token=${token}&to=${email}`}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Set Up Password
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PasswordSetupTemplate;
|
||||||
|
|
||||||
|
PasswordSetupTemplate.PreviewProps = {
|
||||||
|
email: "casey@infisical.com",
|
||||||
|
callback_url: "https://app.infisical.com",
|
||||||
|
isCloud: true
|
||||||
|
} as PasswordSetupTemplateProps;
|
@ -0,0 +1,68 @@
|
|||||||
|
import { Heading, Hr, Section, Text } from "@react-email/components";
|
||||||
|
import React, { Fragment } from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface PkiExpirationAlertTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
alertName: string;
|
||||||
|
alertBeforeDays: number;
|
||||||
|
items: { type: string; friendlyName: string; serialNumber: string; expiryDate: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PkiExpirationAlertTemplate = ({
|
||||||
|
alertName,
|
||||||
|
siteUrl,
|
||||||
|
alertBeforeDays,
|
||||||
|
items
|
||||||
|
}: PkiExpirationAlertTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Infisical CA/Certificate Expiration Notice"
|
||||||
|
preview="One or more of your Infisical certificates is about to expire."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>CA/Certificate Expiration Notice</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text>Hello,</Text>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
This is an automated alert for <strong>{alertName}</strong> triggered for CAs/Certificates expiring in{" "}
|
||||||
|
<strong>{alertBeforeDays}</strong> days.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[14px] leading-[24px] mb-[4px]">
|
||||||
|
<strong>Expiring Items:</strong>
|
||||||
|
</Text>
|
||||||
|
{items.map((item) => (
|
||||||
|
<Fragment key={item.serialNumber}>
|
||||||
|
<Hr className="mb-[16px]" />
|
||||||
|
<strong className="text-[14px]">{item.type}:</strong>
|
||||||
|
<Text className="text-[14px] my-[2px] leading-[24px]">{item.friendlyName}</Text>
|
||||||
|
<strong className="text-[14px]">Serial Number:</strong>
|
||||||
|
<Text className="text-[14px] my-[2px] leading-[24px]">{item.serialNumber}</Text>
|
||||||
|
<strong className="text-[14px]">Expires On:</strong>
|
||||||
|
<Text className="text-[14px] mt-[2px] mb-[16px] leading-[24px]">{item.expiryDate}</Text>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
<Hr />
|
||||||
|
<Text className="text-[14px] leading-[24px]">
|
||||||
|
Please take the necessary actions to renew these items before they expire.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[14px] leading-[24px]">
|
||||||
|
For more details, please log in to your Infisical account and check your PKI management section.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PkiExpirationAlertTemplate;
|
||||||
|
|
||||||
|
PkiExpirationAlertTemplate.PreviewProps = {
|
||||||
|
alertBeforeDays: 5,
|
||||||
|
items: [
|
||||||
|
{ type: "CA", friendlyName: "Example CA", serialNumber: "1234567890", expiryDate: "2022-01-01" },
|
||||||
|
{ type: "Certificate", friendlyName: "Example Certificate", serialNumber: "1234567890", expiryDate: "2022-01-01" }
|
||||||
|
],
|
||||||
|
alertName: "My PKI Alert"
|
||||||
|
} as PkiExpirationAlertTemplateProps;
|
@ -0,0 +1,68 @@
|
|||||||
|
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface ProjectAccessRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
projectName: string;
|
||||||
|
requesterName: string;
|
||||||
|
requesterEmail: string;
|
||||||
|
orgName: string;
|
||||||
|
environment: string;
|
||||||
|
note: string;
|
||||||
|
callback_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectAccessRequestTemplate = ({
|
||||||
|
projectName,
|
||||||
|
siteUrl,
|
||||||
|
requesterName,
|
||||||
|
requesterEmail,
|
||||||
|
orgName,
|
||||||
|
note,
|
||||||
|
callback_url
|
||||||
|
}: ProjectAccessRequestTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Project Access Request"
|
||||||
|
preview="A user has requested access to an Infisical project."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
A user has requested access to the project <strong>{projectName}</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
<strong>{requesterName}</strong> (
|
||||||
|
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
|
||||||
|
{requesterEmail}
|
||||||
|
</Link>
|
||||||
|
) has requested access to the project <strong>{projectName}</strong> in the organization{" "}
|
||||||
|
<strong>{orgName}</strong>.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[14px] text-slate-700 leading-[24px]">
|
||||||
|
<strong className="text-black">User note:</strong> "{note}"
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={callback_url}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Grant Access
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectAccessRequestTemplate;
|
||||||
|
|
||||||
|
ProjectAccessRequestTemplate.PreviewProps = {
|
||||||
|
requesterName: "Abigail Williams",
|
||||||
|
requesterEmail: "abigail@infisical.com",
|
||||||
|
orgName: "Example Org",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
projectName: "Example Project",
|
||||||
|
note: "I need access to the project for the new initiative for HR."
|
||||||
|
} as ProjectAccessRequestTemplateProps;
|
@ -0,0 +1,50 @@
|
|||||||
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface ProjectInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||||
|
callback_url: string;
|
||||||
|
workspaceName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectInvitationTemplate = ({ callback_url, workspaceName, siteUrl }: ProjectInvitationTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Project Invitation"
|
||||||
|
preview="You've been invited to join a project 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 new project on Infisical
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] 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 project <strong>{workspaceName}</strong>.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={callback_url}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Join Project
|
||||||
|
</Button>
|
||||||
|
</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 ProjectInvitationTemplate;
|
||||||
|
|
||||||
|
ProjectInvitationTemplate.PreviewProps = {
|
||||||
|
workspaceName: "Example Project",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
callback_url: "https://app.infisical.com"
|
||||||
|
} as ProjectInvitationTemplateProps;
|
@ -0,0 +1,56 @@
|
|||||||
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface ScimUserProvisionedTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||||
|
organizationName: string;
|
||||||
|
callback_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScimUserProvisionedTemplate = ({
|
||||||
|
organizationName,
|
||||||
|
callback_url,
|
||||||
|
siteUrl
|
||||||
|
}: ScimUserProvisionedTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Organization Invitation"
|
||||||
|
preview="You've been invited to join an organization 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
|
||||||
|
<br />
|
||||||
|
<strong>{organizationName}</strong> on <strong>Infisical</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] 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 you to collaborate on <strong>{organizationName}</strong>.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={callback_url}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Accept Invite
|
||||||
|
</Button>
|
||||||
|
</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 ScimUserProvisionedTemplate;
|
||||||
|
|
||||||
|
ScimUserProvisionedTemplate.PreviewProps = {
|
||||||
|
organizationName: "Example Organization",
|
||||||
|
callback_url: "https://app.infisical.com",
|
||||||
|
siteUrl: "https://app.infisical.com"
|
||||||
|
} as ScimUserProvisionedTemplateProps;
|
@ -0,0 +1,72 @@
|
|||||||
|
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface SecretApprovalRequestBypassedTemplateProps
|
||||||
|
extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
projectName: string;
|
||||||
|
requesterFullName: string;
|
||||||
|
requesterEmail: string;
|
||||||
|
secretPath: string;
|
||||||
|
environment: string;
|
||||||
|
bypassReason: string;
|
||||||
|
approvalUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SecretApprovalRequestBypassedTemplate = ({
|
||||||
|
projectName,
|
||||||
|
siteUrl,
|
||||||
|
requesterFullName,
|
||||||
|
requesterEmail,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
bypassReason,
|
||||||
|
approvalUrl
|
||||||
|
}: SecretApprovalRequestBypassedTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Secret Approval Request Bypassed"
|
||||||
|
preview="A secret approval request has been bypassed."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
A secret approval request has been bypassed in the project <strong>{projectName}</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
<strong>{requesterFullName}</strong> (
|
||||||
|
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
|
||||||
|
{requesterEmail}
|
||||||
|
</Link>
|
||||||
|
) has merged a secret to <strong>{secretPath}</strong> in the <strong>{environment}</strong> environment
|
||||||
|
without obtaining the required approval.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[14px] text-slate-700 leading-[24px]">
|
||||||
|
<strong className="text-black">The following reason was provided for bypassing the policy:</strong> "
|
||||||
|
{bypassReason}"
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={approvalUrl}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Review Bypass
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecretApprovalRequestBypassedTemplate;
|
||||||
|
|
||||||
|
SecretApprovalRequestBypassedTemplate.PreviewProps = {
|
||||||
|
requesterFullName: "Abigail Williams",
|
||||||
|
requesterEmail: "abigail@infisical.com",
|
||||||
|
secretPath: "/api/secrets",
|
||||||
|
environment: "Production",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
projectName: "Example Project",
|
||||||
|
bypassReason: "I needed urgent access for a production misconfiguration."
|
||||||
|
} as SecretApprovalRequestBypassedTemplateProps;
|
@ -0,0 +1,57 @@
|
|||||||
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface SecretApprovalRequestNeedsReviewTemplateProps
|
||||||
|
extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
projectName: string;
|
||||||
|
firstName: string;
|
||||||
|
organizationName: string;
|
||||||
|
approvalUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SecretApprovalRequestNeedsReviewTemplate = ({
|
||||||
|
projectName,
|
||||||
|
siteUrl,
|
||||||
|
firstName,
|
||||||
|
organizationName,
|
||||||
|
approvalUrl
|
||||||
|
}: SecretApprovalRequestNeedsReviewTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Secret Change Approval Request"
|
||||||
|
preview="A secret change approval request requires review."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
A secret approval request for the project <strong>{projectName}</strong> requires review.
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-14px">Hello {firstName},</Text>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
You have a new secret change request pending your review for the project <strong>{projectName}</strong> in the
|
||||||
|
organization <strong>{organizationName}</strong>.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={approvalUrl}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Review Changes
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecretApprovalRequestNeedsReviewTemplate;
|
||||||
|
|
||||||
|
SecretApprovalRequestNeedsReviewTemplate.PreviewProps = {
|
||||||
|
firstName: "Gordon",
|
||||||
|
organizationName: "Example Org",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
approvalUrl: "https://infisical.com",
|
||||||
|
projectName: "Example Project"
|
||||||
|
} as SecretApprovalRequestNeedsReviewTemplateProps;
|
@ -0,0 +1,82 @@
|
|||||||
|
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface SecretLeakIncidentTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
numberOfSecrets: number;
|
||||||
|
pusher_email: string;
|
||||||
|
pusher_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SecretLeakIncidentTemplate = ({
|
||||||
|
numberOfSecrets,
|
||||||
|
siteUrl,
|
||||||
|
pusher_name,
|
||||||
|
pusher_email
|
||||||
|
}: SecretLeakIncidentTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Incident Alert: Secret(s) Leaked"
|
||||||
|
preview="Infisical uncovered one or more leaked secrets."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
Infisical has uncovered <strong>{numberOfSecrets}</strong> secret(s) from a recent commit
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-[14px]">
|
||||||
|
You are receiving this notification because one or more leaked secrets have been detected in a recent commit
|
||||||
|
{(pusher_email || pusher_name) && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
pushed by <strong>{pusher_name ?? "Unknown Pusher"}</strong>{" "}
|
||||||
|
{pusher_email && (
|
||||||
|
<>
|
||||||
|
(
|
||||||
|
<Link href={`mailto:${pusher_email}`} className="text-slate-700 no-underline">
|
||||||
|
{pusher_email}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[14px]">
|
||||||
|
If these are test secrets, please add `infisical-scan:ignore` at the end of the line containing the secret as
|
||||||
|
a comment in the given programming language. This will prevent future notifications from being sent out for
|
||||||
|
these secrets.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[14px] text-red-500">
|
||||||
|
If these are production secrets, please rotate them immediately.
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[14px]">
|
||||||
|
Once you have taken action, be sure to update the status of the risk in the{" "}
|
||||||
|
<Link href={`${siteUrl}/secret-scanning`} className="text-slate-700 no-underline">
|
||||||
|
Infisical Dashboard
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={`${siteUrl}/secret-scanning`}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
View Leaked Secrets
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecretLeakIncidentTemplate;
|
||||||
|
|
||||||
|
SecretLeakIncidentTemplate.PreviewProps = {
|
||||||
|
pusher_name: "Jim",
|
||||||
|
pusher_email: "jim@infisical.com",
|
||||||
|
numberOfSecrets: 3,
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as SecretLeakIncidentTemplateProps;
|
45
backend/src/services/smtp/emails/SecretReminderTemplate.tsx
Normal file
45
backend/src/services/smtp/emails/SecretReminderTemplate.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface SecretReminderTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
projectName: string;
|
||||||
|
organizationName: string;
|
||||||
|
reminderNote?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SecretReminderTemplate = ({
|
||||||
|
siteUrl,
|
||||||
|
reminderNote,
|
||||||
|
projectName,
|
||||||
|
organizationName
|
||||||
|
}: SecretReminderTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper title="Secret Reminder" preview="You have a new secret reminder." siteUrl={siteUrl}>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
Secret Reminder
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-[14px]">
|
||||||
|
You have a new secret reminder from the project <strong>{projectName}</strong> in the{" "}
|
||||||
|
<strong>{organizationName}</strong> organization.
|
||||||
|
</Text>
|
||||||
|
{reminderNote && (
|
||||||
|
<Text className="text-[14px] text-slate-700">
|
||||||
|
<strong className="text-black">Reminder Note:</strong> "{reminderNote}"
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecretReminderTemplate;
|
||||||
|
|
||||||
|
SecretReminderTemplate.PreviewProps = {
|
||||||
|
reminderNote: "Remember to rotate secret.",
|
||||||
|
projectName: "Example Project",
|
||||||
|
organizationName: "Example Organization",
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as SecretReminderTemplateProps;
|
@ -0,0 +1,53 @@
|
|||||||
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface ShareSecretTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
name?: string;
|
||||||
|
respondentUsername: string;
|
||||||
|
secretRequestUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SecretRequestCompletedTemplate = ({
|
||||||
|
name,
|
||||||
|
siteUrl,
|
||||||
|
respondentUsername,
|
||||||
|
secretRequestUrl
|
||||||
|
}: ShareSecretTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper title="Shared Secret" preview="A secret has been shared with you." siteUrl={siteUrl}>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>A secret has been shared with you</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] text-center pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-14px">
|
||||||
|
{respondentUsername ? <strong>{respondentUsername}</strong> : "Someone"} shared a secret{" "}
|
||||||
|
{name && (
|
||||||
|
<>
|
||||||
|
<strong>{name}</strong>{" "}
|
||||||
|
</>
|
||||||
|
)}{" "}
|
||||||
|
with you.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={secretRequestUrl}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
View Secret
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecretRequestCompletedTemplate;
|
||||||
|
|
||||||
|
SecretRequestCompletedTemplate.PreviewProps = {
|
||||||
|
respondentUsername: "Gracie",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
secretRequestUrl: "https://infisical.com",
|
||||||
|
name: "API_TOKEN"
|
||||||
|
} as ShareSecretTemplateProps;
|
@ -0,0 +1,68 @@
|
|||||||
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface SecretRotationFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
rotationType: string;
|
||||||
|
rotationName: string;
|
||||||
|
rotationUrl: string;
|
||||||
|
projectName: string;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SecretRotationFailedTemplate = ({
|
||||||
|
rotationType,
|
||||||
|
rotationName,
|
||||||
|
rotationUrl,
|
||||||
|
projectName,
|
||||||
|
siteUrl,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
content
|
||||||
|
}: SecretRotationFailedTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper title="Secret Rotation Failed" preview="A secret rotation failed." siteUrl={siteUrl}>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
Your <strong>{rotationType}</strong> Rotation <strong>{rotationName}</strong> failed to rotate
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<strong>Name</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{rotationName}</Text>
|
||||||
|
<strong>Type</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{rotationType}</Text>
|
||||||
|
<strong>Project</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{projectName}</Text>
|
||||||
|
<strong>Environment</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{environment}</Text>
|
||||||
|
<strong>Secret Path</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{secretPath}</Text>
|
||||||
|
<strong>Reason:</strong>
|
||||||
|
<Text className="text-[14px] text-red-500 mt-[4px]">{content}</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={`${rotationUrl}?search=${rotationName}&secretPath=${secretPath}`}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
View in Infiscal
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecretRotationFailedTemplate;
|
||||||
|
|
||||||
|
SecretRotationFailedTemplate.PreviewProps = {
|
||||||
|
rotationType: "Auth0 Client Secret",
|
||||||
|
rotationUrl: "https://infisical.com",
|
||||||
|
content: "See Rotation status for details",
|
||||||
|
projectName: "Example Project",
|
||||||
|
secretPath: "/api/secrets",
|
||||||
|
environment: "Production",
|
||||||
|
rotationName: "my-auth0-rotation",
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as SecretRotationFailedTemplateProps;
|
@ -0,0 +1,80 @@
|
|||||||
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface SecretSyncFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
syncDestination: string;
|
||||||
|
syncName: string;
|
||||||
|
syncUrl: string;
|
||||||
|
projectName: string;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
failureMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SecretSyncFailedTemplate = ({
|
||||||
|
syncDestination,
|
||||||
|
syncName,
|
||||||
|
syncUrl,
|
||||||
|
projectName,
|
||||||
|
siteUrl,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
failureMessage
|
||||||
|
}: SecretSyncFailedTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper title="Secret Sync Failed" preview="A secret sync failed." siteUrl={siteUrl}>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
Your <strong>{syncDestination}</strong> Sync <strong>{syncName}</strong> failed to sync
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<strong>Name</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{syncName}</Text>
|
||||||
|
<strong>Destination</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{syncDestination}</Text>
|
||||||
|
<strong>Project</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{projectName}</Text>
|
||||||
|
{environment && (
|
||||||
|
<>
|
||||||
|
<strong>Environment</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{environment}</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{secretPath && (
|
||||||
|
<>
|
||||||
|
<strong>Secret Path</strong>
|
||||||
|
<Text className="text-[14px] mt-[4px]">{secretPath}</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{failureMessage && (
|
||||||
|
<>
|
||||||
|
<strong>Reason:</strong>
|
||||||
|
<Text className="text-[14px] text-red-500 mt-[4px]">{failureMessage}</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={syncUrl}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
View in Infiscal
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecretSyncFailedTemplate;
|
||||||
|
|
||||||
|
SecretSyncFailedTemplate.PreviewProps = {
|
||||||
|
syncDestination: "AWS Parameter Store",
|
||||||
|
syncUrl: "https://infisical.com",
|
||||||
|
failureMessage: "Key name cannot contain a colon (:) or a forward slash (/).",
|
||||||
|
projectName: "Example Project",
|
||||||
|
secretPath: "/api/secrets",
|
||||||
|
environment: "Production",
|
||||||
|
syncName: "my-aws-sync",
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as SecretSyncFailedTemplateProps;
|
@ -0,0 +1,53 @@
|
|||||||
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface ServiceTokenExpiryNoticeTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
tokenName: string;
|
||||||
|
projectName: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServiceTokenExpiryNoticeTemplate = ({
|
||||||
|
tokenName,
|
||||||
|
siteUrl,
|
||||||
|
projectName,
|
||||||
|
url
|
||||||
|
}: ServiceTokenExpiryNoticeTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Service Token Expiring Soon"
|
||||||
|
preview="A service token is about to expire."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>Service token expiry notice</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-14px">
|
||||||
|
Your service token <strong>{tokenName}</strong> for the project <strong>{projectName}</strong> will expire
|
||||||
|
within 24 hours.
|
||||||
|
</Text>
|
||||||
|
<Text>If this token is still needed for your workflow, please create a new one before it expires.</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={url}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Create New Token
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServiceTokenExpiryNoticeTemplate;
|
||||||
|
|
||||||
|
ServiceTokenExpiryNoticeTemplate.PreviewProps = {
|
||||||
|
projectName: "Example Project",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
url: "https://infisical.com",
|
||||||
|
tokenName: "Example Token"
|
||||||
|
} as ServiceTokenExpiryNoticeTemplateProps;
|
@ -0,0 +1,53 @@
|
|||||||
|
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface SignupEmailVerificationTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
code: string;
|
||||||
|
isCloud: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SignupEmailVerificationTemplate = ({ code, siteUrl, isCloud }: SignupEmailVerificationTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Confirm Your Email Address"
|
||||||
|
preview="Verify your email address to get started with Infisical."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>Confirm your email address</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[8px] text-center pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text>Enter the confirmation code below in the browser where you started sign-up.</Text>
|
||||||
|
<Text className="text-[24px] mt-[16px]">
|
||||||
|
<strong>{code}</strong>
|
||||||
|
</Text>
|
||||||
|
</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>Questions about setting up Infisical?</strong>{" "}
|
||||||
|
{isCloud ? (
|
||||||
|
<>
|
||||||
|
Email us at{" "}
|
||||||
|
<Link href={`mailto:support@infisical.com`} className="text-slate-700 no-underline">
|
||||||
|
support@infisical.com
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Contact your administrator"
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignupEmailVerificationTemplate;
|
||||||
|
|
||||||
|
SignupEmailVerificationTemplate.PreviewProps = {
|
||||||
|
code: "124356",
|
||||||
|
isCloud: true,
|
||||||
|
siteUrl: "https://infisical.com"
|
||||||
|
} as SignupEmailVerificationTemplateProps;
|
46
backend/src/services/smtp/emails/UnlockAccountTemplate.tsx
Normal file
46
backend/src/services/smtp/emails/UnlockAccountTemplate.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
|
interface UnlockAccountTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
|
token: string;
|
||||||
|
callback_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UnlockAccountTemplate = ({ token, siteUrl, callback_url }: UnlockAccountTemplateProps) => {
|
||||||
|
return (
|
||||||
|
<BaseEmailWrapper
|
||||||
|
title="Your Infisical Account Has Been Locked"
|
||||||
|
preview="Unlock your Infisical account to continue."
|
||||||
|
siteUrl={siteUrl}
|
||||||
|
>
|
||||||
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
|
<strong>Unlock your Infisical account</strong>
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
|
<Text className="text-14px">
|
||||||
|
Your account has been temporarily locked due to multiple failed login attempts.
|
||||||
|
</Text>
|
||||||
|
<Text>If these attempts were not made by you, reset your password immediately.</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[28px]">
|
||||||
|
<Button
|
||||||
|
href={`${callback_url}?token=${token}`}
|
||||||
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
Unlock Account
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
</BaseEmailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UnlockAccountTemplate;
|
||||||
|
|
||||||
|
UnlockAccountTemplate.PreviewProps = {
|
||||||
|
callback_url: "Example Project",
|
||||||
|
siteUrl: "https://infisical.com",
|
||||||
|
url: "https://infisical.com",
|
||||||
|
token: "Example Token"
|
||||||
|
} as UnlockAccountTemplateProps;
|
28
backend/src/services/smtp/emails/index.ts
Normal file
28
backend/src/services/smtp/emails/index.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export * from "./AccessApprovalRequestTemplate";
|
||||||
|
export * from "./EmailMfaTemplate";
|
||||||
|
export * from "./EmailVerificationTemplate";
|
||||||
|
export * from "./ExternalImportFailedTemplate";
|
||||||
|
export * from "./ExternalImportStartedTemplate";
|
||||||
|
export * from "./ExternalImportSucceededTemplate";
|
||||||
|
export * from "./IntegrationSyncFailedTemplate";
|
||||||
|
export * from "./NewDeviceLoginTemplate";
|
||||||
|
export * from "./OrgAdminBreakglassAccessTemplate";
|
||||||
|
export * from "./OrgAdminProjectGrantAccessTemplate";
|
||||||
|
export * from "./OrganizationInvitationTemplate";
|
||||||
|
export * from "./OrganizationInvitationTemplate";
|
||||||
|
export * from "./PasswordResetTemplate";
|
||||||
|
export * from "./PasswordSetupTemplate";
|
||||||
|
export * from "./PkiExpirationAlertTemplate";
|
||||||
|
export * from "./ProjectAccessRequestTemplate";
|
||||||
|
export * from "./ProjectInvitationTemplate";
|
||||||
|
export * from "./ScimUserProvisionedTemplate";
|
||||||
|
export * from "./SecretApprovalRequestBypassedTemplate";
|
||||||
|
export * from "./SecretApprovalRequestNeedsReviewTemplate";
|
||||||
|
export * from "./SecretLeakIncidentTemplate";
|
||||||
|
export * from "./SecretReminderTemplate";
|
||||||
|
export * from "./SecretRequestCompletedTemplate";
|
||||||
|
export * from "./SecretRotationFailedTemplate";
|
||||||
|
export * from "./SecretSyncFailedTemplate";
|
||||||
|
export * from "./ServiceTokenExpiryNoticeTemplate";
|
||||||
|
export * from "./SignupEmailVerificationTemplate";
|
||||||
|
export * from "./UnlockAccountTemplate";
|
@ -1,112 +0,0 @@
|
|||||||
import fs from "node:fs/promises";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
import handlebars from "handlebars";
|
|
||||||
import { createTransport } from "nodemailer";
|
|
||||||
import SMTPTransport from "nodemailer/lib/smtp-transport";
|
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
|
|
||||||
export type TSmtpConfig = SMTPTransport.Options;
|
|
||||||
export type TSmtpSendMail = {
|
|
||||||
template: SmtpTemplates;
|
|
||||||
subjectLine: string;
|
|
||||||
recipients: string[];
|
|
||||||
substitutions: object;
|
|
||||||
};
|
|
||||||
export type TSmtpService = ReturnType<typeof smtpServiceFactory>;
|
|
||||||
|
|
||||||
export enum SmtpTemplates {
|
|
||||||
SignupEmailVerification = "signupEmailVerification.handlebars",
|
|
||||||
EmailVerification = "emailVerification.handlebars",
|
|
||||||
SecretReminder = "secretReminder.handlebars",
|
|
||||||
EmailMfa = "emailMfa.handlebars",
|
|
||||||
UnlockAccount = "unlockAccount.handlebars",
|
|
||||||
AccessApprovalRequest = "accessApprovalRequest.handlebars",
|
|
||||||
AccessSecretRequestBypassed = "accessSecretRequestBypassed.handlebars",
|
|
||||||
SecretApprovalRequestNeedsReview = "secretApprovalRequestNeedsReview.handlebars",
|
|
||||||
HistoricalSecretList = "historicalSecretLeakIncident.handlebars",
|
|
||||||
NewDeviceJoin = "newDevice.handlebars",
|
|
||||||
OrgInvite = "organizationInvitation.handlebars",
|
|
||||||
ResetPassword = "passwordReset.handlebars",
|
|
||||||
SetupPassword = "passwordSetup.handlebars",
|
|
||||||
SecretLeakIncident = "secretLeakIncident.handlebars",
|
|
||||||
WorkspaceInvite = "workspaceInvitation.handlebars",
|
|
||||||
ScimUserProvisioned = "scimUserProvisioned.handlebars",
|
|
||||||
PkiExpirationAlert = "pkiExpirationAlert.handlebars",
|
|
||||||
IntegrationSyncFailed = "integrationSyncFailed.handlebars",
|
|
||||||
SecretSyncFailed = "secretSyncFailed.handlebars",
|
|
||||||
ExternalImportSuccessful = "externalImportSuccessful.handlebars",
|
|
||||||
ExternalImportFailed = "externalImportFailed.handlebars",
|
|
||||||
ExternalImportStarted = "externalImportStarted.handlebars",
|
|
||||||
SecretRequestCompleted = "secretRequestCompleted.handlebars",
|
|
||||||
SecretRotationFailed = "secretRotationFailed.handlebars",
|
|
||||||
ProjectAccessRequest = "projectAccess.handlebars",
|
|
||||||
OrgAdminProjectDirectAccess = "orgAdminProjectGrantAccess.handlebars",
|
|
||||||
OrgAdminBreakglassAccess = "orgAdminBreakglassAccess.handlebars",
|
|
||||||
ServiceTokenExpired = "serviceTokenExpired.handlebars"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SmtpHost {
|
|
||||||
Sendgrid = "smtp.sendgrid.net",
|
|
||||||
Mailgun = "smtp.mailgun.org",
|
|
||||||
SocketLabs = "smtp.sockerlabs.com",
|
|
||||||
Zohomail = "smtp.zoho.com",
|
|
||||||
Gmail = "smtp.gmail.com",
|
|
||||||
Office365 = "smtp.office365.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const smtpServiceFactory = (cfg: TSmtpConfig) => {
|
|
||||||
const smtp = createTransport(cfg);
|
|
||||||
const isSmtpOn = Boolean(cfg.host);
|
|
||||||
|
|
||||||
handlebars.registerHelper("emailFooter", () => {
|
|
||||||
const { SITE_URL } = getConfig();
|
|
||||||
return new handlebars.SafeString(
|
|
||||||
`<p style="font-size: 12px;">Email sent via Infisical at <a href="${SITE_URL}">${SITE_URL}</a></p>`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const sendMail = async ({ substitutions, recipients, template, subjectLine }: TSmtpSendMail) => {
|
|
||||||
const appCfg = getConfig();
|
|
||||||
const html = await fs.readFile(path.resolve(__dirname, "./templates/", template), "utf8");
|
|
||||||
const temp = handlebars.compile(html);
|
|
||||||
const htmlToSend = temp({ isCloud: appCfg.isCloud, siteUrl: appCfg.SITE_URL, ...substitutions });
|
|
||||||
|
|
||||||
if (isSmtpOn) {
|
|
||||||
await smtp.sendMail({
|
|
||||||
from: cfg.from,
|
|
||||||
to: recipients.join(", "),
|
|
||||||
subject: subjectLine,
|
|
||||||
html: htmlToSend
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logger.info("SMTP is not configured. Outputting it in terminal");
|
|
||||||
logger.info({
|
|
||||||
from: cfg.from,
|
|
||||||
to: recipients.join(", "),
|
|
||||||
subject: subjectLine,
|
|
||||||
html: htmlToSend
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const verify = async () => {
|
|
||||||
const isConnected = smtp
|
|
||||||
.verify()
|
|
||||||
.then(async () => {
|
|
||||||
logger.info("SMTP connected");
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
logger.error("SMTP error");
|
|
||||||
logger.error(err);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return isConnected;
|
|
||||||
};
|
|
||||||
|
|
||||||
return { sendMail, verify };
|
|
||||||
};
|
|
179
backend/src/services/smtp/smtp-service.tsx
Normal file
179
backend/src/services/smtp/smtp-service.tsx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import { render } from "@react-email/components";
|
||||||
|
import handlebars from "handlebars";
|
||||||
|
import { createTransport } from "nodemailer";
|
||||||
|
import SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
AccessApprovalRequestTemplate,
|
||||||
|
EmailMfaTemplate,
|
||||||
|
EmailVerificationTemplate,
|
||||||
|
ExternalImportFailedTemplate,
|
||||||
|
ExternalImportStartedTemplate,
|
||||||
|
ExternalImportSucceededTemplate,
|
||||||
|
IntegrationSyncFailedTemplate,
|
||||||
|
NewDeviceLoginTemplate,
|
||||||
|
OrgAdminBreakglassAccessTemplate,
|
||||||
|
OrganizationInvitationTemplate,
|
||||||
|
PasswordResetTemplate,
|
||||||
|
PasswordSetupTemplate,
|
||||||
|
PkiExpirationAlertTemplate,
|
||||||
|
ProjectAccessRequestTemplate,
|
||||||
|
ProjectInvitationTemplate,
|
||||||
|
ScimUserProvisionedTemplate,
|
||||||
|
SecretApprovalRequestBypassedTemplate,
|
||||||
|
SecretApprovalRequestNeedsReviewTemplate,
|
||||||
|
SecretLeakIncidentTemplate,
|
||||||
|
SecretReminderTemplate,
|
||||||
|
SecretRequestCompletedTemplate,
|
||||||
|
SecretRotationFailedTemplate,
|
||||||
|
SecretSyncFailedTemplate,
|
||||||
|
ServiceTokenExpiryNoticeTemplate,
|
||||||
|
SignupEmailVerificationTemplate,
|
||||||
|
UnlockAccountTemplate
|
||||||
|
} from "src/services/smtp/emails";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
|
import OrgAdminProjectGrantAccessTemplate from "./emails/OrgAdminProjectGrantAccessTemplate";
|
||||||
|
|
||||||
|
export type TSmtpConfig = SMTPTransport.Options;
|
||||||
|
export type TSmtpSendMail = {
|
||||||
|
template: SmtpTemplates;
|
||||||
|
subjectLine: string;
|
||||||
|
recipients: string[];
|
||||||
|
substitutions: object;
|
||||||
|
};
|
||||||
|
export type TSmtpService = ReturnType<typeof smtpServiceFactory>;
|
||||||
|
|
||||||
|
export enum SmtpTemplates {
|
||||||
|
SignupEmailVerification = "signupEmailVerification",
|
||||||
|
EmailVerification = "emailVerification",
|
||||||
|
SecretReminder = "secretReminder",
|
||||||
|
EmailMfa = "emailMfa",
|
||||||
|
UnlockAccount = "unlockAccount",
|
||||||
|
AccessApprovalRequest = "accessApprovalRequest",
|
||||||
|
AccessSecretRequestBypassed = "accessSecretRequestBypassed",
|
||||||
|
SecretApprovalRequestNeedsReview = "secretApprovalRequestNeedsReview",
|
||||||
|
// HistoricalSecretList = "historicalSecretLeakIncident", not used anymore?
|
||||||
|
NewDeviceJoin = "newDevice",
|
||||||
|
OrgInvite = "organizationInvitation",
|
||||||
|
ResetPassword = "passwordReset",
|
||||||
|
SetupPassword = "passwordSetup",
|
||||||
|
SecretLeakIncident = "secretLeakIncident",
|
||||||
|
WorkspaceInvite = "workspaceInvitation",
|
||||||
|
ScimUserProvisioned = "scimUserProvisioned",
|
||||||
|
PkiExpirationAlert = "pkiExpirationAlert",
|
||||||
|
IntegrationSyncFailed = "integrationSyncFailed",
|
||||||
|
SecretSyncFailed = "secretSyncFailed",
|
||||||
|
ExternalImportSuccessful = "externalImportSuccessful",
|
||||||
|
ExternalImportFailed = "externalImportFailed",
|
||||||
|
ExternalImportStarted = "externalImportStarted",
|
||||||
|
SecretRequestCompleted = "secretRequestCompleted",
|
||||||
|
SecretRotationFailed = "secretRotationFailed",
|
||||||
|
ProjectAccessRequest = "projectAccess",
|
||||||
|
OrgAdminProjectDirectAccess = "orgAdminProjectGrantAccess",
|
||||||
|
OrgAdminBreakglassAccess = "orgAdminBreakglassAccess",
|
||||||
|
ServiceTokenExpired = "serviceTokenExpired"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SmtpHost {
|
||||||
|
Sendgrid = "smtp.sendgrid.net",
|
||||||
|
Mailgun = "smtp.mailgun.org",
|
||||||
|
SocketLabs = "smtp.sockerlabs.com",
|
||||||
|
Zohomail = "smtp.zoho.com",
|
||||||
|
Gmail = "smtp.gmail.com",
|
||||||
|
Office365 = "smtp.office365.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const EmailTemplateMap: Record<SmtpTemplates, React.FC<any>> = {
|
||||||
|
[SmtpTemplates.OrgInvite]: OrganizationInvitationTemplate,
|
||||||
|
[SmtpTemplates.NewDeviceJoin]: NewDeviceLoginTemplate,
|
||||||
|
[SmtpTemplates.SignupEmailVerification]: SignupEmailVerificationTemplate,
|
||||||
|
[SmtpTemplates.EmailMfa]: EmailMfaTemplate,
|
||||||
|
[SmtpTemplates.AccessApprovalRequest]: AccessApprovalRequestTemplate,
|
||||||
|
[SmtpTemplates.EmailVerification]: EmailVerificationTemplate,
|
||||||
|
[SmtpTemplates.ExternalImportFailed]: ExternalImportFailedTemplate,
|
||||||
|
[SmtpTemplates.ExternalImportStarted]: ExternalImportStartedTemplate,
|
||||||
|
[SmtpTemplates.ExternalImportSuccessful]: ExternalImportSucceededTemplate,
|
||||||
|
[SmtpTemplates.AccessSecretRequestBypassed]: SecretApprovalRequestBypassedTemplate,
|
||||||
|
[SmtpTemplates.IntegrationSyncFailed]: IntegrationSyncFailedTemplate,
|
||||||
|
[SmtpTemplates.OrgAdminBreakglassAccess]: OrgAdminBreakglassAccessTemplate,
|
||||||
|
[SmtpTemplates.SecretLeakIncident]: SecretLeakIncidentTemplate,
|
||||||
|
[SmtpTemplates.WorkspaceInvite]: ProjectInvitationTemplate,
|
||||||
|
[SmtpTemplates.ScimUserProvisioned]: ScimUserProvisionedTemplate,
|
||||||
|
[SmtpTemplates.SecretRequestCompleted]: SecretRequestCompletedTemplate,
|
||||||
|
[SmtpTemplates.UnlockAccount]: UnlockAccountTemplate,
|
||||||
|
[SmtpTemplates.ServiceTokenExpired]: ServiceTokenExpiryNoticeTemplate,
|
||||||
|
[SmtpTemplates.SecretReminder]: SecretReminderTemplate,
|
||||||
|
[SmtpTemplates.SecretRotationFailed]: SecretRotationFailedTemplate,
|
||||||
|
[SmtpTemplates.SecretSyncFailed]: SecretSyncFailedTemplate,
|
||||||
|
[SmtpTemplates.OrgAdminProjectDirectAccess]: OrgAdminProjectGrantAccessTemplate,
|
||||||
|
[SmtpTemplates.ProjectAccessRequest]: ProjectAccessRequestTemplate,
|
||||||
|
[SmtpTemplates.SecretApprovalRequestNeedsReview]: SecretApprovalRequestNeedsReviewTemplate,
|
||||||
|
[SmtpTemplates.ResetPassword]: PasswordResetTemplate,
|
||||||
|
[SmtpTemplates.SetupPassword]: PasswordSetupTemplate,
|
||||||
|
[SmtpTemplates.PkiExpirationAlert]: PkiExpirationAlertTemplate
|
||||||
|
};
|
||||||
|
|
||||||
|
export const smtpServiceFactory = (cfg: TSmtpConfig) => {
|
||||||
|
const smtp = createTransport(cfg);
|
||||||
|
const isSmtpOn = Boolean(cfg.host);
|
||||||
|
|
||||||
|
handlebars.registerHelper("emailFooter", () => {
|
||||||
|
const { SITE_URL } = getConfig();
|
||||||
|
return new handlebars.SafeString(
|
||||||
|
`<p style="font-size: 12px;">Email sent via Infisical at <a href="${SITE_URL}">${SITE_URL}</a></p>`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendMail = async ({ substitutions, recipients, template, subjectLine }: TSmtpSendMail) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const EmailTemplate = EmailTemplateMap[template];
|
||||||
|
|
||||||
|
if (!EmailTemplate) {
|
||||||
|
throw new Error(`Email template ${template} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlToSend = await render(
|
||||||
|
<EmailTemplate {...substitutions} isCloud={appCfg.isCloud} siteUrl={appCfg.SITE_URL} />
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isSmtpOn) {
|
||||||
|
await smtp.sendMail({
|
||||||
|
from: cfg.from,
|
||||||
|
to: recipients.join(", "),
|
||||||
|
subject: subjectLine,
|
||||||
|
html: htmlToSend
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.info("SMTP is not configured. Outputting it in terminal");
|
||||||
|
logger.info({
|
||||||
|
from: cfg.from,
|
||||||
|
to: recipients.join(", "),
|
||||||
|
subject: subjectLine,
|
||||||
|
html: htmlToSend
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const verify = async () => {
|
||||||
|
const isConnected = smtp
|
||||||
|
.verify()
|
||||||
|
.then(async () => {
|
||||||
|
logger.info("SMTP connected");
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
logger.error("SMTP error");
|
||||||
|
logger.error(err);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return isConnected;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { sendMail, verify };
|
||||||
|
};
|
@ -1,55 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Access Approval Request</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
<h2>New access approval request pending your review</h2>
|
|
||||||
<p>You have a new access approval request pending review in project "{{projectName}}".</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{{requesterFullName}}
|
|
||||||
({{requesterEmail}}) has requested
|
|
||||||
{{#if isTemporary}}
|
|
||||||
temporary
|
|
||||||
{{else}}
|
|
||||||
permanent
|
|
||||||
{{/if}}
|
|
||||||
access to
|
|
||||||
{{secretPath}}
|
|
||||||
in the
|
|
||||||
{{environment}}
|
|
||||||
environment.
|
|
||||||
|
|
||||||
{{#if isTemporary}}
|
|
||||||
<br />
|
|
||||||
This access will expire
|
|
||||||
{{expiresIn}}
|
|
||||||
after it has been approved.
|
|
||||||
{{/if}}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The following permissions are requested:
|
|
||||||
<ul>
|
|
||||||
{{#each permissions}}
|
|
||||||
<li>{{this}}</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
{{#if note}}
|
|
||||||
<p>User Note: "{{note}}"</p>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
View the request and approve or deny it
|
|
||||||
<a href="{{approvalUrl}}">here</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,33 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Secret Approval Request Policy Bypassed</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>Infisical</h1>
|
|
||||||
<h2>Secret Approval Request Bypassed</h2>
|
|
||||||
<p>A secret approval request has been bypassed in the project "{{projectName}}".</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{{requesterFullName}}
|
|
||||||
({{requesterEmail}}) has merged a secret to environment
|
|
||||||
{{environment}}
|
|
||||||
at secret path
|
|
||||||
{{secretPath}}
|
|
||||||
without obtaining the required approvals.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The following reason was provided for bypassing the policy:
|
|
||||||
<em>{{bypassReason}}</em>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
To review this action, please visit the request panel
|
|
||||||
<a href="{{approvalUrl}}">here</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>MFA Code</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
<h2>Sign in attempt requires further verification</h2>
|
|
||||||
<p>Your MFA code is below — enter it where you started signing in to Infisical.</p>
|
|
||||||
<h2>{{code}}</h2>
|
|
||||||
<p>The MFA code will be valid for 2 minutes.</p>
|
|
||||||
<p>Not you? Contact {{#if isCloud}}Infisical{{else}}your administrator{{/if}} immediately.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,17 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Code</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Confirm your email address</h2>
|
|
||||||
<p>Your confirmation code is below — enter it in the browser window where you've started confirming your email.</p>
|
|
||||||
<h1>{{code}}</h1>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,21 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Incident alert: secrets potentially leaked</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h3>Infisical has uncovered {{numberOfSecrets}} secret(s) from historical commits to your repo</h3>
|
|
||||||
<p><a href="{{siteUrl}}/secret-scanning"><strong>View leaked secrets</strong></a></p>
|
|
||||||
|
|
||||||
<p>If these are production secrets, please rotate them immediately.</p>
|
|
||||||
|
|
||||||
<p>Once you have taken action, be sure to update the status of the risk in your
|
|
||||||
<a href="{{siteUrl}}">Infisical dashboard</a>.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,33 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Integration Sync Failed</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>{{count}} integration(s) failed to sync.</p>
|
|
||||||
<a href="{{integrationUrl}}">
|
|
||||||
View your project integrations.
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div>
|
|
||||||
<p><strong>Project</strong>: {{projectName}}</p>
|
|
||||||
<p><strong>Environment</strong>: {{environment}}</p>
|
|
||||||
<p><strong>Secret Path</strong>: {{secretPath}}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if syncMessage}}
|
|
||||||
<p><b>Reason: </b>{{syncMessage}}</p>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Successful login for {{email}} from new device</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
<p>We're verifying a recent login for {{email}}:</p>
|
|
||||||
<p><strong>Timestamp</strong>: {{timestamp}}</p>
|
|
||||||
<p><strong>IP address</strong>: {{ip}}</p>
|
|
||||||
<p><strong>User agent</strong>: {{userAgent}}</p>
|
|
||||||
<p>If you believe that this login is suspicious, please contact
|
|
||||||
{{#if isCloud}}Infisical{{else}}your administrator{{/if}}
|
|
||||||
or reset your password immediately.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Organization admin has bypassed SSO</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
<p>The organization admin {{email}} has bypassed enforced SSO login.</p>
|
|
||||||
<p><strong>Timestamp</strong>: {{timestamp}}</p>
|
|
||||||
<p><strong>IP address</strong>: {{ip}}</p>
|
|
||||||
<p><strong>User agent</strong>: {{userAgent}}</p>
|
|
||||||
<p>If you'd like to disable Admin SSO Bypass, please visit <a href="{{siteUrl}}/organization/settings">Organization Settings</a> > Security.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,16 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Organization admin issued direct access to project</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
<p>The organization admin {{email}} has granted direct access to the project "{{projectName}}".</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
||||||
<title>Organization Invitation</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Join your organization on Infisical</h2>
|
|
||||||
<p>{{inviterFirstName}} ({{inviterUsername}}) has invited you to their Infisical organization named {{organizationName}}</p>
|
|
||||||
<a href="{{callback_url}}?token={{token}}{{#if metadata}}&metadata={{metadata}}{{/if}}&to={{email}}&organization_id={{organizationId}}">Click to join</a>
|
|
||||||
<h3>What is Infisical?</h3>
|
|
||||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,16 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Account Recovery</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Reset your password</h2>
|
|
||||||
<p>Someone requested a password reset.</p>
|
|
||||||
<a href="{{callback_url}}?token={{token}}&to={{email}}">Reset password</a>
|
|
||||||
<p>If you didn't initiate this request, please contact
|
|
||||||
{{#if isCloud}}us immediately at team@infisical.com.{{else}}your administrator immediately.{{/if}}</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,17 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Password Setup</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Setup your password</h2>
|
|
||||||
<p>Someone requested to set up a password for your account.</p>
|
|
||||||
<p><strong>Make sure you are already logged in to Infisical in the current browser before clicking the link below.</strong></p>
|
|
||||||
<a href="{{callback_url}}?token={{token}}&to={{email}}">Setup password</a>
|
|
||||||
<p>If you didn't initiate this request, please contact
|
|
||||||
{{#if isCloud}}us immediately at team@infisical.com.{{else}}your administrator immediately.{{/if}}</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,33 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Infisical CA/Certificate expiration notice</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>Hello,</p>
|
|
||||||
<p>This is an automated alert for "{{alertName}}" triggered for CAs/Certificates expiring in
|
|
||||||
{{alertBeforeDays}}
|
|
||||||
days.</p>
|
|
||||||
|
|
||||||
<p>Expiring Items:</p>
|
|
||||||
<ul>
|
|
||||||
{{#each items}}
|
|
||||||
<li>
|
|
||||||
{{type}}:
|
|
||||||
<strong>{{friendlyName}}</strong>
|
|
||||||
<br />Serial Number:
|
|
||||||
{{serialNumber}}
|
|
||||||
<br />Expires On:
|
|
||||||
{{expiryDate}}
|
|
||||||
</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Please take necessary actions to renew these items before they expire.</p>
|
|
||||||
|
|
||||||
<p>For more details, please log in to your Infisical account and check your PKI management section.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,26 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Project Access Request</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
<h2>You have a new project access request!</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Requester Name: "{{requesterName}}"</li>
|
|
||||||
<li>Requester Email: "{{requesterEmail}}"</li>
|
|
||||||
<li>Project Name: "{{projectName}}"</li>
|
|
||||||
<li>Organization Name: "{{orgName}}"</li>
|
|
||||||
<li>User Note: "{{note}}"</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
Please click on the link below to grant access
|
|
||||||
</p>
|
|
||||||
<a href="{{callback_url}}">Grant Access</a>
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Organization Invitation</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Join your organization on Infisical</h2>
|
|
||||||
<p>You've been invited to join the Infisical organization — {{organizationName}}</p>
|
|
||||||
<a href="{{callback_url}}">Join now</a>
|
|
||||||
<h3>What is Infisical?</h3>
|
|
||||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets
|
|
||||||
and configs.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,24 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Secret Change Approval Request</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Hi {{firstName}},</h2>
|
|
||||||
<h2>New secret change requests are pending review.</h2>
|
|
||||||
<br />
|
|
||||||
<p>You have a secret change request pending your review in project "{{projectName}}", in the "{{organizationName}}"
|
|
||||||
organization.</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
View the request and approve or deny it
|
|
||||||
<a href="{{approvalUrl}}">here</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,27 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Incident alert: secret leaked</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h3>Infisical has uncovered {{numberOfSecrets}} secret(s) from your recent push</h3>
|
|
||||||
<p><a href="{{siteUrl}}/secret-scanning"><strong>View leaked secrets</strong></a></p>
|
|
||||||
<p>You are receiving this notification because one or more secret leaks have been detected in a recent commit pushed
|
|
||||||
by
|
|
||||||
{{pusher_name}}
|
|
||||||
({{pusher_email}}). If these are test secrets, please add `infisical-scan:ignore` at the end of the line
|
|
||||||
containing the secret as comment in the given programming. This will prevent future notifications from being sent
|
|
||||||
out for those secret(s).</p>
|
|
||||||
|
|
||||||
<p>If these are production secrets, please rotate them immediately.</p>
|
|
||||||
|
|
||||||
<p>Once you have taken action, be sure to update the status of the risk in your
|
|
||||||
<a href="{{siteUrl}}">Infisical dashboard</a>.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Secret Reminder</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
<h2>You have a new secret reminder!</h2>
|
|
||||||
<p>You have a new secret reminder from project "{{projectName}}", in {{organizationName}}</p>
|
|
||||||
{{#if reminderNote}}
|
|
||||||
<p>Here's the note included with the reminder: {{reminderNote}}</p>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,33 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Secret Request Completed</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
<h2>A secret has been shared with you</h2>
|
|
||||||
|
|
||||||
{{#if name}}
|
|
||||||
<p>Secret request name: {{name}}</p>
|
|
||||||
{{/if}}
|
|
||||||
{{#if respondentUsername}}
|
|
||||||
<p>Shared by: {{respondentUsername}}</p>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
You can access the secret by clicking the link below.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="{{secretRequestUrl}}">Access Secret</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,31 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Your {{rotationType}} Rotation "{{rotationName}}" Failed to Rotate</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>{{content}}</p>
|
|
||||||
<a href="{{rotationUrl}}?search={{rotationName}}&secretPath={{secretPath}}">
|
|
||||||
View in Infisical to see the cause of failure.
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div>
|
|
||||||
<p><strong>Name</strong>: {{rotationName}}</p>
|
|
||||||
<p><strong>Type</strong>: {{rotationType}}</p>
|
|
||||||
<p><strong>Project</strong>: {{projectName}}</p>
|
|
||||||
<p><strong>Environment</strong>: {{environment}}</p>
|
|
||||||
<p><strong>Secret Path</strong>: {{secretPath}}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,39 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>{{syncDestination}} Sync "{{syncName}}" Failed</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Infisical</h2>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>{{content}}</p>
|
|
||||||
<a href="{{syncUrl}}">
|
|
||||||
View in Infisical.
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div>
|
|
||||||
<p><strong>Name</strong>: {{syncName}}</p>
|
|
||||||
<p><strong>Destination</strong>: {{syncDestination}}</p>
|
|
||||||
<p><strong>Project</strong>: {{projectName}}</p>
|
|
||||||
{{#if environment}}
|
|
||||||
<p><strong>Environment</strong>: {{environment}}</p>
|
|
||||||
{{/if}}
|
|
||||||
{{#if secretPath}}
|
|
||||||
<p><strong>Secret Path</strong>: {{secretPath}}</p>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if failureMessage}}
|
|
||||||
<p><b>Reason: </b>{{failureMessage}}</p>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Service Token Expiring Soon</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Service Token Expiry Notice</h2>
|
|
||||||
<p>Your service token <strong>"{{tokenName}}"</strong> will expire within 24 hours.</p>
|
|
||||||
|
|
||||||
<p>This token is currently being used on project "{{projectName}}". If this token is still needed for your workflow, please create a new one before it expires.</p>
|
|
||||||
|
|
||||||
<a href="{{url}}">Create New Token</a>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,19 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Code</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Confirm your email address</h2>
|
|
||||||
<p>Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.</p>
|
|
||||||
<h1>{{code}}</h1>
|
|
||||||
<p>Questions about setting up Infisical?
|
|
||||||
{{#if isCloud}}Email us at support@infisical.com{{else}}Contact your administrator{{/if}}.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Your Infisical account has been locked</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Unlock your Infisical account</h2>
|
|
||||||
<p>Your account has been temporarily locked due to multiple failed login attempts. </h2>
|
|
||||||
<a href="{{callback_url}}?token={{token}}">To unlock your account, follow the link here</a>
|
|
||||||
<p>If these attempts were not made by you, reset your password immediately.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,17 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
||||||
<title>Project Invitation</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Join your team on Infisical</h2>
|
|
||||||
<p>You have been invited to a new Infisical project named {{workspaceName}}</p>
|
|
||||||
<a href="{{callback_url}}">Click to join</a>
|
|
||||||
<h3>What is Infisical?</h3>
|
|
||||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets
|
|
||||||
and configs.</p>
|
|
||||||
|
|
||||||
{{emailFooter}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -163,8 +163,8 @@ export const superAdminServiceFactory = ({
|
|||||||
|
|
||||||
const canServerAdminAccessAfterApply =
|
const canServerAdminAccessAfterApply =
|
||||||
data.enabledLoginMethods.some((loginMethod) =>
|
data.enabledLoginMethods.some((loginMethod) =>
|
||||||
loginMethodToAuthMethod[loginMethod as LoginMethod].some(
|
loginMethodToAuthMethod[loginMethod as LoginMethod].some((authMethod) =>
|
||||||
(authMethod) => superAdminUser.authMethods?.includes(authMethod)
|
superAdminUser.authMethods?.includes(authMethod)
|
||||||
)
|
)
|
||||||
) ||
|
) ||
|
||||||
isUserSamlAccessEnabled ||
|
isUserSamlAccessEnabled ||
|
||||||
|
@ -25,7 +25,8 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@app/*": ["./src/*"]
|
"@app/*": ["./src/*"]
|
||||||
}
|
},
|
||||||
|
"jsx": "react"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "scripts/**/*", "e2e-test/**/*", "./.eslintrc.js", "./tsup.config.js"],
|
"include": ["src/**/*", "scripts/**/*", "e2e-test/**/*", "./.eslintrc.js", "./tsup.config.js"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
Reference in New Issue
Block a user