Add support for OTPs and enhance sensitive apis with OTP verification (#191)

One-Time Password (OTP) package added with comprehensive token management, including OTP verification for team account deletion and ownership transfer.
This commit is contained in:
Giancarlo Buomprisco
2025-03-01 16:35:09 +07:00
committed by GitHub
parent 20f7fd2c22
commit d31f3eb993
60 changed files with 3543 additions and 1363 deletions

View File

@@ -7,7 +7,7 @@ export function CtaButton(
) {
return (
<Button
className="w-full bg-[#000000] rounded text-white text-[16px] font-semibold no-underline text-center py-3"
className="w-full rounded bg-[#000000] py-3 text-center text-[16px] font-semibold text-white no-underline"
href={props.href}
>
{props.children}

View File

@@ -3,9 +3,7 @@ import { Container, Section } from '@react-email/components';
export function EmailHeader(props: React.PropsWithChildren) {
return (
<Container>
<Section>
{props.children}
</Section>
<Section>{props.children}</Section>
</Container>
);
}

View File

@@ -54,29 +54,29 @@ export async function renderAccountDeleteEmail(props: Props) {
</EmailHeader>
<EmailContent>
<Text className="text-[14px] leading-[24px] text-black">
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:hello`, {
displayName: props.userDisplayName,
})}
</Text>
<Text className="text-[14px] leading-[24px] text-black">
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:paragraph1`, {
productName: props.productName,
})}
</Text>
<Text className="text-[14px] leading-[24px] text-black">
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:paragraph2`)}
</Text>
<Text className="text-[14px] leading-[24px] text-black">
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:paragraph3`, {
productName: props.productName,
})}
</Text>
<Text className="text-[14px] leading-[24px] text-black">
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:paragraph4`, {
productName: props.productName,
})}

View File

@@ -79,12 +79,12 @@ export async function renderInviteEmail(props: Props) {
</EmailHeader>
<EmailContent>
<Text className="text-[14px] leading-[24px] text-black">
<Text className="text-[16px] leading-[24px] text-[#242424]">
{hello}
</Text>
<Text
className="text-[14px] leading-[24px] text-black"
className="text-[16px] leading-[24px] text-[#242424]"
dangerouslySetInnerHTML={{ __html: mainText }}
/>
@@ -107,7 +107,7 @@ export async function renderInviteEmail(props: Props) {
<CtaButton href={props.link}>{joinTeam}</CtaButton>
</Section>
<Text className="text-[14px] leading-[24px] text-black">
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:copyPasteLink`)}{' '}
<Link href={props.link} className="text-blue-600 no-underline">
{props.link}

View File

@@ -0,0 +1,97 @@
import {
Body,
Button,
Head,
Html,
Preview,
Section,
Tailwind,
Text,
render,
} from '@react-email/components';
import { BodyStyle } from '../components/body-style';
import { EmailContent } from '../components/content';
import { EmailFooter } from '../components/footer';
import { EmailHeader } from '../components/header';
import { EmailHeading } from '../components/heading';
import { EmailWrapper } from '../components/wrapper';
import { initializeEmailI18n } from '../lib/i18n';
interface Props {
otp: string;
productName: string;
language?: string;
}
export async function renderOtpEmail(props: Props) {
const namespace = 'otp-email';
const { t } = await initializeEmailI18n({
language: props.language,
namespace,
});
const subject = t(`${namespace}:subject`, {
productName: props.productName,
});
const previewText = subject;
const heading = t(`${namespace}:heading`, {
productName: props.productName,
});
const otpText = t(`${namespace}:otpText`, {
otp: props.otp,
});
const mainText = t(`${namespace}:mainText`);
const footerText = t(`${namespace}:footerText`);
const html = await render(
<Html>
<Head>
<BodyStyle />
</Head>
<Preview>{previewText}</Preview>
<Tailwind>
<Body>
<EmailWrapper>
<EmailHeader>
<EmailHeading>{heading}</EmailHeading>
</EmailHeader>
<EmailContent>
<Text className="text-[16px] text-[#242424]">{mainText}</Text>
<Text className="text-[16px] text-[#242424]">{otpText}</Text>
<Section className="mb-[16px] mt-[16px] text-center">
<Button className={'w-full rounded bg-neutral-950 text-center'}>
<Text className="text-[16px] font-medium font-semibold leading-[16px] text-white">
{props.otp}
</Text>
</Button>
</Section>
<Text
className="text-[16px] text-[#242424]"
dangerouslySetInnerHTML={{ __html: footerText }}
/>
</EmailContent>
<EmailFooter>{props.productName}</EmailFooter>
</EmailWrapper>
</Body>
</Tailwind>
</Html>,
);
return {
html,
subject,
};
}

View File

@@ -1,2 +1,3 @@
export * from './emails/invite.email';
export * from './emails/account-delete.email';
export * from './emails/otp.email';

View File

@@ -0,0 +1,7 @@
{
"subject": "One-time password for {{productName}}",
"heading": "One-time password for {{productName}}",
"otpText": "Your one-time password is: {{otp}}",
"footerText": "Please enter the one-time password in the app to continue.",
"mainText": "You're receiving this email because you need to verify your identity using a one-time password."
}