aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/email.ts
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-07-10 20:50:19 +0000
committerMohamed Bassem <me@mbassem.com>2025-07-12 12:20:41 +0000
commit140311d7419fa2192e5149df8f589c3c3733a399 (patch)
treeddf532bbf09e4f7c947854b5515c0e8674030645 /packages/trpc/email.ts
parent385f9f0b055678420e820b8ed30e595871630e58 (diff)
downloadkarakeep-140311d7419fa2192e5149df8f589c3c3733a399.tar.zst
feat: Support forget and reset password
Diffstat (limited to 'packages/trpc/email.ts')
-rw-r--r--packages/trpc/email.ts112
1 files changed, 71 insertions, 41 deletions
diff --git a/packages/trpc/email.ts b/packages/trpc/email.ts
index ded23ed8..1c0b8800 100644
--- a/packages/trpc/email.ts
+++ b/packages/trpc/email.ts
@@ -1,9 +1,8 @@
import { randomBytes } from "crypto";
-import { and, eq } from "drizzle-orm";
import { createTransport } from "nodemailer";
import { db } from "@karakeep/db";
-import { verificationTokens } from "@karakeep/db/schema";
+import { passwordResetTokens, verificationTokens } from "@karakeep/db/schema";
import serverConfig from "@karakeep/shared/config";
export async function sendVerificationEmail(email: string, name: string) {
@@ -70,45 +69,6 @@ If you didn't create an account with us, please ignore this email.
await transporter.sendMail(mailOptions);
}
-export async function verifyEmailToken(
- email: string,
- token: string,
-): Promise<boolean> {
- const verificationToken = await db.query.verificationTokens.findFirst({
- where: (vt, { and, eq }) =>
- and(eq(vt.identifier, email), eq(vt.token, token)),
- });
-
- if (!verificationToken) {
- return false;
- }
-
- if (verificationToken.expires < new Date()) {
- // Clean up expired token
- await db
- .delete(verificationTokens)
- .where(
- and(
- eq(verificationTokens.identifier, email),
- eq(verificationTokens.token, token),
- ),
- );
- return false;
- }
-
- // Clean up used token
- await db
- .delete(verificationTokens)
- .where(
- and(
- eq(verificationTokens.identifier, email),
- eq(verificationTokens.token, token),
- ),
- );
-
- return true;
-}
-
export async function sendInviteEmail(
email: string,
token: string,
@@ -169,3 +129,73 @@ If you weren't expecting this invitation, you can safely ignore this email.
await transporter.sendMail(mailOptions);
}
+
+export async function sendPasswordResetEmail(
+ email: string,
+ name: string,
+ userId: string,
+) {
+ if (!serverConfig.email.smtp) {
+ throw new Error("SMTP is not configured");
+ }
+
+ const token = randomBytes(32).toString("hex");
+ const expires = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
+
+ // Store password reset token
+ await db.insert(passwordResetTokens).values({
+ userId,
+ token,
+ expires,
+ });
+
+ const transporter = createTransport({
+ host: serverConfig.email.smtp.host,
+ port: serverConfig.email.smtp.port,
+ secure: serverConfig.email.smtp.secure,
+ auth:
+ serverConfig.email.smtp.user && serverConfig.email.smtp.password
+ ? {
+ user: serverConfig.email.smtp.user,
+ pass: serverConfig.email.smtp.password,
+ }
+ : undefined,
+ });
+
+ const resetUrl = `${serverConfig.publicUrl}/reset-password?token=${encodeURIComponent(token)}`;
+
+ const mailOptions = {
+ from: serverConfig.email.smtp.from,
+ to: email,
+ subject: "Reset your password",
+ html: `
+ <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+ <h2>Password Reset Request</h2>
+ <p>Hi ${name},</p>
+ <p>You requested to reset your password for your Karakeep account. Click the link below to reset your password:</p>
+ <p>
+ <a href="${resetUrl}" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">
+ Reset Password
+ </a>
+ </p>
+ <p>If the button doesn't work, you can copy and paste this link into your browser:</p>
+ <p><a href="${resetUrl}">${resetUrl}</a></p>
+ <p>This link will expire in 1 hour.</p>
+ <p>If you didn't request a password reset, please ignore this email. Your password will remain unchanged.</p>
+ </div>
+ `,
+ text: `
+Hi ${name},
+
+You requested to reset your password for your Karakeep account. Visit this link to reset your password:
+${resetUrl}
+
+This link will expire in 1 hour.
+
+If you didn't request a password reset, please ignore this email. Your password will remain unchanged.
+ `,
+ };
+
+ await transporter.sendMail(mailOptions);
+ return token;
+}