Skip to content

Instantly share code, notes, and snippets.

@tomfa
Last active June 15, 2024 13:33
Show Gist options
  • Save tomfa/1d6b67093f643172ce28dbcf18175693 to your computer and use it in GitHub Desktop.
Save tomfa/1d6b67093f643172ce28dbcf18175693 to your computer and use it in GitHub Desktop.
Next Auth Email OTP Provider
export const authOptions: NextAuthOptions = {
// [...other options]
providers: [
env.NODE_ENV === "development"
? ConsoleOtpProvider()
: EmailOtpProvider({ from: env.EMAIL_FROM, server: env.EMAIL_URL }),
],
};
import { EmailOtpProvider } from "~/server/email-otp.auth";
export const ConsoleOtpProvider = () =>
EmailOtpProvider({
server: { host: "localhost", port: 25, auth: { user: "", pass: "" } },
from: "NextAuth <[email protected]>",
sendVerificationRequest: async ({ token }) => {
console.log(`----------- <ConsoleEmail> -----------`);
console.log(JSON.stringify({ message: `OTP ${token}` }, undefined, 2));
console.log(`----------- </ConsoleEmail> -----------`);
},
});
import { createTransport } from "nodemailer";
import EmailProvider, {
type SendVerificationRequestParams,
} from "next-auth/providers/email";
import { type EmailUserConfig } from "@auth/core/providers";
const generateOtpCode = async () => {
return Math.floor(1000 + Math.random() * 9000).toString();
};
export const EmailOtpProvider = (
options: Omit<EmailUserConfig, "sendVerificationRequest"> & {
sendVerificationRequest?: (
options: SendVerificationRequestParams,
) => Promise<void>;
},
) =>
EmailProvider({
maxAge: 10 * 60,
generateVerificationToken: generateOtpCode,
sendVerificationRequest: options.sendVerificationRequest
? options.sendVerificationRequest
: async ({ identifier: email, url, token, provider }) => {
const baseUrl = new URL(url).origin;
const host = baseUrl.replace(/^https?:\/\//, "");
const transport = createTransport(provider.server);
await transport.sendMail({
to: email,
from: provider.from,
subject: `${token} is your authentication code`,
text: text({ host, token }),
html: html({ host, token }),
});
},
...options,
});
function text({ host, token }: { host: string; token: string }) {
return `Use ${token} to sign in to ${host}`;
}
/**
* Email HTML body
* Insert invisible space into domains from being turned into a hyperlink by email
* clients like Outlook and Apple mail, as this is confusing because it seems
* like they are supposed to click on it to sign in.
*
* @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
*/
function html({
theme,
token,
host,
}: {
theme?: { brandColor: string; buttonText: string };
token: string;
host: string;
}) {
const escapedHost = host.replace(/\./g, "&#8203;.");
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const brandColor = theme?.brandColor || "#4470b8";
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const buttonText = theme?.buttonText || "#fff";
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText,
};
return `
<body style="background: ${color.background};">
<table width="100%" border="0" cellspacing="20" cellpadding="0"
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
<tr>
<td align="center"
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
Use code below to sign in to <strong>${escapedHost}</strong>
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><span
style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">${token}</span></td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center"
style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
If you did not request this email you can safely ignore it.
</td>
</tr>
</table>
</body>
`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment