Add reset email password email for oauth user

This commit is contained in:
Carlos Monastyrski
2025-09-26 09:41:02 -03:00
parent 832b5f0f49
commit dd1ae5fcc2
5 changed files with 130 additions and 13 deletions

View File

@@ -785,6 +785,7 @@ export const registerRoutes = async (
smtpService,
authDAL,
userDAL,
orgMembershipDAL,
totpConfigDAL
});

View File

@@ -6,6 +6,7 @@ import { OrgServiceActor } from "@app/lib/types";
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
import { TokenType } from "../auth-token/auth-token-types";
import { TOrgMembershipDALFactory } from "../org-membership/org-membership-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { TTotpConfigDALFactory } from "../totp/totp-config-dal";
import { TUserDALFactory } from "../user/user-dal";
@@ -22,6 +23,7 @@ import { ActorType, AuthMethod, AuthTokenType } from "./auth-type";
type TAuthPasswordServiceFactoryDep = {
authDAL: TAuthDALFactory;
userDAL: TUserDALFactory;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
tokenService: TAuthTokenServiceFactory;
smtpService: TSmtpService;
totpConfigDAL: Pick<TTotpConfigDALFactory, "delete">;
@@ -31,6 +33,7 @@ export type TAuthPasswordFactory = ReturnType<typeof authPaswordServiceFactory>;
export const authPaswordServiceFactory = ({
authDAL,
userDAL,
orgMembershipDAL,
tokenService,
smtpService,
totpConfigDAL
@@ -47,21 +50,46 @@ export const authPaswordServiceFactory = ({
if (user && user.isAccepted) {
const cfg = getConfig();
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_PASSWORD_RESET,
userId: user.id
});
await smtpService.sendMail({
template: SmtpTemplates.ResetPassword,
recipients: [email],
subjectLine: "Infisical password reset",
substitutions: {
const hasEmailAuth = user.authMethods?.includes(AuthMethod.EMAIL);
if (!hasEmailAuth) {
const orgMemberships = await orgMembershipDAL.find({ userId: user.id });
const lastLoginMethod =
orgMemberships.length > 0
? orgMemberships.find((membership) => membership.lastLoginAuthMethod)?.lastLoginAuthMethod || null
: null;
const substitutions = {
email,
token,
callback_url: cfg.SITE_URL ? `${cfg.SITE_URL}/password-reset` : ""
}
});
lastLoginMethod,
isCloud: cfg.isCloud,
siteUrl: cfg.SITE_URL || ""
};
await smtpService.sendMail({
template: SmtpTemplates.OAuthPasswordReset,
recipients: [email],
subjectLine: "Password reset not available",
substitutions
});
} else {
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_PASSWORD_RESET,
userId: user.id
});
await smtpService.sendMail({
template: SmtpTemplates.ResetPassword,
recipients: [email],
subjectLine: "Infisical password reset",
substitutions: {
email,
token,
callback_url: cfg.SITE_URL ? `${cfg.SITE_URL}/password-reset` : ""
}
});
}
}
};

View File

@@ -0,0 +1,84 @@
import { Heading, Section, Text } from "@react-email/components";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface OAuthPasswordResetTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
email: string;
lastLoginMethod?: string | null;
isCloud: boolean;
}
export const OAuthPasswordResetTemplate = ({
email,
lastLoginMethod,
isCloud,
siteUrl
}: OAuthPasswordResetTemplateProps) => {
const getAuthMethodDisplayName = (method: string) => {
return method
.split("-")
.map((word) => {
const upperWord = word.toUpperCase();
if (["SAML", "LDAP", "OIDC", "SSO"].includes(upperWord)) {
return upperWord;
}
return word.charAt(0).toUpperCase() + word.slice(1);
})
.join(" ");
};
const getAuthMethodMessage = () => {
if (lastLoginMethod) {
const displayName = getAuthMethodDisplayName(lastLoginMethod);
return `Please continue signing in with ${displayName}, which aligns with your account's configured authentication method.`;
}
return "Please continue using the same authentication method you previously used to sign in (e.g., SSO, SAML, OAuth, or another configured provider).";
};
return (
<BaseEmailWrapper
title="Password Reset Not Available"
preview="Your account doesn't have password login enabled."
siteUrl={siteUrl}
>
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
<strong>Password Reset Not Available</strong>
</Heading>
<Section className="px-[24px] mb-[28px] 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 ({email}), but your account doesn't have password
login enabled.
</Text>
<Text className="text-[14px]">
<strong>Password reset is not available for this account.</strong>
</Text>
<Text className="text-[14px]">{getAuthMethodMessage()}</Text>
</Section>
<Section className="px-[24px] mb-[28px] pt-[12px] pb-[8px] border border-solid border-orange-200 rounded-md bg-orange-50">
<Text className="text-[14px]">
<strong>Need help?</strong>
</Text>
<Text className="text-[14px]">
If you're having trouble accessing your account or need to change your authentication method, please contact{" "}
{isCloud ? (
<>
our support team at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
</>
) : (
"your instance administrator"
)}
.
</Text>
</Section>
</BaseEmailWrapper>
);
};
export default OAuthPasswordResetTemplate;
OAuthPasswordResetTemplate.PreviewProps = {
email: "user@example.com",
lastLoginMethod: "github",
isCloud: true,
siteUrl: "https://infisical.com"
} as OAuthPasswordResetTemplateProps;

View File

@@ -7,6 +7,7 @@ export * from "./ExternalImportStartedTemplate";
export * from "./ExternalImportSucceededTemplate";
export * from "./IntegrationSyncFailedTemplate";
export * from "./NewDeviceLoginTemplate";
export * from "./OAuthPasswordResetTemplate";
export * from "./OrgAdminBreakglassAccessTemplate";
export * from "./OrgAdminProjectGrantAccessTemplate";
export * from "./OrganizationAssignmentTemplate";

View File

@@ -16,6 +16,7 @@ import {
ExternalImportSucceededTemplate,
IntegrationSyncFailedTemplate,
NewDeviceLoginTemplate,
OAuthPasswordResetTemplate,
OrgAdminBreakglassAccessTemplate,
OrgAdminProjectGrantAccessTemplate,
OrganizationAssignmentTemplate,
@@ -63,6 +64,7 @@ export enum SmtpTemplates {
NewDeviceJoin = "newDevice",
OrgInvite = "organizationInvitation",
OrgAssignment = "organizationAssignment",
OAuthPasswordReset = "oAuthPasswordReset",
ResetPassword = "passwordReset",
SetupPassword = "passwordSetup",
SecretLeakIncident = "secretLeakIncident",
@@ -121,6 +123,7 @@ const EmailTemplateMap: Record<SmtpTemplates, React.FC<any>> = {
[SmtpTemplates.OrgAdminProjectDirectAccess]: OrgAdminProjectGrantAccessTemplate,
[SmtpTemplates.ProjectAccessRequest]: ProjectAccessRequestTemplate,
[SmtpTemplates.SecretApprovalRequestNeedsReview]: SecretApprovalRequestNeedsReviewTemplate,
[SmtpTemplates.OAuthPasswordReset]: OAuthPasswordResetTemplate,
[SmtpTemplates.ResetPassword]: PasswordResetTemplate,
[SmtpTemplates.SetupPassword]: PasswordSetupTemplate,
[SmtpTemplates.PkiExpirationAlert]: PkiExpirationAlertTemplate,