diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-07-10 20:50:19 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2025-07-12 12:20:41 +0000 |
| commit | 140311d7419fa2192e5149df8f589c3c3733a399 (patch) | |
| tree | ddf532bbf09e4f7c947854b5515c0e8674030645 /packages/trpc/email.ts | |
| parent | 385f9f0b055678420e820b8ed30e595871630e58 (diff) | |
| download | karakeep-140311d7419fa2192e5149df8f589c3c3733a399.tar.zst | |
feat: Support forget and reset password
Diffstat (limited to 'packages/trpc/email.ts')
| -rw-r--r-- | packages/trpc/email.ts | 112 |
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; +} |
