From 759acde0450c34791af3d28cdaf6063d1628c3fd Mon Sep 17 00:00:00 2001 From: michivonah Date: Sat, 26 Apr 2025 16:56:09 +0200 Subject: [PATCH] add authentication with next-auth --- README.md | 125 ++++++++++++++++++ dashboard-app-course/app/lib/actions.ts | 21 +++ dashboard-app-course/app/login/page.tsx | 20 +++ .../app/ui/dashboard/sidenav.tsx | 8 +- dashboard-app-course/app/ui/login-form.tsx | 29 +++- dashboard-app-course/auth.config.ts | 21 +++ dashboard-app-course/auth.ts | 42 ++++++ dashboard-app-course/middleware.ts | 9 ++ dashboard-app-course/package-lock.json | 68 +++------- dashboard-app-course/package.json | 2 +- dashboard-app-course/pnpm-lock.yaml | 71 ++++------ 11 files changed, 318 insertions(+), 98 deletions(-) create mode 100644 dashboard-app-course/app/login/page.tsx create mode 100644 dashboard-app-course/auth.config.ts create mode 100644 dashboard-app-course/auth.ts create mode 100644 dashboard-app-course/middleware.ts diff --git a/README.md b/README.md index 8e92ed4..9fdfee0 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,14 @@ npm run dev Afterwards the site can be accessed in the browser at `http://localhost:3000`. ![New Next.js project](./docs/installation/empty_next_js_app.jpg) +## pnpm +`pnpm` is a faster and more efficient packet manager for `Node.js` than `npm`. It is recommended by `Next.js`. + +Install `pnpm` +```zsh +npm install -g pnpm +``` + ## General structure - The whole app is organzied in multiple folders. THe typescript files for the app are stored in `src/app`. @@ -662,6 +670,123 @@ if(!invoice){ Therefor the file `not-found.tsx` should be created with the UI you want to show, when something is not found. Thoose specific error pages will be shown before the general error page. +## Authentication +`NextAuth.js` or `Auth.js` is a popular library for implementing authentication into a `Next.js` application. This library takes away the most complexitiy and effort in building a robust authentication system from scratch. + +Install `Auth.js`: +```tsx +pnpm i next-auth@beta +``` + +Before using `Auth.js` it's required to generate a secret. This can be done with following command: +```zsh +openssl rand -base64 32 +``` + +Than this values has to be added to the `.env`-File as `AUTH_SECRET=`. + +The secret will be used to encrypt cookies for the user sessions. + +To configure the authentication a file `auth.config.ts` has to be created in the root directory of the app. +This file has to export a object with the config. + +Example: +```ts +import type { NextAuthConfig } from 'next-auth'; + +export const authConfig = { + pages: { + signIn: '/login', + }, +} satisfies NextAuthConfig; +``` + +But now the app routes aren't protect and everyone can access them. This can be changed by adding callbacks to `auth.config.ts` which control the pages that need authorization. +```ts +import type { NextAuthConfig } from 'next-auth'; + +export const authConfig = { + pages: { + signIn: '/login', + }, + callbacks: { + authorized({ auth, request: { nextUrl } }) { + const isLoggedIn = !!auth?.user; + const isOnDashboard = nextUrl.pathname.startsWith('/dashboard'); + if (isOnDashboard) { + if (isLoggedIn) return true; + return false; + } else if (isLoggedIn) { + return Response.redirect(new URL('/dashboard', nextUrl)); + } + return true; + }, + }, + providers: [], +} satisfies NextAuthConfig; +``` + +Next a middleware has to be created. This is defined in a file `middleware.ts` in the project's root. +```ts +import NextAuth from 'next-auth'; +import { authConfig } from './auth.config'; + +export default NextAuth(authConfig).auth; + +export const config = { + // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher + matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], +}; +``` + +Additonal a `auth.ts` file is needed. This could look like this: +```ts +import NextAuth from 'next-auth'; +import { authConfig } from './auth.config'; +import Credentials from 'next-auth/providers/credentials'; +import { z } from 'zod'; +import type { User } from '@/app/lib/definitions'; +import bcrypt from 'bcrypt'; +import postgres from 'postgres'; + +const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' }); + +async function getUser(email: string): Promise { + try{ + const user = await sql`SELECT * FROM users WHERE email=${email}`; + return user[0]; + } catch(error){ + console.error('Failed to fetch user:', error); + throw new Error('Failed to fetch user.'); + } +} + +export const { auth, signIn, signOut } = NextAuth({ + ...authConfig, + providers: [Credentials({ + async authorize(credentials){ + const parsedCredentials = z + .object({ email: z.string().email(), password: z.string().min(6)}) + .safeParse(credentials); + + if(parsedCredentials.success){ + const { email, password } = parsedCredentials.data; + const user = await getUser(email); + if(!user) return null; + const passwordsMatch = await bcrypt.compare(password, user.password); + + if(passwordsMatch) return user; + } + + console.log('Invalid credentials'); + return null; + }, + })], +}); +```` + +More details about the authentication can be found in the [Next.js course](https://nextjs.org/learn/dashboard-app/adding-authentication). + ## Ressources - [Next.js Installation](https://nextjs.org/docs/app/getting-started/installation) diff --git a/dashboard-app-course/app/lib/actions.ts b/dashboard-app-course/app/lib/actions.ts index 59ce378..aef0250 100644 --- a/dashboard-app-course/app/lib/actions.ts +++ b/dashboard-app-course/app/lib/actions.ts @@ -4,9 +4,30 @@ import { z } from 'zod'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import postgres from 'postgres'; +import { signIn } from '@/auth'; +import { AuthError } from 'next-auth'; const sql = postgres(process.env.POSTGRES_URL!, {ssl: 'require'}); +export async function authenticate( + prevState: string | undefined, + formData: FormData, +){ + try{ + await signIn('credentials', formData) + } catch(error){ + if(error instanceof AuthError){ + switch(error.type){ + case 'CredentialsSignIn': + return 'Invalid credentials.'; + default: + return 'Something went wrong.'; + } + } + throw error; + } +} + const FormSchema = z.object({ id: z.string(), customerId: z.string({ diff --git a/dashboard-app-course/app/login/page.tsx b/dashboard-app-course/app/login/page.tsx new file mode 100644 index 0000000..609ed5f --- /dev/null +++ b/dashboard-app-course/app/login/page.tsx @@ -0,0 +1,20 @@ +import AcmeLogo from "../ui/acme-logo"; +import LoginForm from "../ui/login-form"; +import { Suspense } from "react"; + +export default function LoginPage(){ + return ( +
+
+
+
+ +
+
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/dashboard-app-course/app/ui/dashboard/sidenav.tsx b/dashboard-app-course/app/ui/dashboard/sidenav.tsx index 3d55b46..4b5efcd 100644 --- a/dashboard-app-course/app/ui/dashboard/sidenav.tsx +++ b/dashboard-app-course/app/ui/dashboard/sidenav.tsx @@ -2,6 +2,7 @@ import Link from 'next/link'; import NavLinks from '@/app/ui/dashboard/nav-links'; import AcmeLogo from '@/app/ui/acme-logo'; import { PowerIcon } from '@heroicons/react/24/outline'; +import { signOut } from '@/auth'; export default function SideNav() { return ( @@ -17,7 +18,12 @@ export default function SideNav() {
-
+ { + 'use server'; + await signOut({redirectTo: '/'}); + }} + >
- -
- {/* Add form errors here */} +
+ {errorMessage && ( + <> + +

{errorMessage}

+ + )}
diff --git a/dashboard-app-course/auth.config.ts b/dashboard-app-course/auth.config.ts new file mode 100644 index 0000000..87dbdf7 --- /dev/null +++ b/dashboard-app-course/auth.config.ts @@ -0,0 +1,21 @@ +import type { NextAuthConfig } from 'next-auth'; + +export const authConfig = { + pages: { + signIn: '/login', + }, + callbacks: { + authorized({ auth, request: { nextUrl } }) { + const isLoggedIn = !!auth?.user; + const isOnDashboard = nextUrl.pathname.startsWith('/dashboard'); + if (isOnDashboard) { + if (isLoggedIn) return true; + return false; + } else if (isLoggedIn) { + return Response.redirect(new URL('/dashboard', nextUrl)); + } + return true; + }, + }, + providers: [], +} satisfies NextAuthConfig; \ No newline at end of file diff --git a/dashboard-app-course/auth.ts b/dashboard-app-course/auth.ts new file mode 100644 index 0000000..427e584 --- /dev/null +++ b/dashboard-app-course/auth.ts @@ -0,0 +1,42 @@ +import NextAuth from 'next-auth'; +import { authConfig } from './auth.config'; +import Credentials from 'next-auth/providers/credentials'; +import { z } from 'zod'; +import type { User } from '@/app/lib/definitions'; +import bcrypt from 'bcrypt'; +import postgres from 'postgres'; + +const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' }); + +async function getUser(email: string): Promise { + try{ + const user = await sql`SELECT * FROM users WHERE email=${email}`; + return user[0]; + } catch(error){ + console.error('Failed to fetch user:', error); + throw new Error('Failed to fetch user.'); + } +} + +export const { auth, signIn, signOut } = NextAuth({ + ...authConfig, + providers: [Credentials({ + async authorize(credentials){ + const parsedCredentials = z + .object({ email: z.string().email(), password: z.string().min(6)}) + .safeParse(credentials); + + if(parsedCredentials.success){ + const { email, password } = parsedCredentials.data; + const user = await getUser(email); + if(!user) return null; + const passwordsMatch = await bcrypt.compare(password, user.password); + + if(passwordsMatch) return user; + } + + console.log('Invalid credentials'); + return null; + }, + })], +}); \ No newline at end of file diff --git a/dashboard-app-course/middleware.ts b/dashboard-app-course/middleware.ts new file mode 100644 index 0000000..1fdda5b --- /dev/null +++ b/dashboard-app-course/middleware.ts @@ -0,0 +1,9 @@ +import NextAuth from 'next-auth'; +import { authConfig } from './auth.config'; + +export default NextAuth(authConfig).auth; + +export const config = { + // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher + matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], +}; \ No newline at end of file diff --git a/dashboard-app-course/package-lock.json b/dashboard-app-course/package-lock.json index 03ca9cf..a9cc35e 100644 --- a/dashboard-app-course/package-lock.json +++ b/dashboard-app-course/package-lock.json @@ -12,7 +12,7 @@ "bcrypt": "^5.1.1", "clsx": "^2.1.1", "next": "latest", - "next-auth": "5.0.0-beta.25", + "next-auth": "5.0.0-beta.26", "postcss": "8.5.1", "postgres": "^3.4.5", "react": "latest", @@ -42,18 +42,16 @@ } }, "node_modules/@auth/core": { - "version": "0.37.2", - "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.2.tgz", - "integrity": "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==", + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.39.0.tgz", + "integrity": "sha512-jusviw/sUSfAh6S/wjY5tRmJOq0Itd3ImF+c/b4HB9DfmfChtcfVJTNJeqCeExeCG8oh4PBKRsMQJsn2W6NhFQ==", "license": "ISC", "dependencies": { "@panva/hkdf": "^1.2.1", - "@types/cookie": "0.6.0", - "cookie": "0.7.1", - "jose": "^5.9.3", - "oauth4webapi": "^3.0.0", - "preact": "10.11.3", - "preact-render-to-string": "5.2.3" + "jose": "^6.0.6", + "oauth4webapi": "^3.3.0", + "preact": "10.24.3", + "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", @@ -841,12 +839,6 @@ "@types/node": "*" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "license": "MIT" - }, "node_modules/@types/node": { "version": "22.10.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", @@ -1267,15 +1259,6 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "license": "ISC" }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1729,9 +1712,9 @@ } }, "node_modules/jose": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", - "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz", + "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -1964,12 +1947,12 @@ } }, "node_modules/next-auth": { - "version": "5.0.0-beta.25", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.25.tgz", - "integrity": "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==", + "version": "5.0.0-beta.26", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.26.tgz", + "integrity": "sha512-yAQLIP2x6FAM+GX6FTlQjoPph6msO/9HI3pjI1z1yws3VnvS77atetcxQOmCpxSLTO4jzvpQqPaBZMgRxDgsYg==", "license": "ISC", "dependencies": { - "@auth/core": "0.37.2" + "@auth/core": "0.39.0" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", @@ -2429,9 +2412,9 @@ "license": "MIT" }, "node_modules/preact": { - "version": "10.11.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", - "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "version": "10.24.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", + "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", "license": "MIT", "funding": { "type": "opencollective", @@ -2439,23 +2422,14 @@ } }, "node_modules/preact-render-to-string": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", - "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", + "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", "license": "MIT", - "dependencies": { - "pretty-format": "^3.8.0" - }, "peerDependencies": { "preact": ">=10" } }, - "node_modules/pretty-format": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", - "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", - "license": "MIT" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", diff --git a/dashboard-app-course/package.json b/dashboard-app-course/package.json index 1301f85..c7bbba2 100644 --- a/dashboard-app-course/package.json +++ b/dashboard-app-course/package.json @@ -14,7 +14,7 @@ "bcrypt": "^5.1.1", "clsx": "^2.1.1", "next": "latest", - "next-auth": "5.0.0-beta.25", + "next-auth": "5.0.0-beta.26", "postcss": "8.5.1", "postgres": "^3.4.5", "react": "latest", diff --git a/dashboard-app-course/pnpm-lock.yaml b/dashboard-app-course/pnpm-lock.yaml index 33e4e1c..c4d5588 100644 --- a/dashboard-app-course/pnpm-lock.yaml +++ b/dashboard-app-course/pnpm-lock.yaml @@ -30,8 +30,8 @@ importers: specifier: latest version: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-auth: - specifier: 5.0.0-beta.25 - version: 5.0.0-beta.25(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) + specifier: 5.0.0-beta.26 + version: 5.0.0-beta.26(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) postcss: specifier: 8.5.1 version: 8.5.1 @@ -76,8 +76,8 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@auth/core@0.37.2': - resolution: {integrity: sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==} + '@auth/core@0.39.0': + resolution: {integrity: sha512-jusviw/sUSfAh6S/wjY5tRmJOq0Itd3ImF+c/b4HB9DfmfChtcfVJTNJeqCeExeCG8oh4PBKRsMQJsn2W6NhFQ==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/server': ^9.0.2 @@ -317,9 +317,6 @@ packages: '@types/bcrypt@5.0.2': resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/node@22.10.7': resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} @@ -462,10 +459,6 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -625,8 +618,8 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - jose@5.9.6: - resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} + jose@6.0.10: + resolution: {integrity: sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==} lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} @@ -693,8 +686,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - next-auth@5.0.0-beta.25: - resolution: {integrity: sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==} + next-auth@5.0.0-beta.26: + resolution: {integrity: sha512-yAQLIP2x6FAM+GX6FTlQjoPph6msO/9HI3pjI1z1yws3VnvS77atetcxQOmCpxSLTO4jzvpQqPaBZMgRxDgsYg==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/server': ^9.0.2 @@ -762,8 +755,8 @@ packages: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. - oauth4webapi@3.1.4: - resolution: {integrity: sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==} + oauth4webapi@3.5.0: + resolution: {integrity: sha512-DF3mLWNuxPkxJkHmWxbSFz4aE5CjWOsm465VBfBdWzmzX4Mg3vF8icxK+iKqfdWrIumBJ2TaoNQWx+SQc2bsPQ==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -895,16 +888,13 @@ packages: resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==} engines: {node: '>=12'} - preact-render-to-string@5.2.3: - resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + preact-render-to-string@6.5.11: + resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} peerDependencies: preact: '>=10' - preact@10.11.3: - resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} - - pretty-format@3.8.0: - resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + preact@10.24.3: + resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1127,15 +1117,13 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@auth/core@0.37.2': + '@auth/core@0.39.0': dependencies: '@panva/hkdf': 1.2.1 - '@types/cookie': 0.6.0 - cookie: 0.7.1 - jose: 5.9.6 - oauth4webapi: 3.1.4 - preact: 10.11.3 - preact-render-to-string: 5.2.3(preact@10.11.3) + jose: 6.0.10 + oauth4webapi: 3.5.0 + preact: 10.24.3 + preact-render-to-string: 6.5.11(preact@10.24.3) '@emnapi/runtime@1.3.1': dependencies: @@ -1325,8 +1313,6 @@ snapshots: dependencies: '@types/node': 22.10.7 - '@types/cookie@0.6.0': {} - '@types/node@22.10.7': dependencies: undici-types: 6.20.0 @@ -1473,8 +1459,6 @@ snapshots: console-control-strings@1.1.0: {} - cookie@0.7.1: {} - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1630,7 +1614,7 @@ snapshots: jiti@1.21.7: {} - jose@5.9.6: {} + jose@6.0.10: {} lilconfig@3.1.3: {} @@ -1684,9 +1668,9 @@ snapshots: nanoid@3.3.8: {} - next-auth@5.0.0-beta.25(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0): + next-auth@5.0.0-beta.26(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0): dependencies: - '@auth/core': 0.37.2 + '@auth/core': 0.39.0 next: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 @@ -1738,7 +1722,7 @@ snapshots: gauge: 3.0.2 set-blocking: 2.0.0 - oauth4webapi@3.1.4: {} + oauth4webapi@3.5.0: {} object-assign@4.1.1: {} @@ -1844,14 +1828,11 @@ snapshots: postgres@3.4.5: {} - preact-render-to-string@5.2.3(preact@10.11.3): + preact-render-to-string@6.5.11(preact@10.24.3): dependencies: - preact: 10.11.3 - pretty-format: 3.8.0 + preact: 10.24.3 - preact@10.11.3: {} - - pretty-format@3.8.0: {} + preact@10.24.3: {} queue-microtask@1.2.3: {}