add authentication with next-auth

This commit is contained in:
Michi 2025-04-26 16:56:09 +02:00
parent 5487bccd8d
commit 759acde045
11 changed files with 318 additions and 98 deletions

125
README.md
View file

@ -50,6 +50,14 @@ npm run dev
Afterwards the site can be accessed in the browser at `http://localhost:3000`. Afterwards the site can be accessed in the browser at `http://localhost:3000`.
![New Next.js project](./docs/installation/empty_next_js_app.jpg) ![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 ## General structure
- The whole app is organzied in multiple folders. THe typescript files for the app are stored in `src/app`. - 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. 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. 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=<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<User | undefined> {
try{
const user = await sql<User[]>`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 ## Ressources
- [Next.js Installation](https://nextjs.org/docs/app/getting-started/installation) - [Next.js Installation](https://nextjs.org/docs/app/getting-started/installation)

View file

@ -4,9 +4,30 @@ import { z } from 'zod';
import { revalidatePath } from 'next/cache'; import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import postgres from 'postgres'; import postgres from 'postgres';
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';
const sql = postgres(process.env.POSTGRES_URL!, {ssl: 'require'}); 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({ const FormSchema = z.object({
id: z.string(), id: z.string(),
customerId: z.string({ customerId: z.string({

View file

@ -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 (
<main className="flex items-center justify-center md:h-screen">
<div className="relative mx-auto flex w-full max-w-[400px] flex-col space-y-2.5 p-4 md:-mt-32">
<div className="flex h-20 w-full items-end rounded-lg bg-blue-500 p-3 md:h-36">
<div className="w-32 text-white md:w-36">
<AcmeLogo />
</div>
</div>
<Suspense>
<LoginForm />
</Suspense>
</div>
</main>
);
}

View file

@ -2,6 +2,7 @@ import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links'; import NavLinks from '@/app/ui/dashboard/nav-links';
import AcmeLogo from '@/app/ui/acme-logo'; import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline'; import { PowerIcon } from '@heroicons/react/24/outline';
import { signOut } from '@/auth';
export default function SideNav() { export default function SideNav() {
return ( return (
@ -17,7 +18,12 @@ export default function SideNav() {
<div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2"> <div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
<NavLinks /> <NavLinks />
<div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div> <div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
<form> <form
action={async () => {
'use server';
await signOut({redirectTo: '/'});
}}
>
<button className="flex h-[48px] w-full grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3"> <button className="flex h-[48px] w-full grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
<PowerIcon className="w-6" /> <PowerIcon className="w-6" />
<div className="hidden md:block">Sign Out</div> <div className="hidden md:block">Sign Out</div>

View file

@ -1,3 +1,5 @@
'use client';
import { lusitana } from '@/app/ui/fonts'; import { lusitana } from '@/app/ui/fonts';
import { import {
AtSymbolIcon, AtSymbolIcon,
@ -6,10 +8,19 @@ import {
} from '@heroicons/react/24/outline'; } from '@heroicons/react/24/outline';
import { ArrowRightIcon } from '@heroicons/react/20/solid'; import { ArrowRightIcon } from '@heroicons/react/20/solid';
import { Button } from './button'; import { Button } from './button';
import { useActionState } from 'react';
import { authenticate } from '../lib/actions';
import { useSearchParams } from 'next/navigation';
export default function LoginForm() { export default function LoginForm() {
const searchParams = useSearchParams();
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard';
const [errorMessage, formAction, isPending] = useActionState(
authenticate,
undefined,
);
return ( return (
<form className="space-y-3"> <form action={formAction} className="space-y-3">
<div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8"> <div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8">
<h1 className={`${lusitana.className} mb-3 text-2xl`}> <h1 className={`${lusitana.className} mb-3 text-2xl`}>
Please log in to continue. Please log in to continue.
@ -55,11 +66,21 @@ export default function LoginForm() {
</div> </div>
</div> </div>
</div> </div>
<Button className="mt-4 w-full"> <input type="hidden" name="redirectTo" value={callbackUrl} />
<Button className="mt-4 w-full" aria-disabled={isPending}>
Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" /> Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
</Button> </Button>
<div className="flex h-8 items-end space-x-1"> <div
{/* Add form errors here */} className="flex h-8 items-end space-x-1"
aria-live="polite"
aria-atomic="true"
>
{errorMessage && (
<>
<ExclamationCircleIcon className="h-5 w-5 text-red-500" />
<p className="text-sm text-red-500">{errorMessage}</p>
</>
)}
</div> </div>
</div> </div>
</form> </form>

View file

@ -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;

View file

@ -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<User | undefined> {
try{
const user = await sql<User[]>`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;
},
})],
});

View file

@ -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$).*)'],
};

View file

@ -12,7 +12,7 @@
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"next": "latest", "next": "latest",
"next-auth": "5.0.0-beta.25", "next-auth": "5.0.0-beta.26",
"postcss": "8.5.1", "postcss": "8.5.1",
"postgres": "^3.4.5", "postgres": "^3.4.5",
"react": "latest", "react": "latest",
@ -42,18 +42,16 @@
} }
}, },
"node_modules/@auth/core": { "node_modules/@auth/core": {
"version": "0.37.2", "version": "0.39.0",
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.2.tgz", "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.39.0.tgz",
"integrity": "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==", "integrity": "sha512-jusviw/sUSfAh6S/wjY5tRmJOq0Itd3ImF+c/b4HB9DfmfChtcfVJTNJeqCeExeCG8oh4PBKRsMQJsn2W6NhFQ==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@panva/hkdf": "^1.2.1", "@panva/hkdf": "^1.2.1",
"@types/cookie": "0.6.0", "jose": "^6.0.6",
"cookie": "0.7.1", "oauth4webapi": "^3.3.0",
"jose": "^5.9.3", "preact": "10.24.3",
"oauth4webapi": "^3.0.0", "preact-render-to-string": "6.5.11"
"preact": "10.11.3",
"preact-render-to-string": "5.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/browser": "^9.0.1",
@ -841,12 +839,6 @@
"@types/node": "*" "@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": { "node_modules/@types/node": {
"version": "22.10.7", "version": "22.10.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
@ -1267,15 +1259,6 @@
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"license": "ISC" "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": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -1729,9 +1712,9 @@
} }
}, },
"node_modules/jose": { "node_modules/jose": {
"version": "5.10.0", "version": "6.0.10",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz",
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/panva" "url": "https://github.com/sponsors/panva"
@ -1964,12 +1947,12 @@
} }
}, },
"node_modules/next-auth": { "node_modules/next-auth": {
"version": "5.0.0-beta.25", "version": "5.0.0-beta.26",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.25.tgz", "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.26.tgz",
"integrity": "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==", "integrity": "sha512-yAQLIP2x6FAM+GX6FTlQjoPph6msO/9HI3pjI1z1yws3VnvS77atetcxQOmCpxSLTO4jzvpQqPaBZMgRxDgsYg==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@auth/core": "0.37.2" "@auth/core": "0.39.0"
}, },
"peerDependencies": { "peerDependencies": {
"@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/browser": "^9.0.1",
@ -2429,9 +2412,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/preact": { "node_modules/preact": {
"version": "10.11.3", "version": "10.24.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
"integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -2439,23 +2422,14 @@
} }
}, },
"node_modules/preact-render-to-string": { "node_modules/preact-render-to-string": {
"version": "5.2.3", "version": "6.5.11",
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
"integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
"license": "MIT", "license": "MIT",
"dependencies": {
"pretty-format": "^3.8.0"
},
"peerDependencies": { "peerDependencies": {
"preact": ">=10" "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": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",

View file

@ -14,7 +14,7 @@
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"next": "latest", "next": "latest",
"next-auth": "5.0.0-beta.25", "next-auth": "5.0.0-beta.26",
"postcss": "8.5.1", "postcss": "8.5.1",
"postgres": "^3.4.5", "postgres": "^3.4.5",
"react": "latest", "react": "latest",

View file

@ -30,8 +30,8 @@ importers:
specifier: latest specifier: latest
version: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) version: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next-auth: next-auth:
specifier: 5.0.0-beta.25 specifier: 5.0.0-beta.26
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) 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: postcss:
specifier: 8.5.1 specifier: 8.5.1
version: 8.5.1 version: 8.5.1
@ -76,8 +76,8 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'} engines: {node: '>=10'}
'@auth/core@0.37.2': '@auth/core@0.39.0':
resolution: {integrity: sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==} resolution: {integrity: sha512-jusviw/sUSfAh6S/wjY5tRmJOq0Itd3ImF+c/b4HB9DfmfChtcfVJTNJeqCeExeCG8oh4PBKRsMQJsn2W6NhFQ==}
peerDependencies: peerDependencies:
'@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/browser': ^9.0.1
'@simplewebauthn/server': ^9.0.2 '@simplewebauthn/server': ^9.0.2
@ -317,9 +317,6 @@ packages:
'@types/bcrypt@5.0.2': '@types/bcrypt@5.0.2':
resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==}
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
'@types/node@22.10.7': '@types/node@22.10.7':
resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==}
@ -462,10 +459,6 @@ packages:
console-control-strings@1.1.0: console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} 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: cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -625,8 +618,8 @@ packages:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true hasBin: true
jose@5.9.6: jose@6.0.10:
resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} resolution: {integrity: sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==}
lilconfig@3.1.3: lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
@ -693,8 +686,8 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
next-auth@5.0.0-beta.25: next-auth@5.0.0-beta.26:
resolution: {integrity: sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==} resolution: {integrity: sha512-yAQLIP2x6FAM+GX6FTlQjoPph6msO/9HI3pjI1z1yws3VnvS77atetcxQOmCpxSLTO4jzvpQqPaBZMgRxDgsYg==}
peerDependencies: peerDependencies:
'@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/browser': ^9.0.1
'@simplewebauthn/server': ^9.0.2 '@simplewebauthn/server': ^9.0.2
@ -762,8 +755,8 @@ packages:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
deprecated: This package is no longer supported. deprecated: This package is no longer supported.
oauth4webapi@3.1.4: oauth4webapi@3.5.0:
resolution: {integrity: sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==} resolution: {integrity: sha512-DF3mLWNuxPkxJkHmWxbSFz4aE5CjWOsm465VBfBdWzmzX4Mg3vF8icxK+iKqfdWrIumBJ2TaoNQWx+SQc2bsPQ==}
object-assign@4.1.1: object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
@ -895,16 +888,13 @@ packages:
resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==} resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==}
engines: {node: '>=12'} engines: {node: '>=12'}
preact-render-to-string@5.2.3: preact-render-to-string@6.5.11:
resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==}
peerDependencies: peerDependencies:
preact: '>=10' preact: '>=10'
preact@10.11.3: preact@10.24.3:
resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==}
pretty-format@3.8.0:
resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -1127,15 +1117,13 @@ snapshots:
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
'@auth/core@0.37.2': '@auth/core@0.39.0':
dependencies: dependencies:
'@panva/hkdf': 1.2.1 '@panva/hkdf': 1.2.1
'@types/cookie': 0.6.0 jose: 6.0.10
cookie: 0.7.1 oauth4webapi: 3.5.0
jose: 5.9.6 preact: 10.24.3
oauth4webapi: 3.1.4 preact-render-to-string: 6.5.11(preact@10.24.3)
preact: 10.11.3
preact-render-to-string: 5.2.3(preact@10.11.3)
'@emnapi/runtime@1.3.1': '@emnapi/runtime@1.3.1':
dependencies: dependencies:
@ -1325,8 +1313,6 @@ snapshots:
dependencies: dependencies:
'@types/node': 22.10.7 '@types/node': 22.10.7
'@types/cookie@0.6.0': {}
'@types/node@22.10.7': '@types/node@22.10.7':
dependencies: dependencies:
undici-types: 6.20.0 undici-types: 6.20.0
@ -1473,8 +1459,6 @@ snapshots:
console-control-strings@1.1.0: {} console-control-strings@1.1.0: {}
cookie@0.7.1: {}
cross-spawn@7.0.6: cross-spawn@7.0.6:
dependencies: dependencies:
path-key: 3.1.1 path-key: 3.1.1
@ -1630,7 +1614,7 @@ snapshots:
jiti@1.21.7: {} jiti@1.21.7: {}
jose@5.9.6: {} jose@6.0.10: {}
lilconfig@3.1.3: {} lilconfig@3.1.3: {}
@ -1684,9 +1668,9 @@ snapshots:
nanoid@3.3.8: {} 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: 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) next: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0 react: 19.0.0
@ -1738,7 +1722,7 @@ snapshots:
gauge: 3.0.2 gauge: 3.0.2
set-blocking: 2.0.0 set-blocking: 2.0.0
oauth4webapi@3.1.4: {} oauth4webapi@3.5.0: {}
object-assign@4.1.1: {} object-assign@4.1.1: {}
@ -1844,14 +1828,11 @@ snapshots:
postgres@3.4.5: {} 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: dependencies:
preact: 10.11.3 preact: 10.24.3
pretty-format: 3.8.0
preact@10.11.3: {} preact@10.24.3: {}
pretty-format@3.8.0: {}
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}