├── .DS_Store
├── .gitignore
├── LICENSE
├── README.md
├── nextauth-node.png
├── nextauth
├── .env.development
├── .eslintrc.json
├── README.md
├── jsconfig.json
├── next.config.js
├── package-lock.json
├── package.json
├── public
│ ├── avatar3.png
│ ├── bg.jpg
│ ├── logo-sm.png
│ ├── next copy.svg
│ ├── next.svg
│ ├── post1.jpg
│ ├── post10.jpg
│ ├── post2.jpg
│ ├── post3.jpg
│ ├── post4.jpg
│ ├── post5.jpg
│ ├── post6.jpg
│ ├── post7.jpg
│ ├── post8.jpg
│ ├── post9.jpg
│ ├── vercel copy.svg
│ └── vercel.svg
└── src
│ ├── app
│ ├── (site)
│ │ ├── articles
│ │ │ ├── error.js
│ │ │ └── page.js
│ │ ├── auth
│ │ │ └── error
│ │ │ │ └── page.js
│ │ ├── blog
│ │ │ ├── error.js
│ │ │ └── page.js
│ │ ├── create-post
│ │ │ └── page.js
│ │ ├── dashboard
│ │ │ ├── error.js
│ │ │ └── page.js
│ │ ├── posts
│ │ │ ├── error.js
│ │ │ └── page.js
│ │ ├── profile
│ │ │ ├── error.js
│ │ │ └── page.js
│ │ └── users
│ │ │ ├── error.js
│ │ │ └── page.js
│ ├── api
│ │ ├── auth
│ │ │ ├── [...nextauth]
│ │ │ │ └── route.js
│ │ │ └── authOptions.js
│ │ ├── posts
│ │ │ └── route.js
│ │ └── users
│ │ │ └── route.js
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.js
│ ├── page.js
│ └── page.module.css
│ ├── components
│ ├── atoms
│ │ ├── MainNavbar.js
│ │ ├── PostCard.js
│ │ ├── SiteNavbar.js
│ │ ├── UsersTable.js
│ │ └── index.js
│ └── pages
│ │ ├── AdminDashboard.js
│ │ ├── ArticlePage.js
│ │ ├── CreatePostPage.js
│ │ ├── DashboardPage.js
│ │ ├── HomePage.js
│ │ ├── ProfilePage.js
│ │ ├── UserDashboard.js
│ │ ├── UsersPage.js
│ │ └── index.js
│ ├── config
│ └── index.js
│ ├── db
│ └── index.js
│ ├── hooks
│ └── index.js
│ ├── lib
│ ├── post.js
│ └── user.js
│ ├── middleware.js
│ ├── models
│ ├── account.js
│ ├── index.js
│ ├── post.js
│ └── user.js
│ ├── providers
│ └── index.js
│ ├── schema
│ └── index.js
│ ├── services
│ ├── index.js
│ ├── post.js
│ └── user.js
│ ├── utils
│ └── index.js
│ └── validators
│ └── index.js
└── node-server
├── .env
├── controllers
├── index.mjs
└── post.mjs
├── db
└── index.mjs
├── index.mjs
├── middleware
└── index.mjs
├── models
├── index.mjs
├── post.mjs
└── user.mjs
├── package-lock.json
├── package.json
├── routes
├── index.mjs
└── post.mjs
├── services
├── index.mjs
└── post.mjs
└── utils
└── index.mjs
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /nextauth/node_modules
5 | /nextauth/.pnp
6 | /nextauth/.pnp.js
7 | /nextauth/.yarn/install-state.gz
8 |
9 | # testing
10 | /nextauth/coverage
11 |
12 | # next.js
13 | /nextauth/.next/
14 | /nextauth/out/
15 |
16 | # production
17 | /nextauth/build
18 |
19 | # misc
20 | /nextauth/.DS_Store
21 | /nextauth/*.pem
22 |
23 | # debug
24 | /nextauth/npm-debug.log*
25 | /nextauth/yarn-debug.log*
26 | /nextauth/yarn-error.log*
27 |
28 | # local env files
29 | /nextauth/.env*.local
30 |
31 | # vercel
32 | /nextauth/.vercel
33 |
34 | # typescript
35 | /nextauth/*.tsbuildinfo
36 | /nextauth/next-env.d.ts
37 |
38 | /node-server/node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Peter Kelvin Torver
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 | 
2 |
3 | ## 🚀 Nextjs 14 Authentication and Role-Based Authorization Using NextAuth with Mongodb Adapter
4 |
5 | 🔥 Key Highlights:
6 |
7 | - Seamlessly integrate NextAuth into your NextJS 14 application
8 | - Harness the power of MongoDB as your authentication backend
9 | - Implement role-based authorization to control user access
10 | - Elevate your NextJS development skills to the next level
11 | - Connect Nextjs App with Node server
12 | - Implement Access and refresh token functionality
13 | - Implement nextauth jwt & database strategy with database adapter
14 |
15 | 🎯 Target Audience:
16 |
17 | - Developers seeking to enhance their NextJS authentication skills
18 | - Aspiring web security specialists
19 | - Enthusiasts eager to explore MongoDB's authentication capabilities
20 |
21 | 🔒🚦 Embrace the Future of Secure NextJS Development
22 |
23 | Embark on a comprehensive journey with our exclusive guide to NextJS 14 Authentication & Role-Based Authorization! This tutorial unpacks the power of NextAuth and MongoDb Adapter, Json web token, empowering you to seamlessly implement authentication and robust role-based authorization within your NextJS 14 applications.
24 |
25 | 🔐 Learn Effortless User Authentication:
26 | Discover how to set up secure user authentication effortlessly.
27 |
28 | 🔑 Flawless Role-Based Access Control:
29 | Implement role-based access control to manage user permissions seamlessly.
30 |
31 | 🛠️ Harness NextJS 14, NextAuth, and MongoDb Adapter:
32 | Leverage their potential for a smooth, secure, and scalable user authentication experience.
33 |
34 | Whether you're a beginner or an experienced developer, this video equips you with the tools and knowledge to effortlessly implement advanced authentication and authorization features in your NextJS projects! Level up your NextJS game and don't miss this opportunity to enhance your skills! 📈💥
35 |
36 | 🎬🔓 **Watch now and unlock the world of seamless authentication and role-based authorization in NextJS 14!** Don't forget to like, subscribe, and share this knowledge with fellow developers! 🌟🚀
--------------------------------------------------------------------------------
/nextauth-node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth-node.png
--------------------------------------------------------------------------------
/nextauth/.env.development:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_BASE_URL = "http://localhost:3000/api"
2 |
3 | NEXT_PUBLIC_BASE_NODE_URL = "http://localhost:5001/api"
4 |
5 | NEXTAUTH_URL = "http://localhost:3000"
6 |
7 | NEXTAUTH_SECRET = "ZXwBUbYq8uw7Psg5BZrlAndbNvrn1l1Cgin08nJ9TmM="
8 |
9 | MONGODB_URI = ""
10 |
11 | GITHUB_ID = ""
12 |
13 | GITHUB_SECRET = ""
14 |
15 | GOOGLE_ID = ""
16 |
17 | GOOOGLE_SECRET = ""
18 |
19 | CRYPTO_ENCRYPTION_KEY = "e883993939ijjdiuduuehyrhuuer"
20 |
21 | CRYPTO_IV_KEY = "8289eudjjdue88e9e0e"
22 |
23 | ACCESS_TOKEN_PRIVATE_KEY = "ek8qvPCLavjSOlSjR/nHGS/iJV6fOhg/MftYpOMMIDbMKC+U0grPFtu6q8jg/dJBJEjTL2dafb+0ADqeSjsF5w0EzpY5PIWIXvP52anY1hpH3FjYeeFe2PS2jmukwb1J
24 | d7zhlh9i7MA55QU+7LH7PYupK1k23KMP6sZheblAD17HTg=="
--------------------------------------------------------------------------------
/nextauth/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/nextauth/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/nextauth/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./src/*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/nextauth/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/nextauth/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextauth",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@auth/mongodb-adapter": "^2.0.4",
13 | "@emotion/react": "^11.11.1",
14 | "@emotion/styled": "^11.11.0",
15 | "@fontsource/roboto": "^5.0.8",
16 | "@mui/icons-material": "^5.14.18",
17 | "@mui/material": "^5.14.18",
18 | "axios": "^1.6.2",
19 | "bcrypt": "^5.1.1",
20 | "crypto-js": "^4.2.0",
21 | "jose": "^5.1.1",
22 | "mongodb": "^6.2.0",
23 | "mongoose": "^8.0.1",
24 | "nanoid": "^5.0.3",
25 | "next": "14.0.2",
26 | "next-auth": "^4.24.5",
27 | "react": "^18",
28 | "react-dom": "^18",
29 | "slugify": "^1.6.6",
30 | "swr": "^2.2.4",
31 | "zod": "^3.22.4"
32 | },
33 | "devDependencies": {
34 | "eslint": "^8",
35 | "eslint-config-next": "14.0.2"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/nextauth/public/avatar3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/avatar3.png
--------------------------------------------------------------------------------
/nextauth/public/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/bg.jpg
--------------------------------------------------------------------------------
/nextauth/public/logo-sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/logo-sm.png
--------------------------------------------------------------------------------
/nextauth/public/next copy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nextauth/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nextauth/public/post1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post1.jpg
--------------------------------------------------------------------------------
/nextauth/public/post10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post10.jpg
--------------------------------------------------------------------------------
/nextauth/public/post2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post2.jpg
--------------------------------------------------------------------------------
/nextauth/public/post3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post3.jpg
--------------------------------------------------------------------------------
/nextauth/public/post4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post4.jpg
--------------------------------------------------------------------------------
/nextauth/public/post5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post5.jpg
--------------------------------------------------------------------------------
/nextauth/public/post6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post6.jpg
--------------------------------------------------------------------------------
/nextauth/public/post7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post7.jpg
--------------------------------------------------------------------------------
/nextauth/public/post8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post8.jpg
--------------------------------------------------------------------------------
/nextauth/public/post9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/public/post9.jpg
--------------------------------------------------------------------------------
/nextauth/public/vercel copy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nextauth/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/articles/error.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { getErrorMessage } from '@/utils'
3 | import { Box, Button, Container, Stack, Typography } from '@mui/material'
4 | import Link from 'next/link'
5 | import React, { Fragment } from 'react'
6 |
7 | const Error = ({error, reset}) => {
8 | return (
9 |
10 |
11 |
12 | Oops something went wrong!
13 |
14 | {getErrorMessage(error)}
15 |
16 |
17 |
18 |
21 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default Error
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/articles/page.js:
--------------------------------------------------------------------------------
1 | import { ArticlePage } from "@/components/pages";
2 | import { getPublicPosts } from "@/lib/post";
3 |
4 | export default async function Home() {
5 | const posts = await getPublicPosts()
6 | return
7 | }
8 |
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/auth/error/page.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Box, Container, Typography } from '@mui/material'
3 | import React, { Fragment } from 'react'
4 |
5 | const Page = ({params, searchParams}) => {
6 | const {error} = searchParams
7 | return (
8 |
9 |
10 |
11 | Oops something went wrong!
12 |
13 | {error}
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | export default Page
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/blog/error.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { getErrorMessage } from '@/utils'
3 | import { Box, Button, Container, Stack, Typography } from '@mui/material'
4 | import Link from 'next/link'
5 | import React, { Fragment } from 'react'
6 |
7 | const Error = ({error, reset}) => {
8 | return (
9 |
10 |
11 |
12 | Oops something went wrong!
13 |
14 | {getErrorMessage(error)}
15 |
16 |
17 |
18 |
21 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default Error
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/blog/page.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { ArticlePage } from "@/components/pages";
3 | import { useAuthUser } from "@/hooks";
4 | import { getAllPosts } from "@/lib/post";
5 | import { getErrorMessage } from "@/utils";
6 | import { Box, CircularProgress, Typography } from "@mui/material";
7 | import { redirect } from "next/navigation";
8 | import useSWR from "swr";
9 |
10 | export default function Page() {
11 | const {session, isLoading: isSessionLoading, isUnAuthenticated} = useAuthUser()
12 |
13 | const {data: posts, isLoading, error} = useSWR(["/posts", session?.accessToken], ([url, token]) => getAllPosts(token))
14 |
15 | if(isLoading || isSessionLoading){
16 | return ()
17 | }
18 |
19 | if(isUnAuthenticated) return redirect("/api/auth/signin?callbackUrl=/blog")
20 |
21 | if(error){
22 | return (
23 |
24 |
25 | {getErrorMessage(error)}
26 |
27 | )
28 | }
29 | return
30 | }
31 |
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/create-post/page.js:
--------------------------------------------------------------------------------
1 | import authOptions from "@/app/api/auth/authOptions";
2 | import { CreatePostPage } from "@/components/pages";
3 | import { getServerSession } from "next-auth";
4 | import { redirect } from "next/navigation";
5 |
6 | export default async function Home() {
7 | const session = await getServerSession(authOptions)
8 | if(!session) return redirect("/api/auth/signin?callbackUrl=/create-post")
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/dashboard/error.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { getErrorMessage } from '@/utils'
3 | import { Box, Button, Container, Stack, Typography } from '@mui/material'
4 | import Link from 'next/link'
5 | import React, { Fragment } from 'react'
6 |
7 | const Error = ({error, reset}) => {
8 | return (
9 |
10 |
11 |
12 | Oops something went wrong!
13 |
14 | {getErrorMessage(error)}
15 |
16 |
17 |
18 |
21 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default Error
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/dashboard/page.js:
--------------------------------------------------------------------------------
1 | import authOptions from "@/app/api/auth/authOptions";
2 | import { DashboardPage } from "@/components/pages";
3 | import { getServerSession } from "next-auth";
4 | import { redirect } from "next/navigation";
5 |
6 | export default async function Home() {
7 | const session = await getServerSession(authOptions)
8 | if(!session) return redirect("/api/auth/signin?callbackUrl=/dashboard")
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/posts/error.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { getErrorMessage } from '@/utils'
3 | import { Box, Button, Container, Stack, Typography } from '@mui/material'
4 | import Link from 'next/link'
5 | import React, { Fragment } from 'react'
6 |
7 | const Error = ({error, reset}) => {
8 | return (
9 |
10 |
11 |
12 | Oops something went wrong!
13 |
14 | {getErrorMessage(error)}
15 |
16 |
17 |
18 |
21 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default Error
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/posts/page.js:
--------------------------------------------------------------------------------
1 | import authOptions from "@/app/api/auth/authOptions";
2 | import { ArticlePage } from "@/components/pages";
3 | import { getAllNodeAPIPosts } from "@/lib/post";
4 | import { getServerSession } from "next-auth";
5 | import { redirect } from "next/navigation";
6 |
7 | export default async function Page() {
8 | const session = await getServerSession(authOptions)
9 | if(!session) return redirect("/api/auth/signin?callbackUrl=/posts")
10 | const posts = await getAllNodeAPIPosts(session?.accessToken)
11 | console.log("Node api posts ", posts)
12 | return
13 | }
14 |
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/profile/error.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { getErrorMessage } from '@/utils'
3 | import { Box, Button, Container, Stack, Typography } from '@mui/material'
4 | import Link from 'next/link'
5 | import React, { Fragment } from 'react'
6 |
7 | const Error = ({error, reset}) => {
8 | return (
9 |
10 |
11 |
12 | Oops something went wrong!
13 |
14 | {getErrorMessage(error)}
15 |
16 |
17 |
18 |
21 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default Error
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/profile/page.js:
--------------------------------------------------------------------------------
1 | import authOptions from "@/app/api/auth/authOptions";
2 | import { ProfilePage } from "@/components/pages";
3 | import { getServerSession } from "next-auth";
4 | import { redirect } from "next/navigation";
5 |
6 | export default async function Home() {
7 | const session = await getServerSession(authOptions)
8 | if(!session) return redirect("/api/auth/signin?callbackUrl=/dashboard")
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/users/error.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { getErrorMessage } from '@/utils'
3 | import { Box, Button, Container, Stack, Typography } from '@mui/material'
4 | import Link from 'next/link'
5 | import React, { Fragment } from 'react'
6 |
7 | const Error = ({error, reset}) => {
8 | return (
9 |
10 |
11 |
12 | Oops something went wrong!
13 |
14 | {getErrorMessage(error)}
15 |
16 |
17 |
18 |
21 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default Error
--------------------------------------------------------------------------------
/nextauth/src/app/(site)/users/page.js:
--------------------------------------------------------------------------------
1 | import authOptions from "@/app/api/auth/authOptions";
2 | import { UsersPage } from "@/components/pages";
3 | import { getAllUsers } from "@/lib/user";
4 | import { getServerSession } from "next-auth";
5 | import { redirect } from "next/navigation";
6 |
7 | export default async function Home() {
8 | const session = await getServerSession(authOptions)
9 | if(!session) return redirect("/api/auth/signin?callbackUrl=/dashboard")
10 | const users = await getAllUsers(session?.accessToken)
11 | console.log(users)
12 | return
13 |
14 |
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/nextauth/src/app/api/auth/[...nextauth]/route.js:
--------------------------------------------------------------------------------
1 | import NextAuth from "next-auth"
2 | import authOptions from "../authOptions"
3 |
4 | const handler = NextAuth(authOptions)
5 |
6 | export { handler as GET, handler as POST }
--------------------------------------------------------------------------------
/nextauth/src/app/api/auth/authOptions.js:
--------------------------------------------------------------------------------
1 | import GithubProvider from 'next-auth/providers/github'
2 | import GoogleProvider from 'next-auth/providers/google'
3 | import CrendentialsProvider from 'next-auth/providers/credentials'
4 | import { MongoDBAdapter } from '@auth/mongodb-adapter'
5 | import { nanoid } from 'nanoid'
6 | import clientPromise from '@/db'
7 | import { validateZodInput } from '@/validators'
8 | import { UserZodSchema } from '@/schema'
9 | import { UserService } from '@/services'
10 | import { encryptString, signJwtToken, verifyJwtToken } from '@/utils'
11 |
12 | const generateAccessToken = async({accessToken, user, isRefresh}) => {
13 | try {
14 | const verifAccessToken = await verifyJwtToken(accessToken)
15 | if(!isRefresh && !accessToken){
16 | const encryptData = encryptString(JSON.stringify(user))
17 | const signToken = await signJwtToken({user: encryptData})
18 | return signToken
19 | }
20 | if(isRefresh && verifAccessToken.isError){
21 | // make a db call
22 | const result = await UserService.getCurrentUser(user.id)
23 | if(result.isError) throw new Error(result.message)
24 | const encryptData = encryptString(JSON.stringify(result.data))
25 | const signToken = await signJwtToken({user: encryptData})
26 | return signToken
27 | }
28 | return accessToken
29 | } catch (error) {
30 | throw new Error(error.message)
31 | }
32 | }
33 |
34 |
35 | const authOptions = {
36 | adapter: MongoDBAdapter(clientPromise),
37 | session: {
38 | strategy: "jwt",
39 | maxAge: 30 * 24 * 60 * 60, // 30 days
40 | updateAge: 24 * 60 * 60,
41 | generateSessionToken: () => {
42 | return nanoid(32)
43 | }
44 | },
45 | providers: [
46 | CrendentialsProvider({
47 | name: 'crendentials',
48 | credentials: {
49 | email: {
50 | label: "Email",
51 | type: "email",
52 | placeholder: "Enter your email e.g. me@example.com"
53 | },
54 | password: {
55 | label: "Password",
56 | type: "password",
57 | placeholder: "Enter your password"
58 | }
59 | },
60 | async authorize(credentials) {
61 | try {
62 | // validate the inputs
63 | const parsedResult = validateZodInput(credentials, UserZodSchema)
64 | if(parsedResult.isError) throw new Error(parsedResult.message)
65 | // make db calls her
66 | const result = await UserService.createUser(parsedResult.data)
67 | if(result.isError) throw new Error(result.message)
68 | return result.data
69 | } catch (error) {
70 | throw new Error(error.message);
71 | }
72 | }
73 | }),
74 | GithubProvider({
75 | clientId: process.env.GITHUB_ID,
76 | clientSecret: process.env.GITHUB_SECRET,
77 | profile: async(user) => {
78 | const isBlocked = user?.hasOwnProperty("isBlocked") ? user.isBlocked : false;
79 | const role = user?.hasOwnProperty("role") ? user.role : "user";
80 | const isVerifiedEmail = user?.hasOwnProperty("emailVerified") ? user?.emailVerified : true
81 | const avatar = user?.avatar_url
82 | const createdAt = user?.hasOwnProperty("createdAt") ? user?.createdAt : new Date().toISOString()
83 | const updatedAt = user?.hasOwnProperty("updatedAt") ? user?.updatedAt : new Date().toISOString()
84 | return {id: user?.id, name: user?.name, email: user?.email, role, isBlocked, avatar, isVerifiedEmail,createdAt,updatedAt}
85 | }
86 | }),
87 | GoogleProvider({
88 | clientId: process.env.GOOGLE_ID,
89 | clientSecret: process.env.GOOOGLE_SECRET,
90 | profile: async(user) => {
91 | const isBlocked = user?.hasOwnProperty("isBlocked") ? user.isBlocked : false;
92 | const role = user?.hasOwnProperty("role") ? user.role : "user";
93 | const isVerifiedEmail = user?.hasOwnProperty("email_verified") ? user?.email_verified : true
94 | const avatar = user?.picture
95 | const createdAt = user?.hasOwnProperty("createdAt") ? user?.createdAt : new Date().toISOString()
96 | const updatedAt = user?.hasOwnProperty("updatedAt") ? user?.updatedAt : new Date().toISOString()
97 | const id = user?.id ? user?.id : user?.sub
98 | return {id, name: user?.name, email: user?.email, role, isBlocked, isVerifiedEmail, avatar, createdAt, updatedAt }
99 | }
100 | })
101 | ],
102 | callbacks: {
103 | async signIn({user, account, profile, email, crendentials}) {
104 | return true
105 | },
106 | async redirect({url, baseUrl}) {
107 | if(url.startsWith('/')) return url
108 | return baseUrl
109 | },
110 | async session({session, token, user}) {
111 | if(token){
112 | session.user = token?.user
113 | session.accessToken = token?.accessToken
114 | }
115 | return session
116 | },
117 | async jwt({token, user, account, profile, isNewUser}) {
118 |
119 | if(token && user ){
120 | const _user = {
121 | id: user.id,
122 | email: user.email,
123 | role: user.role,
124 | name: user.name,
125 | avatar: user.avatar
126 | }
127 | // create a fn generate access token
128 | const accessToken = await generateAccessToken({
129 | accessToken: token?.accessToken,
130 | user: _user,
131 | isRefresh: false
132 | })
133 | token.user = _user
134 | token.accessToken = accessToken
135 | }
136 | if(token && !user){
137 | const _user = token?.user
138 | // create a fn generate access token
139 | const accessToken = await generateAccessToken({
140 | accessToken: token?.accessToken,
141 | user: _user,
142 | isRefresh: true
143 | })
144 | token.user = _user
145 | token.accessToken = accessToken
146 | }
147 | return token
148 | }
149 | },
150 | pages: {
151 | error: "/auth/error"
152 | }
153 | }
154 |
155 | export default authOptions
156 |
--------------------------------------------------------------------------------
/nextauth/src/app/api/posts/route.js:
--------------------------------------------------------------------------------
1 | import { PostZodSchema } from "@/schema";
2 | import { PostService } from "@/services";
3 | import { checkUserRole } from "@/utils";
4 | import { validateZodInput } from "@/validators";
5 |
6 | export async function POST(req) {
7 | const body = await req.json();
8 | const requestHeaders = new Headers(req.headers)
9 |
10 | const user = JSON.parse(requestHeaders.get('user'));
11 |
12 | if(!user || !checkUserRole(user?.role).isAdminRole){
13 | return Response.json("You are not authorized to access this resource", { status: 403 })
14 | }
15 |
16 | const parsedResult = validateZodInput(body, PostZodSchema);
17 |
18 |
19 | if(parsedResult.isError) return Response.json(parsedResult.message, {status: 400});
20 |
21 | // create a new post
22 |
23 | const result = await PostService.createNewPost({...parsedResult.data, user: user?.id})
24 |
25 | if(result.isError) return Response.json(result.message, {status: 400});
26 |
27 | return Response.json(result.data);
28 |
29 | }
30 |
31 | export async function GET(request) {
32 | const result = await PostService.getAllPosts();
33 | if(result.isError) return Response.json(result.message, {status: 500});
34 | return Response.json(result.data)
35 | }
--------------------------------------------------------------------------------
/nextauth/src/app/api/users/route.js:
--------------------------------------------------------------------------------
1 | import { UserService } from "@/services";
2 |
3 | export async function GET(request) {
4 | const result = await UserService.getAllUsers();
5 | if(result.isError) return Response.json(result.message, {status: 500});
6 | return Response.json(result.data)
7 | }
--------------------------------------------------------------------------------
/nextauth/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/torver213/nextjs14-nextauth-nodeapi-yt/9a4fb7475526c40364187b14428d9b97e07e74f6/nextauth/src/app/favicon.ico
--------------------------------------------------------------------------------
/nextauth/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/icon?family=Material+Icons");
2 |
3 | * {
4 | box-sizing: border-box;
5 | padding: 0;
6 | margin: 0;
7 | }
8 |
9 | html,
10 | body {
11 | max-width: 100vw;
12 | overflow-x: hidden;
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/nextauth/src/app/layout.js:
--------------------------------------------------------------------------------
1 | import "@fontsource/roboto/300.css";
2 | import "@fontsource/roboto/400.css";
3 | import "@fontsource/roboto/500.css";
4 | import "@fontsource/roboto/700.css";
5 | import "./globals.css";
6 | import { MainNavbar } from "@/components/atoms";
7 | import { NextAuthSessionProvider } from "@/providers";
8 |
9 | export const metadata = {
10 | title: "Create Next App",
11 | description: "Generated by create next app",
12 | };
13 |
14 | export default function RootLayout({ children }) {
15 | return (
16 |
17 |
18 |
19 |
20 | {children}
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/nextauth/src/app/page.js:
--------------------------------------------------------------------------------
1 | import { HomePage } from "@/components/pages";
2 |
3 | export default function Home() {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/nextauth/src/app/page.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | align-items: center;
6 | padding: 6rem;
7 | min-height: 100vh;
8 | }
9 |
10 | .description {
11 | display: inherit;
12 | justify-content: inherit;
13 | align-items: inherit;
14 | font-size: 0.85rem;
15 | max-width: var(--max-width);
16 | width: 100%;
17 | z-index: 2;
18 | font-family: var(--font-mono);
19 | }
20 |
21 | .description a {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | gap: 0.5rem;
26 | }
27 |
28 | .description p {
29 | position: relative;
30 | margin: 0;
31 | padding: 1rem;
32 | background-color: rgba(var(--callout-rgb), 0.5);
33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34 | border-radius: var(--border-radius);
35 | }
36 |
37 | .code {
38 | font-weight: 700;
39 | font-family: var(--font-mono);
40 | }
41 |
42 | .grid {
43 | display: grid;
44 | grid-template-columns: repeat(4, minmax(25%, auto));
45 | max-width: 100%;
46 | width: var(--max-width);
47 | }
48 |
49 | .card {
50 | padding: 1rem 1.2rem;
51 | border-radius: var(--border-radius);
52 | background: rgba(var(--card-rgb), 0);
53 | border: 1px solid rgba(var(--card-border-rgb), 0);
54 | transition: background 200ms, border 200ms;
55 | }
56 |
57 | .card span {
58 | display: inline-block;
59 | transition: transform 200ms;
60 | }
61 |
62 | .card h2 {
63 | font-weight: 600;
64 | margin-bottom: 0.7rem;
65 | }
66 |
67 | .card p {
68 | margin: 0;
69 | opacity: 0.6;
70 | font-size: 0.9rem;
71 | line-height: 1.5;
72 | max-width: 30ch;
73 | }
74 |
75 | .center {
76 | display: flex;
77 | justify-content: center;
78 | align-items: center;
79 | position: relative;
80 | padding: 4rem 0;
81 | }
82 |
83 | .center::before {
84 | background: var(--secondary-glow);
85 | border-radius: 50%;
86 | width: 480px;
87 | height: 360px;
88 | margin-left: -400px;
89 | }
90 |
91 | .center::after {
92 | background: var(--primary-glow);
93 | width: 240px;
94 | height: 180px;
95 | z-index: -1;
96 | }
97 |
98 | .center::before,
99 | .center::after {
100 | content: '';
101 | left: 50%;
102 | position: absolute;
103 | filter: blur(45px);
104 | transform: translateZ(0);
105 | }
106 |
107 | .logo {
108 | position: relative;
109 | }
110 | /* Enable hover only on non-touch devices */
111 | @media (hover: hover) and (pointer: fine) {
112 | .card:hover {
113 | background: rgba(var(--card-rgb), 0.1);
114 | border: 1px solid rgba(var(--card-border-rgb), 0.15);
115 | }
116 |
117 | .card:hover span {
118 | transform: translateX(4px);
119 | }
120 | }
121 |
122 | @media (prefers-reduced-motion) {
123 | .card:hover span {
124 | transform: none;
125 | }
126 | }
127 |
128 | /* Mobile */
129 | @media (max-width: 700px) {
130 | .content {
131 | padding: 4rem;
132 | }
133 |
134 | .grid {
135 | grid-template-columns: 1fr;
136 | margin-bottom: 120px;
137 | max-width: 320px;
138 | text-align: center;
139 | }
140 |
141 | .card {
142 | padding: 1rem 2.5rem;
143 | }
144 |
145 | .card h2 {
146 | margin-bottom: 0.5rem;
147 | }
148 |
149 | .center {
150 | padding: 8rem 0 6rem;
151 | }
152 |
153 | .center::before {
154 | transform: none;
155 | height: 300px;
156 | }
157 |
158 | .description {
159 | font-size: 0.8rem;
160 | }
161 |
162 | .description a {
163 | padding: 1rem;
164 | }
165 |
166 | .description p,
167 | .description div {
168 | display: flex;
169 | justify-content: center;
170 | position: fixed;
171 | width: 100%;
172 | }
173 |
174 | .description p {
175 | align-items: center;
176 | inset: 0 0 auto;
177 | padding: 2rem 1rem 1.4rem;
178 | border-radius: 0;
179 | border: none;
180 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
181 | background: linear-gradient(
182 | to bottom,
183 | rgba(var(--background-start-rgb), 1),
184 | rgba(var(--callout-rgb), 0.5)
185 | );
186 | background-clip: padding-box;
187 | backdrop-filter: blur(24px);
188 | }
189 |
190 | .description div {
191 | align-items: flex-end;
192 | pointer-events: none;
193 | inset: auto 0 0;
194 | padding: 2rem;
195 | height: 200px;
196 | background: linear-gradient(
197 | to bottom,
198 | transparent 0%,
199 | rgb(var(--background-end-rgb)) 40%
200 | );
201 | z-index: 1;
202 | }
203 | }
204 |
205 | /* Tablet and Smaller Desktop */
206 | @media (min-width: 701px) and (max-width: 1120px) {
207 | .grid {
208 | grid-template-columns: repeat(2, 50%);
209 | }
210 | }
211 |
212 | @media (prefers-color-scheme: dark) {
213 | .vercelLogo {
214 | filter: invert(1);
215 | }
216 |
217 | .logo {
218 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
219 | }
220 | }
221 |
222 | @keyframes rotate {
223 | from {
224 | transform: rotate(360deg);
225 | }
226 | to {
227 | transform: rotate(0deg);
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/nextauth/src/components/atoms/MainNavbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import SiteNavbar from './SiteNavbar'
3 | import { getServerSession } from 'next-auth'
4 | import authOptions from '@/app/api/auth/authOptions'
5 |
6 | const MainNavbar = async() => {
7 | const session = await getServerSession(authOptions)
8 | return ()
9 | }
10 |
11 | export default MainNavbar
--------------------------------------------------------------------------------
/nextauth/src/components/atoms/PostCard.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { formatDate } from '@/utils'
3 | import { Avatar, Box, Paper, Stack, Typography } from '@mui/material'
4 | import Image from 'next/image'
5 | import Link from 'next/link'
6 | import React from 'react'
7 |
8 | const PostCard = ({post}) => {
9 | return (
10 |
11 |
12 |
15 |
16 |
17 |
18 |
20 | {post.title}
21 |
22 | {post.description}
23 |
24 |
25 | Posted on:
26 | {formatDate(post.createdAt)}
27 |
28 |
29 |
30 |
31 |
32 | {post?.user?.name}
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | export default PostCard
--------------------------------------------------------------------------------
/nextauth/src/components/atoms/SiteNavbar.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import * as React from "react";
3 | import AppBar from "@mui/material/AppBar";
4 | import Box from "@mui/material/Box";
5 | import Toolbar from "@mui/material/Toolbar";
6 | import IconButton from "@mui/material/IconButton";
7 | import Typography from "@mui/material/Typography";
8 | import Menu from "@mui/material/Menu";
9 | import MenuIcon from "@mui/icons-material/Menu";
10 | import Container from "@mui/material/Container";
11 | import Avatar from "@mui/material/Avatar";
12 | import Button from "@mui/material/Button";
13 | import Tooltip from "@mui/material/Tooltip";
14 | import MenuItem from "@mui/material/MenuItem";
15 | import AdbIcon from "@mui/icons-material/Adb";
16 | import { checkUserRole } from "@/utils";
17 | import Link from "next/link";
18 | import { Person } from "@mui/icons-material";
19 | import { signIn, signOut } from "next-auth/react";
20 | import { usePathname } from "next/navigation";
21 |
22 | const publicPages = [{ title: "Articles", url: "/articles" }];
23 |
24 | const adminPages = [
25 | { title: "Dashboard", url: "/dashboard" },
26 | { title: "Server Articles", url: "/articles" },
27 | { title: "Client Blog", url: "/blog" },
28 | { title: "Node API Posts", url: "/posts" },
29 | { title: "All Users", url: "/users" },
30 | ];
31 |
32 | const adminNavItems = [
33 | { title: "Dashboard", url: "/dashboard" },
34 | { title: "Profile", url: "/profile" },
35 | { title: "Create Post", url: "/create-post" },
36 | { title: "Logout", url: "/api/auth/signout" },
37 | ];
38 |
39 | const userPages = [
40 | { title: "Dashboard", url: "/dashboard" },
41 | { title: "Server Articles", url: "/articles" },
42 | { title: "Client Blog", url: "/blog" },
43 | { title: "Node API Posts", url: "/posts" },
44 | ];
45 |
46 | const userMenuItems = [
47 | { title: "Dashboard", url: "/dashboard" },
48 | { title: "Profile", url: "/profile" },
49 | { title: "Logout", url: "/api/auth/signout" },
50 | ];
51 |
52 | function SiteNavbar({ session }) {
53 | const [anchorElNav, setAnchorElNav] = React.useState(null);
54 | const [anchorElUser, setAnchorElUser] = React.useState(null);
55 | const pathname = usePathname()
56 |
57 |
58 | const handleOpenNavMenu = (event) => {
59 | setAnchorElNav(event.currentTarget);
60 | };
61 | const handleOpenUserMenu = (event) => {
62 | setAnchorElUser(event.currentTarget);
63 | };
64 |
65 | const handleCloseNavMenu = () => {
66 | setAnchorElNav(null);
67 | };
68 |
69 | const handleCloseUserMenu = () => {
70 | setAnchorElUser(null);
71 | };
72 |
73 |
74 | const checkRole = checkUserRole(session?.user?.role);
75 |
76 | const pages = checkRole.isAdminRole
77 | ? adminPages
78 | : checkRole.isUserRole
79 | ? userPages
80 | : publicPages;
81 |
82 | const settings = checkRole.isAdminRole
83 | ? adminNavItems
84 | : checkRole.isUserRole
85 | ? userMenuItems
86 | : [];
87 | return (
88 |
89 |
90 |
91 |
92 |
108 | LOGO
109 |
110 |
111 |
112 |
120 |
121 |
122 |
148 |
149 |
150 |
167 | LOGO
168 |
169 |
170 | {pages.map((page) => (
171 |
180 | ))}
181 |
182 |
183 | {!session ? (
184 |
185 |
186 | signIn()} sx={{ p: 0 }}>
187 |
188 |
189 |
190 |
191 |
192 |
193 | ) : (
194 |
195 |
196 |
197 |
198 |
199 |
200 |
222 |
223 | )}
224 |
225 |
226 |
227 | );
228 | }
229 | export default SiteNavbar;
230 |
--------------------------------------------------------------------------------
/nextauth/src/components/atoms/UsersTable.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import * as React from 'react';
3 | import Table from '@mui/material/Table';
4 | import TableBody from '@mui/material/TableBody';
5 | import TableCell from '@mui/material/TableCell';
6 | import TableContainer from '@mui/material/TableContainer';
7 | import TableHead from '@mui/material/TableHead';
8 | import TableRow from '@mui/material/TableRow';
9 | import Paper from '@mui/material/Paper';
10 | import { Avatar } from '@mui/material';
11 |
12 |
13 | export default function UsersTable({users}) {
14 | return (
15 |
16 |
17 |
18 |
19 | ID
20 | Avatar
21 | Name
22 | Email
23 | Role
24 |
25 |
26 |
27 | {users.map((row) => (
28 |
32 |
33 | {row.id}
34 |
35 |
36 |
37 |
38 | {row.name}
39 | {row.email}
40 | {row.role}
41 |
42 | ))}
43 |
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/nextauth/src/components/atoms/index.js:
--------------------------------------------------------------------------------
1 | export {default as MainNavbar } from './MainNavbar'
--------------------------------------------------------------------------------
/nextauth/src/components/pages/AdminDashboard.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Box, Container, Grid, Paper, Stack, Typography } from '@mui/material'
3 | import React from 'react'
4 |
5 | const items = [
6 | {title: "Users", total: 10000},
7 | {title: "Articles", total: 200},
8 | {title: "Visitors", total: 2000},
9 | {title: "Authors", total: 700},
10 | {title: "Comments", total: 1500},
11 | {title: "Reactions", total: 1200},
12 | {title: "Videos", total: 150},
13 | {title: "Ads", total: 77},
14 | ]
15 |
16 | const AdminDashboard = ({session}) => {
17 | return (
18 | theme.palette.grey[300], minHeight: "100vh"}}>
19 |
20 |
21 |
22 | Admin Dashboard
23 |
24 |
25 | Welcome {session?.user?.name}, {session?.user?.role}
26 |
27 |
28 |
29 | {
30 | items.map(item => (
31 |
32 |
33 |
34 | {item.title}
35 | {item.total}
36 |
37 |
38 |
39 | ))
40 | }
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | export default AdminDashboard
--------------------------------------------------------------------------------
/nextauth/src/components/pages/ArticlePage.js:
--------------------------------------------------------------------------------
1 | import { Box, Container, Grid, Typography } from '@mui/material'
2 | import React from 'react'
3 | import PostCard from '../atoms/PostCard'
4 |
5 | const ArticlePage = ({title, posts}) => {
6 | return (
7 |
8 |
9 | {title}
10 |
11 | {
12 | posts.map(post =>(
13 |
14 |
15 | ))
16 | }
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | export default ArticlePage
--------------------------------------------------------------------------------
/nextauth/src/components/pages/CreatePostPage.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { createNewPost } from '@/lib/post'
3 | import { PostZodSchema } from '@/schema'
4 | import { validateZodInput } from '@/validators'
5 | import { Box, Button, CircularProgress, Container, Grid, Paper, TextField, Typography } from '@mui/material'
6 | import React, { useState } from 'react'
7 |
8 |
9 | const photos = ["/post1.jpg", "/post2.jpg", "/post3.jpg", "/post4.jpg", "/post5.jpg", "/post6.jpg", "/post7.jpg", "/post8.jpg", "/post9.jpg", "/post10.jpg"]
10 |
11 | const getPhoto = () => {
12 | const randomNumber = Math.round(Math.random() * (photos.length - 1))
13 | return photos[randomNumber]
14 | }
15 |
16 | const initialState = { body: {title: "", description: "", content: ""}, message: "", isLoading: false}
17 |
18 | const CreatePostPage = ({session}) => {
19 | const [state, setState] = useState(initialState)
20 |
21 | const handleInputChange = event => {
22 | setState(prev => ({...prev, message: "", body: {...prev.body, [event.target.name]: event.target.value}}))
23 | }
24 |
25 | const handleSubmit = async(event) => {
26 | event.preventDefault()
27 | try {
28 | const photo = getPhoto()
29 | const parsedResult = validateZodInput({...state.body, photo}, PostZodSchema )
30 | if(parsedResult.isError) return setState(prev => ({...prev, message: parsedResult.message, }))
31 | setState(prev => ({...prev, isLoading: true}))
32 | const result = await createNewPost(parsedResult.data, session?.accessToken)
33 | if(!result.isError) return setState(() => ({...initialState, isLoading: false, message: result.message}))
34 | setState(prev => ({...prev, isLoading: false, message: result.message}))
35 | } catch (error) {
36 | setState(prev => ({...prev, isLoading: false, message: error.message}))
37 |
38 | }
39 | }
40 |
41 |
42 | return (
43 | theme.palette.grey[300], minHeight: "100vh", pb: 4}}>
44 |
45 |
46 | Create New Post
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | {state.message && state.message}
63 |
64 |
65 |
66 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | )
83 | }
84 |
85 | export default CreatePostPage
--------------------------------------------------------------------------------
/nextauth/src/components/pages/DashboardPage.js:
--------------------------------------------------------------------------------
1 | import { checkUserRole } from '@/utils'
2 | import React from 'react'
3 | import AdminDashboard from './AdminDashboard'
4 | import UserDashboard from './UserDashboard'
5 |
6 | const DashboardPage = ({session}) => {
7 | const checkRole = checkUserRole(session?.user?.role)
8 | if(checkRole.isAdminRole) return
9 | if(checkRole.isUserRole) return
10 | return null
11 | }
12 |
13 | export default DashboardPage
--------------------------------------------------------------------------------
/nextauth/src/components/pages/HomePage.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Box, Typography } from '@mui/material'
3 | import Image from 'next/image'
4 | import React from 'react'
5 |
6 | const HomePage = () => {
7 | return (
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | theme.palette.common.white, p: 4, fontSize: {lg: "3.75rem", md: 20, sm: 20, xs: 20}}}>
22 | NextJs 14 Authentication & Role Based Authorisation Using NextAuth with Mongodb Adapter
23 |
24 |
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | export default HomePage
--------------------------------------------------------------------------------
/nextauth/src/components/pages/ProfilePage.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Box, Container, Paper, Stack, Typography } from '@mui/material'
3 | import React from 'react'
4 |
5 | const ProfilePage = ({session}) => {
6 | return (
7 | theme.palette.grey[300], height: "100vh"}}>
8 |
9 | My Profile
10 |
11 |
12 | Name:
13 | {session?.user?.name}
14 |
15 |
16 |
17 | Email:
18 | {session?.user?.email}
19 |
20 |
21 |
22 | Role:
23 | {session?.user?.role}
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default ProfilePage
--------------------------------------------------------------------------------
/nextauth/src/components/pages/UserDashboard.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Box, Container, Grid, Paper, Stack, Typography } from '@mui/material'
3 | import React from 'react'
4 |
5 | const items = [
6 | {title: "Followers", total: 10000},
7 | {title: "Following", total: 1400},
8 | {title: "Comments", total: 760},
9 | {title: "Likes", total: 1350}
10 | ]
11 |
12 | const UserDashboard = ({session}) => {
13 | return (
14 | theme.palette.grey[300], height: "100vh"}}>
15 |
16 |
17 |
18 | User Dashboard
19 |
20 |
21 | Welcome {session?.user?.name}, {session?.user?.role}
22 |
23 |
24 |
25 | {
26 | items.map(item => (
27 |
28 |
29 |
30 | {item.title}
31 | {item.total}
32 |
33 |
34 |
35 | ))
36 | }
37 |
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | export default UserDashboard
--------------------------------------------------------------------------------
/nextauth/src/components/pages/UsersPage.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Box, Container, Paper, Stack, Typography } from '@mui/material'
3 | import React from 'react'
4 | import UsersTable from '../atoms/UsersTable'
5 |
6 |
7 |
8 | const UsersPage = ({users}) => {
9 | return (
10 | theme.palette.grey[300], height: "100vh", pt: 8}}>
11 |
12 | All Users
13 |
14 |
15 |
16 | )
17 | }
18 |
19 | export default UsersPage
--------------------------------------------------------------------------------
/nextauth/src/components/pages/index.js:
--------------------------------------------------------------------------------
1 | export { default as HomePage } from './HomePage'
2 | export { default as CreatePostPage } from './CreatePostPage'
3 | export { default as ArticlePage } from './ArticlePage'
4 | export { default as DashboardPage } from './DashboardPage'
5 | export { default as ProfilePage } from './ProfilePage'
6 | export { default as UsersPage } from './UsersPage'
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/nextauth/src/config/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export const axiosPublic = axios.create({baseURL: process.env.NEXT_PUBLIC_BASE_URL, withCredentials: true})
4 |
5 | export const authAxios = axios.create({baseURL: process.env.NEXT_PUBLIC_BASE_URL, withCredentials: true})
6 |
7 | // intercept the request
8 | authAxios.interceptors.request.use(async(config) => {
9 | const accessToken = authAxios.accessToken
10 | if(!accessToken) return Promise.reject(new Error("No access token provided"))
11 | config.headers = {
12 | "Authorization": "Bearer " + accessToken,
13 | "Accept": "application/json"
14 | }
15 | return config
16 | }, error => Promise.reject(new Error(error)))
17 |
18 | // intercept the response
19 |
20 | authAxios.interceptors.response.use(async(response) => {
21 | return response
22 | }, async function(error) {
23 | const originalRequest = error.config
24 | if(error?.response?.status === 403 && !originalRequest._retry){
25 | try {
26 | originalRequest._retry = true
27 | const result = await refreshAccessToken()
28 | authAxios.accessToken = result?.accessToken
29 | return authAxios(originalRequest)
30 | } catch (err) {
31 | if(err?.response && err?.response?.data){
32 | return Promise.reject(err?.response?.data)
33 | }
34 | return Promise.reject(err)
35 | }
36 | }
37 | return Promise.reject(error)
38 | })
39 |
40 |
41 |
42 | export const authNodeAxios = axios.create({baseURL: process.env.NEXT_PUBLIC_BASE_NODE_URL, withCredentials: true})
43 |
44 | // intercept the request
45 | authNodeAxios.interceptors.request.use(async(config) => {
46 | const accessToken = authNodeAxios.accessToken
47 | if(!accessToken) return Promise.reject(new Error("No access token provided"))
48 | config.headers = {
49 | "Authorization": "Bearer " + accessToken,
50 | "Accept": "application/json"
51 | }
52 | return config
53 | }, error => Promise.reject(new Error(error)))
54 |
55 | // intercept the response
56 |
57 | authNodeAxios.interceptors.response.use(async(response) => {
58 | return response
59 | }, async function(error) {
60 | const originalRequest = error.config
61 | if(error?.response?.status === 403 && !originalRequest._retry){
62 | try {
63 | originalRequest._retry = true
64 | const result = await refreshAccessToken()
65 | authNodeAxios.accessToken = result?.accessToken
66 | return authNodeAxios(originalRequest)
67 | } catch (err) {
68 | if(err?.response && err?.response?.data){
69 | return Promise.reject(err?.response?.data)
70 | }
71 | return Promise.reject(err)
72 | }
73 | }
74 | return Promise.reject(error)
75 | })
76 |
77 |
78 | const refreshAccessToken = async() => {
79 | try {
80 | const response = await axiosPublic.get("/auth/session")
81 | return response.data
82 | } catch (error) {
83 | throw error
84 | }
85 | }
--------------------------------------------------------------------------------
/nextauth/src/db/index.js:
--------------------------------------------------------------------------------
1 | // This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
2 | import { MongoClient } from "mongodb"
3 |
4 | if (!process.env.MONGODB_URI) {
5 | throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
6 | }
7 |
8 | const uri = process.env.MONGODB_URI
9 | const options = {}
10 |
11 | let client
12 | let clientPromise
13 |
14 | if (process.env.NODE_ENV === "development") {
15 | // In development mode, use a global variable so that the value
16 | // is preserved across module reloads caused by HMR (Hot Module Replacement).
17 | if (!global._mongoClientPromise) {
18 | client = new MongoClient(uri, options)
19 | global._mongoClientPromise = client.connect()
20 | }
21 | clientPromise = global._mongoClientPromise
22 | } else {
23 | // In production mode, it's best to not use a global variable.
24 | client = new MongoClient(uri, options)
25 | clientPromise = client.connect()
26 | }
27 |
28 | // Export a module-scoped MongoClient promise. By doing this in a
29 | // separate module, the client can be shared across functions.
30 | export default clientPromise
--------------------------------------------------------------------------------
/nextauth/src/hooks/index.js:
--------------------------------------------------------------------------------
1 | import { useSession } from "next-auth/react"
2 |
3 | export const useAuthUser = (url = "/") => {
4 | const {data: session, status} = useSession()
5 | const isLoading = status === "loading"
6 | const isAuthenticated = status === "authenticated"
7 | const isUnAuthenticated = status === "unauthenticated"
8 |
9 | return { session, isLoading, isAuthenticated, isUnAuthenticated}
10 |
11 | }
--------------------------------------------------------------------------------
/nextauth/src/lib/post.js:
--------------------------------------------------------------------------------
1 | import { authAxios, authNodeAxios, axiosPublic } from "@/config"
2 | import { getErrorMessage } from "@/utils"
3 |
4 | export const createNewPost = async(body, accessToken) => {
5 | try {
6 | authAxios.accessToken = accessToken
7 | const result = await authAxios.post("/posts", body)
8 | return {isError: false, data: result.data, message: 'Post created successfully'}
9 | } catch (error) {
10 | return {isError: true, data:null, message: getErrorMessage(error)}
11 | }
12 | }
13 |
14 | export const getPublicPosts = async() => {
15 | try {
16 | const result = await axiosPublic.get("/posts?type=public")
17 | return result.data
18 | } catch (error) {
19 | throw new Error(error.message)
20 | }
21 | }
22 |
23 | export const getAllPosts = async(accessToken) => {
24 | try {
25 | authAxios.accessToken = accessToken
26 | const result = await authAxios.get("/posts")
27 | return result.data
28 | } catch (error) {
29 | throw new Error(error.message)
30 | }
31 | }
32 |
33 | export const getAllNodeAPIPosts = async(accessToken) => {
34 | try {
35 | authNodeAxios.accessToken = accessToken
36 | const result = await authNodeAxios.get("/posts")
37 | return result.data
38 | } catch (error) {
39 | throw new Error(error.message)
40 | }
41 | }
--------------------------------------------------------------------------------
/nextauth/src/lib/user.js:
--------------------------------------------------------------------------------
1 | import { authAxios } from "@/config"
2 |
3 | export const getAllUsers = async(accessToken) => {
4 | try {
5 | authAxios.accessToken = accessToken
6 | const result = await authAxios.get("/users")
7 | return result.data
8 | } catch (error) {
9 | throw new Error(error.message)
10 | }
11 | }
--------------------------------------------------------------------------------
/nextauth/src/middleware.js:
--------------------------------------------------------------------------------
1 | import {headers} from 'next/headers'
2 | import { decryptString, encryptString, verifyJwtToken } from './utils'
3 | import { NextResponse } from 'next/server'
4 |
5 | export const config = {
6 | matcher: ["/api/users", "/api/users/:path*", "/api/posts", "/api/posts/:path*"]
7 | }
8 |
9 | const publicPostsRegexPattern = /^\/api\/posts\?type=public$/;
10 |
11 | export async function middleware(request){
12 | try {
13 | const nextUrl = request.nextUrl
14 |
15 | const pathname = nextUrl.pathname
16 |
17 | const search = nextUrl?.search
18 |
19 | const path = `${pathname}${search}`
20 |
21 | const httpMethod = request.method
22 |
23 | // /api/posts?type=public
24 | // public posts
25 | if(publicPostsRegexPattern.test(path) && httpMethod === "GET"){
26 | request.headers.set("user", null)
27 | return NextResponse.next({request})
28 | }
29 |
30 | // authenticate the request
31 |
32 | const headerList = headers()
33 | const authorization = headerList.get('Authorization')
34 | if(!authorization) return Response.json("You are not authenticated, please login to continue", {status: 401})
35 | const accessToken = authorization.split("Bearer ")[1]
36 |
37 | if(!accessToken) return Response.json("You are not authenticated, please login to continue", {status: 401})
38 |
39 | const result = await verifyJwtToken(accessToken)
40 |
41 | console.log("verifyJwtToken ", result)
42 |
43 | if(result.isError) return Response.json("You are not authorised to access this resource, please login to continue", {status: 403})
44 |
45 | const decrypted = decryptString(result?.data?.user)
46 |
47 | if(!decrypted) return Response.json("You are not authorised to access this resource, please login to continue", {status: 403})
48 |
49 | request.headers.set("user", JSON.stringify(decrypted))
50 |
51 | return NextResponse.next({request})
52 | } catch (error) {
53 | return Response.json(error.message, {status: 500})
54 | }
55 | }
--------------------------------------------------------------------------------
/nextauth/src/models/account.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const { Schema } = mongoose
4 |
5 | const AccountSchema = new Schema({
6 | id: {type: Schema.ObjectId},
7 | provider: {type: String, default: "nextApp"},
8 | type: {type: String, default: "credentials"},
9 | providerAccountId: {type: String, default: "782389209ej"},
10 | access_token: {type: String, default: "ehdhjdiuuiweuie"},
11 | scope: {type: String, default: "all"},
12 | userId: {type: Schema.ObjectId, ref: "User"}
13 | })
14 |
15 | const AccountModel = mongoose.models.Account || mongoose.model("Account", AccountSchema)
16 |
17 | export default AccountModel
18 |
--------------------------------------------------------------------------------
/nextauth/src/models/index.js:
--------------------------------------------------------------------------------
1 | export { default as UserModel } from './user'
2 | export { default as AccountModel } from './account'
3 | export { default as PostModel } from './post'
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/nextauth/src/models/post.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const { Schema } = mongoose
4 |
5 | const PostSchema = new Schema({
6 | id: {type: Schema.ObjectId},
7 | title: String,
8 | slug: String,
9 | description: String,
10 | content: String,
11 | photo: String,
12 | user: {type: Schema.ObjectId, ref: "User"}
13 | }, {timestamps: true})
14 |
15 | const PostModel = mongoose.models.Post || mongoose.model("Post", PostSchema)
16 |
17 | export default PostModel
--------------------------------------------------------------------------------
/nextauth/src/models/user.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const { Schema } = mongoose
4 |
5 | mongoose.connect(process.env.MONGODB_URI)
6 |
7 | mongoose.Promise = global.Promise
8 |
9 | const UserSchema = new Schema({
10 | id: {type: Schema.ObjectId},
11 | name: String,
12 | email: String,
13 | password: String,
14 | role: String,
15 | avatar: {type: String, default: "/avatar3.png"},
16 | isVerifiedEmail: {type: Boolean, default: false},
17 | isBlocked: {type: Boolean, default: false}
18 | }, {timestamps: true})
19 |
20 | const UserModel = mongoose.models.User || mongoose.model("User", UserSchema)
21 |
22 | export default UserModel
--------------------------------------------------------------------------------
/nextauth/src/providers/index.js:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { SessionProvider } from "next-auth/react"
3 |
4 | export const NextAuthSessionProvider = ({children}) => {
5 | return
6 | {children}
7 |
8 | }
--------------------------------------------------------------------------------
/nextauth/src/schema/index.js:
--------------------------------------------------------------------------------
1 | import z from 'zod'
2 |
3 | export const UserZodSchema = z.object({
4 | id: z.string({required_error: "User Id must be provided"}).optional(),
5 |
6 | email: z.string({required_error: "Email must be provided"})
7 | .email(z.string({required_error: "Email must be a valid email address"}))
8 | .min(3, { message: "Enter a valid email address"}),
9 |
10 | password: z.string({required_error: "Password must be provided"})
11 | .min(8, { message: "Password must be at least 8 characters"}).
12 | refine((pw) => /[0-9]/.test(pw), "Password must contain a number")
13 |
14 | })
15 |
16 | export const PostZodSchema = z.object({
17 |
18 | title: z.string({required_error: "Title cannot be empty"})
19 | .min(10, { message: "Title must be at least 10 characters"})
20 | .max(150,{ message: "Title is too long, Max is 150 characters"}),
21 |
22 | description: z.string({required_error: "Description cannot be empty"})
23 | .min(10, { message: "Description must be at least 10 characters"})
24 | .max(255,{ message: "Description is too long, Max is 255 characters"}),
25 |
26 | content: z.string({required_error: "Content cannot be empty"})
27 | .min(10, { message: "Content must be at least 10 characters"}),
28 |
29 | photo: z.string({required_error: "Photo url cannot be empty"})
30 | .min(2, { message: "Photo url must be provided"})
31 | })
--------------------------------------------------------------------------------
/nextauth/src/services/index.js:
--------------------------------------------------------------------------------
1 | export { default as UserService } from './user'
2 | export { default as PostService } from './post'
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nextauth/src/services/post.js:
--------------------------------------------------------------------------------
1 | import { PostModel } from "@/models"
2 | import { nanoid } from "nanoid"
3 | import slugify from "slugify"
4 |
5 | const createNewPost = async(body) => {
6 | try {
7 | const title = `${body.title}-${nanoid(5)}`
8 | const slug = slugify(title, {replacement: '-'})
9 | const result = await PostModel.create({...body, slug})
10 | return {isError: false, data: result, message: "success" }
11 | } catch (error) {
12 | return {isError: true, data: null, message: error.message }
13 |
14 | }
15 | }
16 |
17 | const getAllPosts = async() =>{
18 | try {
19 | const result = await PostModel.find({}).populate({path: "user", select: ["name", "email", "avatar", "_id"]}).lean().exec()
20 | const data = result?.map(post => ({...post, id: post._id?.toString(), user: {...post.user, id: post?.user?._id.toString()} }))
21 | return {isError: false, data, message: "success" }
22 | } catch (error) {
23 | return {isError: true, data: null, message: error.message }
24 | }
25 | }
26 |
27 | const PostService = { createNewPost, getAllPosts}
28 |
29 | export default PostService
30 |
31 |
--------------------------------------------------------------------------------
/nextauth/src/services/user.js:
--------------------------------------------------------------------------------
1 | const { UserModel, AccountModel } = require("@/models");
2 | import bcrypt from "bcrypt";
3 |
4 |
5 | const createUser = async(body) => {
6 | try {
7 | const result = await UserModel.findOne({email: body.email}).lean().exec();
8 | const name = body?.email?.split('@')[0]
9 | if(!result){
10 | const hashPassword = await bcrypt.hash(body.password, 10)
11 | const newUser = await UserModel.create({...body, name, role: "user", password: hashPassword})
12 | await AccountModel.create({userId: newUser._id})
13 | return { isError: false, data: {id: newUser?._id?.toString(), name: newUser.name, role: newUser.role, email: newUser.email, avatar: newUser.avatar}, message: "Success"}
14 | }
15 | // if account is blocked
16 | if(result?.isBlocked) return {isError: true, data: null, message: "This account is suspended, please contact the support"}
17 | // check for password
18 | const isMatch = await bcrypt.compare(body.password, result?.password)
19 | if(!isMatch) return {isError: true, data: null, message: "Wrong Email/Password provided"}
20 |
21 | return {isError: false, data: {id: result?._id?.toString(), name: result.name, role: result.role, email: result.email, avatar: result.avatar}, message: "Success"}
22 |
23 | } catch (error) {
24 | return {isError: true, data: null, message: "An error occurred trying to process the request, please try again later"};
25 | }
26 | }
27 |
28 |
29 | const getCurrentUser = async(id) => {
30 | try {
31 | const result = await UserModel.findOne({_id: id}).lean().exec();
32 | if(!result) return {isError: true, data: null, message: "User does not exist"};
33 | // if account is blocked
34 | if(result?.isBlocked) return {isError: true, data: null, message: "This account is suspended, please contact the support"}
35 | return {isError: false, data: {id: result?._id?.toString(), name: result.name, role: result.role, email: result.email, avatar: result.avatar}, message: "Success"}
36 |
37 | } catch (error) {
38 | return {isError: true, data: null, message: "An error occurred trying to process the request, please try again later"};
39 | }
40 | }
41 |
42 |
43 | const getAllUsers = async() => {
44 | try {
45 | const results = await UserModel.find({}).lean().exec()
46 | const data = results?.map(user => ({id: user?._id?.toString(), name: user.name, email: user.email, role: user.role, avatar: user.avatar}))
47 | return {isError: false, data, message: "success"}
48 | } catch (error) {
49 | return {isError: true, data: null, message: "An error occurred trying to process the request, please try again later"};
50 | }
51 | }
52 |
53 |
54 | const UserService = { createUser, getCurrentUser, getAllUsers }
55 |
56 | export default UserService
--------------------------------------------------------------------------------
/nextauth/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import * as joseJwt from "jose"
2 | import { nanoid } from "nanoid"
3 | import CryptoJS from "crypto-js"
4 |
5 | export const signJwtToken = async(payload) => {
6 | const privateKey = process.env.ACCESS_TOKEN_PRIVATE_KEY
7 | const expiresIn = "5m"
8 | const token = await new joseJwt.SignJWT(payload)
9 | .setProtectedHeader({alg: "HS256"})
10 | .setJti(nanoid())
11 | .setIssuedAt()
12 | .setExpirationTime(expiresIn)
13 | .sign(new TextEncoder().encode(privateKey))
14 | return token
15 | }
16 |
17 | export const verifyJwtToken = async(token) => {
18 | try {
19 | const privateKey = process.env.ACCESS_TOKEN_PRIVATE_KEY
20 | const result = await joseJwt.jwtVerify(token, new TextEncoder().encode(privateKey))
21 | return {data: result.payload, isError: false, message: "success" }
22 | } catch (error) {
23 | return { data: null, isError: true, message:error.message }
24 | }
25 | }
26 |
27 | export const decodeJwtToken = (token) => {
28 | try {
29 | const payload = joseJwt.decodeJwt(token)
30 | return {data: payload, isError: false, message: "success" }
31 | } catch (error) {
32 | return { data: null, isError: true, message:error.message }
33 | }
34 | }
35 |
36 | export const encryptString = (str) => {
37 | const secretKey = CryptoJS.enc.Utf8.parse(process.env.CRYPTO_ENCRYPTION_KEY)
38 | const iv = CryptoJS.enc.Utf8.parse(process.env.CRYPTO_IV_KEY)
39 | const encrypted = CryptoJS.AES.encrypt(str, secretKey, {iv})
40 | const hexString = CryptoJS.enc.Hex.stringify(encrypted.ciphertext)
41 | return hexString
42 | }
43 |
44 | export const decryptString = (hexString) => {
45 | const secretKey = CryptoJS.enc.Utf8.parse(process.env.CRYPTO_ENCRYPTION_KEY)
46 | const iv = CryptoJS.enc.Utf8.parse(process.env.CRYPTO_IV_KEY)
47 | const ciphertext = CryptoJS.enc.Hex.parse(hexString)
48 | const decrypted = CryptoJS.AES.decrypt({ciphertext}, secretKey, {iv}).toString(CryptoJS.enc.Utf8)
49 | console.log("decrypted: ", decrypted)
50 | return JSON.parse(decrypted)
51 | }
52 |
53 | export const checkUserRole =(role) => {
54 | return {
55 | isAdminRole: role === 'admin',
56 | isUserRole: role === 'user',
57 | }
58 | }
59 |
60 | export const formatDate = (date) => {
61 | return new Intl.DateTimeFormat(undefined, { dateStyle: "short", timeStyle: "short", }).format(new Date(date))
62 | }
63 |
64 | export const getErrorMessage = (error) => {
65 | let message = error.message
66 | if(error?.response?.data){
67 | message = error.response.data
68 | }
69 | return message
70 | }
--------------------------------------------------------------------------------
/nextauth/src/validators/index.js:
--------------------------------------------------------------------------------
1 | export const validateZodInput = (payload, schema, isArrayErrorResult = false) => {
2 | try {
3 | const parseResult = schema.parse(payload)
4 | return { isError: false, data: parseResult, message: "success" }
5 | } catch (error) {
6 | const errors = {}
7 | let issues = error.issues
8 | const message = issues?.map(issue => issue.message)?.join("\r\n")
9 | if(isArrayErrorResult){
10 | issues = issues.map(issue => issue.message)
11 | return { isError: true, message, data: issues}
12 | }
13 | for (const issue of issues){
14 | errors[issue?.path[0]] = issue.message
15 | }
16 | return {isError: true, message, data: errors}
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/node-server/.env:
--------------------------------------------------------------------------------
1 | CRYPTO_ENCRYPTION_KEY = "e883993939ijjdiuduuehyrhuuer"
2 |
3 | CRYPTO_IV_KEY = "8289eudjjdue88e9e0e"
4 |
5 | ACCESS_TOKEN_PRIVATE_KEY = "ek8qvPCLavjSOlSjR/nHGS/iJV6fOhg/MftYpOMMIDbMKC+U0grPFtu6q8jg/dJBJEjTL2dafb+0ADqeSjsF5w0EzpY5PIWIXvP52anY1hpH3FjYeeFe2PS2jmukwb1J
6 | d7zhlh9i7MA55QU+7LH7PYupK1k23KMP6sZheblAD17HTg=="
7 |
8 | MONGODB_URI = ""
9 |
--------------------------------------------------------------------------------
/node-server/controllers/index.mjs:
--------------------------------------------------------------------------------
1 | export { default as PostController } from './post.mjs'
--------------------------------------------------------------------------------
/node-server/controllers/post.mjs:
--------------------------------------------------------------------------------
1 | import PostService from "../services/post.mjs"
2 |
3 | export const getPostsController = async (req, res) => {
4 | const result = await PostService.getAllPosts()
5 | console.log("Node API",result)
6 | if(result.isError) return res.status(result.status).send(result.message)
7 | return res.status(result.status).json(result.data)
8 | }
9 |
10 | const PostController = { getPostsController }
11 |
12 | export default PostController
--------------------------------------------------------------------------------
/node-server/db/index.mjs:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 |
3 | const connectDb = async() => {
4 | try {
5 | await mongoose.connect(process.env.MONGODB_URI)
6 | console.log("Mongodb database connection established")
7 | } catch (error) {
8 | console.log("Mongodb database connection error ", error.messsage)
9 | }
10 | }
11 |
12 | export default connectDb
--------------------------------------------------------------------------------
/node-server/index.mjs:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv'
2 | dotenv.config()
3 | import express from "express";
4 | import http from "http";
5 | import cors from "cors";
6 | import compression from "compression";
7 | import connectDb from './db/index.mjs';
8 | import { postRoutes } from './routes/index.mjs';
9 |
10 | const app = express();
11 |
12 | app.use(cors({origin: true}))
13 | app.use(compression());
14 | app.use(express.json());
15 | app.use(express.urlencoded({ extended: false}));
16 |
17 | app.use("/api/", postRoutes)
18 |
19 | // connect db
20 | connectDb()
21 |
22 |
23 | app.use((req, res) => {
24 | res.send("Server is up & running")
25 | })
26 |
27 | const server = http.createServer(app);
28 |
29 | server.listen(5001, () => {
30 | console.log(`Server running on port 5001 - http://localhost:5001`)
31 | })
--------------------------------------------------------------------------------
/node-server/middleware/index.mjs:
--------------------------------------------------------------------------------
1 | import { decryptString, verifyJwtToken } from "../utils/index.mjs";
2 |
3 | export const authenticateUser = async (req, res, next) => {
4 | try {
5 | const authorization = req.headers["authorization"]
6 | if (!authorization){
7 | return res.status(401).send("You are not authenticated, please login to continue");
8 | }
9 | const accessToken = authorization.split("Bearer ")[1];
10 |
11 | if (!accessToken){
12 | return res.status(401).send("You are not authenticated, please login to continue");
13 | }
14 |
15 | const result = await verifyJwtToken(accessToken);
16 |
17 | console.log("verifyJwtToken ", result);
18 |
19 | if (result.isError){
20 | return res.status(403).send("You are not authorised to access this resource, please login to continue");
21 | }
22 |
23 | const decrypted = decryptString(result?.data?.user);
24 |
25 | if (!decrypted){
26 | return res.status(401).send("You are not authorised to access this resource, please login to continue");
27 | }
28 | req.user = decrypted
29 | return next()
30 | } catch (error) {
31 | return res.status(500).send("Sorry, something went wrong. Please, try again later")
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/node-server/models/index.mjs:
--------------------------------------------------------------------------------
1 | export {default as PostModel } from './post.mjs'
2 | export {default as UserModel } from './user.mjs'
3 |
--------------------------------------------------------------------------------
/node-server/models/post.mjs:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const { Schema } = mongoose
4 |
5 | const PostSchema = new Schema({
6 | id: {type: Schema.ObjectId},
7 | title: String,
8 | slug: String,
9 | description: String,
10 | content: String,
11 | photo: String,
12 | user: {type: Schema.ObjectId, ref: "User"}
13 | }, {timestamps: true})
14 |
15 | const PostModel = mongoose.model("Post", PostSchema)
16 |
17 | export default PostModel
--------------------------------------------------------------------------------
/node-server/models/user.mjs:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const { Schema } = mongoose
4 |
5 | const UserSchema = new Schema({
6 | id: {type: Schema.Types.ObjectId},
7 | name: String,
8 | email: String,
9 | password: String,
10 | role: String,
11 | avatar: {type: String, default: "/avatar3.png"},
12 | isVerifiedEmail: {type: Boolean, default: false},
13 | isBlocked: {type: Boolean, default: false}
14 | }, {timestamps: true})
15 |
16 | const UserModel = mongoose.model("User", UserSchema)
17 |
18 | export default UserModel
--------------------------------------------------------------------------------
/node-server/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "server",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "bcrypt": "^5.1.1",
13 | "compression": "^1.7.4",
14 | "cors": "^2.8.5",
15 | "crypto-js": "^4.2.0",
16 | "dotenv": "^16.3.1",
17 | "express": "^4.18.2",
18 | "jose": "^5.1.1",
19 | "mongoose": "^8.0.1",
20 | "nanoid": "^5.0.3"
21 | },
22 | "devDependencies": {
23 | "nodemon": "^3.0.1"
24 | }
25 | },
26 | "node_modules/@mapbox/node-pre-gyp": {
27 | "version": "1.0.11",
28 | "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
29 | "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
30 | "dependencies": {
31 | "detect-libc": "^2.0.0",
32 | "https-proxy-agent": "^5.0.0",
33 | "make-dir": "^3.1.0",
34 | "node-fetch": "^2.6.7",
35 | "nopt": "^5.0.0",
36 | "npmlog": "^5.0.1",
37 | "rimraf": "^3.0.2",
38 | "semver": "^7.3.5",
39 | "tar": "^6.1.11"
40 | },
41 | "bin": {
42 | "node-pre-gyp": "bin/node-pre-gyp"
43 | }
44 | },
45 | "node_modules/@mongodb-js/saslprep": {
46 | "version": "1.1.1",
47 | "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz",
48 | "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==",
49 | "dependencies": {
50 | "sparse-bitfield": "^3.0.3"
51 | }
52 | },
53 | "node_modules/@types/node": {
54 | "version": "20.9.1",
55 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.1.tgz",
56 | "integrity": "sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==",
57 | "dependencies": {
58 | "undici-types": "~5.26.4"
59 | }
60 | },
61 | "node_modules/@types/webidl-conversions": {
62 | "version": "7.0.3",
63 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
64 | "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
65 | },
66 | "node_modules/@types/whatwg-url": {
67 | "version": "8.2.2",
68 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
69 | "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
70 | "dependencies": {
71 | "@types/node": "*",
72 | "@types/webidl-conversions": "*"
73 | }
74 | },
75 | "node_modules/abbrev": {
76 | "version": "1.1.1",
77 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
78 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
79 | },
80 | "node_modules/accepts": {
81 | "version": "1.3.8",
82 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
83 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
84 | "dependencies": {
85 | "mime-types": "~2.1.34",
86 | "negotiator": "0.6.3"
87 | },
88 | "engines": {
89 | "node": ">= 0.6"
90 | }
91 | },
92 | "node_modules/agent-base": {
93 | "version": "6.0.2",
94 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
95 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
96 | "dependencies": {
97 | "debug": "4"
98 | },
99 | "engines": {
100 | "node": ">= 6.0.0"
101 | }
102 | },
103 | "node_modules/agent-base/node_modules/debug": {
104 | "version": "4.3.4",
105 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
106 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
107 | "dependencies": {
108 | "ms": "2.1.2"
109 | },
110 | "engines": {
111 | "node": ">=6.0"
112 | },
113 | "peerDependenciesMeta": {
114 | "supports-color": {
115 | "optional": true
116 | }
117 | }
118 | },
119 | "node_modules/agent-base/node_modules/ms": {
120 | "version": "2.1.2",
121 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
122 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
123 | },
124 | "node_modules/ansi-regex": {
125 | "version": "5.0.1",
126 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
127 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
128 | "engines": {
129 | "node": ">=8"
130 | }
131 | },
132 | "node_modules/anymatch": {
133 | "version": "3.1.3",
134 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
135 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
136 | "dev": true,
137 | "dependencies": {
138 | "normalize-path": "^3.0.0",
139 | "picomatch": "^2.0.4"
140 | },
141 | "engines": {
142 | "node": ">= 8"
143 | }
144 | },
145 | "node_modules/aproba": {
146 | "version": "2.0.0",
147 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
148 | "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
149 | },
150 | "node_modules/are-we-there-yet": {
151 | "version": "2.0.0",
152 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
153 | "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
154 | "dependencies": {
155 | "delegates": "^1.0.0",
156 | "readable-stream": "^3.6.0"
157 | },
158 | "engines": {
159 | "node": ">=10"
160 | }
161 | },
162 | "node_modules/array-flatten": {
163 | "version": "1.1.1",
164 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
165 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
166 | },
167 | "node_modules/balanced-match": {
168 | "version": "1.0.2",
169 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
170 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
171 | },
172 | "node_modules/bcrypt": {
173 | "version": "5.1.1",
174 | "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
175 | "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
176 | "hasInstallScript": true,
177 | "dependencies": {
178 | "@mapbox/node-pre-gyp": "^1.0.11",
179 | "node-addon-api": "^5.0.0"
180 | },
181 | "engines": {
182 | "node": ">= 10.0.0"
183 | }
184 | },
185 | "node_modules/binary-extensions": {
186 | "version": "2.2.0",
187 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
188 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
189 | "dev": true,
190 | "engines": {
191 | "node": ">=8"
192 | }
193 | },
194 | "node_modules/body-parser": {
195 | "version": "1.20.1",
196 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
197 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
198 | "dependencies": {
199 | "bytes": "3.1.2",
200 | "content-type": "~1.0.4",
201 | "debug": "2.6.9",
202 | "depd": "2.0.0",
203 | "destroy": "1.2.0",
204 | "http-errors": "2.0.0",
205 | "iconv-lite": "0.4.24",
206 | "on-finished": "2.4.1",
207 | "qs": "6.11.0",
208 | "raw-body": "2.5.1",
209 | "type-is": "~1.6.18",
210 | "unpipe": "1.0.0"
211 | },
212 | "engines": {
213 | "node": ">= 0.8",
214 | "npm": "1.2.8000 || >= 1.4.16"
215 | }
216 | },
217 | "node_modules/body-parser/node_modules/bytes": {
218 | "version": "3.1.2",
219 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
220 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
221 | "engines": {
222 | "node": ">= 0.8"
223 | }
224 | },
225 | "node_modules/brace-expansion": {
226 | "version": "1.1.11",
227 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
228 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
229 | "dependencies": {
230 | "balanced-match": "^1.0.0",
231 | "concat-map": "0.0.1"
232 | }
233 | },
234 | "node_modules/braces": {
235 | "version": "3.0.2",
236 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
237 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
238 | "dev": true,
239 | "dependencies": {
240 | "fill-range": "^7.0.1"
241 | },
242 | "engines": {
243 | "node": ">=8"
244 | }
245 | },
246 | "node_modules/bson": {
247 | "version": "6.2.0",
248 | "resolved": "https://registry.npmjs.org/bson/-/bson-6.2.0.tgz",
249 | "integrity": "sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==",
250 | "engines": {
251 | "node": ">=16.20.1"
252 | }
253 | },
254 | "node_modules/bytes": {
255 | "version": "3.0.0",
256 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
257 | "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
258 | "engines": {
259 | "node": ">= 0.8"
260 | }
261 | },
262 | "node_modules/call-bind": {
263 | "version": "1.0.5",
264 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
265 | "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
266 | "dependencies": {
267 | "function-bind": "^1.1.2",
268 | "get-intrinsic": "^1.2.1",
269 | "set-function-length": "^1.1.1"
270 | },
271 | "funding": {
272 | "url": "https://github.com/sponsors/ljharb"
273 | }
274 | },
275 | "node_modules/chokidar": {
276 | "version": "3.5.3",
277 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
278 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
279 | "dev": true,
280 | "funding": [
281 | {
282 | "type": "individual",
283 | "url": "https://paulmillr.com/funding/"
284 | }
285 | ],
286 | "dependencies": {
287 | "anymatch": "~3.1.2",
288 | "braces": "~3.0.2",
289 | "glob-parent": "~5.1.2",
290 | "is-binary-path": "~2.1.0",
291 | "is-glob": "~4.0.1",
292 | "normalize-path": "~3.0.0",
293 | "readdirp": "~3.6.0"
294 | },
295 | "engines": {
296 | "node": ">= 8.10.0"
297 | },
298 | "optionalDependencies": {
299 | "fsevents": "~2.3.2"
300 | }
301 | },
302 | "node_modules/chownr": {
303 | "version": "2.0.0",
304 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
305 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
306 | "engines": {
307 | "node": ">=10"
308 | }
309 | },
310 | "node_modules/color-support": {
311 | "version": "1.1.3",
312 | "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
313 | "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
314 | "bin": {
315 | "color-support": "bin.js"
316 | }
317 | },
318 | "node_modules/compressible": {
319 | "version": "2.0.18",
320 | "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
321 | "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
322 | "dependencies": {
323 | "mime-db": ">= 1.43.0 < 2"
324 | },
325 | "engines": {
326 | "node": ">= 0.6"
327 | }
328 | },
329 | "node_modules/compression": {
330 | "version": "1.7.4",
331 | "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
332 | "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
333 | "dependencies": {
334 | "accepts": "~1.3.5",
335 | "bytes": "3.0.0",
336 | "compressible": "~2.0.16",
337 | "debug": "2.6.9",
338 | "on-headers": "~1.0.2",
339 | "safe-buffer": "5.1.2",
340 | "vary": "~1.1.2"
341 | },
342 | "engines": {
343 | "node": ">= 0.8.0"
344 | }
345 | },
346 | "node_modules/concat-map": {
347 | "version": "0.0.1",
348 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
349 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
350 | },
351 | "node_modules/console-control-strings": {
352 | "version": "1.1.0",
353 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
354 | "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
355 | },
356 | "node_modules/content-disposition": {
357 | "version": "0.5.4",
358 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
359 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
360 | "dependencies": {
361 | "safe-buffer": "5.2.1"
362 | },
363 | "engines": {
364 | "node": ">= 0.6"
365 | }
366 | },
367 | "node_modules/content-disposition/node_modules/safe-buffer": {
368 | "version": "5.2.1",
369 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
370 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
371 | "funding": [
372 | {
373 | "type": "github",
374 | "url": "https://github.com/sponsors/feross"
375 | },
376 | {
377 | "type": "patreon",
378 | "url": "https://www.patreon.com/feross"
379 | },
380 | {
381 | "type": "consulting",
382 | "url": "https://feross.org/support"
383 | }
384 | ]
385 | },
386 | "node_modules/content-type": {
387 | "version": "1.0.5",
388 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
389 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
390 | "engines": {
391 | "node": ">= 0.6"
392 | }
393 | },
394 | "node_modules/cookie": {
395 | "version": "0.5.0",
396 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
397 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
398 | "engines": {
399 | "node": ">= 0.6"
400 | }
401 | },
402 | "node_modules/cookie-signature": {
403 | "version": "1.0.6",
404 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
405 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
406 | },
407 | "node_modules/cors": {
408 | "version": "2.8.5",
409 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
410 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
411 | "dependencies": {
412 | "object-assign": "^4",
413 | "vary": "^1"
414 | },
415 | "engines": {
416 | "node": ">= 0.10"
417 | }
418 | },
419 | "node_modules/crypto-js": {
420 | "version": "4.2.0",
421 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
422 | "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
423 | },
424 | "node_modules/debug": {
425 | "version": "2.6.9",
426 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
427 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
428 | "dependencies": {
429 | "ms": "2.0.0"
430 | }
431 | },
432 | "node_modules/define-data-property": {
433 | "version": "1.1.1",
434 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
435 | "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
436 | "dependencies": {
437 | "get-intrinsic": "^1.2.1",
438 | "gopd": "^1.0.1",
439 | "has-property-descriptors": "^1.0.0"
440 | },
441 | "engines": {
442 | "node": ">= 0.4"
443 | }
444 | },
445 | "node_modules/delegates": {
446 | "version": "1.0.0",
447 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
448 | "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
449 | },
450 | "node_modules/depd": {
451 | "version": "2.0.0",
452 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
453 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
454 | "engines": {
455 | "node": ">= 0.8"
456 | }
457 | },
458 | "node_modules/destroy": {
459 | "version": "1.2.0",
460 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
461 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
462 | "engines": {
463 | "node": ">= 0.8",
464 | "npm": "1.2.8000 || >= 1.4.16"
465 | }
466 | },
467 | "node_modules/detect-libc": {
468 | "version": "2.0.2",
469 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
470 | "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
471 | "engines": {
472 | "node": ">=8"
473 | }
474 | },
475 | "node_modules/dotenv": {
476 | "version": "16.3.1",
477 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
478 | "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
479 | "engines": {
480 | "node": ">=12"
481 | },
482 | "funding": {
483 | "url": "https://github.com/motdotla/dotenv?sponsor=1"
484 | }
485 | },
486 | "node_modules/ee-first": {
487 | "version": "1.1.1",
488 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
489 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
490 | },
491 | "node_modules/emoji-regex": {
492 | "version": "8.0.0",
493 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
494 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
495 | },
496 | "node_modules/encodeurl": {
497 | "version": "1.0.2",
498 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
499 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
500 | "engines": {
501 | "node": ">= 0.8"
502 | }
503 | },
504 | "node_modules/escape-html": {
505 | "version": "1.0.3",
506 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
507 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
508 | },
509 | "node_modules/etag": {
510 | "version": "1.8.1",
511 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
512 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
513 | "engines": {
514 | "node": ">= 0.6"
515 | }
516 | },
517 | "node_modules/express": {
518 | "version": "4.18.2",
519 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
520 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
521 | "dependencies": {
522 | "accepts": "~1.3.8",
523 | "array-flatten": "1.1.1",
524 | "body-parser": "1.20.1",
525 | "content-disposition": "0.5.4",
526 | "content-type": "~1.0.4",
527 | "cookie": "0.5.0",
528 | "cookie-signature": "1.0.6",
529 | "debug": "2.6.9",
530 | "depd": "2.0.0",
531 | "encodeurl": "~1.0.2",
532 | "escape-html": "~1.0.3",
533 | "etag": "~1.8.1",
534 | "finalhandler": "1.2.0",
535 | "fresh": "0.5.2",
536 | "http-errors": "2.0.0",
537 | "merge-descriptors": "1.0.1",
538 | "methods": "~1.1.2",
539 | "on-finished": "2.4.1",
540 | "parseurl": "~1.3.3",
541 | "path-to-regexp": "0.1.7",
542 | "proxy-addr": "~2.0.7",
543 | "qs": "6.11.0",
544 | "range-parser": "~1.2.1",
545 | "safe-buffer": "5.2.1",
546 | "send": "0.18.0",
547 | "serve-static": "1.15.0",
548 | "setprototypeof": "1.2.0",
549 | "statuses": "2.0.1",
550 | "type-is": "~1.6.18",
551 | "utils-merge": "1.0.1",
552 | "vary": "~1.1.2"
553 | },
554 | "engines": {
555 | "node": ">= 0.10.0"
556 | }
557 | },
558 | "node_modules/express/node_modules/safe-buffer": {
559 | "version": "5.2.1",
560 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
561 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
562 | "funding": [
563 | {
564 | "type": "github",
565 | "url": "https://github.com/sponsors/feross"
566 | },
567 | {
568 | "type": "patreon",
569 | "url": "https://www.patreon.com/feross"
570 | },
571 | {
572 | "type": "consulting",
573 | "url": "https://feross.org/support"
574 | }
575 | ]
576 | },
577 | "node_modules/fill-range": {
578 | "version": "7.0.1",
579 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
580 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
581 | "dev": true,
582 | "dependencies": {
583 | "to-regex-range": "^5.0.1"
584 | },
585 | "engines": {
586 | "node": ">=8"
587 | }
588 | },
589 | "node_modules/finalhandler": {
590 | "version": "1.2.0",
591 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
592 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
593 | "dependencies": {
594 | "debug": "2.6.9",
595 | "encodeurl": "~1.0.2",
596 | "escape-html": "~1.0.3",
597 | "on-finished": "2.4.1",
598 | "parseurl": "~1.3.3",
599 | "statuses": "2.0.1",
600 | "unpipe": "~1.0.0"
601 | },
602 | "engines": {
603 | "node": ">= 0.8"
604 | }
605 | },
606 | "node_modules/forwarded": {
607 | "version": "0.2.0",
608 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
609 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
610 | "engines": {
611 | "node": ">= 0.6"
612 | }
613 | },
614 | "node_modules/fresh": {
615 | "version": "0.5.2",
616 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
617 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
618 | "engines": {
619 | "node": ">= 0.6"
620 | }
621 | },
622 | "node_modules/fs-minipass": {
623 | "version": "2.1.0",
624 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
625 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
626 | "dependencies": {
627 | "minipass": "^3.0.0"
628 | },
629 | "engines": {
630 | "node": ">= 8"
631 | }
632 | },
633 | "node_modules/fs-minipass/node_modules/minipass": {
634 | "version": "3.3.6",
635 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
636 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
637 | "dependencies": {
638 | "yallist": "^4.0.0"
639 | },
640 | "engines": {
641 | "node": ">=8"
642 | }
643 | },
644 | "node_modules/fs.realpath": {
645 | "version": "1.0.0",
646 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
647 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
648 | },
649 | "node_modules/fsevents": {
650 | "version": "2.3.3",
651 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
652 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
653 | "dev": true,
654 | "hasInstallScript": true,
655 | "optional": true,
656 | "os": [
657 | "darwin"
658 | ],
659 | "engines": {
660 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
661 | }
662 | },
663 | "node_modules/function-bind": {
664 | "version": "1.1.2",
665 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
666 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
667 | "funding": {
668 | "url": "https://github.com/sponsors/ljharb"
669 | }
670 | },
671 | "node_modules/gauge": {
672 | "version": "3.0.2",
673 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
674 | "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
675 | "dependencies": {
676 | "aproba": "^1.0.3 || ^2.0.0",
677 | "color-support": "^1.1.2",
678 | "console-control-strings": "^1.0.0",
679 | "has-unicode": "^2.0.1",
680 | "object-assign": "^4.1.1",
681 | "signal-exit": "^3.0.0",
682 | "string-width": "^4.2.3",
683 | "strip-ansi": "^6.0.1",
684 | "wide-align": "^1.1.2"
685 | },
686 | "engines": {
687 | "node": ">=10"
688 | }
689 | },
690 | "node_modules/get-intrinsic": {
691 | "version": "1.2.2",
692 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
693 | "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
694 | "dependencies": {
695 | "function-bind": "^1.1.2",
696 | "has-proto": "^1.0.1",
697 | "has-symbols": "^1.0.3",
698 | "hasown": "^2.0.0"
699 | },
700 | "funding": {
701 | "url": "https://github.com/sponsors/ljharb"
702 | }
703 | },
704 | "node_modules/glob": {
705 | "version": "7.2.3",
706 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
707 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
708 | "dependencies": {
709 | "fs.realpath": "^1.0.0",
710 | "inflight": "^1.0.4",
711 | "inherits": "2",
712 | "minimatch": "^3.1.1",
713 | "once": "^1.3.0",
714 | "path-is-absolute": "^1.0.0"
715 | },
716 | "engines": {
717 | "node": "*"
718 | },
719 | "funding": {
720 | "url": "https://github.com/sponsors/isaacs"
721 | }
722 | },
723 | "node_modules/glob-parent": {
724 | "version": "5.1.2",
725 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
726 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
727 | "dev": true,
728 | "dependencies": {
729 | "is-glob": "^4.0.1"
730 | },
731 | "engines": {
732 | "node": ">= 6"
733 | }
734 | },
735 | "node_modules/gopd": {
736 | "version": "1.0.1",
737 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
738 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
739 | "dependencies": {
740 | "get-intrinsic": "^1.1.3"
741 | },
742 | "funding": {
743 | "url": "https://github.com/sponsors/ljharb"
744 | }
745 | },
746 | "node_modules/has-flag": {
747 | "version": "3.0.0",
748 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
749 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
750 | "dev": true,
751 | "engines": {
752 | "node": ">=4"
753 | }
754 | },
755 | "node_modules/has-property-descriptors": {
756 | "version": "1.0.1",
757 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
758 | "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
759 | "dependencies": {
760 | "get-intrinsic": "^1.2.2"
761 | },
762 | "funding": {
763 | "url": "https://github.com/sponsors/ljharb"
764 | }
765 | },
766 | "node_modules/has-proto": {
767 | "version": "1.0.1",
768 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
769 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
770 | "engines": {
771 | "node": ">= 0.4"
772 | },
773 | "funding": {
774 | "url": "https://github.com/sponsors/ljharb"
775 | }
776 | },
777 | "node_modules/has-symbols": {
778 | "version": "1.0.3",
779 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
780 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
781 | "engines": {
782 | "node": ">= 0.4"
783 | },
784 | "funding": {
785 | "url": "https://github.com/sponsors/ljharb"
786 | }
787 | },
788 | "node_modules/has-unicode": {
789 | "version": "2.0.1",
790 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
791 | "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
792 | },
793 | "node_modules/hasown": {
794 | "version": "2.0.0",
795 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
796 | "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
797 | "dependencies": {
798 | "function-bind": "^1.1.2"
799 | },
800 | "engines": {
801 | "node": ">= 0.4"
802 | }
803 | },
804 | "node_modules/http-errors": {
805 | "version": "2.0.0",
806 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
807 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
808 | "dependencies": {
809 | "depd": "2.0.0",
810 | "inherits": "2.0.4",
811 | "setprototypeof": "1.2.0",
812 | "statuses": "2.0.1",
813 | "toidentifier": "1.0.1"
814 | },
815 | "engines": {
816 | "node": ">= 0.8"
817 | }
818 | },
819 | "node_modules/https-proxy-agent": {
820 | "version": "5.0.1",
821 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
822 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
823 | "dependencies": {
824 | "agent-base": "6",
825 | "debug": "4"
826 | },
827 | "engines": {
828 | "node": ">= 6"
829 | }
830 | },
831 | "node_modules/https-proxy-agent/node_modules/debug": {
832 | "version": "4.3.4",
833 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
834 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
835 | "dependencies": {
836 | "ms": "2.1.2"
837 | },
838 | "engines": {
839 | "node": ">=6.0"
840 | },
841 | "peerDependenciesMeta": {
842 | "supports-color": {
843 | "optional": true
844 | }
845 | }
846 | },
847 | "node_modules/https-proxy-agent/node_modules/ms": {
848 | "version": "2.1.2",
849 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
850 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
851 | },
852 | "node_modules/iconv-lite": {
853 | "version": "0.4.24",
854 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
855 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
856 | "dependencies": {
857 | "safer-buffer": ">= 2.1.2 < 3"
858 | },
859 | "engines": {
860 | "node": ">=0.10.0"
861 | }
862 | },
863 | "node_modules/ignore-by-default": {
864 | "version": "1.0.1",
865 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
866 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
867 | "dev": true
868 | },
869 | "node_modules/inflight": {
870 | "version": "1.0.6",
871 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
872 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
873 | "dependencies": {
874 | "once": "^1.3.0",
875 | "wrappy": "1"
876 | }
877 | },
878 | "node_modules/inherits": {
879 | "version": "2.0.4",
880 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
881 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
882 | },
883 | "node_modules/ipaddr.js": {
884 | "version": "1.9.1",
885 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
886 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
887 | "engines": {
888 | "node": ">= 0.10"
889 | }
890 | },
891 | "node_modules/is-binary-path": {
892 | "version": "2.1.0",
893 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
894 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
895 | "dev": true,
896 | "dependencies": {
897 | "binary-extensions": "^2.0.0"
898 | },
899 | "engines": {
900 | "node": ">=8"
901 | }
902 | },
903 | "node_modules/is-extglob": {
904 | "version": "2.1.1",
905 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
906 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
907 | "dev": true,
908 | "engines": {
909 | "node": ">=0.10.0"
910 | }
911 | },
912 | "node_modules/is-fullwidth-code-point": {
913 | "version": "3.0.0",
914 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
915 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
916 | "engines": {
917 | "node": ">=8"
918 | }
919 | },
920 | "node_modules/is-glob": {
921 | "version": "4.0.3",
922 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
923 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
924 | "dev": true,
925 | "dependencies": {
926 | "is-extglob": "^2.1.1"
927 | },
928 | "engines": {
929 | "node": ">=0.10.0"
930 | }
931 | },
932 | "node_modules/is-number": {
933 | "version": "7.0.0",
934 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
935 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
936 | "dev": true,
937 | "engines": {
938 | "node": ">=0.12.0"
939 | }
940 | },
941 | "node_modules/jose": {
942 | "version": "5.1.1",
943 | "resolved": "https://registry.npmjs.org/jose/-/jose-5.1.1.tgz",
944 | "integrity": "sha512-bfB+lNxowY49LfrBO0ITUn93JbUhxUN8I11K6oI5hJu/G6PO6fEUddVLjqdD0cQ9SXIHWXuWh7eJYwZF7Z0N/g==",
945 | "funding": {
946 | "url": "https://github.com/sponsors/panva"
947 | }
948 | },
949 | "node_modules/kareem": {
950 | "version": "2.5.1",
951 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz",
952 | "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==",
953 | "engines": {
954 | "node": ">=12.0.0"
955 | }
956 | },
957 | "node_modules/lru-cache": {
958 | "version": "6.0.0",
959 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
960 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
961 | "dependencies": {
962 | "yallist": "^4.0.0"
963 | },
964 | "engines": {
965 | "node": ">=10"
966 | }
967 | },
968 | "node_modules/make-dir": {
969 | "version": "3.1.0",
970 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
971 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
972 | "dependencies": {
973 | "semver": "^6.0.0"
974 | },
975 | "engines": {
976 | "node": ">=8"
977 | },
978 | "funding": {
979 | "url": "https://github.com/sponsors/sindresorhus"
980 | }
981 | },
982 | "node_modules/make-dir/node_modules/semver": {
983 | "version": "6.3.1",
984 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
985 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
986 | "bin": {
987 | "semver": "bin/semver.js"
988 | }
989 | },
990 | "node_modules/media-typer": {
991 | "version": "0.3.0",
992 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
993 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
994 | "engines": {
995 | "node": ">= 0.6"
996 | }
997 | },
998 | "node_modules/memory-pager": {
999 | "version": "1.5.0",
1000 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
1001 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
1002 | },
1003 | "node_modules/merge-descriptors": {
1004 | "version": "1.0.1",
1005 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
1006 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
1007 | },
1008 | "node_modules/methods": {
1009 | "version": "1.1.2",
1010 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
1011 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
1012 | "engines": {
1013 | "node": ">= 0.6"
1014 | }
1015 | },
1016 | "node_modules/mime": {
1017 | "version": "1.6.0",
1018 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
1019 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
1020 | "bin": {
1021 | "mime": "cli.js"
1022 | },
1023 | "engines": {
1024 | "node": ">=4"
1025 | }
1026 | },
1027 | "node_modules/mime-db": {
1028 | "version": "1.52.0",
1029 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1030 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1031 | "engines": {
1032 | "node": ">= 0.6"
1033 | }
1034 | },
1035 | "node_modules/mime-types": {
1036 | "version": "2.1.35",
1037 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1038 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1039 | "dependencies": {
1040 | "mime-db": "1.52.0"
1041 | },
1042 | "engines": {
1043 | "node": ">= 0.6"
1044 | }
1045 | },
1046 | "node_modules/minimatch": {
1047 | "version": "3.1.2",
1048 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
1049 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
1050 | "dependencies": {
1051 | "brace-expansion": "^1.1.7"
1052 | },
1053 | "engines": {
1054 | "node": "*"
1055 | }
1056 | },
1057 | "node_modules/minipass": {
1058 | "version": "5.0.0",
1059 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
1060 | "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
1061 | "engines": {
1062 | "node": ">=8"
1063 | }
1064 | },
1065 | "node_modules/minizlib": {
1066 | "version": "2.1.2",
1067 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
1068 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
1069 | "dependencies": {
1070 | "minipass": "^3.0.0",
1071 | "yallist": "^4.0.0"
1072 | },
1073 | "engines": {
1074 | "node": ">= 8"
1075 | }
1076 | },
1077 | "node_modules/minizlib/node_modules/minipass": {
1078 | "version": "3.3.6",
1079 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
1080 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
1081 | "dependencies": {
1082 | "yallist": "^4.0.0"
1083 | },
1084 | "engines": {
1085 | "node": ">=8"
1086 | }
1087 | },
1088 | "node_modules/mkdirp": {
1089 | "version": "1.0.4",
1090 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
1091 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
1092 | "bin": {
1093 | "mkdirp": "bin/cmd.js"
1094 | },
1095 | "engines": {
1096 | "node": ">=10"
1097 | }
1098 | },
1099 | "node_modules/mongodb": {
1100 | "version": "6.2.0",
1101 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.2.0.tgz",
1102 | "integrity": "sha512-d7OSuGjGWDZ5usZPqfvb36laQ9CPhnWkAGHT61x5P95p/8nMVeH8asloMwW6GcYFeB0Vj4CB/1wOTDG2RA9BFA==",
1103 | "dependencies": {
1104 | "@mongodb-js/saslprep": "^1.1.0",
1105 | "bson": "^6.2.0",
1106 | "mongodb-connection-string-url": "^2.6.0"
1107 | },
1108 | "engines": {
1109 | "node": ">=16.20.1"
1110 | },
1111 | "peerDependencies": {
1112 | "@aws-sdk/credential-providers": "^3.188.0",
1113 | "@mongodb-js/zstd": "^1.1.0",
1114 | "gcp-metadata": "^5.2.0",
1115 | "kerberos": "^2.0.1",
1116 | "mongodb-client-encryption": ">=6.0.0 <7",
1117 | "snappy": "^7.2.2",
1118 | "socks": "^2.7.1"
1119 | },
1120 | "peerDependenciesMeta": {
1121 | "@aws-sdk/credential-providers": {
1122 | "optional": true
1123 | },
1124 | "@mongodb-js/zstd": {
1125 | "optional": true
1126 | },
1127 | "gcp-metadata": {
1128 | "optional": true
1129 | },
1130 | "kerberos": {
1131 | "optional": true
1132 | },
1133 | "mongodb-client-encryption": {
1134 | "optional": true
1135 | },
1136 | "snappy": {
1137 | "optional": true
1138 | },
1139 | "socks": {
1140 | "optional": true
1141 | }
1142 | }
1143 | },
1144 | "node_modules/mongodb-connection-string-url": {
1145 | "version": "2.6.0",
1146 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
1147 | "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
1148 | "dependencies": {
1149 | "@types/whatwg-url": "^8.2.1",
1150 | "whatwg-url": "^11.0.0"
1151 | }
1152 | },
1153 | "node_modules/mongodb-connection-string-url/node_modules/tr46": {
1154 | "version": "3.0.0",
1155 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
1156 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
1157 | "dependencies": {
1158 | "punycode": "^2.1.1"
1159 | },
1160 | "engines": {
1161 | "node": ">=12"
1162 | }
1163 | },
1164 | "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
1165 | "version": "7.0.0",
1166 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1167 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1168 | "engines": {
1169 | "node": ">=12"
1170 | }
1171 | },
1172 | "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
1173 | "version": "11.0.0",
1174 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
1175 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
1176 | "dependencies": {
1177 | "tr46": "^3.0.0",
1178 | "webidl-conversions": "^7.0.0"
1179 | },
1180 | "engines": {
1181 | "node": ">=12"
1182 | }
1183 | },
1184 | "node_modules/mongoose": {
1185 | "version": "8.0.1",
1186 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.0.1.tgz",
1187 | "integrity": "sha512-O3TJrtLCt4H1eGf2HoHGcnOcCTWloQkpmIP3hA9olybX3OX2KUjdIIq135HD5paGjZEDJYKn9fw4eH5N477zqQ==",
1188 | "dependencies": {
1189 | "bson": "^6.2.0",
1190 | "kareem": "2.5.1",
1191 | "mongodb": "6.2.0",
1192 | "mpath": "0.9.0",
1193 | "mquery": "5.0.0",
1194 | "ms": "2.1.3",
1195 | "sift": "16.0.1"
1196 | },
1197 | "engines": {
1198 | "node": ">=16.20.1"
1199 | },
1200 | "funding": {
1201 | "type": "opencollective",
1202 | "url": "https://opencollective.com/mongoose"
1203 | }
1204 | },
1205 | "node_modules/mongoose/node_modules/ms": {
1206 | "version": "2.1.3",
1207 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1208 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1209 | },
1210 | "node_modules/mpath": {
1211 | "version": "0.9.0",
1212 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
1213 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
1214 | "engines": {
1215 | "node": ">=4.0.0"
1216 | }
1217 | },
1218 | "node_modules/mquery": {
1219 | "version": "5.0.0",
1220 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
1221 | "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
1222 | "dependencies": {
1223 | "debug": "4.x"
1224 | },
1225 | "engines": {
1226 | "node": ">=14.0.0"
1227 | }
1228 | },
1229 | "node_modules/mquery/node_modules/debug": {
1230 | "version": "4.3.4",
1231 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
1232 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
1233 | "dependencies": {
1234 | "ms": "2.1.2"
1235 | },
1236 | "engines": {
1237 | "node": ">=6.0"
1238 | },
1239 | "peerDependenciesMeta": {
1240 | "supports-color": {
1241 | "optional": true
1242 | }
1243 | }
1244 | },
1245 | "node_modules/mquery/node_modules/ms": {
1246 | "version": "2.1.2",
1247 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1248 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
1249 | },
1250 | "node_modules/ms": {
1251 | "version": "2.0.0",
1252 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1253 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
1254 | },
1255 | "node_modules/nanoid": {
1256 | "version": "5.0.3",
1257 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.3.tgz",
1258 | "integrity": "sha512-I7X2b22cxA4LIHXPSqbBCEQSL+1wv8TuoefejsX4HFWyC6jc5JG7CEaxOltiKjc1M+YCS2YkrZZcj4+dytw9GA==",
1259 | "funding": [
1260 | {
1261 | "type": "github",
1262 | "url": "https://github.com/sponsors/ai"
1263 | }
1264 | ],
1265 | "bin": {
1266 | "nanoid": "bin/nanoid.js"
1267 | },
1268 | "engines": {
1269 | "node": "^18 || >=20"
1270 | }
1271 | },
1272 | "node_modules/negotiator": {
1273 | "version": "0.6.3",
1274 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1275 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
1276 | "engines": {
1277 | "node": ">= 0.6"
1278 | }
1279 | },
1280 | "node_modules/node-addon-api": {
1281 | "version": "5.1.0",
1282 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
1283 | "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
1284 | },
1285 | "node_modules/node-fetch": {
1286 | "version": "2.7.0",
1287 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
1288 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
1289 | "dependencies": {
1290 | "whatwg-url": "^5.0.0"
1291 | },
1292 | "engines": {
1293 | "node": "4.x || >=6.0.0"
1294 | },
1295 | "peerDependencies": {
1296 | "encoding": "^0.1.0"
1297 | },
1298 | "peerDependenciesMeta": {
1299 | "encoding": {
1300 | "optional": true
1301 | }
1302 | }
1303 | },
1304 | "node_modules/nodemon": {
1305 | "version": "3.0.1",
1306 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz",
1307 | "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==",
1308 | "dev": true,
1309 | "dependencies": {
1310 | "chokidar": "^3.5.2",
1311 | "debug": "^3.2.7",
1312 | "ignore-by-default": "^1.0.1",
1313 | "minimatch": "^3.1.2",
1314 | "pstree.remy": "^1.1.8",
1315 | "semver": "^7.5.3",
1316 | "simple-update-notifier": "^2.0.0",
1317 | "supports-color": "^5.5.0",
1318 | "touch": "^3.1.0",
1319 | "undefsafe": "^2.0.5"
1320 | },
1321 | "bin": {
1322 | "nodemon": "bin/nodemon.js"
1323 | },
1324 | "engines": {
1325 | "node": ">=10"
1326 | },
1327 | "funding": {
1328 | "type": "opencollective",
1329 | "url": "https://opencollective.com/nodemon"
1330 | }
1331 | },
1332 | "node_modules/nodemon/node_modules/debug": {
1333 | "version": "3.2.7",
1334 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
1335 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
1336 | "dev": true,
1337 | "dependencies": {
1338 | "ms": "^2.1.1"
1339 | }
1340 | },
1341 | "node_modules/nodemon/node_modules/ms": {
1342 | "version": "2.1.3",
1343 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1344 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1345 | "dev": true
1346 | },
1347 | "node_modules/nopt": {
1348 | "version": "5.0.0",
1349 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
1350 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
1351 | "dependencies": {
1352 | "abbrev": "1"
1353 | },
1354 | "bin": {
1355 | "nopt": "bin/nopt.js"
1356 | },
1357 | "engines": {
1358 | "node": ">=6"
1359 | }
1360 | },
1361 | "node_modules/normalize-path": {
1362 | "version": "3.0.0",
1363 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1364 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1365 | "dev": true,
1366 | "engines": {
1367 | "node": ">=0.10.0"
1368 | }
1369 | },
1370 | "node_modules/npmlog": {
1371 | "version": "5.0.1",
1372 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
1373 | "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
1374 | "dependencies": {
1375 | "are-we-there-yet": "^2.0.0",
1376 | "console-control-strings": "^1.1.0",
1377 | "gauge": "^3.0.0",
1378 | "set-blocking": "^2.0.0"
1379 | }
1380 | },
1381 | "node_modules/object-assign": {
1382 | "version": "4.1.1",
1383 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1384 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1385 | "engines": {
1386 | "node": ">=0.10.0"
1387 | }
1388 | },
1389 | "node_modules/object-inspect": {
1390 | "version": "1.13.1",
1391 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
1392 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
1393 | "funding": {
1394 | "url": "https://github.com/sponsors/ljharb"
1395 | }
1396 | },
1397 | "node_modules/on-finished": {
1398 | "version": "2.4.1",
1399 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1400 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1401 | "dependencies": {
1402 | "ee-first": "1.1.1"
1403 | },
1404 | "engines": {
1405 | "node": ">= 0.8"
1406 | }
1407 | },
1408 | "node_modules/on-headers": {
1409 | "version": "1.0.2",
1410 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
1411 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
1412 | "engines": {
1413 | "node": ">= 0.8"
1414 | }
1415 | },
1416 | "node_modules/once": {
1417 | "version": "1.4.0",
1418 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1419 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1420 | "dependencies": {
1421 | "wrappy": "1"
1422 | }
1423 | },
1424 | "node_modules/parseurl": {
1425 | "version": "1.3.3",
1426 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1427 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1428 | "engines": {
1429 | "node": ">= 0.8"
1430 | }
1431 | },
1432 | "node_modules/path-is-absolute": {
1433 | "version": "1.0.1",
1434 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1435 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
1436 | "engines": {
1437 | "node": ">=0.10.0"
1438 | }
1439 | },
1440 | "node_modules/path-to-regexp": {
1441 | "version": "0.1.7",
1442 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
1443 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
1444 | },
1445 | "node_modules/picomatch": {
1446 | "version": "2.3.1",
1447 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1448 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1449 | "dev": true,
1450 | "engines": {
1451 | "node": ">=8.6"
1452 | },
1453 | "funding": {
1454 | "url": "https://github.com/sponsors/jonschlinkert"
1455 | }
1456 | },
1457 | "node_modules/proxy-addr": {
1458 | "version": "2.0.7",
1459 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1460 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1461 | "dependencies": {
1462 | "forwarded": "0.2.0",
1463 | "ipaddr.js": "1.9.1"
1464 | },
1465 | "engines": {
1466 | "node": ">= 0.10"
1467 | }
1468 | },
1469 | "node_modules/pstree.remy": {
1470 | "version": "1.1.8",
1471 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
1472 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
1473 | "dev": true
1474 | },
1475 | "node_modules/punycode": {
1476 | "version": "2.3.1",
1477 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1478 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1479 | "engines": {
1480 | "node": ">=6"
1481 | }
1482 | },
1483 | "node_modules/qs": {
1484 | "version": "6.11.0",
1485 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
1486 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
1487 | "dependencies": {
1488 | "side-channel": "^1.0.4"
1489 | },
1490 | "engines": {
1491 | "node": ">=0.6"
1492 | },
1493 | "funding": {
1494 | "url": "https://github.com/sponsors/ljharb"
1495 | }
1496 | },
1497 | "node_modules/range-parser": {
1498 | "version": "1.2.1",
1499 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1500 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1501 | "engines": {
1502 | "node": ">= 0.6"
1503 | }
1504 | },
1505 | "node_modules/raw-body": {
1506 | "version": "2.5.1",
1507 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
1508 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
1509 | "dependencies": {
1510 | "bytes": "3.1.2",
1511 | "http-errors": "2.0.0",
1512 | "iconv-lite": "0.4.24",
1513 | "unpipe": "1.0.0"
1514 | },
1515 | "engines": {
1516 | "node": ">= 0.8"
1517 | }
1518 | },
1519 | "node_modules/raw-body/node_modules/bytes": {
1520 | "version": "3.1.2",
1521 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
1522 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
1523 | "engines": {
1524 | "node": ">= 0.8"
1525 | }
1526 | },
1527 | "node_modules/readable-stream": {
1528 | "version": "3.6.2",
1529 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
1530 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
1531 | "dependencies": {
1532 | "inherits": "^2.0.3",
1533 | "string_decoder": "^1.1.1",
1534 | "util-deprecate": "^1.0.1"
1535 | },
1536 | "engines": {
1537 | "node": ">= 6"
1538 | }
1539 | },
1540 | "node_modules/readdirp": {
1541 | "version": "3.6.0",
1542 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1543 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1544 | "dev": true,
1545 | "dependencies": {
1546 | "picomatch": "^2.2.1"
1547 | },
1548 | "engines": {
1549 | "node": ">=8.10.0"
1550 | }
1551 | },
1552 | "node_modules/rimraf": {
1553 | "version": "3.0.2",
1554 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
1555 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
1556 | "dependencies": {
1557 | "glob": "^7.1.3"
1558 | },
1559 | "bin": {
1560 | "rimraf": "bin.js"
1561 | },
1562 | "funding": {
1563 | "url": "https://github.com/sponsors/isaacs"
1564 | }
1565 | },
1566 | "node_modules/safe-buffer": {
1567 | "version": "5.1.2",
1568 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1569 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
1570 | },
1571 | "node_modules/safer-buffer": {
1572 | "version": "2.1.2",
1573 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1574 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1575 | },
1576 | "node_modules/semver": {
1577 | "version": "7.5.4",
1578 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
1579 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
1580 | "dependencies": {
1581 | "lru-cache": "^6.0.0"
1582 | },
1583 | "bin": {
1584 | "semver": "bin/semver.js"
1585 | },
1586 | "engines": {
1587 | "node": ">=10"
1588 | }
1589 | },
1590 | "node_modules/send": {
1591 | "version": "0.18.0",
1592 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
1593 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
1594 | "dependencies": {
1595 | "debug": "2.6.9",
1596 | "depd": "2.0.0",
1597 | "destroy": "1.2.0",
1598 | "encodeurl": "~1.0.2",
1599 | "escape-html": "~1.0.3",
1600 | "etag": "~1.8.1",
1601 | "fresh": "0.5.2",
1602 | "http-errors": "2.0.0",
1603 | "mime": "1.6.0",
1604 | "ms": "2.1.3",
1605 | "on-finished": "2.4.1",
1606 | "range-parser": "~1.2.1",
1607 | "statuses": "2.0.1"
1608 | },
1609 | "engines": {
1610 | "node": ">= 0.8.0"
1611 | }
1612 | },
1613 | "node_modules/send/node_modules/ms": {
1614 | "version": "2.1.3",
1615 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1616 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1617 | },
1618 | "node_modules/serve-static": {
1619 | "version": "1.15.0",
1620 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
1621 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
1622 | "dependencies": {
1623 | "encodeurl": "~1.0.2",
1624 | "escape-html": "~1.0.3",
1625 | "parseurl": "~1.3.3",
1626 | "send": "0.18.0"
1627 | },
1628 | "engines": {
1629 | "node": ">= 0.8.0"
1630 | }
1631 | },
1632 | "node_modules/set-blocking": {
1633 | "version": "2.0.0",
1634 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
1635 | "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
1636 | },
1637 | "node_modules/set-function-length": {
1638 | "version": "1.1.1",
1639 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
1640 | "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
1641 | "dependencies": {
1642 | "define-data-property": "^1.1.1",
1643 | "get-intrinsic": "^1.2.1",
1644 | "gopd": "^1.0.1",
1645 | "has-property-descriptors": "^1.0.0"
1646 | },
1647 | "engines": {
1648 | "node": ">= 0.4"
1649 | }
1650 | },
1651 | "node_modules/setprototypeof": {
1652 | "version": "1.2.0",
1653 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1654 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1655 | },
1656 | "node_modules/side-channel": {
1657 | "version": "1.0.4",
1658 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
1659 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
1660 | "dependencies": {
1661 | "call-bind": "^1.0.0",
1662 | "get-intrinsic": "^1.0.2",
1663 | "object-inspect": "^1.9.0"
1664 | },
1665 | "funding": {
1666 | "url": "https://github.com/sponsors/ljharb"
1667 | }
1668 | },
1669 | "node_modules/sift": {
1670 | "version": "16.0.1",
1671 | "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz",
1672 | "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ=="
1673 | },
1674 | "node_modules/signal-exit": {
1675 | "version": "3.0.7",
1676 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
1677 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
1678 | },
1679 | "node_modules/simple-update-notifier": {
1680 | "version": "2.0.0",
1681 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
1682 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
1683 | "dev": true,
1684 | "dependencies": {
1685 | "semver": "^7.5.3"
1686 | },
1687 | "engines": {
1688 | "node": ">=10"
1689 | }
1690 | },
1691 | "node_modules/sparse-bitfield": {
1692 | "version": "3.0.3",
1693 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1694 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1695 | "dependencies": {
1696 | "memory-pager": "^1.0.2"
1697 | }
1698 | },
1699 | "node_modules/statuses": {
1700 | "version": "2.0.1",
1701 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1702 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1703 | "engines": {
1704 | "node": ">= 0.8"
1705 | }
1706 | },
1707 | "node_modules/string_decoder": {
1708 | "version": "1.3.0",
1709 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
1710 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
1711 | "dependencies": {
1712 | "safe-buffer": "~5.2.0"
1713 | }
1714 | },
1715 | "node_modules/string_decoder/node_modules/safe-buffer": {
1716 | "version": "5.2.1",
1717 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1718 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1719 | "funding": [
1720 | {
1721 | "type": "github",
1722 | "url": "https://github.com/sponsors/feross"
1723 | },
1724 | {
1725 | "type": "patreon",
1726 | "url": "https://www.patreon.com/feross"
1727 | },
1728 | {
1729 | "type": "consulting",
1730 | "url": "https://feross.org/support"
1731 | }
1732 | ]
1733 | },
1734 | "node_modules/string-width": {
1735 | "version": "4.2.3",
1736 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
1737 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
1738 | "dependencies": {
1739 | "emoji-regex": "^8.0.0",
1740 | "is-fullwidth-code-point": "^3.0.0",
1741 | "strip-ansi": "^6.0.1"
1742 | },
1743 | "engines": {
1744 | "node": ">=8"
1745 | }
1746 | },
1747 | "node_modules/strip-ansi": {
1748 | "version": "6.0.1",
1749 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1750 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1751 | "dependencies": {
1752 | "ansi-regex": "^5.0.1"
1753 | },
1754 | "engines": {
1755 | "node": ">=8"
1756 | }
1757 | },
1758 | "node_modules/supports-color": {
1759 | "version": "5.5.0",
1760 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1761 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1762 | "dev": true,
1763 | "dependencies": {
1764 | "has-flag": "^3.0.0"
1765 | },
1766 | "engines": {
1767 | "node": ">=4"
1768 | }
1769 | },
1770 | "node_modules/tar": {
1771 | "version": "6.2.0",
1772 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
1773 | "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
1774 | "dependencies": {
1775 | "chownr": "^2.0.0",
1776 | "fs-minipass": "^2.0.0",
1777 | "minipass": "^5.0.0",
1778 | "minizlib": "^2.1.1",
1779 | "mkdirp": "^1.0.3",
1780 | "yallist": "^4.0.0"
1781 | },
1782 | "engines": {
1783 | "node": ">=10"
1784 | }
1785 | },
1786 | "node_modules/to-regex-range": {
1787 | "version": "5.0.1",
1788 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1789 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1790 | "dev": true,
1791 | "dependencies": {
1792 | "is-number": "^7.0.0"
1793 | },
1794 | "engines": {
1795 | "node": ">=8.0"
1796 | }
1797 | },
1798 | "node_modules/toidentifier": {
1799 | "version": "1.0.1",
1800 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1801 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1802 | "engines": {
1803 | "node": ">=0.6"
1804 | }
1805 | },
1806 | "node_modules/touch": {
1807 | "version": "3.1.0",
1808 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
1809 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
1810 | "dev": true,
1811 | "dependencies": {
1812 | "nopt": "~1.0.10"
1813 | },
1814 | "bin": {
1815 | "nodetouch": "bin/nodetouch.js"
1816 | }
1817 | },
1818 | "node_modules/touch/node_modules/nopt": {
1819 | "version": "1.0.10",
1820 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
1821 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
1822 | "dev": true,
1823 | "dependencies": {
1824 | "abbrev": "1"
1825 | },
1826 | "bin": {
1827 | "nopt": "bin/nopt.js"
1828 | },
1829 | "engines": {
1830 | "node": "*"
1831 | }
1832 | },
1833 | "node_modules/tr46": {
1834 | "version": "0.0.3",
1835 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
1836 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
1837 | },
1838 | "node_modules/type-is": {
1839 | "version": "1.6.18",
1840 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1841 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1842 | "dependencies": {
1843 | "media-typer": "0.3.0",
1844 | "mime-types": "~2.1.24"
1845 | },
1846 | "engines": {
1847 | "node": ">= 0.6"
1848 | }
1849 | },
1850 | "node_modules/undefsafe": {
1851 | "version": "2.0.5",
1852 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1853 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1854 | "dev": true
1855 | },
1856 | "node_modules/undici-types": {
1857 | "version": "5.26.5",
1858 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
1859 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
1860 | },
1861 | "node_modules/unpipe": {
1862 | "version": "1.0.0",
1863 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1864 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1865 | "engines": {
1866 | "node": ">= 0.8"
1867 | }
1868 | },
1869 | "node_modules/util-deprecate": {
1870 | "version": "1.0.2",
1871 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1872 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
1873 | },
1874 | "node_modules/utils-merge": {
1875 | "version": "1.0.1",
1876 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1877 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1878 | "engines": {
1879 | "node": ">= 0.4.0"
1880 | }
1881 | },
1882 | "node_modules/vary": {
1883 | "version": "1.1.2",
1884 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1885 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1886 | "engines": {
1887 | "node": ">= 0.8"
1888 | }
1889 | },
1890 | "node_modules/webidl-conversions": {
1891 | "version": "3.0.1",
1892 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
1893 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
1894 | },
1895 | "node_modules/whatwg-url": {
1896 | "version": "5.0.0",
1897 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
1898 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
1899 | "dependencies": {
1900 | "tr46": "~0.0.3",
1901 | "webidl-conversions": "^3.0.0"
1902 | }
1903 | },
1904 | "node_modules/wide-align": {
1905 | "version": "1.1.5",
1906 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
1907 | "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
1908 | "dependencies": {
1909 | "string-width": "^1.0.2 || 2 || 3 || 4"
1910 | }
1911 | },
1912 | "node_modules/wrappy": {
1913 | "version": "1.0.2",
1914 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1915 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
1916 | },
1917 | "node_modules/yallist": {
1918 | "version": "4.0.0",
1919 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
1920 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
1921 | }
1922 | }
1923 | }
1924 |
--------------------------------------------------------------------------------
/node-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.mjs",
6 | "scripts": {
7 | "start": "node index.mjs",
8 | "dev": "nodemon index.mjs",
9 | "test": "jest"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "bcrypt": "^5.1.1",
16 | "compression": "^1.7.4",
17 | "cors": "^2.8.5",
18 | "crypto-js": "^4.2.0",
19 | "dotenv": "^16.3.1",
20 | "express": "^4.18.2",
21 | "jose": "^5.1.1",
22 | "mongoose": "^8.0.1",
23 | "nanoid": "^5.0.3"
24 | },
25 | "devDependencies": {
26 | "nodemon": "^3.0.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/node-server/routes/index.mjs:
--------------------------------------------------------------------------------
1 | export { default as postRoutes } from './post.mjs'
--------------------------------------------------------------------------------
/node-server/routes/post.mjs:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import PostController from '../controllers/post.mjs';
3 | import { authenticateUser } from '../middleware/index.mjs';
4 |
5 | const router = express.Router();
6 |
7 |
8 | router.get('/posts', authenticateUser, PostController.getPostsController )
9 |
10 | export default router
--------------------------------------------------------------------------------
/node-server/services/index.mjs:
--------------------------------------------------------------------------------
1 | export { default as PostService } from './post.mjs'
--------------------------------------------------------------------------------
/node-server/services/post.mjs:
--------------------------------------------------------------------------------
1 | import PostModel from "../models/post.mjs"
2 | import UserModel from "../models/user.mjs"
3 |
4 | const getAllPosts = async() =>{
5 | try {
6 | const result = await PostModel.find({}).populate({path: "user", select: ["name", "email", "avatar", "_id"], model: UserModel}).lean().exec()
7 | const data = result?.map(post => ({...post, id: post._id?.toString(), user: {...post.user, id: post?.user?._id.toString()} }))
8 | return {isError: false, data, message: "success", status: 200 }
9 | } catch (error) {
10 | return {isError: false, data: null, message: error.message, status: 500 }
11 | }
12 | }
13 |
14 | const PostService = { getAllPosts}
15 |
16 | export default PostService
--------------------------------------------------------------------------------
/node-server/utils/index.mjs:
--------------------------------------------------------------------------------
1 | import * as joseJwt from "jose"
2 | import CryptoJS from "crypto-js"
3 |
4 |
5 | export const verifyJwtToken = async(token) => {
6 | try {
7 | const privateKey = process.env.ACCESS_TOKEN_PRIVATE_KEY
8 | const result = await joseJwt.jwtVerify(token, new TextEncoder().encode(privateKey))
9 | return {data: result.payload, isError: false, message: "success" }
10 | } catch (error) {
11 | return { data: null, isError: true, message:error.message }
12 | }
13 | }
14 |
15 | export const decryptString = (hexString) => {
16 | const secretKey = CryptoJS.enc.Utf8.parse(process.env.CRYPTO_ENCRYPTION_KEY)
17 | const iv = CryptoJS.enc.Utf8.parse(process.env.CRYPTO_IV_KEY)
18 | const ciphertext = CryptoJS.enc.Hex.parse(hexString)
19 | const decrypted = CryptoJS.AES.decrypt({ciphertext}, secretKey, {iv}).toString(CryptoJS.enc.Utf8)
20 | console.log("decrypted: ", decrypted)
21 | return JSON.parse(decrypted)
22 | }
23 |
--------------------------------------------------------------------------------