├── .gitignore
├── LICENSE
├── README.md
├── backend
├── controllers
│ └── auth.controller.js
├── db
│ └── connectDB.js
├── index.js
├── mailtrap
│ ├── emailTemplates.js
│ ├── emails.js
│ └── mailtrap.config.js
├── middleware
│ └── verifyToken.js
├── models
│ └── user.model.js
├── routes
│ └── auth.route.js
└── utils
│ └── generateTokenAndSetCookie.js
├── frontend
├── .eslintrc.cjs
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ ├── screenshot-for-readme.png
│ └── vite.svg
├── src
│ ├── App.jsx
│ ├── components
│ │ ├── FloatingShape.jsx
│ │ ├── Input.jsx
│ │ ├── LoadingSpinner.jsx
│ │ └── PasswordStrengthMeter.jsx
│ ├── index.css
│ ├── main.jsx
│ ├── pages
│ │ ├── DashboardPage.jsx
│ │ ├── EmailVerificationPage.jsx
│ │ ├── ForgotPasswordPage.jsx
│ │ ├── LoginPage.jsx
│ │ ├── ResetPasswordPage.jsx
│ │ └── SignUpPage.jsx
│ ├── store
│ │ └── authStore.js
│ └── utils
│ │ └── date.js
├── tailwind.config.js
└── vite.config.js
├── package-lock.json
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | .env
16 |
17 | # Editor directories and files
18 | .vscode/*
19 | !.vscode/extensions.json
20 | .idea
21 | .DS_Store
22 | *.suo
23 | *.ntvs*
24 | *.njsproj
25 | *.sln
26 | *.sw?
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Burak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Advanced Auth Tutorial 🔒
2 |
3 | 
4 |
5 | [Video Tutorial on Youtube](https://youtu.be/pmvEgZC55Cg)
6 |
7 | About This Course:
8 |
9 | - 🔧 Backend Setup
10 | - 🗄️ Database Setup
11 | - 🔐 Signup Endpoint
12 | - 📧 Sending Verify Account Email
13 | - 🔍 Verify Email Endpoint
14 | - 📄 Building a Welcome Email Template
15 | - 🚪 Logout Endpoint
16 | - 🔑 Login Endpoint
17 | - 🔄 Forgot Password Endpoint
18 | - 🔁 Reset Password Endpoint
19 | - ✔️ Check Auth Endpoint
20 | - 🌐 Frontend Setup
21 | - 📋 Signup Page UI
22 | - 🔓 Login Page UI
23 | - ✅ Email Verification Page UI
24 | - 📤 Implementing Signup
25 | - 📧 Implementing Email Verification
26 | - 🔒 Protecting Our Routes
27 | - 🔑 Implementing Login
28 | - 🏠 Dashboard Page
29 | - 🔄 Implementing Forgot Password
30 | - 🚀 Super Detailed Deployment
31 | - ✅ This is a lot of work. Support my work by subscribing to the [Channel](https://www.youtube.com/@asaprogrammer_)
32 |
33 | ### Setup .env file
34 |
35 | ```bash
36 | MONGO_URI=your_mongo_uri
37 | PORT=5000
38 | JWT_SECRET=your_secret_key
39 | NODE_ENV=development
40 |
41 | MAILTRAP_TOKEN=your_mailtrap_token
42 | MAILTRAP_ENDPOINT=https://send.api.mailtrap.io/
43 |
44 | CLIENT_URL= http://localhost:5173
45 | ```
46 |
47 | ### Run this app locally
48 |
49 | ```shell
50 | npm run build
51 | ```
52 |
53 | ### Start the app
54 |
55 | ```shell
56 | npm run start
57 | ```
58 |
59 | ### I'll see you in the next one! 🚀
60 |
--------------------------------------------------------------------------------
/backend/controllers/auth.controller.js:
--------------------------------------------------------------------------------
1 | import bcryptjs from "bcryptjs";
2 | import crypto from "crypto";
3 |
4 | import { generateTokenAndSetCookie } from "../utils/generateTokenAndSetCookie.js";
5 | import {
6 | sendPasswordResetEmail,
7 | sendResetSuccessEmail,
8 | sendVerificationEmail,
9 | sendWelcomeEmail,
10 | } from "../mailtrap/emails.js";
11 | import { User } from "../models/user.model.js";
12 |
13 | export const signup = async (req, res) => {
14 | const { email, password, name } = req.body;
15 |
16 | try {
17 | if (!email || !password || !name) {
18 | throw new Error("All fields are required");
19 | }
20 |
21 | const userAlreadyExists = await User.findOne({ email });
22 | console.log("userAlreadyExists", userAlreadyExists);
23 |
24 | if (userAlreadyExists) {
25 | return res.status(400).json({ success: false, message: "User already exists" });
26 | }
27 |
28 | const hashedPassword = await bcryptjs.hash(password, 10);
29 | const verificationToken = Math.floor(100000 + Math.random() * 900000).toString();
30 |
31 | const user = new User({
32 | email,
33 | password: hashedPassword,
34 | name,
35 | verificationToken,
36 | verificationTokenExpiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
37 | });
38 |
39 | await user.save();
40 |
41 | // jwt
42 | generateTokenAndSetCookie(res, user._id);
43 |
44 | await sendVerificationEmail(user.email, verificationToken);
45 |
46 | res.status(201).json({
47 | success: true,
48 | message: "User created successfully",
49 | user: {
50 | ...user._doc,
51 | password: undefined,
52 | },
53 | });
54 | } catch (error) {
55 | res.status(400).json({ success: false, message: error.message });
56 | }
57 | };
58 |
59 | export const verifyEmail = async (req, res) => {
60 | const { code } = req.body;
61 | try {
62 | const user = await User.findOne({
63 | verificationToken: code,
64 | verificationTokenExpiresAt: { $gt: Date.now() },
65 | });
66 |
67 | if (!user) {
68 | return res.status(400).json({ success: false, message: "Invalid or expired verification code" });
69 | }
70 |
71 | user.isVerified = true;
72 | user.verificationToken = undefined;
73 | user.verificationTokenExpiresAt = undefined;
74 | await user.save();
75 |
76 | await sendWelcomeEmail(user.email, user.name);
77 |
78 | res.status(200).json({
79 | success: true,
80 | message: "Email verified successfully",
81 | user: {
82 | ...user._doc,
83 | password: undefined,
84 | },
85 | });
86 | } catch (error) {
87 | console.log("error in verifyEmail ", error);
88 | res.status(500).json({ success: false, message: "Server error" });
89 | }
90 | };
91 |
92 | export const login = async (req, res) => {
93 | const { email, password } = req.body;
94 | try {
95 | const user = await User.findOne({ email });
96 | if (!user) {
97 | return res.status(400).json({ success: false, message: "Invalid credentials" });
98 | }
99 | const isPasswordValid = await bcryptjs.compare(password, user.password);
100 | if (!isPasswordValid) {
101 | return res.status(400).json({ success: false, message: "Invalid credentials" });
102 | }
103 |
104 | generateTokenAndSetCookie(res, user._id);
105 |
106 | user.lastLogin = new Date();
107 | await user.save();
108 |
109 | res.status(200).json({
110 | success: true,
111 | message: "Logged in successfully",
112 | user: {
113 | ...user._doc,
114 | password: undefined,
115 | },
116 | });
117 | } catch (error) {
118 | console.log("Error in login ", error);
119 | res.status(400).json({ success: false, message: error.message });
120 | }
121 | };
122 |
123 | export const logout = async (req, res) => {
124 | res.clearCookie("token");
125 | res.status(200).json({ success: true, message: "Logged out successfully" });
126 | };
127 |
128 | export const forgotPassword = async (req, res) => {
129 | const { email } = req.body;
130 | try {
131 | const user = await User.findOne({ email });
132 |
133 | if (!user) {
134 | return res.status(400).json({ success: false, message: "User not found" });
135 | }
136 |
137 | // Generate reset token
138 | const resetToken = crypto.randomBytes(20).toString("hex");
139 | const resetTokenExpiresAt = Date.now() + 1 * 60 * 60 * 1000; // 1 hour
140 |
141 | user.resetPasswordToken = resetToken;
142 | user.resetPasswordExpiresAt = resetTokenExpiresAt;
143 |
144 | await user.save();
145 |
146 | // send email
147 | await sendPasswordResetEmail(user.email, `${process.env.CLIENT_URL}/reset-password/${resetToken}`);
148 |
149 | res.status(200).json({ success: true, message: "Password reset link sent to your email" });
150 | } catch (error) {
151 | console.log("Error in forgotPassword ", error);
152 | res.status(400).json({ success: false, message: error.message });
153 | }
154 | };
155 |
156 | export const resetPassword = async (req, res) => {
157 | try {
158 | const { token } = req.params;
159 | const { password } = req.body;
160 |
161 | const user = await User.findOne({
162 | resetPasswordToken: token,
163 | resetPasswordExpiresAt: { $gt: Date.now() },
164 | });
165 |
166 | if (!user) {
167 | return res.status(400).json({ success: false, message: "Invalid or expired reset token" });
168 | }
169 |
170 | // update password
171 | const hashedPassword = await bcryptjs.hash(password, 10);
172 |
173 | user.password = hashedPassword;
174 | user.resetPasswordToken = undefined;
175 | user.resetPasswordExpiresAt = undefined;
176 | await user.save();
177 |
178 | await sendResetSuccessEmail(user.email);
179 |
180 | res.status(200).json({ success: true, message: "Password reset successful" });
181 | } catch (error) {
182 | console.log("Error in resetPassword ", error);
183 | res.status(400).json({ success: false, message: error.message });
184 | }
185 | };
186 |
187 | export const checkAuth = async (req, res) => {
188 | try {
189 | const user = await User.findById(req.userId).select("-password");
190 | if (!user) {
191 | return res.status(400).json({ success: false, message: "User not found" });
192 | }
193 |
194 | res.status(200).json({ success: true, user });
195 | } catch (error) {
196 | console.log("Error in checkAuth ", error);
197 | res.status(400).json({ success: false, message: error.message });
198 | }
199 | };
200 |
--------------------------------------------------------------------------------
/backend/db/connectDB.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | export const connectDB = async () => {
4 | try {
5 | console.log("mongo_uri: ", process.env.MONGO_URI);
6 | const conn = await mongoose.connect(process.env.MONGO_URI);
7 | console.log(`MongoDB Connected: ${conn.connection.host}`);
8 | } catch (error) {
9 | console.log("Error connection to MongoDB: ", error.message);
10 | process.exit(1); // 1 is failure, 0 status code is success
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/backend/index.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import dotenv from "dotenv";
3 | import cors from "cors";
4 | import cookieParser from "cookie-parser";
5 | import path from "path";
6 |
7 | import { connectDB } from "./db/connectDB.js";
8 |
9 | import authRoutes from "./routes/auth.route.js";
10 |
11 | dotenv.config();
12 |
13 | const app = express();
14 | const PORT = process.env.PORT || 5000;
15 | const __dirname = path.resolve();
16 |
17 | app.use(cors({ origin: "http://localhost:5173", credentials: true }));
18 |
19 | app.use(express.json()); // allows us to parse incoming requests:req.body
20 | app.use(cookieParser()); // allows us to parse incoming cookies
21 |
22 | app.use("/api/auth", authRoutes);
23 |
24 | if (process.env.NODE_ENV === "production") {
25 | app.use(express.static(path.join(__dirname, "/frontend/dist")));
26 |
27 | app.get("*", (req, res) => {
28 | res.sendFile(path.resolve(__dirname, "frontend", "dist", "index.html"));
29 | });
30 | }
31 |
32 | app.listen(PORT, () => {
33 | connectDB();
34 | console.log("Server is running on port: ", PORT);
35 | });
36 |
--------------------------------------------------------------------------------
/backend/mailtrap/emailTemplates.js:
--------------------------------------------------------------------------------
1 | export const VERIFICATION_EMAIL_TEMPLATE = `
2 |
3 |
4 |
5 |
6 |
7 | Verify Your Email
8 |
9 |
10 |
11 |
Verify Your Email
12 |
13 |
14 |
Hello,
15 |
Thank you for signing up! Your verification code is:
16 |
17 | {verificationCode}
18 |
19 |
Enter this code on the verification page to complete your registration.
20 |
This code will expire in 15 minutes for security reasons.
21 |
If you didn't create an account with us, please ignore this email.
22 |
Best regards,
Your App Team
23 |
24 |
25 |
This is an automated message, please do not reply to this email.
26 |
27 |
28 |
29 | `;
30 |
31 | export const PASSWORD_RESET_SUCCESS_TEMPLATE = `
32 |
33 |
34 |
35 |
36 |
37 | Password Reset Successful
38 |
39 |
40 |
41 |
Password Reset Successful
42 |
43 |
44 |
Hello,
45 |
We're writing to confirm that your password has been successfully reset.
46 |
51 |
If you did not initiate this password reset, please contact our support team immediately.
52 |
For security reasons, we recommend that you:
53 |
54 | - Use a strong, unique password
55 | - Enable two-factor authentication if available
56 | - Avoid using the same password across multiple sites
57 |
58 |
Thank you for helping us keep your account secure.
59 |
Best regards,
Your App Team
60 |
61 |
62 |
This is an automated message, please do not reply to this email.
63 |
64 |
65 |
66 | `;
67 |
68 | export const PASSWORD_RESET_REQUEST_TEMPLATE = `
69 |
70 |
71 |
72 |
73 |
74 | Reset Your Password
75 |
76 |
77 |
78 |
Password Reset
79 |
80 |
81 |
Hello,
82 |
We received a request to reset your password. If you didn't make this request, please ignore this email.
83 |
To reset your password, click the button below:
84 |
87 |
This link will expire in 1 hour for security reasons.
88 |
Best regards,
Your App Team
89 |
90 |
91 |
This is an automated message, please do not reply to this email.
92 |
93 |
94 |
95 | `;
96 |
--------------------------------------------------------------------------------
/backend/mailtrap/emails.js:
--------------------------------------------------------------------------------
1 | import {
2 | PASSWORD_RESET_REQUEST_TEMPLATE,
3 | PASSWORD_RESET_SUCCESS_TEMPLATE,
4 | VERIFICATION_EMAIL_TEMPLATE,
5 | } from "./emailTemplates.js";
6 | import { mailtrapClient, sender } from "./mailtrap.config.js";
7 |
8 | export const sendVerificationEmail = async (email, verificationToken) => {
9 | const recipient = [{ email }];
10 |
11 | try {
12 | const response = await mailtrapClient.send({
13 | from: sender,
14 | to: recipient,
15 | subject: "Verify your email",
16 | html: VERIFICATION_EMAIL_TEMPLATE.replace("{verificationCode}", verificationToken),
17 | category: "Email Verification",
18 | });
19 |
20 | console.log("Email sent successfully", response);
21 | } catch (error) {
22 | console.error(`Error sending verification`, error);
23 |
24 | throw new Error(`Error sending verification email: ${error}`);
25 | }
26 | };
27 |
28 | export const sendWelcomeEmail = async (email, name) => {
29 | const recipient = [{ email }];
30 |
31 | try {
32 | const response = await mailtrapClient.send({
33 | from: sender,
34 | to: recipient,
35 | template_uuid: "e65925d1-a9d1-4a40-ae7c-d92b37d593df",
36 | template_variables: {
37 | company_info_name: "Auth Company",
38 | name: name,
39 | },
40 | });
41 |
42 | console.log("Welcome email sent successfully", response);
43 | } catch (error) {
44 | console.error(`Error sending welcome email`, error);
45 |
46 | throw new Error(`Error sending welcome email: ${error}`);
47 | }
48 | };
49 |
50 | export const sendPasswordResetEmail = async (email, resetURL) => {
51 | const recipient = [{ email }];
52 |
53 | try {
54 | const response = await mailtrapClient.send({
55 | from: sender,
56 | to: recipient,
57 | subject: "Reset your password",
58 | html: PASSWORD_RESET_REQUEST_TEMPLATE.replace("{resetURL}", resetURL),
59 | category: "Password Reset",
60 | });
61 | } catch (error) {
62 | console.error(`Error sending password reset email`, error);
63 |
64 | throw new Error(`Error sending password reset email: ${error}`);
65 | }
66 | };
67 |
68 | export const sendResetSuccessEmail = async (email) => {
69 | const recipient = [{ email }];
70 |
71 | try {
72 | const response = await mailtrapClient.send({
73 | from: sender,
74 | to: recipient,
75 | subject: "Password Reset Successful",
76 | html: PASSWORD_RESET_SUCCESS_TEMPLATE,
77 | category: "Password Reset",
78 | });
79 |
80 | console.log("Password reset email sent successfully", response);
81 | } catch (error) {
82 | console.error(`Error sending password reset success email`, error);
83 |
84 | throw new Error(`Error sending password reset success email: ${error}`);
85 | }
86 | };
87 |
--------------------------------------------------------------------------------
/backend/mailtrap/mailtrap.config.js:
--------------------------------------------------------------------------------
1 | import { MailtrapClient } from "mailtrap";
2 | import dotenv from "dotenv";
3 |
4 | dotenv.config();
5 |
6 | export const mailtrapClient = new MailtrapClient({
7 | endpoint: process.env.MAILTRAP_ENDPOINT,
8 | token: process.env.MAILTRAP_TOKEN,
9 | });
10 |
11 | export const sender = {
12 | email: "mailtrap@demomailtrap.com",
13 | name: "Burak",
14 | };
15 |
--------------------------------------------------------------------------------
/backend/middleware/verifyToken.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 |
3 | export const verifyToken = (req, res, next) => {
4 | const token = req.cookies.token;
5 | if (!token) return res.status(401).json({ success: false, message: "Unauthorized - no token provided" });
6 | try {
7 | const decoded = jwt.verify(token, process.env.JWT_SECRET);
8 |
9 | if (!decoded) return res.status(401).json({ success: false, message: "Unauthorized - invalid token" });
10 |
11 | req.userId = decoded.userId;
12 | next();
13 | } catch (error) {
14 | console.log("Error in verifyToken ", error);
15 | return res.status(500).json({ success: false, message: "Server error" });
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/backend/models/user.model.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const userSchema = new mongoose.Schema(
4 | {
5 | email: {
6 | type: String,
7 | required: true,
8 | unique: true,
9 | },
10 | password: {
11 | type: String,
12 | required: true,
13 | },
14 | name: {
15 | type: String,
16 | required: true,
17 | },
18 | lastLogin: {
19 | type: Date,
20 | default: Date.now,
21 | },
22 | isVerified: {
23 | type: Boolean,
24 | default: false,
25 | },
26 | resetPasswordToken: String,
27 | resetPasswordExpiresAt: Date,
28 | verificationToken: String,
29 | verificationTokenExpiresAt: Date,
30 | },
31 | { timestamps: true }
32 | );
33 |
34 | export const User = mongoose.model("User", userSchema);
35 |
--------------------------------------------------------------------------------
/backend/routes/auth.route.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | login,
4 | logout,
5 | signup,
6 | verifyEmail,
7 | forgotPassword,
8 | resetPassword,
9 | checkAuth,
10 | } from "../controllers/auth.controller.js";
11 | import { verifyToken } from "../middleware/verifyToken.js";
12 |
13 | const router = express.Router();
14 |
15 | router.get("/check-auth", verifyToken, checkAuth);
16 |
17 | router.post("/signup", signup);
18 | router.post("/login", login);
19 | router.post("/logout", logout);
20 |
21 | router.post("/verify-email", verifyEmail);
22 | router.post("/forgot-password", forgotPassword);
23 |
24 | router.post("/reset-password/:token", resetPassword);
25 |
26 | export default router;
27 |
--------------------------------------------------------------------------------
/backend/utils/generateTokenAndSetCookie.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 |
3 | export const generateTokenAndSetCookie = (res, userId) => {
4 | const token = jwt.sign({ userId }, process.env.JWT_SECRET, {
5 | expiresIn: "7d",
6 | });
7 |
8 | res.cookie("token", token, {
9 | httpOnly: true,
10 | secure: process.env.NODE_ENV === "production",
11 | sameSite: "strict",
12 | maxAge: 7 * 24 * 60 * 60 * 1000,
13 | });
14 |
15 | return token;
16 | };
17 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | "eslint:recommended",
6 | "plugin:react/recommended",
7 | "plugin:react/jsx-runtime",
8 | "plugin:react-hooks/recommended",
9 | ],
10 | ignorePatterns: ["dist", ".eslintrc.cjs"],
11 | parserOptions: { ecmaVersion: "latest", sourceType: "module" },
12 | settings: { react: { version: "18.2" } },
13 | plugins: ["react-refresh"],
14 | rules: {
15 | "react/prop-types": "off",
16 | "react/no-unescaped-entities": "off",
17 | "react/jsx-no-target-blank": "off",
18 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "auth-tutorial": "file:..",
14 | "axios": "^1.7.3",
15 | "framer-motion": "^11.3.21",
16 | "lucide-react": "^0.424.0",
17 | "react": "^18.3.1",
18 | "react-dom": "^18.3.1",
19 | "react-hot-toast": "^2.4.1",
20 | "react-router-dom": "^6.26.0",
21 | "zustand": "^4.5.4"
22 | },
23 | "devDependencies": {
24 | "@types/react": "^18.3.3",
25 | "@types/react-dom": "^18.3.0",
26 | "@vitejs/plugin-react": "^4.3.1",
27 | "autoprefixer": "^10.4.20",
28 | "eslint": "^8.57.0",
29 | "eslint-plugin-react": "^7.34.3",
30 | "eslint-plugin-react-hooks": "^4.6.2",
31 | "eslint-plugin-react-refresh": "^0.4.7",
32 | "postcss": "^8.4.40",
33 | "tailwindcss": "^3.4.7",
34 | "vite": "^5.3.4"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/public/screenshot-for-readme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/mern-advanced-auth/fe0f65940913f8a0a067124369b290750c296208/frontend/public/screenshot-for-readme.png
--------------------------------------------------------------------------------
/frontend/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Navigate, Route, Routes } from "react-router-dom";
2 | import FloatingShape from "./components/FloatingShape";
3 |
4 | import SignUpPage from "./pages/SignUpPage";
5 | import LoginPage from "./pages/LoginPage";
6 | import EmailVerificationPage from "./pages/EmailVerificationPage";
7 | import DashboardPage from "./pages/DashboardPage";
8 | import ForgotPasswordPage from "./pages/ForgotPasswordPage";
9 | import ResetPasswordPage from "./pages/ResetPasswordPage";
10 |
11 | import LoadingSpinner from "./components/LoadingSpinner";
12 |
13 | import { Toaster } from "react-hot-toast";
14 | import { useAuthStore } from "./store/authStore";
15 | import { useEffect } from "react";
16 |
17 | // protect routes that require authentication
18 | const ProtectedRoute = ({ children }) => {
19 | const { isAuthenticated, user } = useAuthStore();
20 |
21 | if (!isAuthenticated) {
22 | return ;
23 | }
24 |
25 | if (!user.isVerified) {
26 | return ;
27 | }
28 |
29 | return children;
30 | };
31 |
32 | // redirect authenticated users to the home page
33 | const RedirectAuthenticatedUser = ({ children }) => {
34 | const { isAuthenticated, user } = useAuthStore();
35 |
36 | if (isAuthenticated && user.isVerified) {
37 | return ;
38 | }
39 |
40 | return children;
41 | };
42 |
43 | function App() {
44 | const { isCheckingAuth, checkAuth } = useAuthStore();
45 |
46 | useEffect(() => {
47 | checkAuth();
48 | }, [checkAuth]);
49 |
50 | if (isCheckingAuth) return ;
51 |
52 | return (
53 |
57 |
58 |
59 |
60 |
61 |
62 |
66 |
67 |
68 | }
69 | />
70 |
74 |
75 |
76 | }
77 | />
78 |
82 |
83 |
84 | }
85 | />
86 | } />
87 |
91 |
92 |
93 | }
94 | />
95 |
96 |
100 |
101 |
102 | }
103 | />
104 | {/* catch all routes */}
105 | } />
106 |
107 |
108 |
109 | );
110 | }
111 |
112 | export default App;
113 |
--------------------------------------------------------------------------------
/frontend/src/components/FloatingShape.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 |
3 | const FloatingShape = ({ color, size, top, left, delay }) => {
4 | return (
5 |
21 | );
22 | };
23 | export default FloatingShape;
24 |
--------------------------------------------------------------------------------
/frontend/src/components/Input.jsx:
--------------------------------------------------------------------------------
1 | const Input = ({ icon: Icon, ...props }) => {
2 | return (
3 |
12 | );
13 | };
14 | export default Input;
15 |
--------------------------------------------------------------------------------
/frontend/src/components/LoadingSpinner.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 |
3 | const LoadingSpinner = () => {
4 | return (
5 |
6 | {/* Simple Loading Spinner */}
7 |
12 |
13 | );
14 | };
15 |
16 | export default LoadingSpinner;
17 |
--------------------------------------------------------------------------------
/frontend/src/components/PasswordStrengthMeter.jsx:
--------------------------------------------------------------------------------
1 | import { Check, X } from "lucide-react";
2 |
3 | const PasswordCriteria = ({ password }) => {
4 | const criteria = [
5 | { label: "At least 6 characters", met: password.length >= 6 },
6 | { label: "Contains uppercase letter", met: /[A-Z]/.test(password) },
7 | { label: "Contains lowercase letter", met: /[a-z]/.test(password) },
8 | { label: "Contains a number", met: /\d/.test(password) },
9 | { label: "Contains special character", met: /[^A-Za-z0-9]/.test(password) },
10 | ];
11 |
12 | return (
13 |
14 | {criteria.map((item) => (
15 |
16 | {item.met ? (
17 |
18 | ) : (
19 |
20 | )}
21 | {item.label}
22 |
23 | ))}
24 |
25 | );
26 | };
27 |
28 | const PasswordStrengthMeter = ({ password }) => {
29 | const getStrength = (pass) => {
30 | let strength = 0;
31 | if (pass.length >= 6) strength++;
32 | if (pass.match(/[a-z]/) && pass.match(/[A-Z]/)) strength++;
33 | if (pass.match(/\d/)) strength++;
34 | if (pass.match(/[^a-zA-Z\d]/)) strength++;
35 | return strength;
36 | };
37 | const strength = getStrength(password);
38 |
39 | const getColor = (strength) => {
40 | if (strength === 0) return "bg-red-500";
41 | if (strength === 1) return "bg-red-400";
42 | if (strength === 2) return "bg-yellow-500";
43 | if (strength === 3) return "bg-yellow-400";
44 | return "bg-green-500";
45 | };
46 |
47 | const getStrengthText = (strength) => {
48 | if (strength === 0) return "Very Weak";
49 | if (strength === 1) return "Weak";
50 | if (strength === 2) return "Fair";
51 | if (strength === 3) return "Good";
52 | return "Strong";
53 | };
54 |
55 | return (
56 |
57 |
58 | Password strength
59 | {getStrengthText(strength)}
60 |
61 |
62 |
63 | {[...Array(4)].map((_, index) => (
64 |
70 | ))}
71 |
72 |
73 |
74 | );
75 | };
76 | export default PasswordStrengthMeter;
77 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/frontend/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import "./index.css";
5 | import { BrowserRouter } from "react-router-dom";
6 |
7 | ReactDOM.createRoot(document.getElementById("root")).render(
8 |
9 |
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/frontend/src/pages/DashboardPage.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { useAuthStore } from "../store/authStore";
3 | import { formatDate } from "../utils/date";
4 |
5 | const DashboardPage = () => {
6 | const { user, logout } = useAuthStore();
7 |
8 | const handleLogout = () => {
9 | logout();
10 | };
11 | return (
12 |
19 |
20 | Dashboard
21 |
22 |
23 |
24 |
30 | Profile Information
31 | Name: {user.name}
32 | Email: {user.email}
33 |
34 |
40 | Account Activity
41 |
42 | Joined:
43 | {new Date(user.createdAt).toLocaleDateString("en-US", {
44 | year: "numeric",
45 | month: "long",
46 | day: "numeric",
47 | })}
48 |
49 |
50 | Last Login:
51 |
52 | {formatDate(user.lastLogin)}
53 |
54 |
55 |
56 |
57 |
63 |
71 | Logout
72 |
73 |
74 |
75 | );
76 | };
77 | export default DashboardPage;
78 |
--------------------------------------------------------------------------------
/frontend/src/pages/EmailVerificationPage.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import { motion } from "framer-motion";
4 | import { useAuthStore } from "../store/authStore";
5 | import toast from "react-hot-toast";
6 |
7 | const EmailVerificationPage = () => {
8 | const [code, setCode] = useState(["", "", "", "", "", ""]);
9 | const inputRefs = useRef([]);
10 | const navigate = useNavigate();
11 |
12 | const { error, isLoading, verifyEmail } = useAuthStore();
13 |
14 | const handleChange = (index, value) => {
15 | const newCode = [...code];
16 |
17 | // Handle pasted content
18 | if (value.length > 1) {
19 | const pastedCode = value.slice(0, 6).split("");
20 | for (let i = 0; i < 6; i++) {
21 | newCode[i] = pastedCode[i] || "";
22 | }
23 | setCode(newCode);
24 |
25 | // Focus on the last non-empty input or the first empty one
26 | const lastFilledIndex = newCode.findLastIndex((digit) => digit !== "");
27 | const focusIndex = lastFilledIndex < 5 ? lastFilledIndex + 1 : 5;
28 | inputRefs.current[focusIndex].focus();
29 | } else {
30 | newCode[index] = value;
31 | setCode(newCode);
32 |
33 | // Move focus to the next input field if value is entered
34 | if (value && index < 5) {
35 | inputRefs.current[index + 1].focus();
36 | }
37 | }
38 | };
39 |
40 | const handleKeyDown = (index, e) => {
41 | if (e.key === "Backspace" && !code[index] && index > 0) {
42 | inputRefs.current[index - 1].focus();
43 | }
44 | };
45 |
46 | const handleSubmit = async (e) => {
47 | e.preventDefault();
48 | const verificationCode = code.join("");
49 | try {
50 | await verifyEmail(verificationCode);
51 | navigate("/");
52 | toast.success("Email verified successfully");
53 | } catch (error) {
54 | console.log(error);
55 | }
56 | };
57 |
58 | // Auto submit when all fields are filled
59 | useEffect(() => {
60 | if (code.every((digit) => digit !== "")) {
61 | handleSubmit(new Event("submit"));
62 | }
63 | }, [code]);
64 |
65 | return (
66 |
67 |
73 |
74 | Verify Your Email
75 |
76 | Enter the 6-digit code sent to your email address.
77 |
78 |
104 |
105 |
106 | );
107 | };
108 | export default EmailVerificationPage;
109 |
--------------------------------------------------------------------------------
/frontend/src/pages/ForgotPasswordPage.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { useState } from "react";
3 | import { useAuthStore } from "../store/authStore";
4 | import Input from "../components/Input";
5 | import { ArrowLeft, Loader, Mail } from "lucide-react";
6 | import { Link } from "react-router-dom";
7 |
8 | const ForgotPasswordPage = () => {
9 | const [email, setEmail] = useState("");
10 | const [isSubmitted, setIsSubmitted] = useState(false);
11 |
12 | const { isLoading, forgotPassword } = useAuthStore();
13 |
14 | const handleSubmit = async (e) => {
15 | e.preventDefault();
16 | await forgotPassword(email);
17 | setIsSubmitted(true);
18 | };
19 |
20 | return (
21 |
27 |
28 |
29 | Forgot Password
30 |
31 |
32 | {!isSubmitted ? (
33 |
54 | ) : (
55 |
56 |
62 |
63 |
64 |
65 | If an account exists for {email}, you will receive a password reset link shortly.
66 |
67 |
68 | )}
69 |
70 |
71 |
72 |
73 |
Back to Login
74 |
75 |
76 |
77 | );
78 | };
79 | export default ForgotPasswordPage;
80 |
--------------------------------------------------------------------------------
/frontend/src/pages/LoginPage.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { motion } from "framer-motion";
3 | import { Mail, Lock, Loader } from "lucide-react";
4 | import { Link } from "react-router-dom";
5 | import Input from "../components/Input";
6 | import { useAuthStore } from "../store/authStore";
7 |
8 | const LoginPage = () => {
9 | const [email, setEmail] = useState("");
10 | const [password, setPassword] = useState("");
11 |
12 | const { login, isLoading, error } = useAuthStore();
13 |
14 | const handleLogin = async (e) => {
15 | e.preventDefault();
16 | await login(email, password);
17 | };
18 |
19 | return (
20 |
26 |
27 |
28 | Welcome Back
29 |
30 |
31 |
65 |
66 |
67 |
68 | Don't have an account?{" "}
69 |
70 | Sign up
71 |
72 |
73 |
74 |
75 | );
76 | };
77 | export default LoginPage;
78 |
--------------------------------------------------------------------------------
/frontend/src/pages/ResetPasswordPage.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { motion } from "framer-motion";
3 | import { useAuthStore } from "../store/authStore";
4 | import { useNavigate, useParams } from "react-router-dom";
5 | import Input from "../components/Input";
6 | import { Lock } from "lucide-react";
7 | import toast from "react-hot-toast";
8 |
9 | const ResetPasswordPage = () => {
10 | const [password, setPassword] = useState("");
11 | const [confirmPassword, setConfirmPassword] = useState("");
12 | const { resetPassword, error, isLoading, message } = useAuthStore();
13 |
14 | const { token } = useParams();
15 | const navigate = useNavigate();
16 |
17 | const handleSubmit = async (e) => {
18 | e.preventDefault();
19 |
20 | if (password !== confirmPassword) {
21 | alert("Passwords do not match");
22 | return;
23 | }
24 | try {
25 | await resetPassword(token, password);
26 |
27 | toast.success("Password reset successfully, redirecting to login page...");
28 | setTimeout(() => {
29 | navigate("/login");
30 | }, 2000);
31 | } catch (error) {
32 | console.error(error);
33 | toast.error(error.message || "Error resetting password");
34 | }
35 | };
36 |
37 | return (
38 |
44 |
45 |
46 | Reset Password
47 |
48 | {error &&
{error}
}
49 | {message &&
{message}
}
50 |
51 |
80 |
81 |
82 | );
83 | };
84 | export default ResetPasswordPage;
85 |
--------------------------------------------------------------------------------
/frontend/src/pages/SignUpPage.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import Input from "../components/Input";
3 | import { Loader, Lock, Mail, User } from "lucide-react";
4 | import { useState } from "react";
5 | import { Link, useNavigate } from "react-router-dom";
6 | import PasswordStrengthMeter from "../components/PasswordStrengthMeter";
7 | import { useAuthStore } from "../store/authStore";
8 |
9 | const SignUpPage = () => {
10 | const [name, setName] = useState("");
11 | const [email, setEmail] = useState("");
12 | const [password, setPassword] = useState("");
13 | const navigate = useNavigate();
14 |
15 | const { signup, error, isLoading } = useAuthStore();
16 |
17 | const handleSignUp = async (e) => {
18 | e.preventDefault();
19 |
20 | try {
21 | await signup(email, password, name);
22 | navigate("/verify-email");
23 | } catch (error) {
24 | console.log(error);
25 | }
26 | };
27 | return (
28 |
35 |
36 |
37 | Create Account
38 |
39 |
40 |
78 |
79 |
80 |
81 | Already have an account?{" "}
82 |
83 | Login
84 |
85 |
86 |
87 |
88 | );
89 | };
90 | export default SignUpPage;
91 |
--------------------------------------------------------------------------------
/frontend/src/store/authStore.js:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 | import axios from "axios";
3 |
4 | const API_URL = import.meta.env.MODE === "development" ? "http://localhost:5000/api/auth" : "/api/auth";
5 |
6 | axios.defaults.withCredentials = true;
7 |
8 | export const useAuthStore = create((set) => ({
9 | user: null,
10 | isAuthenticated: false,
11 | error: null,
12 | isLoading: false,
13 | isCheckingAuth: true,
14 | message: null,
15 |
16 | signup: async (email, password, name) => {
17 | set({ isLoading: true, error: null });
18 | try {
19 | const response = await axios.post(`${API_URL}/signup`, { email, password, name });
20 | set({ user: response.data.user, isAuthenticated: true, isLoading: false });
21 | } catch (error) {
22 | set({ error: error.response.data.message || "Error signing up", isLoading: false });
23 | throw error;
24 | }
25 | },
26 | login: async (email, password) => {
27 | set({ isLoading: true, error: null });
28 | try {
29 | const response = await axios.post(`${API_URL}/login`, { email, password });
30 | set({
31 | isAuthenticated: true,
32 | user: response.data.user,
33 | error: null,
34 | isLoading: false,
35 | });
36 | } catch (error) {
37 | set({ error: error.response?.data?.message || "Error logging in", isLoading: false });
38 | throw error;
39 | }
40 | },
41 |
42 | logout: async () => {
43 | set({ isLoading: true, error: null });
44 | try {
45 | await axios.post(`${API_URL}/logout`);
46 | set({ user: null, isAuthenticated: false, error: null, isLoading: false });
47 | } catch (error) {
48 | set({ error: "Error logging out", isLoading: false });
49 | throw error;
50 | }
51 | },
52 | verifyEmail: async (code) => {
53 | set({ isLoading: true, error: null });
54 | try {
55 | const response = await axios.post(`${API_URL}/verify-email`, { code });
56 | set({ user: response.data.user, isAuthenticated: true, isLoading: false });
57 | return response.data;
58 | } catch (error) {
59 | set({ error: error.response.data.message || "Error verifying email", isLoading: false });
60 | throw error;
61 | }
62 | },
63 | checkAuth: async () => {
64 | set({ isCheckingAuth: true, error: null });
65 | try {
66 | const response = await axios.get(`${API_URL}/check-auth`);
67 | set({ user: response.data.user, isAuthenticated: true, isCheckingAuth: false });
68 | } catch (error) {
69 | set({ error: null, isCheckingAuth: false, isAuthenticated: false });
70 | }
71 | },
72 | forgotPassword: async (email) => {
73 | set({ isLoading: true, error: null });
74 | try {
75 | const response = await axios.post(`${API_URL}/forgot-password`, { email });
76 | set({ message: response.data.message, isLoading: false });
77 | } catch (error) {
78 | set({
79 | isLoading: false,
80 | error: error.response.data.message || "Error sending reset password email",
81 | });
82 | throw error;
83 | }
84 | },
85 | resetPassword: async (token, password) => {
86 | set({ isLoading: true, error: null });
87 | try {
88 | const response = await axios.post(`${API_URL}/reset-password/${token}`, { password });
89 | set({ message: response.data.message, isLoading: false });
90 | } catch (error) {
91 | set({
92 | isLoading: false,
93 | error: error.response.data.message || "Error resetting password",
94 | });
95 | throw error;
96 | }
97 | },
98 | }));
99 |
--------------------------------------------------------------------------------
/frontend/src/utils/date.js:
--------------------------------------------------------------------------------
1 | export const formatDate = (dateString) => {
2 | const date = new Date(dateString);
3 | if (isNaN(date.getTime())) {
4 | return "Invalid Date";
5 | }
6 |
7 | return date.toLocaleString("en-US", {
8 | year: "numeric",
9 | month: "short",
10 | day: "numeric",
11 | hour: "2-digit",
12 | minute: "2-digit",
13 | hour12: true,
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------
/frontend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auth-tutorial",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "auth-tutorial",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "bcryptjs": "^2.4.3",
13 | "cookie-parser": "^1.4.6",
14 | "cors": "^2.8.5",
15 | "crypto": "^1.0.1",
16 | "dotenv": "^16.4.5",
17 | "express": "^4.19.2",
18 | "jsonwebtoken": "^9.0.2",
19 | "mailtrap": "^3.4.0",
20 | "mongoose": "^8.5.2"
21 | },
22 | "devDependencies": {
23 | "nodemon": "^3.1.4"
24 | }
25 | },
26 | "node_modules/@mongodb-js/saslprep": {
27 | "version": "1.1.8",
28 | "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz",
29 | "integrity": "sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==",
30 | "dependencies": {
31 | "sparse-bitfield": "^3.0.3"
32 | }
33 | },
34 | "node_modules/@types/webidl-conversions": {
35 | "version": "7.0.3",
36 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
37 | "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
38 | },
39 | "node_modules/@types/whatwg-url": {
40 | "version": "11.0.5",
41 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
42 | "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
43 | "dependencies": {
44 | "@types/webidl-conversions": "*"
45 | }
46 | },
47 | "node_modules/accepts": {
48 | "version": "1.3.8",
49 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
50 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
51 | "dependencies": {
52 | "mime-types": "~2.1.34",
53 | "negotiator": "0.6.3"
54 | },
55 | "engines": {
56 | "node": ">= 0.6"
57 | }
58 | },
59 | "node_modules/anymatch": {
60 | "version": "3.1.3",
61 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
62 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
63 | "dev": true,
64 | "dependencies": {
65 | "normalize-path": "^3.0.0",
66 | "picomatch": "^2.0.4"
67 | },
68 | "engines": {
69 | "node": ">= 8"
70 | }
71 | },
72 | "node_modules/array-flatten": {
73 | "version": "1.1.1",
74 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
75 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
76 | },
77 | "node_modules/asynckit": {
78 | "version": "0.4.0",
79 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
80 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
81 | },
82 | "node_modules/axios": {
83 | "version": "1.7.3",
84 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz",
85 | "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==",
86 | "dependencies": {
87 | "follow-redirects": "^1.15.6",
88 | "form-data": "^4.0.0",
89 | "proxy-from-env": "^1.1.0"
90 | }
91 | },
92 | "node_modules/balanced-match": {
93 | "version": "1.0.2",
94 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
95 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
96 | "dev": true
97 | },
98 | "node_modules/bcryptjs": {
99 | "version": "2.4.3",
100 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
101 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
102 | },
103 | "node_modules/binary-extensions": {
104 | "version": "2.3.0",
105 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
106 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
107 | "dev": true,
108 | "engines": {
109 | "node": ">=8"
110 | },
111 | "funding": {
112 | "url": "https://github.com/sponsors/sindresorhus"
113 | }
114 | },
115 | "node_modules/body-parser": {
116 | "version": "1.20.2",
117 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
118 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
119 | "dependencies": {
120 | "bytes": "3.1.2",
121 | "content-type": "~1.0.5",
122 | "debug": "2.6.9",
123 | "depd": "2.0.0",
124 | "destroy": "1.2.0",
125 | "http-errors": "2.0.0",
126 | "iconv-lite": "0.4.24",
127 | "on-finished": "2.4.1",
128 | "qs": "6.11.0",
129 | "raw-body": "2.5.2",
130 | "type-is": "~1.6.18",
131 | "unpipe": "1.0.0"
132 | },
133 | "engines": {
134 | "node": ">= 0.8",
135 | "npm": "1.2.8000 || >= 1.4.16"
136 | }
137 | },
138 | "node_modules/brace-expansion": {
139 | "version": "1.1.11",
140 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
141 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
142 | "dev": true,
143 | "dependencies": {
144 | "balanced-match": "^1.0.0",
145 | "concat-map": "0.0.1"
146 | }
147 | },
148 | "node_modules/braces": {
149 | "version": "3.0.3",
150 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
151 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
152 | "dev": true,
153 | "dependencies": {
154 | "fill-range": "^7.1.1"
155 | },
156 | "engines": {
157 | "node": ">=8"
158 | }
159 | },
160 | "node_modules/bson": {
161 | "version": "6.8.0",
162 | "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz",
163 | "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==",
164 | "engines": {
165 | "node": ">=16.20.1"
166 | }
167 | },
168 | "node_modules/buffer-equal-constant-time": {
169 | "version": "1.0.1",
170 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
171 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
172 | },
173 | "node_modules/bytes": {
174 | "version": "3.1.2",
175 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
176 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
177 | "engines": {
178 | "node": ">= 0.8"
179 | }
180 | },
181 | "node_modules/call-bind": {
182 | "version": "1.0.7",
183 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
184 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
185 | "dependencies": {
186 | "es-define-property": "^1.0.0",
187 | "es-errors": "^1.3.0",
188 | "function-bind": "^1.1.2",
189 | "get-intrinsic": "^1.2.4",
190 | "set-function-length": "^1.2.1"
191 | },
192 | "engines": {
193 | "node": ">= 0.4"
194 | },
195 | "funding": {
196 | "url": "https://github.com/sponsors/ljharb"
197 | }
198 | },
199 | "node_modules/chokidar": {
200 | "version": "3.6.0",
201 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
202 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
203 | "dev": true,
204 | "dependencies": {
205 | "anymatch": "~3.1.2",
206 | "braces": "~3.0.2",
207 | "glob-parent": "~5.1.2",
208 | "is-binary-path": "~2.1.0",
209 | "is-glob": "~4.0.1",
210 | "normalize-path": "~3.0.0",
211 | "readdirp": "~3.6.0"
212 | },
213 | "engines": {
214 | "node": ">= 8.10.0"
215 | },
216 | "funding": {
217 | "url": "https://paulmillr.com/funding/"
218 | },
219 | "optionalDependencies": {
220 | "fsevents": "~2.3.2"
221 | }
222 | },
223 | "node_modules/combined-stream": {
224 | "version": "1.0.8",
225 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
226 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
227 | "dependencies": {
228 | "delayed-stream": "~1.0.0"
229 | },
230 | "engines": {
231 | "node": ">= 0.8"
232 | }
233 | },
234 | "node_modules/concat-map": {
235 | "version": "0.0.1",
236 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
237 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
238 | "dev": true
239 | },
240 | "node_modules/content-disposition": {
241 | "version": "0.5.4",
242 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
243 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
244 | "dependencies": {
245 | "safe-buffer": "5.2.1"
246 | },
247 | "engines": {
248 | "node": ">= 0.6"
249 | }
250 | },
251 | "node_modules/content-type": {
252 | "version": "1.0.5",
253 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
254 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
255 | "engines": {
256 | "node": ">= 0.6"
257 | }
258 | },
259 | "node_modules/cookie": {
260 | "version": "0.4.1",
261 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
262 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
263 | "engines": {
264 | "node": ">= 0.6"
265 | }
266 | },
267 | "node_modules/cookie-parser": {
268 | "version": "1.4.6",
269 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
270 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
271 | "dependencies": {
272 | "cookie": "0.4.1",
273 | "cookie-signature": "1.0.6"
274 | },
275 | "engines": {
276 | "node": ">= 0.8.0"
277 | }
278 | },
279 | "node_modules/cookie-signature": {
280 | "version": "1.0.6",
281 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
282 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
283 | },
284 | "node_modules/cors": {
285 | "version": "2.8.5",
286 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
287 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
288 | "dependencies": {
289 | "object-assign": "^4",
290 | "vary": "^1"
291 | },
292 | "engines": {
293 | "node": ">= 0.10"
294 | }
295 | },
296 | "node_modules/crypto": {
297 | "version": "1.0.1",
298 | "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
299 | "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
300 | "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
301 | },
302 | "node_modules/debug": {
303 | "version": "2.6.9",
304 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
305 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
306 | "dependencies": {
307 | "ms": "2.0.0"
308 | }
309 | },
310 | "node_modules/define-data-property": {
311 | "version": "1.1.4",
312 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
313 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
314 | "dependencies": {
315 | "es-define-property": "^1.0.0",
316 | "es-errors": "^1.3.0",
317 | "gopd": "^1.0.1"
318 | },
319 | "engines": {
320 | "node": ">= 0.4"
321 | },
322 | "funding": {
323 | "url": "https://github.com/sponsors/ljharb"
324 | }
325 | },
326 | "node_modules/delayed-stream": {
327 | "version": "1.0.0",
328 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
329 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
330 | "engines": {
331 | "node": ">=0.4.0"
332 | }
333 | },
334 | "node_modules/depd": {
335 | "version": "2.0.0",
336 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
337 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
338 | "engines": {
339 | "node": ">= 0.8"
340 | }
341 | },
342 | "node_modules/destroy": {
343 | "version": "1.2.0",
344 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
345 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
346 | "engines": {
347 | "node": ">= 0.8",
348 | "npm": "1.2.8000 || >= 1.4.16"
349 | }
350 | },
351 | "node_modules/dotenv": {
352 | "version": "16.4.5",
353 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
354 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
355 | "engines": {
356 | "node": ">=12"
357 | },
358 | "funding": {
359 | "url": "https://dotenvx.com"
360 | }
361 | },
362 | "node_modules/ecdsa-sig-formatter": {
363 | "version": "1.0.11",
364 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
365 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
366 | "dependencies": {
367 | "safe-buffer": "^5.0.1"
368 | }
369 | },
370 | "node_modules/ee-first": {
371 | "version": "1.1.1",
372 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
373 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
374 | },
375 | "node_modules/encodeurl": {
376 | "version": "1.0.2",
377 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
378 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
379 | "engines": {
380 | "node": ">= 0.8"
381 | }
382 | },
383 | "node_modules/es-define-property": {
384 | "version": "1.0.0",
385 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
386 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
387 | "dependencies": {
388 | "get-intrinsic": "^1.2.4"
389 | },
390 | "engines": {
391 | "node": ">= 0.4"
392 | }
393 | },
394 | "node_modules/es-errors": {
395 | "version": "1.3.0",
396 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
397 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
398 | "engines": {
399 | "node": ">= 0.4"
400 | }
401 | },
402 | "node_modules/escape-html": {
403 | "version": "1.0.3",
404 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
405 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
406 | },
407 | "node_modules/etag": {
408 | "version": "1.8.1",
409 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
410 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
411 | "engines": {
412 | "node": ">= 0.6"
413 | }
414 | },
415 | "node_modules/express": {
416 | "version": "4.19.2",
417 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
418 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
419 | "dependencies": {
420 | "accepts": "~1.3.8",
421 | "array-flatten": "1.1.1",
422 | "body-parser": "1.20.2",
423 | "content-disposition": "0.5.4",
424 | "content-type": "~1.0.4",
425 | "cookie": "0.6.0",
426 | "cookie-signature": "1.0.6",
427 | "debug": "2.6.9",
428 | "depd": "2.0.0",
429 | "encodeurl": "~1.0.2",
430 | "escape-html": "~1.0.3",
431 | "etag": "~1.8.1",
432 | "finalhandler": "1.2.0",
433 | "fresh": "0.5.2",
434 | "http-errors": "2.0.0",
435 | "merge-descriptors": "1.0.1",
436 | "methods": "~1.1.2",
437 | "on-finished": "2.4.1",
438 | "parseurl": "~1.3.3",
439 | "path-to-regexp": "0.1.7",
440 | "proxy-addr": "~2.0.7",
441 | "qs": "6.11.0",
442 | "range-parser": "~1.2.1",
443 | "safe-buffer": "5.2.1",
444 | "send": "0.18.0",
445 | "serve-static": "1.15.0",
446 | "setprototypeof": "1.2.0",
447 | "statuses": "2.0.1",
448 | "type-is": "~1.6.18",
449 | "utils-merge": "1.0.1",
450 | "vary": "~1.1.2"
451 | },
452 | "engines": {
453 | "node": ">= 0.10.0"
454 | }
455 | },
456 | "node_modules/express/node_modules/cookie": {
457 | "version": "0.6.0",
458 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
459 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
460 | "engines": {
461 | "node": ">= 0.6"
462 | }
463 | },
464 | "node_modules/fill-range": {
465 | "version": "7.1.1",
466 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
467 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
468 | "dev": true,
469 | "dependencies": {
470 | "to-regex-range": "^5.0.1"
471 | },
472 | "engines": {
473 | "node": ">=8"
474 | }
475 | },
476 | "node_modules/finalhandler": {
477 | "version": "1.2.0",
478 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
479 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
480 | "dependencies": {
481 | "debug": "2.6.9",
482 | "encodeurl": "~1.0.2",
483 | "escape-html": "~1.0.3",
484 | "on-finished": "2.4.1",
485 | "parseurl": "~1.3.3",
486 | "statuses": "2.0.1",
487 | "unpipe": "~1.0.0"
488 | },
489 | "engines": {
490 | "node": ">= 0.8"
491 | }
492 | },
493 | "node_modules/follow-redirects": {
494 | "version": "1.15.6",
495 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
496 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
497 | "funding": [
498 | {
499 | "type": "individual",
500 | "url": "https://github.com/sponsors/RubenVerborgh"
501 | }
502 | ],
503 | "engines": {
504 | "node": ">=4.0"
505 | },
506 | "peerDependenciesMeta": {
507 | "debug": {
508 | "optional": true
509 | }
510 | }
511 | },
512 | "node_modules/form-data": {
513 | "version": "4.0.0",
514 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
515 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
516 | "dependencies": {
517 | "asynckit": "^0.4.0",
518 | "combined-stream": "^1.0.8",
519 | "mime-types": "^2.1.12"
520 | },
521 | "engines": {
522 | "node": ">= 6"
523 | }
524 | },
525 | "node_modules/forwarded": {
526 | "version": "0.2.0",
527 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
528 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
529 | "engines": {
530 | "node": ">= 0.6"
531 | }
532 | },
533 | "node_modules/fresh": {
534 | "version": "0.5.2",
535 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
536 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
537 | "engines": {
538 | "node": ">= 0.6"
539 | }
540 | },
541 | "node_modules/fsevents": {
542 | "version": "2.3.3",
543 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
544 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
545 | "dev": true,
546 | "hasInstallScript": true,
547 | "optional": true,
548 | "os": [
549 | "darwin"
550 | ],
551 | "engines": {
552 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
553 | }
554 | },
555 | "node_modules/function-bind": {
556 | "version": "1.1.2",
557 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
558 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
559 | "funding": {
560 | "url": "https://github.com/sponsors/ljharb"
561 | }
562 | },
563 | "node_modules/get-intrinsic": {
564 | "version": "1.2.4",
565 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
566 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
567 | "dependencies": {
568 | "es-errors": "^1.3.0",
569 | "function-bind": "^1.1.2",
570 | "has-proto": "^1.0.1",
571 | "has-symbols": "^1.0.3",
572 | "hasown": "^2.0.0"
573 | },
574 | "engines": {
575 | "node": ">= 0.4"
576 | },
577 | "funding": {
578 | "url": "https://github.com/sponsors/ljharb"
579 | }
580 | },
581 | "node_modules/glob-parent": {
582 | "version": "5.1.2",
583 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
584 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
585 | "dev": true,
586 | "dependencies": {
587 | "is-glob": "^4.0.1"
588 | },
589 | "engines": {
590 | "node": ">= 6"
591 | }
592 | },
593 | "node_modules/gopd": {
594 | "version": "1.0.1",
595 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
596 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
597 | "dependencies": {
598 | "get-intrinsic": "^1.1.3"
599 | },
600 | "funding": {
601 | "url": "https://github.com/sponsors/ljharb"
602 | }
603 | },
604 | "node_modules/has-flag": {
605 | "version": "3.0.0",
606 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
607 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
608 | "dev": true,
609 | "engines": {
610 | "node": ">=4"
611 | }
612 | },
613 | "node_modules/has-property-descriptors": {
614 | "version": "1.0.2",
615 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
616 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
617 | "dependencies": {
618 | "es-define-property": "^1.0.0"
619 | },
620 | "funding": {
621 | "url": "https://github.com/sponsors/ljharb"
622 | }
623 | },
624 | "node_modules/has-proto": {
625 | "version": "1.0.3",
626 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
627 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
628 | "engines": {
629 | "node": ">= 0.4"
630 | },
631 | "funding": {
632 | "url": "https://github.com/sponsors/ljharb"
633 | }
634 | },
635 | "node_modules/has-symbols": {
636 | "version": "1.0.3",
637 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
638 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
639 | "engines": {
640 | "node": ">= 0.4"
641 | },
642 | "funding": {
643 | "url": "https://github.com/sponsors/ljharb"
644 | }
645 | },
646 | "node_modules/hasown": {
647 | "version": "2.0.2",
648 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
649 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
650 | "dependencies": {
651 | "function-bind": "^1.1.2"
652 | },
653 | "engines": {
654 | "node": ">= 0.4"
655 | }
656 | },
657 | "node_modules/http-errors": {
658 | "version": "2.0.0",
659 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
660 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
661 | "dependencies": {
662 | "depd": "2.0.0",
663 | "inherits": "2.0.4",
664 | "setprototypeof": "1.2.0",
665 | "statuses": "2.0.1",
666 | "toidentifier": "1.0.1"
667 | },
668 | "engines": {
669 | "node": ">= 0.8"
670 | }
671 | },
672 | "node_modules/iconv-lite": {
673 | "version": "0.4.24",
674 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
675 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
676 | "dependencies": {
677 | "safer-buffer": ">= 2.1.2 < 3"
678 | },
679 | "engines": {
680 | "node": ">=0.10.0"
681 | }
682 | },
683 | "node_modules/ignore-by-default": {
684 | "version": "1.0.1",
685 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
686 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
687 | "dev": true
688 | },
689 | "node_modules/inherits": {
690 | "version": "2.0.4",
691 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
692 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
693 | },
694 | "node_modules/ipaddr.js": {
695 | "version": "1.9.1",
696 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
697 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
698 | "engines": {
699 | "node": ">= 0.10"
700 | }
701 | },
702 | "node_modules/is-binary-path": {
703 | "version": "2.1.0",
704 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
705 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
706 | "dev": true,
707 | "dependencies": {
708 | "binary-extensions": "^2.0.0"
709 | },
710 | "engines": {
711 | "node": ">=8"
712 | }
713 | },
714 | "node_modules/is-extglob": {
715 | "version": "2.1.1",
716 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
717 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
718 | "dev": true,
719 | "engines": {
720 | "node": ">=0.10.0"
721 | }
722 | },
723 | "node_modules/is-glob": {
724 | "version": "4.0.3",
725 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
726 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
727 | "dev": true,
728 | "dependencies": {
729 | "is-extglob": "^2.1.1"
730 | },
731 | "engines": {
732 | "node": ">=0.10.0"
733 | }
734 | },
735 | "node_modules/is-number": {
736 | "version": "7.0.0",
737 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
738 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
739 | "dev": true,
740 | "engines": {
741 | "node": ">=0.12.0"
742 | }
743 | },
744 | "node_modules/jsonwebtoken": {
745 | "version": "9.0.2",
746 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
747 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
748 | "dependencies": {
749 | "jws": "^3.2.2",
750 | "lodash.includes": "^4.3.0",
751 | "lodash.isboolean": "^3.0.3",
752 | "lodash.isinteger": "^4.0.4",
753 | "lodash.isnumber": "^3.0.3",
754 | "lodash.isplainobject": "^4.0.6",
755 | "lodash.isstring": "^4.0.1",
756 | "lodash.once": "^4.0.0",
757 | "ms": "^2.1.1",
758 | "semver": "^7.5.4"
759 | },
760 | "engines": {
761 | "node": ">=12",
762 | "npm": ">=6"
763 | }
764 | },
765 | "node_modules/jsonwebtoken/node_modules/ms": {
766 | "version": "2.1.3",
767 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
768 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
769 | },
770 | "node_modules/jwa": {
771 | "version": "1.4.1",
772 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
773 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
774 | "dependencies": {
775 | "buffer-equal-constant-time": "1.0.1",
776 | "ecdsa-sig-formatter": "1.0.11",
777 | "safe-buffer": "^5.0.1"
778 | }
779 | },
780 | "node_modules/jws": {
781 | "version": "3.2.2",
782 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
783 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
784 | "dependencies": {
785 | "jwa": "^1.4.1",
786 | "safe-buffer": "^5.0.1"
787 | }
788 | },
789 | "node_modules/kareem": {
790 | "version": "2.6.3",
791 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
792 | "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
793 | "engines": {
794 | "node": ">=12.0.0"
795 | }
796 | },
797 | "node_modules/lodash.includes": {
798 | "version": "4.3.0",
799 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
800 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
801 | },
802 | "node_modules/lodash.isboolean": {
803 | "version": "3.0.3",
804 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
805 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
806 | },
807 | "node_modules/lodash.isinteger": {
808 | "version": "4.0.4",
809 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
810 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
811 | },
812 | "node_modules/lodash.isnumber": {
813 | "version": "3.0.3",
814 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
815 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
816 | },
817 | "node_modules/lodash.isplainobject": {
818 | "version": "4.0.6",
819 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
820 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
821 | },
822 | "node_modules/lodash.isstring": {
823 | "version": "4.0.1",
824 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
825 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
826 | },
827 | "node_modules/lodash.once": {
828 | "version": "4.1.1",
829 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
830 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
831 | },
832 | "node_modules/mailtrap": {
833 | "version": "3.4.0",
834 | "resolved": "https://registry.npmjs.org/mailtrap/-/mailtrap-3.4.0.tgz",
835 | "integrity": "sha512-gegg90/gMY8hvfxB+WMtE8RRZyhQr90jUw00QOLApIAomItumqFBCpZv5IfG51EUKThu9+p7X4QdNA4buryenw==",
836 | "dependencies": {
837 | "axios": ">=0.27"
838 | },
839 | "engines": {
840 | "node": ">=16.20.1",
841 | "yarn": ">=1.22.17"
842 | },
843 | "peerDependencies": {
844 | "@types/nodemailer": "^6.4.9",
845 | "nodemailer": "^6.9.4"
846 | },
847 | "peerDependenciesMeta": {
848 | "@types/nodemailer": {
849 | "optional": true
850 | },
851 | "nodemailer": {
852 | "optional": true
853 | }
854 | }
855 | },
856 | "node_modules/media-typer": {
857 | "version": "0.3.0",
858 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
859 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
860 | "engines": {
861 | "node": ">= 0.6"
862 | }
863 | },
864 | "node_modules/memory-pager": {
865 | "version": "1.5.0",
866 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
867 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
868 | },
869 | "node_modules/merge-descriptors": {
870 | "version": "1.0.1",
871 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
872 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
873 | },
874 | "node_modules/methods": {
875 | "version": "1.1.2",
876 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
877 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
878 | "engines": {
879 | "node": ">= 0.6"
880 | }
881 | },
882 | "node_modules/mime": {
883 | "version": "1.6.0",
884 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
885 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
886 | "bin": {
887 | "mime": "cli.js"
888 | },
889 | "engines": {
890 | "node": ">=4"
891 | }
892 | },
893 | "node_modules/mime-db": {
894 | "version": "1.52.0",
895 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
896 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
897 | "engines": {
898 | "node": ">= 0.6"
899 | }
900 | },
901 | "node_modules/mime-types": {
902 | "version": "2.1.35",
903 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
904 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
905 | "dependencies": {
906 | "mime-db": "1.52.0"
907 | },
908 | "engines": {
909 | "node": ">= 0.6"
910 | }
911 | },
912 | "node_modules/minimatch": {
913 | "version": "3.1.2",
914 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
915 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
916 | "dev": true,
917 | "dependencies": {
918 | "brace-expansion": "^1.1.7"
919 | },
920 | "engines": {
921 | "node": "*"
922 | }
923 | },
924 | "node_modules/mongodb": {
925 | "version": "6.7.0",
926 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.7.0.tgz",
927 | "integrity": "sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==",
928 | "dependencies": {
929 | "@mongodb-js/saslprep": "^1.1.5",
930 | "bson": "^6.7.0",
931 | "mongodb-connection-string-url": "^3.0.0"
932 | },
933 | "engines": {
934 | "node": ">=16.20.1"
935 | },
936 | "peerDependencies": {
937 | "@aws-sdk/credential-providers": "^3.188.0",
938 | "@mongodb-js/zstd": "^1.1.0",
939 | "gcp-metadata": "^5.2.0",
940 | "kerberos": "^2.0.1",
941 | "mongodb-client-encryption": ">=6.0.0 <7",
942 | "snappy": "^7.2.2",
943 | "socks": "^2.7.1"
944 | },
945 | "peerDependenciesMeta": {
946 | "@aws-sdk/credential-providers": {
947 | "optional": true
948 | },
949 | "@mongodb-js/zstd": {
950 | "optional": true
951 | },
952 | "gcp-metadata": {
953 | "optional": true
954 | },
955 | "kerberos": {
956 | "optional": true
957 | },
958 | "mongodb-client-encryption": {
959 | "optional": true
960 | },
961 | "snappy": {
962 | "optional": true
963 | },
964 | "socks": {
965 | "optional": true
966 | }
967 | }
968 | },
969 | "node_modules/mongodb-connection-string-url": {
970 | "version": "3.0.1",
971 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
972 | "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
973 | "dependencies": {
974 | "@types/whatwg-url": "^11.0.2",
975 | "whatwg-url": "^13.0.0"
976 | }
977 | },
978 | "node_modules/mongoose": {
979 | "version": "8.5.2",
980 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.5.2.tgz",
981 | "integrity": "sha512-GZB4rHMdYfGatV+23IpCrqFbyCOjCNOHXgWbirr92KRwTEncBrtW3kgU9vmpKjsGf7nMmnAy06SwWUv1vhDkSg==",
982 | "dependencies": {
983 | "bson": "^6.7.0",
984 | "kareem": "2.6.3",
985 | "mongodb": "6.7.0",
986 | "mpath": "0.9.0",
987 | "mquery": "5.0.0",
988 | "ms": "2.1.3",
989 | "sift": "17.1.3"
990 | },
991 | "engines": {
992 | "node": ">=16.20.1"
993 | },
994 | "funding": {
995 | "type": "opencollective",
996 | "url": "https://opencollective.com/mongoose"
997 | }
998 | },
999 | "node_modules/mongoose/node_modules/ms": {
1000 | "version": "2.1.3",
1001 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1002 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1003 | },
1004 | "node_modules/mpath": {
1005 | "version": "0.9.0",
1006 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
1007 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
1008 | "engines": {
1009 | "node": ">=4.0.0"
1010 | }
1011 | },
1012 | "node_modules/mquery": {
1013 | "version": "5.0.0",
1014 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
1015 | "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
1016 | "dependencies": {
1017 | "debug": "4.x"
1018 | },
1019 | "engines": {
1020 | "node": ">=14.0.0"
1021 | }
1022 | },
1023 | "node_modules/mquery/node_modules/debug": {
1024 | "version": "4.3.6",
1025 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
1026 | "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
1027 | "dependencies": {
1028 | "ms": "2.1.2"
1029 | },
1030 | "engines": {
1031 | "node": ">=6.0"
1032 | },
1033 | "peerDependenciesMeta": {
1034 | "supports-color": {
1035 | "optional": true
1036 | }
1037 | }
1038 | },
1039 | "node_modules/mquery/node_modules/ms": {
1040 | "version": "2.1.2",
1041 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1042 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
1043 | },
1044 | "node_modules/ms": {
1045 | "version": "2.0.0",
1046 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1047 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
1048 | },
1049 | "node_modules/negotiator": {
1050 | "version": "0.6.3",
1051 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1052 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
1053 | "engines": {
1054 | "node": ">= 0.6"
1055 | }
1056 | },
1057 | "node_modules/nodemon": {
1058 | "version": "3.1.4",
1059 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz",
1060 | "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==",
1061 | "dev": true,
1062 | "dependencies": {
1063 | "chokidar": "^3.5.2",
1064 | "debug": "^4",
1065 | "ignore-by-default": "^1.0.1",
1066 | "minimatch": "^3.1.2",
1067 | "pstree.remy": "^1.1.8",
1068 | "semver": "^7.5.3",
1069 | "simple-update-notifier": "^2.0.0",
1070 | "supports-color": "^5.5.0",
1071 | "touch": "^3.1.0",
1072 | "undefsafe": "^2.0.5"
1073 | },
1074 | "bin": {
1075 | "nodemon": "bin/nodemon.js"
1076 | },
1077 | "engines": {
1078 | "node": ">=10"
1079 | },
1080 | "funding": {
1081 | "type": "opencollective",
1082 | "url": "https://opencollective.com/nodemon"
1083 | }
1084 | },
1085 | "node_modules/nodemon/node_modules/debug": {
1086 | "version": "4.3.6",
1087 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
1088 | "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
1089 | "dev": true,
1090 | "dependencies": {
1091 | "ms": "2.1.2"
1092 | },
1093 | "engines": {
1094 | "node": ">=6.0"
1095 | },
1096 | "peerDependenciesMeta": {
1097 | "supports-color": {
1098 | "optional": true
1099 | }
1100 | }
1101 | },
1102 | "node_modules/nodemon/node_modules/ms": {
1103 | "version": "2.1.2",
1104 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1105 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
1106 | "dev": true
1107 | },
1108 | "node_modules/normalize-path": {
1109 | "version": "3.0.0",
1110 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1111 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1112 | "dev": true,
1113 | "engines": {
1114 | "node": ">=0.10.0"
1115 | }
1116 | },
1117 | "node_modules/object-assign": {
1118 | "version": "4.1.1",
1119 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1120 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1121 | "engines": {
1122 | "node": ">=0.10.0"
1123 | }
1124 | },
1125 | "node_modules/object-inspect": {
1126 | "version": "1.13.2",
1127 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
1128 | "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
1129 | "engines": {
1130 | "node": ">= 0.4"
1131 | },
1132 | "funding": {
1133 | "url": "https://github.com/sponsors/ljharb"
1134 | }
1135 | },
1136 | "node_modules/on-finished": {
1137 | "version": "2.4.1",
1138 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1139 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1140 | "dependencies": {
1141 | "ee-first": "1.1.1"
1142 | },
1143 | "engines": {
1144 | "node": ">= 0.8"
1145 | }
1146 | },
1147 | "node_modules/parseurl": {
1148 | "version": "1.3.3",
1149 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1150 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1151 | "engines": {
1152 | "node": ">= 0.8"
1153 | }
1154 | },
1155 | "node_modules/path-to-regexp": {
1156 | "version": "0.1.7",
1157 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
1158 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
1159 | },
1160 | "node_modules/picomatch": {
1161 | "version": "2.3.1",
1162 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1163 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1164 | "dev": true,
1165 | "engines": {
1166 | "node": ">=8.6"
1167 | },
1168 | "funding": {
1169 | "url": "https://github.com/sponsors/jonschlinkert"
1170 | }
1171 | },
1172 | "node_modules/proxy-addr": {
1173 | "version": "2.0.7",
1174 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1175 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1176 | "dependencies": {
1177 | "forwarded": "0.2.0",
1178 | "ipaddr.js": "1.9.1"
1179 | },
1180 | "engines": {
1181 | "node": ">= 0.10"
1182 | }
1183 | },
1184 | "node_modules/proxy-from-env": {
1185 | "version": "1.1.0",
1186 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
1187 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
1188 | },
1189 | "node_modules/pstree.remy": {
1190 | "version": "1.1.8",
1191 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
1192 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
1193 | "dev": true
1194 | },
1195 | "node_modules/punycode": {
1196 | "version": "2.3.1",
1197 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1198 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1199 | "engines": {
1200 | "node": ">=6"
1201 | }
1202 | },
1203 | "node_modules/qs": {
1204 | "version": "6.11.0",
1205 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
1206 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
1207 | "dependencies": {
1208 | "side-channel": "^1.0.4"
1209 | },
1210 | "engines": {
1211 | "node": ">=0.6"
1212 | },
1213 | "funding": {
1214 | "url": "https://github.com/sponsors/ljharb"
1215 | }
1216 | },
1217 | "node_modules/range-parser": {
1218 | "version": "1.2.1",
1219 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1220 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1221 | "engines": {
1222 | "node": ">= 0.6"
1223 | }
1224 | },
1225 | "node_modules/raw-body": {
1226 | "version": "2.5.2",
1227 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
1228 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
1229 | "dependencies": {
1230 | "bytes": "3.1.2",
1231 | "http-errors": "2.0.0",
1232 | "iconv-lite": "0.4.24",
1233 | "unpipe": "1.0.0"
1234 | },
1235 | "engines": {
1236 | "node": ">= 0.8"
1237 | }
1238 | },
1239 | "node_modules/readdirp": {
1240 | "version": "3.6.0",
1241 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1242 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1243 | "dev": true,
1244 | "dependencies": {
1245 | "picomatch": "^2.2.1"
1246 | },
1247 | "engines": {
1248 | "node": ">=8.10.0"
1249 | }
1250 | },
1251 | "node_modules/safe-buffer": {
1252 | "version": "5.2.1",
1253 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1254 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1255 | "funding": [
1256 | {
1257 | "type": "github",
1258 | "url": "https://github.com/sponsors/feross"
1259 | },
1260 | {
1261 | "type": "patreon",
1262 | "url": "https://www.patreon.com/feross"
1263 | },
1264 | {
1265 | "type": "consulting",
1266 | "url": "https://feross.org/support"
1267 | }
1268 | ]
1269 | },
1270 | "node_modules/safer-buffer": {
1271 | "version": "2.1.2",
1272 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1273 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1274 | },
1275 | "node_modules/semver": {
1276 | "version": "7.6.3",
1277 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
1278 | "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
1279 | "bin": {
1280 | "semver": "bin/semver.js"
1281 | },
1282 | "engines": {
1283 | "node": ">=10"
1284 | }
1285 | },
1286 | "node_modules/send": {
1287 | "version": "0.18.0",
1288 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
1289 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
1290 | "dependencies": {
1291 | "debug": "2.6.9",
1292 | "depd": "2.0.0",
1293 | "destroy": "1.2.0",
1294 | "encodeurl": "~1.0.2",
1295 | "escape-html": "~1.0.3",
1296 | "etag": "~1.8.1",
1297 | "fresh": "0.5.2",
1298 | "http-errors": "2.0.0",
1299 | "mime": "1.6.0",
1300 | "ms": "2.1.3",
1301 | "on-finished": "2.4.1",
1302 | "range-parser": "~1.2.1",
1303 | "statuses": "2.0.1"
1304 | },
1305 | "engines": {
1306 | "node": ">= 0.8.0"
1307 | }
1308 | },
1309 | "node_modules/send/node_modules/ms": {
1310 | "version": "2.1.3",
1311 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1312 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1313 | },
1314 | "node_modules/serve-static": {
1315 | "version": "1.15.0",
1316 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
1317 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
1318 | "dependencies": {
1319 | "encodeurl": "~1.0.2",
1320 | "escape-html": "~1.0.3",
1321 | "parseurl": "~1.3.3",
1322 | "send": "0.18.0"
1323 | },
1324 | "engines": {
1325 | "node": ">= 0.8.0"
1326 | }
1327 | },
1328 | "node_modules/set-function-length": {
1329 | "version": "1.2.2",
1330 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
1331 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
1332 | "dependencies": {
1333 | "define-data-property": "^1.1.4",
1334 | "es-errors": "^1.3.0",
1335 | "function-bind": "^1.1.2",
1336 | "get-intrinsic": "^1.2.4",
1337 | "gopd": "^1.0.1",
1338 | "has-property-descriptors": "^1.0.2"
1339 | },
1340 | "engines": {
1341 | "node": ">= 0.4"
1342 | }
1343 | },
1344 | "node_modules/setprototypeof": {
1345 | "version": "1.2.0",
1346 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1347 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1348 | },
1349 | "node_modules/side-channel": {
1350 | "version": "1.0.6",
1351 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
1352 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
1353 | "dependencies": {
1354 | "call-bind": "^1.0.7",
1355 | "es-errors": "^1.3.0",
1356 | "get-intrinsic": "^1.2.4",
1357 | "object-inspect": "^1.13.1"
1358 | },
1359 | "engines": {
1360 | "node": ">= 0.4"
1361 | },
1362 | "funding": {
1363 | "url": "https://github.com/sponsors/ljharb"
1364 | }
1365 | },
1366 | "node_modules/sift": {
1367 | "version": "17.1.3",
1368 | "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
1369 | "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
1370 | },
1371 | "node_modules/simple-update-notifier": {
1372 | "version": "2.0.0",
1373 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
1374 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
1375 | "dev": true,
1376 | "dependencies": {
1377 | "semver": "^7.5.3"
1378 | },
1379 | "engines": {
1380 | "node": ">=10"
1381 | }
1382 | },
1383 | "node_modules/sparse-bitfield": {
1384 | "version": "3.0.3",
1385 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1386 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1387 | "dependencies": {
1388 | "memory-pager": "^1.0.2"
1389 | }
1390 | },
1391 | "node_modules/statuses": {
1392 | "version": "2.0.1",
1393 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1394 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1395 | "engines": {
1396 | "node": ">= 0.8"
1397 | }
1398 | },
1399 | "node_modules/supports-color": {
1400 | "version": "5.5.0",
1401 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1402 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1403 | "dev": true,
1404 | "dependencies": {
1405 | "has-flag": "^3.0.0"
1406 | },
1407 | "engines": {
1408 | "node": ">=4"
1409 | }
1410 | },
1411 | "node_modules/to-regex-range": {
1412 | "version": "5.0.1",
1413 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1414 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1415 | "dev": true,
1416 | "dependencies": {
1417 | "is-number": "^7.0.0"
1418 | },
1419 | "engines": {
1420 | "node": ">=8.0"
1421 | }
1422 | },
1423 | "node_modules/toidentifier": {
1424 | "version": "1.0.1",
1425 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1426 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1427 | "engines": {
1428 | "node": ">=0.6"
1429 | }
1430 | },
1431 | "node_modules/touch": {
1432 | "version": "3.1.1",
1433 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
1434 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
1435 | "dev": true,
1436 | "bin": {
1437 | "nodetouch": "bin/nodetouch.js"
1438 | }
1439 | },
1440 | "node_modules/tr46": {
1441 | "version": "4.1.1",
1442 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
1443 | "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
1444 | "dependencies": {
1445 | "punycode": "^2.3.0"
1446 | },
1447 | "engines": {
1448 | "node": ">=14"
1449 | }
1450 | },
1451 | "node_modules/type-is": {
1452 | "version": "1.6.18",
1453 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1454 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1455 | "dependencies": {
1456 | "media-typer": "0.3.0",
1457 | "mime-types": "~2.1.24"
1458 | },
1459 | "engines": {
1460 | "node": ">= 0.6"
1461 | }
1462 | },
1463 | "node_modules/undefsafe": {
1464 | "version": "2.0.5",
1465 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1466 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1467 | "dev": true
1468 | },
1469 | "node_modules/unpipe": {
1470 | "version": "1.0.0",
1471 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1472 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1473 | "engines": {
1474 | "node": ">= 0.8"
1475 | }
1476 | },
1477 | "node_modules/utils-merge": {
1478 | "version": "1.0.1",
1479 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1480 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1481 | "engines": {
1482 | "node": ">= 0.4.0"
1483 | }
1484 | },
1485 | "node_modules/vary": {
1486 | "version": "1.1.2",
1487 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1488 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1489 | "engines": {
1490 | "node": ">= 0.8"
1491 | }
1492 | },
1493 | "node_modules/webidl-conversions": {
1494 | "version": "7.0.0",
1495 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1496 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1497 | "engines": {
1498 | "node": ">=12"
1499 | }
1500 | },
1501 | "node_modules/whatwg-url": {
1502 | "version": "13.0.0",
1503 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
1504 | "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
1505 | "dependencies": {
1506 | "tr46": "^4.1.1",
1507 | "webidl-conversions": "^7.0.0"
1508 | },
1509 | "engines": {
1510 | "node": ">=16"
1511 | }
1512 | }
1513 | }
1514 | }
1515 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auth-tutorial",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "backend/index.js",
6 | "scripts": {
7 | "dev": "NODE_ENV=development nodemon backend/index.js",
8 | "start": "NODE_ENV=production node backend/index.js",
9 | "build": "npm install && npm install --prefix frontend && npm run build --prefix frontend"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "type": "module",
14 | "license": "ISC",
15 | "dependencies": {
16 | "bcryptjs": "^2.4.3",
17 | "cookie-parser": "^1.4.6",
18 | "cors": "^2.8.5",
19 | "crypto": "^1.0.1",
20 | "dotenv": "^16.4.5",
21 | "express": "^4.19.2",
22 | "jsonwebtoken": "^9.0.2",
23 | "mailtrap": "^3.4.0",
24 | "mongoose": "^8.5.2"
25 | },
26 | "devDependencies": {
27 | "nodemon": "^3.1.4"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------