UNPKG

nuxt-users

Version:

A comprehensive user management module for Nuxt 3 and Nuxt 4 applications with authentication, authorization, database support, and CLI tools

139 lines (132 loc) 6.03 kB
import { createTransport } from "nodemailer"; import crypto from "node:crypto"; import bcrypt from "bcrypt"; import { findUserByEmail, updateUserPassword, useDb } from "../utils/index.js"; const TOKEN_EXPIRATION_HOURS = 1; export const sendPasswordResetLink = async (email, options) => { const user = await findUserByEmail(email, options); if (!user) { console.log(`[Nuxt Users] Password reset requested for non-existent email: ${email}`); return; } const db = await useDb(options); const token = crypto.randomBytes(32).toString("hex"); const hashedToken = await bcrypt.hash(token, 10); const passwordResetTokensTable = options.tables.passwordResetTokens; await db.sql` INSERT INTO {${passwordResetTokensTable}} (email, token, created_at) VALUES (${email}, ${hashedToken}, CURRENT_TIMESTAMP) `; if (!options.mailer) { console.error("[Nuxt Users] Mailer configuration is missing. Cannot send password reset email."); return; } const transporter = createTransport({ host: options.mailer.host, port: options.mailer.port, secure: options.mailer.secure, auth: { user: options.mailer.auth.user, pass: options.mailer.auth.pass } }); const resetUrl = new URL("/reset-password", options.passwordResetBaseUrl || "http://localhost:3000"); resetUrl.searchParams.set("token", token); resetUrl.searchParams.set("email", email); const resetLink = resetUrl.toString(); try { await transporter.sendMail({ from: options.mailer.defaults?.from || '"Nuxt Users" <noreply@example.com>', to: email, subject: "Password Reset Request", text: `Please click the following link to reset your password: ${resetLink} This link will expire in ${TOKEN_EXPIRATION_HOURS} hour(s). If you did not request this password reset, please ignore this email.`, html: ` <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;"> <h2>Password Reset Request</h2> <p>You have requested to reset your password. Please click the button below to set a new password:</p> <div style="text-align: center; margin: 30px 0;"> <a href="${resetLink}" style="background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;"> Reset Password </a> </div> <p>Or copy and paste the following link into your browser:</p> <p style="word-break: break-all; background-color: #f8f9fa; padding: 10px; border-radius: 3px;"> <a href="${resetLink}">${resetLink}</a> </p> <p style="color: #6c757d; font-size: 14px;"> This link will expire in ${TOKEN_EXPIRATION_HOURS} hour(s). </p> <p style="color: #6c757d; font-size: 14px;"> If you did not request this password reset, please ignore this email. Your password will remain unchanged. </p> </div> ` }); console.log(`[Nuxt Users] Password reset email sent to ${email}`); } catch (error) { console.error(`[Nuxt Users] Failed to send password reset email to ${email}:`, error); } }; export const resetPassword = async (token, email, newPassword, options) => { const db = await useDb(options); const passwordResetTokensTable = options.tables.passwordResetTokens; const tokenRecords = await db.sql` SELECT * FROM {${passwordResetTokensTable}} WHERE email = ${email} ORDER BY created_at DESC `; if (tokenRecords.rows.length === 0) { console.log(`[Nuxt Users] No password reset tokens found for email: ${email}`); return false; } let validTokenRecord = null; for (const record of tokenRecords.rows) { const tokenMatch = await bcrypt.compare(token, record.token); if (tokenMatch) { validTokenRecord = record; break; } } if (!validTokenRecord || !validTokenRecord.created_at) { console.log(`[Nuxt Users] Invalid password reset token provided for email: ${email}`); return false; } const now = /* @__PURE__ */ new Date(); const currentTimeString = now.toISOString().slice(0, 19).replace("T", " "); const [datePart, timePart] = validTokenRecord.created_at.split(/[ T]/); const [year, month, day] = datePart.split("-").map(Number); const [hour, minute, second] = timePart.split(":").map(Number); let expirationHour = hour + TOKEN_EXPIRATION_HOURS; let expirationDay = day; const expirationMonth = month; const expirationYear = year; if (expirationHour >= 24) { expirationHour -= 24; expirationDay++; } const expirationTimeString = `${expirationYear.toString().padStart(4, "0")}-${expirationMonth.toString().padStart(2, "0")}-${expirationDay.toString().padStart(2, "0")} ${expirationHour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}:${second.toString().padStart(2, "0")}`; if (currentTimeString > expirationTimeString) { console.log(`[Nuxt Users] Expired password reset token for email: ${email}`); await db.sql`DELETE FROM {${passwordResetTokensTable}} WHERE id = ${validTokenRecord.id}`; return false; } await updateUserPassword(email, newPassword, options); await db.sql`DELETE FROM {${passwordResetTokensTable}} WHERE email = ${email}`; console.log(`[Nuxt Users] Password reset successful for email: ${email}`); return true; }; export const deleteExpiredPasswordResetTokens = async (options) => { const db = await useDb(options); const passwordResetTokensTable = options.tables.passwordResetTokens; const expirationDate = /* @__PURE__ */ new Date(); expirationDate.setHours(expirationDate.getHours() - TOKEN_EXPIRATION_HOURS); const expirationDateString = expirationDate.toISOString(); await db.sql` DELETE FROM {${passwordResetTokensTable}} WHERE created_at < ${expirationDateString} `; console.log(`[Nuxt Users] Expired password reset tokens older than ${expirationDateString} deleted.`); };