├── .env.example ├── .gitignore ├── LICENSE.MD ├── README.md ├── app ├── (protected) │ └── dashboard │ │ └── page.tsx ├── api │ └── auth │ │ └── [...nextauth] │ │ └── route.ts ├── favicon.ico ├── globals.css ├── layout.tsx ├── lib │ └── actions.ts ├── page.tsx ├── sign-in │ └── page.tsx └── sign-up │ └── page.tsx ├── auth.config.ts ├── auth.ts ├── components └── SubmitButton.tsx ├── config.ts ├── lib ├── actions.ts ├── form-schemas.ts └── prisma.ts ├── middleware.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── prisma └── schema.prisma ├── public ├── next.svg └── vercel.svg ├── remove_mee.png ├── routes.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL="mongodb+srv://test:test@cluster0.ns1yp.mongodb.net/myFirstDatabase" 2 | AUTH_SECRET=MeowMeowMeow -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MARK MATTHEW M. VERGARA 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 | # Next.js MongoDB Prisma Auth Template 2 | 3 | [**`🌐 App Demo`**](https://nextjs-mongodb-prisma-auth-template.vercel.app/) 4 | 5 |

6 | 7 |

8 | 9 | This is a template repository for building a Next.js application with MongoDB, Prisma, and Next Auth **V5**. 10 | 11 | ## Features 12 | 13 | - 🚀 Next Auth **V5** with user registration, login, and logout functionality 14 | - 🚀 Protected Routes 15 | - 🚀 Next.js framework for server-side rendering and client-side rendering 16 | - 🚀 MongoDB for database storage 17 | - 🚀 Prisma for database ORM 18 | 19 | ## Getting Started 20 | 21 | 1. Clone the repository 22 | 2. Install dependencies: `npm install` 23 | 3. Set up your environment variables by creating a `.env` `or` `.env.local` file based on the `.env.example` file. 24 | 4. Generate and DB Push Prisma Client 25 | ```bash 26 | npx prisma generate 27 | npx prisma db push 28 | ``` 29 | 5. Start the development server: `npm run dev` 30 | 31 | ## What you need to know 32 | 33 | - `auth.config.ts` `&&` `app/lib/actions.ts` handles auth logic 34 | - `/lib/form-schemas.ts` zod for form validation 35 | - `middleware.ts` handles protected routes 36 | 37 | 38 | ## More Starter Templates 39 | 40 | - [React Supabase Auth Template 🌟](https://github.com/mmvergara/react-supabase-auth-template) 41 | - [React Supabase ShadCN Auth Template](https://github.com/mmvergara/react-supabase-shadcn-auth-template) 42 | - [NextJs MongoDB Prisma Auth Template 🌟](https://github.com/mmvergara/nextjs-mongodb-prisma-auth-template) 43 | - [NextJs Discord Bot Template 🌟](https://github.com/mmvergara/nextjs-discord-bot-boilerplate) 44 | - [React Firebase🔥 Auth Template 🌟](https://github.com/mmvergara/react-firebase-auth-template) 45 | - [Golang Postgres Auth Template](https://github.com/mmvergara/golang-postgresql-auth-template) 46 | - [Vue Supabase Auth Template](https://github.com/mmvergara/vue-supabase-auth-starter-template) 47 | - [Remix Drizzle Auth Template](https://github.com/mmvergara/remix-drizzle-auth-template) 48 | -------------------------------------------------------------------------------- /app/(protected)/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from "@/auth"; 2 | 3 | import Link from "next/link"; 4 | 5 | const DashboardPage = async () => { 6 | const session = await auth(); 7 | return ( 8 |
9 | 10 | ◄ Home 11 | 12 |
13 |

This is a Protected Page

14 |

Current User username : {session?.user?.email || "None"}

15 |
16 |
17 | ); 18 | }; 19 | 20 | export default DashboardPage; 21 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { handlers } from "@/auth"; // Referring to the auth.ts we just created 2 | export const { GET, POST } = handlers; 3 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmvergara/nextjs-mongodb-prisma-auth-template/4bda47ee44a59929a42c441841da96e8551a59f7/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: "Inter", sans-serif !important; 6 | } 7 | 8 | body { 9 | background-color: hsl(240, 4%, 9%); 10 | color: white; 11 | } 12 | 13 | main { 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | margin-top: 10vh; 18 | } 19 | 20 | a { 21 | text-decoration: none; 22 | } 23 | 24 | .header-text { 25 | display: flex; 26 | gap: 0.5em; 27 | align-items: center; 28 | padding: 0.5em 1em; 29 | font-weight: bold; 30 | background: none; 31 | text-align: center; 32 | } 33 | 34 | .main-container { 35 | display: flex; 36 | flex-direction: column; 37 | align-items: center; 38 | background-color: hsl(240, 4%, 12%); 39 | padding: 2em 2em; 40 | width: 100%; 41 | max-width: 500px; 42 | border-radius: 2px; 43 | box-shadow: 0 0 60px rgba(0, 0, 0, 0.2); 44 | border-top: 5px; 45 | border-width: 10px 0px 0px 0px; 46 | border-style: solid; 47 | border-color: #9faab8; 48 | } 49 | 50 | #github-repo-link { 51 | display: flex; 52 | gap: 0.5em; 53 | margin-top: 0; 54 | align-items: center; 55 | padding: 0.5em 1em; 56 | font-weight: bold; 57 | background: none; 58 | cursor: pointer; 59 | color: #9faab8; 60 | } 61 | 62 | #github-repo-link:hover { 63 | background-color: rgba(159, 170, 184, 0.1); 64 | } 65 | 66 | input { 67 | padding: 1rem; 68 | border-radius: 0.125rem; 69 | outline: none; 70 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); 71 | background-color: hsl(240, 3%, 9%); 72 | border: 0; 73 | font-size: 1rem; 74 | color: white; 75 | margin-top: 7px; 76 | width: 300px; 77 | } 78 | 79 | button, 80 | a { 81 | font-size: 16px; 82 | padding: 1em; 83 | background: none; 84 | cursor: pointer; 85 | color: white; 86 | background: #9faab8; 87 | border: none; 88 | border-radius: 0.125em; 89 | transition: background-color 0.3s; 90 | width: 300px; 91 | text-align: center; 92 | margin-top: 1em; 93 | display: flex; 94 | justify-content: center; 95 | align-items: center; 96 | height: 40px; 97 | font-weight: bold; 98 | } 99 | 100 | button:hover, 101 | a:hover { 102 | background: #8c99a9; 103 | } 104 | 105 | .home-link { 106 | margin-bottom: 1em; 107 | } 108 | 109 | .auth-link { 110 | background: transparent; 111 | color: #9faab8; 112 | } 113 | 114 | .auth-link:hover { 115 | background: transparent; 116 | text-decoration: underline; 117 | } 118 | 119 | #divider { 120 | width: 70%; 121 | height: 2px; 122 | background-color: #9faab8; 123 | margin: 1em 0; 124 | border-radius: 100%; 125 | } 126 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "NextJS MongoDB Prisma Starter", 9 | description: "NextJS MongoDB Prisma Starter with TypeScript and TailwindCSS", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/lib/actions.ts: -------------------------------------------------------------------------------- 1 | import { signIn } from "@/auth"; 2 | import { AuthError } from "next-auth"; 3 | 4 | // ... 5 | 6 | export async function authenticate( 7 | prevState: string | undefined, 8 | formData: FormData 9 | ) { 10 | try { 11 | await signIn("credentials", formData); 12 | } catch (error) { 13 | if (error instanceof AuthError) { 14 | switch (error.type) { 15 | case "CredentialsSignin": 16 | return "Invalid credentials."; 17 | default: 18 | return "Something went wrong."; 19 | } 20 | } 21 | throw error; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth, signOut } from "@/auth"; 2 | import Link from "next/link"; 3 | import { 4 | isRedirectError, 5 | redirect, 6 | } from "next/dist/client/components/redirect"; 7 | import { SubmitButton } from "@/components/SubmitButton"; 8 | 9 | export default async function Home() { 10 | const session = await auth(); 11 | return ( 12 |
13 |
14 |

NextJS MongoDB Prisma Auth

15 |

Current User : {session?.user?.email || "None"}

16 | {session?.user ? ( 17 |
{ 19 | "use server"; 20 | try { 21 | await signOut({ redirect: false }); 22 | } catch (err) { 23 | if (isRedirectError(err)) { 24 | console.error(err); 25 | throw err; 26 | } 27 | } finally { 28 | redirect("/"); 29 | } 30 | }} 31 | > 32 | 36 | Sign Out 37 | 38 |
39 | ) : ( 40 | Sign In 41 | )} 42 | Protected Page 🛡️ 43 |
44 | 50 | 58 | 62 | 63 | Github Repository 64 | 65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /app/sign-in/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { SubmitButton } from "@/components/SubmitButton"; 3 | import { signInAction } from "@/lib/actions"; 4 | import Link from "next/link"; 5 | import { redirect } from "next/navigation"; 6 | 7 | export default function SignInPage() { 8 | const handleFormSubmit = async (formData: FormData) => { 9 | const email = formData.get("email") as string; 10 | const password = formData.get("password") as string; 11 | const res = await signInAction({ email, password }); 12 | if (res.error) { 13 | alert(res.error); 14 | return; 15 | } 16 | redirect("/"); 17 | }; 18 | 19 | return ( 20 |
21 | 22 | ◄ Home 23 | 24 |
25 |

Sign In

26 | 27 | 28 | Login 29 | 30 | Don't have an account? Sign Up 31 | 32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /app/sign-up/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { signUpSchema } from "@/lib/form-schemas"; 3 | import { SubmitButton } from "@/components/SubmitButton"; 4 | import { signUpAction } from "@/lib/actions"; 5 | import Link from "next/link"; 6 | 7 | export default function SignUpPage() { 8 | const handleFormSubmit = async (formData: FormData) => { 9 | const formValues = { 10 | username: formData.get("username") as string, 11 | email: formData.get("email") as string, 12 | password: formData.get("password") as string, 13 | }; 14 | 15 | const { error } = await signUpSchema.safeParseAsync(formValues); 16 | if (error) { 17 | alert(error.issues[0].message); 18 | } 19 | 20 | const res = await signUpAction(formValues); 21 | if (res?.error) { 22 | alert(res.error); 23 | } 24 | }; 25 | 26 | return ( 27 |
28 | 29 | ◄ Home 30 | 31 |
32 |

Sign Up

33 |

40 | Demo app, please don't use your real email or password 41 |

42 | 43 | 44 | 45 | 46 | Create Account 47 | 48 | 49 | Already have an account? Sign In 50 | 51 |
52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /auth.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextAuthConfig } from "next-auth"; 2 | import { signInSchema } from "./lib/form-schemas"; 3 | import { compare } from "bcryptjs"; 4 | import Credentials from "next-auth/providers/credentials"; 5 | import prisma from "./lib/prisma"; 6 | 7 | export const authConfig = { 8 | providers: [ 9 | Credentials({ 10 | async authorize(credentials) { 11 | // Validate the fields 12 | const validatedFields = signInSchema.safeParse(credentials); 13 | if (!validatedFields.success) { 14 | return null; 15 | } 16 | 17 | // Validate that the user exists 18 | const { email, password } = validatedFields.data; 19 | const user = await prisma.user.findUnique({ 20 | where: { email }, 21 | }); 22 | if (!user) { 23 | return null; 24 | } 25 | 26 | // Check the password 27 | const isPasswordMatch = await compare(password, user.password); 28 | if (!isPasswordMatch) { 29 | return null; 30 | } 31 | 32 | return user; 33 | }, 34 | }), 35 | ], 36 | } satisfies NextAuthConfig; 37 | -------------------------------------------------------------------------------- /auth.ts: -------------------------------------------------------------------------------- 1 | import { PrismaAdapter } from "@auth/prisma-adapter"; 2 | import prisma from "./lib/prisma"; 3 | import { Adapter } from "next-auth/adapters"; 4 | import NextAuth from "next-auth"; 5 | import { authConfig } from "./auth.config"; 6 | 7 | export const { handlers, auth, signIn, signOut } = NextAuth({ 8 | adapter: PrismaAdapter(prisma) as Adapter, 9 | session: { strategy: "jwt" }, 10 | ...authConfig, 11 | }); 12 | -------------------------------------------------------------------------------- /components/SubmitButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useFormStatus } from "react-dom"; 4 | import { type ComponentProps } from "react"; 5 | 6 | type Props = ComponentProps<"button"> & { 7 | pendingText: string; 8 | }; 9 | 10 | export function SubmitButton({ children, pendingText, ...props }: Props) { 11 | const { pending } = useFormStatus(); 12 | 13 | return ( 14 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /config.ts: -------------------------------------------------------------------------------- 1 | if (!process.env.DATABASE_URL) throw new Error("DATABASE_URL must be set"); 2 | export const DATABASE_URL = process.env.DATABASE_URL; 3 | console.log("DATABASE_URL", DATABASE_URL); 4 | -------------------------------------------------------------------------------- /lib/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { SignInValues, SignUpValues, signUpSchema } from "./form-schemas"; 3 | import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library"; 4 | import { AuthError } from "next-auth"; 5 | import { hashSync } from "bcryptjs"; 6 | import { signIn } from "@/auth"; 7 | import prisma from "./prisma"; 8 | import { redirect } from "next/navigation"; 9 | 10 | export const signInAction = async (signInValues: SignInValues) => { 11 | try { 12 | await signIn("credentials", signInValues); 13 | } catch (error) { 14 | if (error instanceof AuthError) { 15 | switch (error.type) { 16 | case "CredentialsSignin": 17 | return { error: "Invalid Credentials" }; 18 | default: 19 | return { error: "An error occurred" }; 20 | } 21 | } 22 | throw error; 23 | } 24 | redirect("/dashboard"); 25 | }; 26 | 27 | export const signUpAction = async (signUpValues: SignUpValues) => { 28 | const { data } = await signUpSchema.safeParseAsync(signUpValues); 29 | if (!data) return { error: "Invalid data" }; 30 | try { 31 | await prisma.user.create({ 32 | data: { 33 | ...data, 34 | password: hashSync(data.password, 10), 35 | }, 36 | }); 37 | } catch (error) { 38 | console.log("ERROR OCCURED SIGNUP ACTION", error); 39 | if (error instanceof PrismaClientKnownRequestError) { 40 | console.log(error.code); 41 | switch (error.code) { 42 | case "P2002": 43 | return { error: "Email already exists" }; 44 | default: 45 | return { error: "An error occurred" }; 46 | } 47 | } 48 | return { error: "An error occurred" }; 49 | } 50 | redirect("/sign-in"); 51 | }; 52 | -------------------------------------------------------------------------------- /lib/form-schemas.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | const { object, string } = z; 3 | 4 | export const signInSchema = object({ 5 | email: string({ required_error: "Email is required" }).email("Invalid email"), 6 | password: string({ required_error: "Password is required" }) 7 | .min(8, "Password must be more than 8 characters") 8 | .max(32, "Password must be less than 32 characters"), 9 | }); 10 | export type SignInValues = z.infer; 11 | 12 | export const signUpSchema = object({ 13 | email: string({ required_error: "Email is required" }).email("Invalid email"), 14 | password: string({ required_error: "Password is required" }) 15 | .min(8, "Password must be more than 8 characters") 16 | .max(32, "Password must be less than 32 characters"), 17 | username: string({ required_error: "Username is required" }) 18 | .min(4, "Username is required") 19 | .max(32, "Username must be less than 32 characters"), 20 | }); 21 | export type SignUpValues = z.infer; 22 | -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const prismaClientSingleton = () => { 4 | return new PrismaClient(); 5 | }; 6 | 7 | type PrismaClientSingleton = ReturnType; 8 | 9 | // eslint-disable-next-line 10 | const globalForPrisma = globalThis as unknown as { 11 | prisma: PrismaClientSingleton | undefined; 12 | }; 13 | 14 | const prisma = globalForPrisma.prisma ?? prismaClientSingleton(); 15 | 16 | export default prisma; 17 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { auth } from "./auth"; 3 | import { apiAuthPrefix, authRoutes, publicRoutes } from "./routes"; 4 | 5 | export default auth((req) => { 6 | const { nextUrl } = req; 7 | const isLoggedIn = !!req.auth; 8 | 9 | const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix); 10 | const isPublicRotue = publicRoutes.includes(nextUrl.pathname); 11 | const isAuthRoute = authRoutes.includes(nextUrl.pathname); 12 | 13 | // if it is an API Next Auth route, we don't want to redirect 14 | if (isApiAuthRoute) return; 15 | 16 | if (isAuthRoute) { 17 | if (isLoggedIn) { 18 | // if the user is already logged in and is in sign-in or sign-up page 19 | // redirect to the default logged in page (which is dashboard in this case) 20 | console.log("redirecting to dashboard"); 21 | return NextResponse.redirect(new URL("/dashboard", nextUrl)); 22 | } 23 | // if the user is not logged in and is in sign-in or sign-up page, let them be 24 | return; 25 | } 26 | 27 | if (!isLoggedIn && !isPublicRotue) { 28 | // if the user is not logged in and is not in a public route, redirect to sign-in page 29 | return NextResponse.redirect(new URL("/sign-in", nextUrl)); 30 | } 31 | 32 | return; 33 | }); 34 | 35 | export const config = { 36 | matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"], 37 | }; 38 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-mongodb-prisma-auth-template", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nextjs-mongodb-prisma-auth-template", 9 | "version": "0.1.0", 10 | "dependencies": { 11 | "@auth/prisma-adapter": "^2.0.0", 12 | "@prisma/client": "^5.13.0", 13 | "bcryptjs": "^2.4.3", 14 | "next": "^14.2.18", 15 | "next-auth": "^5.0.0-beta.17", 16 | "react": "^18", 17 | "react-dom": "^18", 18 | "zod": "^3.23.5" 19 | }, 20 | "devDependencies": { 21 | "@types/bcryptjs": "^2.4.6", 22 | "@types/node": "^20", 23 | "@types/react": "^18", 24 | "@types/react-dom": "^18", 25 | "prisma": "^5.13.0", 26 | "typescript": "^5" 27 | } 28 | }, 29 | "node_modules/@auth/core": { 30 | "version": "0.37.4", 31 | "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.4.tgz", 32 | "integrity": "sha512-HOXJwXWXQRhbBDHlMU0K/6FT1v+wjtzdKhsNg0ZN7/gne6XPsIrjZ4daMcFnbq0Z/vsAbYBinQhhua0d77v7qw==", 33 | "license": "ISC", 34 | "dependencies": { 35 | "@panva/hkdf": "^1.2.1", 36 | "jose": "^5.9.6", 37 | "oauth4webapi": "^3.1.1", 38 | "preact": "10.24.3", 39 | "preact-render-to-string": "6.5.11" 40 | }, 41 | "peerDependencies": { 42 | "@simplewebauthn/browser": "^9.0.1", 43 | "@simplewebauthn/server": "^9.0.2", 44 | "nodemailer": "^6.8.0" 45 | }, 46 | "peerDependenciesMeta": { 47 | "@simplewebauthn/browser": { 48 | "optional": true 49 | }, 50 | "@simplewebauthn/server": { 51 | "optional": true 52 | }, 53 | "nodemailer": { 54 | "optional": true 55 | } 56 | } 57 | }, 58 | "node_modules/@auth/prisma-adapter": { 59 | "version": "2.7.4", 60 | "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-2.7.4.tgz", 61 | "integrity": "sha512-3T/X94R9J1sxOLQtsD3ijIZ0JGHPXlZQxRr/8NpnZBJ3KGxun/mNsZ1MwMRhTxy0mmn9JWXk7u9+xCcVn0pu3A==", 62 | "license": "ISC", 63 | "dependencies": { 64 | "@auth/core": "0.37.4" 65 | }, 66 | "peerDependencies": { 67 | "@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5" 68 | } 69 | }, 70 | "node_modules/@next/env": { 71 | "version": "14.2.18", 72 | "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.18.tgz", 73 | "integrity": "sha512-2vWLOUwIPgoqMJKG6dt35fVXVhgM09tw4tK3/Q34GFXDrfiHlG7iS33VA4ggnjWxjiz9KV5xzfsQzJX6vGAekA==", 74 | "license": "MIT" 75 | }, 76 | "node_modules/@next/swc-darwin-arm64": { 77 | "version": "14.2.18", 78 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.18.tgz", 79 | "integrity": "sha512-tOBlDHCjGdyLf0ube/rDUs6VtwNOajaWV+5FV/ajPgrvHeisllEdymY/oDgv2cx561+gJksfMUtqf8crug7sbA==", 80 | "cpu": [ 81 | "arm64" 82 | ], 83 | "license": "MIT", 84 | "optional": true, 85 | "os": [ 86 | "darwin" 87 | ], 88 | "engines": { 89 | "node": ">= 10" 90 | } 91 | }, 92 | "node_modules/@next/swc-darwin-x64": { 93 | "version": "14.2.18", 94 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.18.tgz", 95 | "integrity": "sha512-uJCEjutt5VeJ30jjrHV1VIHCsbMYnEqytQgvREx+DjURd/fmKy15NaVK4aR/u98S1LGTnjq35lRTnRyygglxoA==", 96 | "cpu": [ 97 | "x64" 98 | ], 99 | "license": "MIT", 100 | "optional": true, 101 | "os": [ 102 | "darwin" 103 | ], 104 | "engines": { 105 | "node": ">= 10" 106 | } 107 | }, 108 | "node_modules/@next/swc-linux-arm64-gnu": { 109 | "version": "14.2.18", 110 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.18.tgz", 111 | "integrity": "sha512-IL6rU8vnBB+BAm6YSWZewc+qvdL1EaA+VhLQ6tlUc0xp+kkdxQrVqAnh8Zek1ccKHlTDFRyAft0e60gteYmQ4A==", 112 | "cpu": [ 113 | "arm64" 114 | ], 115 | "license": "MIT", 116 | "optional": true, 117 | "os": [ 118 | "linux" 119 | ], 120 | "engines": { 121 | "node": ">= 10" 122 | } 123 | }, 124 | "node_modules/@next/swc-linux-arm64-musl": { 125 | "version": "14.2.18", 126 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.18.tgz", 127 | "integrity": "sha512-RCaENbIZqKKqTlL8KNd+AZV/yAdCsovblOpYFp0OJ7ZxgLNbV5w23CUU1G5On+0fgafrsGcW+GdMKdFjaRwyYA==", 128 | "cpu": [ 129 | "arm64" 130 | ], 131 | "license": "MIT", 132 | "optional": true, 133 | "os": [ 134 | "linux" 135 | ], 136 | "engines": { 137 | "node": ">= 10" 138 | } 139 | }, 140 | "node_modules/@next/swc-linux-x64-gnu": { 141 | "version": "14.2.18", 142 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.18.tgz", 143 | "integrity": "sha512-3kmv8DlyhPRCEBM1Vavn8NjyXtMeQ49ID0Olr/Sut7pgzaQTo4h01S7Z8YNE0VtbowyuAL26ibcz0ka6xCTH5g==", 144 | "cpu": [ 145 | "x64" 146 | ], 147 | "license": "MIT", 148 | "optional": true, 149 | "os": [ 150 | "linux" 151 | ], 152 | "engines": { 153 | "node": ">= 10" 154 | } 155 | }, 156 | "node_modules/@next/swc-linux-x64-musl": { 157 | "version": "14.2.18", 158 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.18.tgz", 159 | "integrity": "sha512-mliTfa8seVSpTbVEcKEXGjC18+TDII8ykW4a36au97spm9XMPqQTpdGPNBJ9RySSFw9/hLuaCMByluQIAnkzlw==", 160 | "cpu": [ 161 | "x64" 162 | ], 163 | "license": "MIT", 164 | "optional": true, 165 | "os": [ 166 | "linux" 167 | ], 168 | "engines": { 169 | "node": ">= 10" 170 | } 171 | }, 172 | "node_modules/@next/swc-win32-arm64-msvc": { 173 | "version": "14.2.18", 174 | "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.18.tgz", 175 | "integrity": "sha512-J5g0UFPbAjKYmqS3Cy7l2fetFmWMY9Oao32eUsBPYohts26BdrMUyfCJnZFQkX9npYaHNDOWqZ6uV9hSDPw9NA==", 176 | "cpu": [ 177 | "arm64" 178 | ], 179 | "license": "MIT", 180 | "optional": true, 181 | "os": [ 182 | "win32" 183 | ], 184 | "engines": { 185 | "node": ">= 10" 186 | } 187 | }, 188 | "node_modules/@next/swc-win32-ia32-msvc": { 189 | "version": "14.2.18", 190 | "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.18.tgz", 191 | "integrity": "sha512-Ynxuk4ZgIpdcN7d16ivJdjsDG1+3hTvK24Pp8DiDmIa2+A4CfhJSEHHVndCHok6rnLUzAZD+/UOKESQgTsAZGg==", 192 | "cpu": [ 193 | "ia32" 194 | ], 195 | "license": "MIT", 196 | "optional": true, 197 | "os": [ 198 | "win32" 199 | ], 200 | "engines": { 201 | "node": ">= 10" 202 | } 203 | }, 204 | "node_modules/@next/swc-win32-x64-msvc": { 205 | "version": "14.2.18", 206 | "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.18.tgz", 207 | "integrity": "sha512-dtRGMhiU9TN5nyhwzce+7c/4CCeykYS+ipY/4mIrGzJ71+7zNo55ZxCB7cAVuNqdwtYniFNR2c9OFQ6UdFIMcg==", 208 | "cpu": [ 209 | "x64" 210 | ], 211 | "license": "MIT", 212 | "optional": true, 213 | "os": [ 214 | "win32" 215 | ], 216 | "engines": { 217 | "node": ">= 10" 218 | } 219 | }, 220 | "node_modules/@panva/hkdf": { 221 | "version": "1.2.1", 222 | "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", 223 | "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", 224 | "license": "MIT", 225 | "funding": { 226 | "url": "https://github.com/sponsors/panva" 227 | } 228 | }, 229 | "node_modules/@prisma/client": { 230 | "version": "5.22.0", 231 | "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", 232 | "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", 233 | "hasInstallScript": true, 234 | "license": "Apache-2.0", 235 | "engines": { 236 | "node": ">=16.13" 237 | }, 238 | "peerDependencies": { 239 | "prisma": "*" 240 | }, 241 | "peerDependenciesMeta": { 242 | "prisma": { 243 | "optional": true 244 | } 245 | } 246 | }, 247 | "node_modules/@prisma/debug": { 248 | "version": "5.22.0", 249 | "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", 250 | "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", 251 | "devOptional": true, 252 | "license": "Apache-2.0" 253 | }, 254 | "node_modules/@prisma/engines": { 255 | "version": "5.22.0", 256 | "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", 257 | "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", 258 | "devOptional": true, 259 | "hasInstallScript": true, 260 | "license": "Apache-2.0", 261 | "dependencies": { 262 | "@prisma/debug": "5.22.0", 263 | "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", 264 | "@prisma/fetch-engine": "5.22.0", 265 | "@prisma/get-platform": "5.22.0" 266 | } 267 | }, 268 | "node_modules/@prisma/engines-version": { 269 | "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", 270 | "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", 271 | "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", 272 | "devOptional": true, 273 | "license": "Apache-2.0" 274 | }, 275 | "node_modules/@prisma/fetch-engine": { 276 | "version": "5.22.0", 277 | "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", 278 | "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", 279 | "devOptional": true, 280 | "license": "Apache-2.0", 281 | "dependencies": { 282 | "@prisma/debug": "5.22.0", 283 | "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", 284 | "@prisma/get-platform": "5.22.0" 285 | } 286 | }, 287 | "node_modules/@prisma/get-platform": { 288 | "version": "5.22.0", 289 | "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", 290 | "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", 291 | "devOptional": true, 292 | "license": "Apache-2.0", 293 | "dependencies": { 294 | "@prisma/debug": "5.22.0" 295 | } 296 | }, 297 | "node_modules/@swc/counter": { 298 | "version": "0.1.3", 299 | "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", 300 | "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", 301 | "license": "Apache-2.0" 302 | }, 303 | "node_modules/@swc/helpers": { 304 | "version": "0.5.5", 305 | "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", 306 | "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", 307 | "license": "Apache-2.0", 308 | "dependencies": { 309 | "@swc/counter": "^0.1.3", 310 | "tslib": "^2.4.0" 311 | } 312 | }, 313 | "node_modules/@types/bcryptjs": { 314 | "version": "2.4.6", 315 | "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", 316 | "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", 317 | "dev": true, 318 | "license": "MIT" 319 | }, 320 | "node_modules/@types/cookie": { 321 | "version": "0.6.0", 322 | "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", 323 | "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", 324 | "license": "MIT" 325 | }, 326 | "node_modules/@types/node": { 327 | "version": "20.17.9", 328 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", 329 | "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", 330 | "dev": true, 331 | "license": "MIT", 332 | "dependencies": { 333 | "undici-types": "~6.19.2" 334 | } 335 | }, 336 | "node_modules/@types/prop-types": { 337 | "version": "15.7.13", 338 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", 339 | "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", 340 | "dev": true, 341 | "license": "MIT" 342 | }, 343 | "node_modules/@types/react": { 344 | "version": "18.3.12", 345 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", 346 | "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", 347 | "dev": true, 348 | "license": "MIT", 349 | "dependencies": { 350 | "@types/prop-types": "*", 351 | "csstype": "^3.0.2" 352 | } 353 | }, 354 | "node_modules/@types/react-dom": { 355 | "version": "18.3.1", 356 | "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", 357 | "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", 358 | "dev": true, 359 | "license": "MIT", 360 | "dependencies": { 361 | "@types/react": "*" 362 | } 363 | }, 364 | "node_modules/bcryptjs": { 365 | "version": "2.4.3", 366 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 367 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", 368 | "license": "MIT" 369 | }, 370 | "node_modules/busboy": { 371 | "version": "1.6.0", 372 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", 373 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", 374 | "dependencies": { 375 | "streamsearch": "^1.1.0" 376 | }, 377 | "engines": { 378 | "node": ">=10.16.0" 379 | } 380 | }, 381 | "node_modules/caniuse-lite": { 382 | "version": "1.0.30001685", 383 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001685.tgz", 384 | "integrity": "sha512-e/kJN1EMyHQzgcMEEgoo+YTCO1NGCmIYHk5Qk8jT6AazWemS5QFKJ5ShCJlH3GZrNIdZofcNCEwZqbMjjKzmnA==", 385 | "funding": [ 386 | { 387 | "type": "opencollective", 388 | "url": "https://opencollective.com/browserslist" 389 | }, 390 | { 391 | "type": "tidelift", 392 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 393 | }, 394 | { 395 | "type": "github", 396 | "url": "https://github.com/sponsors/ai" 397 | } 398 | ], 399 | "license": "CC-BY-4.0" 400 | }, 401 | "node_modules/client-only": { 402 | "version": "0.0.1", 403 | "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", 404 | "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", 405 | "license": "MIT" 406 | }, 407 | "node_modules/cookie": { 408 | "version": "0.7.1", 409 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 410 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 411 | "license": "MIT", 412 | "engines": { 413 | "node": ">= 0.6" 414 | } 415 | }, 416 | "node_modules/csstype": { 417 | "version": "3.1.3", 418 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 419 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 420 | "dev": true, 421 | "license": "MIT" 422 | }, 423 | "node_modules/fsevents": { 424 | "version": "2.3.3", 425 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 426 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 427 | "dev": true, 428 | "hasInstallScript": true, 429 | "license": "MIT", 430 | "optional": true, 431 | "os": [ 432 | "darwin" 433 | ], 434 | "engines": { 435 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 436 | } 437 | }, 438 | "node_modules/graceful-fs": { 439 | "version": "4.2.11", 440 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 441 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 442 | "license": "ISC" 443 | }, 444 | "node_modules/jose": { 445 | "version": "5.9.6", 446 | "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", 447 | "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", 448 | "license": "MIT", 449 | "funding": { 450 | "url": "https://github.com/sponsors/panva" 451 | } 452 | }, 453 | "node_modules/js-tokens": { 454 | "version": "4.0.0", 455 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 456 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 457 | "license": "MIT" 458 | }, 459 | "node_modules/loose-envify": { 460 | "version": "1.4.0", 461 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 462 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 463 | "license": "MIT", 464 | "dependencies": { 465 | "js-tokens": "^3.0.0 || ^4.0.0" 466 | }, 467 | "bin": { 468 | "loose-envify": "cli.js" 469 | } 470 | }, 471 | "node_modules/nanoid": { 472 | "version": "3.3.8", 473 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", 474 | "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", 475 | "funding": [ 476 | { 477 | "type": "github", 478 | "url": "https://github.com/sponsors/ai" 479 | } 480 | ], 481 | "license": "MIT", 482 | "bin": { 483 | "nanoid": "bin/nanoid.cjs" 484 | }, 485 | "engines": { 486 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 487 | } 488 | }, 489 | "node_modules/next": { 490 | "version": "14.2.18", 491 | "resolved": "https://registry.npmjs.org/next/-/next-14.2.18.tgz", 492 | "integrity": "sha512-H9qbjDuGivUDEnK6wa+p2XKO+iMzgVgyr9Zp/4Iv29lKa+DYaxJGjOeEA+5VOvJh/M7HLiskehInSa0cWxVXUw==", 493 | "license": "MIT", 494 | "dependencies": { 495 | "@next/env": "14.2.18", 496 | "@swc/helpers": "0.5.5", 497 | "busboy": "1.6.0", 498 | "caniuse-lite": "^1.0.30001579", 499 | "graceful-fs": "^4.2.11", 500 | "postcss": "8.4.31", 501 | "styled-jsx": "5.1.1" 502 | }, 503 | "bin": { 504 | "next": "dist/bin/next" 505 | }, 506 | "engines": { 507 | "node": ">=18.17.0" 508 | }, 509 | "optionalDependencies": { 510 | "@next/swc-darwin-arm64": "14.2.18", 511 | "@next/swc-darwin-x64": "14.2.18", 512 | "@next/swc-linux-arm64-gnu": "14.2.18", 513 | "@next/swc-linux-arm64-musl": "14.2.18", 514 | "@next/swc-linux-x64-gnu": "14.2.18", 515 | "@next/swc-linux-x64-musl": "14.2.18", 516 | "@next/swc-win32-arm64-msvc": "14.2.18", 517 | "@next/swc-win32-ia32-msvc": "14.2.18", 518 | "@next/swc-win32-x64-msvc": "14.2.18" 519 | }, 520 | "peerDependencies": { 521 | "@opentelemetry/api": "^1.1.0", 522 | "@playwright/test": "^1.41.2", 523 | "react": "^18.2.0", 524 | "react-dom": "^18.2.0", 525 | "sass": "^1.3.0" 526 | }, 527 | "peerDependenciesMeta": { 528 | "@opentelemetry/api": { 529 | "optional": true 530 | }, 531 | "@playwright/test": { 532 | "optional": true 533 | }, 534 | "sass": { 535 | "optional": true 536 | } 537 | } 538 | }, 539 | "node_modules/next-auth": { 540 | "version": "5.0.0-beta.25", 541 | "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.25.tgz", 542 | "integrity": "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==", 543 | "license": "ISC", 544 | "dependencies": { 545 | "@auth/core": "0.37.2" 546 | }, 547 | "peerDependencies": { 548 | "@simplewebauthn/browser": "^9.0.1", 549 | "@simplewebauthn/server": "^9.0.2", 550 | "next": "^14.0.0-0 || ^15.0.0-0", 551 | "nodemailer": "^6.6.5", 552 | "react": "^18.2.0 || ^19.0.0-0" 553 | }, 554 | "peerDependenciesMeta": { 555 | "@simplewebauthn/browser": { 556 | "optional": true 557 | }, 558 | "@simplewebauthn/server": { 559 | "optional": true 560 | }, 561 | "nodemailer": { 562 | "optional": true 563 | } 564 | } 565 | }, 566 | "node_modules/next-auth/node_modules/@auth/core": { 567 | "version": "0.37.2", 568 | "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.2.tgz", 569 | "integrity": "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==", 570 | "license": "ISC", 571 | "dependencies": { 572 | "@panva/hkdf": "^1.2.1", 573 | "@types/cookie": "0.6.0", 574 | "cookie": "0.7.1", 575 | "jose": "^5.9.3", 576 | "oauth4webapi": "^3.0.0", 577 | "preact": "10.11.3", 578 | "preact-render-to-string": "5.2.3" 579 | }, 580 | "peerDependencies": { 581 | "@simplewebauthn/browser": "^9.0.1", 582 | "@simplewebauthn/server": "^9.0.2", 583 | "nodemailer": "^6.8.0" 584 | }, 585 | "peerDependenciesMeta": { 586 | "@simplewebauthn/browser": { 587 | "optional": true 588 | }, 589 | "@simplewebauthn/server": { 590 | "optional": true 591 | }, 592 | "nodemailer": { 593 | "optional": true 594 | } 595 | } 596 | }, 597 | "node_modules/next-auth/node_modules/preact": { 598 | "version": "10.11.3", 599 | "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", 600 | "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", 601 | "license": "MIT", 602 | "funding": { 603 | "type": "opencollective", 604 | "url": "https://opencollective.com/preact" 605 | } 606 | }, 607 | "node_modules/next-auth/node_modules/preact-render-to-string": { 608 | "version": "5.2.3", 609 | "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", 610 | "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", 611 | "license": "MIT", 612 | "dependencies": { 613 | "pretty-format": "^3.8.0" 614 | }, 615 | "peerDependencies": { 616 | "preact": ">=10" 617 | } 618 | }, 619 | "node_modules/oauth4webapi": { 620 | "version": "3.1.3", 621 | "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.3.tgz", 622 | "integrity": "sha512-dik5wEMdFL5p3JlijYvM7wMNCgaPhblLIDCZtdXcaZp5wgu5Iwmsu7lMzgFhIDTi5d0BJo03LVoOoFQvXMeOeQ==", 623 | "license": "MIT", 624 | "funding": { 625 | "url": "https://github.com/sponsors/panva" 626 | } 627 | }, 628 | "node_modules/picocolors": { 629 | "version": "1.1.1", 630 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 631 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 632 | "license": "ISC" 633 | }, 634 | "node_modules/postcss": { 635 | "version": "8.4.31", 636 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 637 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 638 | "funding": [ 639 | { 640 | "type": "opencollective", 641 | "url": "https://opencollective.com/postcss/" 642 | }, 643 | { 644 | "type": "tidelift", 645 | "url": "https://tidelift.com/funding/github/npm/postcss" 646 | }, 647 | { 648 | "type": "github", 649 | "url": "https://github.com/sponsors/ai" 650 | } 651 | ], 652 | "license": "MIT", 653 | "dependencies": { 654 | "nanoid": "^3.3.6", 655 | "picocolors": "^1.0.0", 656 | "source-map-js": "^1.0.2" 657 | }, 658 | "engines": { 659 | "node": "^10 || ^12 || >=14" 660 | } 661 | }, 662 | "node_modules/preact": { 663 | "version": "10.24.3", 664 | "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", 665 | "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", 666 | "license": "MIT", 667 | "funding": { 668 | "type": "opencollective", 669 | "url": "https://opencollective.com/preact" 670 | } 671 | }, 672 | "node_modules/preact-render-to-string": { 673 | "version": "6.5.11", 674 | "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", 675 | "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", 676 | "license": "MIT", 677 | "peerDependencies": { 678 | "preact": ">=10" 679 | } 680 | }, 681 | "node_modules/pretty-format": { 682 | "version": "3.8.0", 683 | "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", 684 | "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", 685 | "license": "MIT" 686 | }, 687 | "node_modules/prisma": { 688 | "version": "5.22.0", 689 | "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", 690 | "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", 691 | "devOptional": true, 692 | "hasInstallScript": true, 693 | "license": "Apache-2.0", 694 | "dependencies": { 695 | "@prisma/engines": "5.22.0" 696 | }, 697 | "bin": { 698 | "prisma": "build/index.js" 699 | }, 700 | "engines": { 701 | "node": ">=16.13" 702 | }, 703 | "optionalDependencies": { 704 | "fsevents": "2.3.3" 705 | } 706 | }, 707 | "node_modules/react": { 708 | "version": "18.3.1", 709 | "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", 710 | "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", 711 | "license": "MIT", 712 | "dependencies": { 713 | "loose-envify": "^1.1.0" 714 | }, 715 | "engines": { 716 | "node": ">=0.10.0" 717 | } 718 | }, 719 | "node_modules/react-dom": { 720 | "version": "18.3.1", 721 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", 722 | "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", 723 | "license": "MIT", 724 | "dependencies": { 725 | "loose-envify": "^1.1.0", 726 | "scheduler": "^0.23.2" 727 | }, 728 | "peerDependencies": { 729 | "react": "^18.3.1" 730 | } 731 | }, 732 | "node_modules/scheduler": { 733 | "version": "0.23.2", 734 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", 735 | "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", 736 | "license": "MIT", 737 | "dependencies": { 738 | "loose-envify": "^1.1.0" 739 | } 740 | }, 741 | "node_modules/source-map-js": { 742 | "version": "1.2.1", 743 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 744 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 745 | "license": "BSD-3-Clause", 746 | "engines": { 747 | "node": ">=0.10.0" 748 | } 749 | }, 750 | "node_modules/streamsearch": { 751 | "version": "1.1.0", 752 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", 753 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", 754 | "engines": { 755 | "node": ">=10.0.0" 756 | } 757 | }, 758 | "node_modules/styled-jsx": { 759 | "version": "5.1.1", 760 | "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", 761 | "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", 762 | "license": "MIT", 763 | "dependencies": { 764 | "client-only": "0.0.1" 765 | }, 766 | "engines": { 767 | "node": ">= 12.0.0" 768 | }, 769 | "peerDependencies": { 770 | "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" 771 | }, 772 | "peerDependenciesMeta": { 773 | "@babel/core": { 774 | "optional": true 775 | }, 776 | "babel-plugin-macros": { 777 | "optional": true 778 | } 779 | } 780 | }, 781 | "node_modules/tslib": { 782 | "version": "2.8.1", 783 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 784 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 785 | "license": "0BSD" 786 | }, 787 | "node_modules/typescript": { 788 | "version": "5.7.2", 789 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 790 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 791 | "dev": true, 792 | "license": "Apache-2.0", 793 | "bin": { 794 | "tsc": "bin/tsc", 795 | "tsserver": "bin/tsserver" 796 | }, 797 | "engines": { 798 | "node": ">=14.17" 799 | } 800 | }, 801 | "node_modules/undici-types": { 802 | "version": "6.19.8", 803 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 804 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 805 | "dev": true, 806 | "license": "MIT" 807 | }, 808 | "node_modules/zod": { 809 | "version": "3.23.8", 810 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", 811 | "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", 812 | "license": "MIT", 813 | "funding": { 814 | "url": "https://github.com/sponsors/colinhacks" 815 | } 816 | } 817 | } 818 | } 819 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-mongodb-prisma-auth-template", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "mmvergara", 6 | "description": "Template repository for building a Next.js application with MongoDB, Prisma, and Next Auth authentication", 7 | "scripts": { 8 | "dev": "next dev", 9 | "build": "prisma generate && next build", 10 | "start": "next start", 11 | "lint": "next lint" 12 | }, 13 | "dependencies": { 14 | "@auth/prisma-adapter": "^2.0.0", 15 | "@prisma/client": "^5.13.0", 16 | "bcryptjs": "^2.4.3", 17 | "next": "^14.2.18", 18 | "next-auth": "^5.0.0-beta.17", 19 | "react": "^18", 20 | "react-dom": "^18", 21 | "zod": "^3.23.5" 22 | }, 23 | "devDependencies": { 24 | "@types/bcryptjs": "^2.4.6", 25 | "@types/node": "^20", 26 | "@types/react": "^18", 27 | "@types/react-dom": "^18", 28 | "prisma": "^5.13.0", 29 | "typescript": "^5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | 2 | generator client { 3 | provider = "prisma-client-js" 4 | } 5 | 6 | datasource db { 7 | provider = "mongodb" 8 | url = env("DATABASE_URL") 9 | } 10 | 11 | 12 | 13 | model User { 14 | id String @id @default(auto()) @map("_id") @db.ObjectId 15 | username String 16 | email String @unique 17 | password String 18 | image String? 19 | accounts Account[] 20 | 21 | createdAt DateTime @default(now()) 22 | updatedAt DateTime @updatedAt 23 | } 24 | 25 | model Account { 26 | id String @id @default(auto()) @map("_id") @db.ObjectId 27 | userId String @db.ObjectId 28 | type String 29 | provider String 30 | providerAccountId String 31 | refresh_token String? @db.String 32 | access_token String? @db.String 33 | expires_at Int? 34 | token_type String? 35 | scope String? 36 | id_token String? @db.String 37 | session_state String? 38 | 39 | createdAt DateTime @default(now()) 40 | updatedAt DateTime @updatedAt 41 | 42 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 43 | 44 | @@unique([provider, providerAccountId]) 45 | } 46 | 47 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /remove_mee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmvergara/nextjs-mongodb-prisma-auth-template/4bda47ee44a59929a42c441841da96e8551a59f7/remove_mee.png -------------------------------------------------------------------------------- /routes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Public routes 3 | * @type {string[]} 4 | */ 5 | export const publicRoutes = ["/", "/sign-in", "/sign-up"]; 6 | 7 | export const authRoutes = ["/sign-in", "/sign-up"]; 8 | 9 | export const apiAuthPrefix = "/api/auth"; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | --------------------------------------------------------------------------------