From 92d742bc69901aa14b5fdf77d6afcc2fd555081f Mon Sep 17 00:00:00 2001 From: Anand Suthar Date: Tue, 7 May 2024 18:49:30 +0530 Subject: [PATCH] Minor bug fixes and improvments --- README.md | 3 +- app/(pages)/(auth)/signup/page.tsx | 28 +- app/api/auth/login/route.ts | 46 ++- app/api/auth/session/route.ts | 38 -- app/api/auth/signup/route.ts | 41 +- app/api/auth/verifyotp/route.ts | 31 +- app/components/otp/index.tsx | 3 - app/layouts/LandingPage/index.tsx | 2 +- app/utils/constants/index.ts | 2 +- lib/actions.ts | 2 - .../otpmail.tsx => lib/emails/templates.tsx | 4 +- lib/sessions/checkSession.ts | 26 -- lib/sessions/setSessionReq.ts | 18 - middleware.ts | 8 +- package-lock.json | 365 ++++++++++++++++++ package.json | 4 +- 16 files changed, 464 insertions(+), 157 deletions(-) delete mode 100644 app/api/auth/session/route.ts rename emails/otpmail.tsx => lib/emails/templates.tsx (98%) delete mode 100644 lib/sessions/checkSession.ts delete mode 100644 lib/sessions/setSessionReq.ts diff --git a/README.md b/README.md index 7b3fd26e..c74cb6ec 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## [![My Skills](https://skillicons.dev/icons?i=nextjs,tailwindcss,githubactions,mongodb,vercel,ts,docker&theme=dark)](https://skillicons.dev) -[![Build](https://img.shields.io/github/actions/workflow/status/ad956/patient-fitness-tracker/tests.yml?branch=main)](https://img.shields.io) +[![Build](https://img.shields.io/github/actions/workflow/status/ad956/patient-fitness-tracker/playwright.yml?branch=main)](https://img.shields.io) The Patient Fitness Tracker is a modern healthcare platform designed to streamline patient management and monitoring across multiple hospitals. It empowers patients to take control of their health journey while enabling healthcare providers to deliver personalized care efficiently. @@ -47,6 +47,7 @@ The Patient Fitness Tracker is a modern healthcare platform designed to streamli | SMTP_PASSWORD | Password for SMTP authentication. | | SMTP_FROM_EMAIL | Email address for sending emails. | | JWT_SECRET | Secret key for JWT token generation and verification. | + | BCRYPT_SALT_ROUNDS | Number of salt rounds for bcrypt password hashing | 5. **Start the development server:** ```bash diff --git a/app/(pages)/(auth)/signup/page.tsx b/app/(pages)/(auth)/signup/page.tsx index 968c612a..53b7fd45 100644 --- a/app/(pages)/(auth)/signup/page.tsx +++ b/app/(pages)/(auth)/signup/page.tsx @@ -167,7 +167,13 @@ export default function Signup() { const formData = new FormData(e.currentTarget as HTMLFormElement); try { + toast.loading("Please wait ...", { + position: "bottom-center", + }); + const signUpSuccess = await signupAction(formData); + toast.dismiss(); + if (signUpSuccess.failure) { toast.error(signUpSuccess.msg); } else { @@ -179,28 +185,18 @@ export default function Signup() { role: userRole?.toString() || "", }); - const sendingOtpPromise = new Promise((resolve) => { - setTimeout(() => { - resolve(true); - setShowOtp(true); - }, 2000); - }); - - toast.promise( - sendingOtpPromise, + toast.success( + "Almost done! Please use the OTP you received to complete signup. Thank you!", { - loading: "Please wait...", - success: - "Almost done! Please use the OTP you received to complete signup. Thank you!", - error: "Error while sending OTP", - }, - { position: "bottom-center", duration: 5000 } + position: "bottom-center", + } ); + setShowOtp(true); } } } catch (error) { - toast.error("Error signing up. Please try again!"); console.error("Error signing up"); + toast.error("Error signing up. Please try again!"); } } diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index 037a842c..b58dd2a8 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -1,8 +1,9 @@ import dbConfig from "@lib/db"; -import WelcomeTemplate from "@/emails/otpmail"; +import OtpTemplate from "@lib/emails/templates"; import { sendEmail } from "@lib/email"; import { render } from "@react-email/render"; import { generateSecureOTP } from "@utils/generateOtp"; +import bcrypt from "bcrypt"; type LoginBody = { email: string; @@ -10,22 +11,25 @@ type LoginBody = { role: string; }; +const allowedRoles = ["patient", "hospital", "doctor", "receptionist"]; + export async function POST(req: Request) { try { const body: LoginBody = await req.json(); - switch (body.role) { - case "patient": - return setOTP(body); - case "hospital": - return setOTP(body); - case "doctor": - return setOTP(body); - case "receptionist": - return setOTP(body); - default: - return Response.json({ error: "Invalid user" }); + if (!body || !body.email || !body.password || !body.role) { + return Response.json({ + error: + "Invalid request body. Please provide email, password, and role.", + }); + } + + if (!allowedRoles.includes(body.role)) { + return Response.json({ error: "User role isn't valid." }); } + + const result = await setOTP(body); + return result; } catch (error) { console.error("Error during login:", error); return Response.json({ error: "Internal Server Error" }); @@ -37,9 +41,21 @@ async function setOTP(loginBody: LoginBody) { const collection = db.collection(loginBody.role); const email = loginBody.email; - const user = await collection.findOne({ email }); + const projection = { + _id: 0, + email: 1, + firstname: 1, + lastname: 1, + password: 1, + }; + const user = await collection.findOne( + { email }, + { + projection, + } + ); - if (!user || user.password !== loginBody.password) { + if (!user || !(await bcrypt.compare(loginBody.password, user.password))) { return Response.json( { error: "Invalid email or password" }, { status: 401 } @@ -59,7 +75,7 @@ async function setOTP(loginBody: LoginBody) { const mailsent = await sendEmail({ to: send.to, subject: send.subject, - html: render(WelcomeTemplate(send.name, send.otp)), + html: render(OtpTemplate(send.name, send.otp)), from: { name: "Patient Fitness Tracker", address: "support@patientfitnesstracker.com", diff --git a/app/api/auth/session/route.ts b/app/api/auth/session/route.ts deleted file mode 100644 index 2fc65f1a..00000000 --- a/app/api/auth/session/route.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { getSession, setSession } from "@sessions/sessionUtils"; - -// get session -export async function GET() { - // from sessionUtils - const res = await getSession(); - try { - if (res) { - return Response.json( - { message: "Session is available", res }, - { status: 200 } - ); - } - return Response.json( - { message: "Session isn't available", res }, - { status: 401 } - ); - } catch (error) { - console.error("Error getting session:", error); - return Response.json({ error: "Error getting session" }, { status: 500 }); - } -} - -// setting session -export async function POST(req: Request) { - const { email, role } = await req.json(); - try { - // from sessionUtils - await setSession(email, role); - return Response.json( - { message: "Session set successfully" }, - { status: 200 } - ); - } catch (error) { - console.error("Error setting session:", error); - return Response.json({ error: "Error setting session" }, { status: 500 }); - } -} diff --git a/app/api/auth/signup/route.ts b/app/api/auth/signup/route.ts index 3734d2ed..db693bce 100644 --- a/app/api/auth/signup/route.ts +++ b/app/api/auth/signup/route.ts @@ -1,5 +1,5 @@ import dbConfig from "@lib/db"; -import WelcomeTemplate from "@/emails/otpmail"; +import OtpTemplate from "@/lib/emails/templates"; import { sendEmail } from "@lib/email"; import { render } from "@react-email/render"; import { generateSecureOTP } from "@utils/generateOtp"; @@ -9,6 +9,7 @@ import { patientadditionalDetails, receptionistadditionalDetails, } from "@constants/index"; +import bcrypt from "bcrypt"; type SignupBody = { firstname: string; @@ -19,6 +20,8 @@ type SignupBody = { role: string; }; +const allowedRoles = ["patient", "hospital", "doctor", "receptionist"]; + export async function POST(req: Request) { try { const body: SignupBody = await req.json(); @@ -30,21 +33,12 @@ export async function POST(req: Request) { ); } - switch (body.role) { - case "patient": - return createAccount(body); - case "hospital": - return createAccount(body); - case "doctor": - return createAccount(body); - case "receptionist": - return createAccount(body); - - default: - return Response.json({ - error: "Error creating account. Invalid user role!", - }); + if (!allowedRoles.includes(body.role)) { + return Response.json({ error: "User role isn't valid." }); } + + const result = await createAccount(body); + return result; } catch (error) { console.error("Error during signup:", error); return Response.json({ error: "Internal Server Error" }); @@ -57,6 +51,7 @@ async function createAccount(signupBody: SignupBody) { const collection = db.collection(signupBody.role); const email = signupBody.email; const username = signupBody.username; + const existingUser = await collection.findOne({ $or: [{ email }, { username }], }); @@ -91,7 +86,13 @@ async function createAccount(signupBody: SignupBody) { break; } - const user = { ...signupBody, ...additionalDetails }; + const hashedPassword = await hashPassword(signupBody.password); + + const user = { + ...signupBody, + ...additionalDetails, + password: hashedPassword, + }; await collection.insertOne(user); @@ -108,7 +109,7 @@ async function createAccount(signupBody: SignupBody) { const mailsent = await sendEmail({ to: send.to, subject: send.subject, - html: render(WelcomeTemplate(send.name, send.otp)), + html: render(OtpTemplate(send.name, send.otp)), from: { name: "Patient Fitness Tracker", address: "support@patientfitnesstracker.com", @@ -138,3 +139,9 @@ function checkMissingElements(body: SignupBody) { } return false; } + +async function hashPassword(password: string) { + const saltRounds = parseInt(process.env.BCRYPT_SALT_ROUNDS || "10"); // Read salt rounds from environment variable or default to "10" + const hashedPassword = await bcrypt.hash(password, saltRounds); + return hashedPassword; +} diff --git a/app/api/auth/verifyotp/route.ts b/app/api/auth/verifyotp/route.ts index 661e2270..4b668b72 100644 --- a/app/api/auth/verifyotp/route.ts +++ b/app/api/auth/verifyotp/route.ts @@ -1,3 +1,4 @@ +import { setSession } from "@sessions/sessionUtils"; import dbConfig from "@lib/db"; type bodyType = { @@ -6,27 +7,24 @@ type bodyType = { role: string; }; +const allowedRoles = ["patient", "hospital", "doctor", "receptionist"]; + export async function POST(req: Request) { try { - const body = await req.json(); + const body: bodyType = await req.json(); - if (!body.otp) { - return Response.json({ error: "OTP Not Provided" }); + if (!body || !body.email || !body.role || !body.otp) { + return Response.json({ + error: "Email, OTP, and role are required fields in the request body.", + }); } - switch (body.role) { - case "patient": - return checkOTP(body); - case "hospital": - return checkOTP(body); - case "doctor": - return checkOTP(body); - case "receptionist": - return checkOTP(body); - - default: - return Response.json({ error: "Invalid user" }); + if (!allowedRoles.includes(body.role)) { + return Response.json({ error: "User role isn't valid." }); } + + const result = await checkOTP(body); + return result; } catch (error) { console.error("Error during otp verification:", error); return Response.json({ error: "Internal Server Error" }); @@ -50,5 +48,8 @@ async function checkOTP(body: bodyType) { await collection.updateOne({ email }, { $set: { otp: "" } }); + // setting session for user (stores jwt token in cookies named session) + await setSession(email, body.role); + return Response.json({ message: "ok" }, { status: 200 }); } diff --git a/app/components/otp/index.tsx b/app/components/otp/index.tsx index d0a2f203..7a06aad2 100644 --- a/app/components/otp/index.tsx +++ b/app/components/otp/index.tsx @@ -1,6 +1,5 @@ "use client"; -import setSessionReq from "@sessions/setSessionReq"; import { Modal, ModalBody, @@ -66,8 +65,6 @@ export default function OtpSection({ userData }: userDataType) { const sendingOtpPromise = new Promise((resolve) => { setTimeout(async () => { resolve(true); - // as the setSession method uses next/headers it can't be called from a client componnet - await setSessionReq(userData.email, userData.role); router.push(`/${userData.role}`); }, 1000); }); diff --git a/app/layouts/LandingPage/index.tsx b/app/layouts/LandingPage/index.tsx index 3cd04d98..c9f992cb 100644 --- a/app/layouts/LandingPage/index.tsx +++ b/app/layouts/LandingPage/index.tsx @@ -15,7 +15,7 @@ import { export default function LandingPage() { return ( -
+
diff --git a/app/utils/constants/index.ts b/app/utils/constants/index.ts index f6eaf2f9..fd334805 100644 --- a/app/utils/constants/index.ts +++ b/app/utils/constants/index.ts @@ -56,7 +56,7 @@ const commonadditionalDetails = { gender: "", contact: "", profile: - "https://cdn.pixabay.com/photo/2021/07/02/04/48/user-6380868_1280.png", + "https://res.cloudinary.com/dtkfvp2ic/image/upload/v1715082439/110505291-heart-shape-illustration-health-medicine-concept-people-running-for-exercise-awareness-or-sport_eyu2iw.jpg", address: { address_line_1: "", address_line_2: "", diff --git a/lib/actions.ts b/lib/actions.ts index 09b3637d..92995fa6 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -39,8 +39,6 @@ export async function signupAction(formData: FormData) { const user = { firstname, lastname, username, email, password, role }; - console.log(user); - try { const serverUrl = getBaseUrl(); const response = await fetch(`${serverUrl}/api/auth/signup`, { diff --git a/emails/otpmail.tsx b/lib/emails/templates.tsx similarity index 98% rename from emails/otpmail.tsx rename to lib/emails/templates.tsx index 1ef47241..0b1d43e8 100644 --- a/emails/otpmail.tsx +++ b/lib/emails/templates.tsx @@ -1,7 +1,7 @@ import { Html } from "@react-email/html"; -import { getCurrentDateFormatted } from "@/app/utils/getDate"; +import { getCurrentDateFormatted } from "@utils/getDate"; -export default function WelcomeEmail(name: string, otp: string) { +export default function OtpTemplate(name: string, otp: string) { return (
=6" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", @@ -5055,6 +5095,15 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -5594,6 +5643,17 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5656,6 +5716,23 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -5964,6 +6041,19 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -6212,6 +6302,14 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -6349,6 +6447,14 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/color2k": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", @@ -6381,6 +6487,11 @@ "proto-list": "~1.2.1" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", @@ -6673,6 +6784,11 @@ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -6682,6 +6798,14 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -7756,6 +7880,28 @@ } } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -7809,6 +7955,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -8106,6 +8294,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", @@ -8155,6 +8348,18 @@ "entities": "^4.4.0" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -8998,6 +9203,28 @@ "node": "14 || >=16.14" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -9173,6 +9400,40 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mongodb": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", @@ -9364,6 +9625,49 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -9426,6 +9730,17 @@ "node": ">=0.10.0" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -11574,6 +11889,30 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/terser": { "version": "5.29.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.2.tgz", @@ -12269,6 +12608,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", diff --git a/package.json b/package.json index 50b120ee..fc641c56 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@react-email/html": "0.0.7", "@react-email/section": "0.0.11", "@react-email/text": "0.0.7", + "bcrypt": "^5.1.1", "date-fns": "^3.4.0", "dom-to-image": "^2.6.0", "framer-motion": "^11.1.7", @@ -37,6 +38,7 @@ }, "devDependencies": { "@playwright/test": "^1.43.1", + "@types/bcrypt": "^5.0.2", "@types/dom-to-image": "^2.6.7", "@types/js-cookie": "^3.0.6", "@types/node": "^20", @@ -50,4 +52,4 @@ "tailwindcss": "^3.3.0", "typescript": "^5" } -} \ No newline at end of file +}