From f8d6a16051ec7c2123993747b94f71325c85bc08 Mon Sep 17 00:00:00 2001 From: Jason Gerbes Date: Mon, 25 Nov 2024 10:35:22 +1300 Subject: [PATCH 1/2] Next 15 --- README.md | 9 +- actions/webauthn.ts | 3 +- app/2fa/passkey/actions.ts | 5 +- app/2fa/passkey/page.tsx | 7 +- app/2fa/passkey/register/actions.ts | 5 +- app/2fa/passkey/register/components.tsx | 4 +- app/2fa/passkey/register/page.tsx | 7 +- app/2fa/reset/actions.ts | 5 +- app/2fa/reset/components.tsx | 4 +- app/2fa/reset/page.tsx | 7 +- app/2fa/route.ts | 5 +- app/2fa/security-key/actions.ts | 5 +- app/2fa/security-key/page.tsx | 7 +- app/2fa/security-key/register/actions.ts | 5 +- app/2fa/security-key/register/components.tsx | 4 +- app/2fa/security-key/register/page.tsx | 7 +- app/2fa/setup/page.tsx | 7 +- app/2fa/totp/actions.ts | 5 +- app/2fa/totp/components.tsx | 4 +- app/2fa/totp/page.tsx | 7 +- app/2fa/totp/setup/actions.ts | 5 +- app/2fa/totp/setup/components.tsx | 4 +- app/2fa/totp/setup/page.tsx | 7 +- app/actions.ts | 7 +- app/components.tsx | 4 +- app/forgot-password/actions.ts | 8 +- app/forgot-password/components.tsx | 4 +- app/forgot-password/page.tsx | 5 +- app/login/actions.ts | 13 +- app/login/components.tsx | 5 +- app/login/page.tsx | 7 +- app/page.tsx | 7 +- app/recovery-code/page.tsx | 7 +- app/reset-password/2fa/passkey/actions.ts | 5 +- app/reset-password/2fa/passkey/page.tsx | 7 +- .../2fa/recovery-code/actions.ts | 5 +- .../2fa/recovery-code/components.tsx | 4 +- app/reset-password/2fa/recovery-code/page.tsx | 7 +- app/reset-password/2fa/route.ts | 5 +- .../2fa/security-key/actions.ts | 5 +- app/reset-password/2fa/security-key/page.tsx | 7 +- app/reset-password/2fa/totp/actions.ts | 5 +- app/reset-password/2fa/totp/components.tsx | 4 +- app/reset-password/2fa/totp/page.tsx | 7 +- app/reset-password/actions.ts | 9 +- app/reset-password/components.tsx | 4 +- app/reset-password/page.tsx | 7 +- app/reset-password/verify-email/actions.ts | 5 +- .../verify-email/components.tsx | 4 +- app/reset-password/verify-email/page.tsx | 7 +- app/settings/actions.ts | 34 +- app/settings/components.tsx | 13 +- app/settings/page.tsx | 7 +- app/signup/actions.ts | 10 +- app/signup/components.tsx | 4 +- app/signup/page.tsx | 7 +- app/verify-email/actions.ts | 18 +- app/verify-email/components.tsx | 6 +- app/verify-email/page.tsx | 9 +- lib/server/email-verification.ts | 19 +- lib/server/password-reset.ts | 17 +- lib/server/request.ts | 10 +- lib/server/session.ts | 15 +- next.config.mjs | 4 +- package.json | 17 +- pnpm-lock.yaml | 701 ++++++++++-------- 66 files changed, 674 insertions(+), 509 deletions(-) diff --git a/README.md b/README.md index 5274039..71d9803 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,12 @@ Emails are just logged to the console. Rate limiting is implemented using JavaSc Create `sqlite.db` and run `setup.sql`. -``` +```bash sqlite3 sqlite.db +.read setup.sql ``` -Create a .env file. Generate a 128 bit (16 byte) string, base64 encode it, and set it as `ENCRYPTION_KEY`. +Create a `.env` file. Generate a 128 bit (16 byte) string, base64 encode it, and set it as `ENCRYPTION_KEY`. ```bash ENCRYPTION_KEY="L9pmqRJnO1ZJSQ2svbHuBA==" @@ -30,12 +31,12 @@ ENCRYPTION_KEY="L9pmqRJnO1ZJSQ2svbHuBA==" > You can use OpenSSL to quickly generate a secure key. > > ```bash -> openssl rand --base64 16 +> openssl rand -base64 16 > ``` Install dependencies and run the application: -``` +```bash pnpm i pnpm dev ``` diff --git a/actions/webauthn.ts b/actions/webauthn.ts index 76fc83e..1d61720 100644 --- a/actions/webauthn.ts +++ b/actions/webauthn.ts @@ -9,7 +9,8 @@ const webauthnChallengeRateLimitBucket = new RefillingTokenBucket(30, 10 export async function createWebAuthnChallengeAction(): Promise { console.log("create"); - const clientIP = headers().get("X-Forwarded-For"); + const headersList = await headers(); + const clientIP = headersList.get("X-Forwarded-For"); if (clientIP !== null && !webauthnChallengeRateLimitBucket.consume(clientIP, 1)) { throw new Error("Too many requests"); } diff --git a/app/2fa/passkey/actions.ts b/app/2fa/passkey/actions.ts index ff0ca92..48414cf 100644 --- a/app/2fa/passkey/actions.ts +++ b/app/2fa/passkey/actions.ts @@ -20,13 +20,14 @@ import { globalPOSTRateLimit } from "@/lib/server/request"; import type { AuthenticatorData, ClientData } from "@oslojs/webauthn"; export async function verify2FAWithPasskeyAction(data: unknown): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { error: "Too many requests" }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null || user === null) { return { error: "Not authenticated" diff --git a/app/2fa/passkey/page.tsx b/app/2fa/passkey/page.tsx index ae2e40a..1cf8fd8 100644 --- a/app/2fa/passkey/page.tsx +++ b/app/2fa/passkey/page.tsx @@ -8,12 +8,13 @@ import { redirect } from "next/navigation"; import { encodeBase64 } from "@oslojs/encoding"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null || user === null) { return redirect("/login"); } diff --git a/app/2fa/passkey/register/actions.ts b/app/2fa/passkey/register/actions.ts index 6fc997f..538a2f9 100644 --- a/app/2fa/passkey/register/actions.ts +++ b/app/2fa/passkey/register/actions.ts @@ -28,13 +28,14 @@ import type { import type { WebAuthnUserCredential } from "@/lib/server/webauthn"; export async function registerPasskeyAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null || user === null) { return { message: "Not authenticated" diff --git a/app/2fa/passkey/register/components.tsx b/app/2fa/passkey/register/components.tsx index 5e7a1c9..3d083b8 100644 --- a/app/2fa/passkey/register/components.tsx +++ b/app/2fa/passkey/register/components.tsx @@ -3,7 +3,7 @@ import { createChallenge } from "@/lib/client/webauthn"; import { decodeBase64, encodeBase64 } from "@oslojs/encoding"; import { useState } from "react"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { registerPasskeyAction } from "./actions"; import type { User } from "@/lib/server/user"; @@ -19,7 +19,7 @@ export function RegisterPasskeyForm(props: { }) { const [encodedAttestationObject, setEncodedAttestationObject] = useState(null); const [encodedClientDataJSON, setEncodedClientDataJSON] = useState(null); - const [formState, action] = useFormState(registerPasskeyAction, initialRegisterPasskeyState); + const [formState, action] = useActionState(registerPasskeyAction, initialRegisterPasskeyState); return ( <> diff --git a/app/forgot-password/actions.ts b/app/forgot-password/actions.ts index 85d6138..ebf0aee 100644 --- a/app/forgot-password/actions.ts +++ b/app/forgot-password/actions.ts @@ -18,14 +18,16 @@ const passwordResetEmailIPBucket = new RefillingTokenBucket(3, 60); const passwordResetEmailUserBucket = new RefillingTokenBucket(3, 60); export async function forgotPasswordAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } // TODO: Assumes X-Forwarded-For is always included. - const clientIP = headers().get("X-Forwarded-For"); + const headersList = await headers(); + const clientIP = headersList.get("X-Forwarded-For"); if (clientIP !== null && !passwordResetEmailIPBucket.check(clientIP, 1)) { return { message: "Too many requests" @@ -64,7 +66,7 @@ export async function forgotPasswordAction(_prev: ActionResult, formData: FormDa const session = createPasswordResetSession(sessionToken, user.id, user.email); sendPasswordResetEmail(session.email, session.code); - setPasswordResetSessionTokenCookie(sessionToken, session.expiresAt); + await setPasswordResetSessionTokenCookie(sessionToken, session.expiresAt); return redirect("/reset-password/verify-email"); } diff --git a/app/forgot-password/components.tsx b/app/forgot-password/components.tsx index eb244e6..a81b5f8 100644 --- a/app/forgot-password/components.tsx +++ b/app/forgot-password/components.tsx @@ -1,6 +1,6 @@ "use client"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { forgotPasswordAction } from "./actions"; const initialForgotPasswordState = { @@ -8,7 +8,7 @@ const initialForgotPasswordState = { }; export function ForgotPasswordForm() { - const [state, action] = useFormState(forgotPasswordAction, initialForgotPasswordState); + const [state, action] = useActionState(forgotPasswordAction, initialForgotPasswordState); return ( diff --git a/app/forgot-password/page.tsx b/app/forgot-password/page.tsx index 65c56ee..397209f 100644 --- a/app/forgot-password/page.tsx +++ b/app/forgot-password/page.tsx @@ -3,8 +3,9 @@ import Link from "next/link"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } diff --git a/app/login/actions.ts b/app/login/actions.ts index a5a187d..a0be0fd 100644 --- a/app/login/actions.ts +++ b/app/login/actions.ts @@ -31,13 +31,15 @@ const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 180, 300]); const ipBucket = new RefillingTokenBucket(20, 1); export async function loginAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } // TODO: Assumes X-Forwarded-For is always included. - const clientIP = headers().get("X-Forwarded-For"); + const headersList = await headers(); + const clientIP = headersList.get("X-Forwarded-For"); if (clientIP !== null && !ipBucket.check(clientIP, 1)) { return { message: "Too many requests" @@ -90,7 +92,7 @@ export async function loginAction(_prev: ActionResult, formData: FormData): Prom }; const sessionToken = generateSessionToken(); const session = createSession(sessionToken, user.id, sessionFlags); - setSessionTokenCookie(sessionToken, session.expiresAt); + await setSessionTokenCookie(sessionToken, session.expiresAt); if (!user.emailVerified) { return redirect("/verify-email"); @@ -102,7 +104,8 @@ export async function loginAction(_prev: ActionResult, formData: FormData): Prom } export async function loginWithPasskeyAction(data: unknown): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; @@ -222,7 +225,7 @@ export async function loginWithPasskeyAction(data: unknown): Promise diff --git a/app/login/page.tsx b/app/login/page.tsx index bb33b43..e7a1275 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -6,12 +6,13 @@ import { redirect } from "next/navigation"; import { get2FARedirect } from "@/lib/server/2fa"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session !== null) { if (!user.emailVerified) { return redirect("/verify-email"); diff --git a/app/page.tsx b/app/page.tsx index 9b75d5b..79b6d78 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,12 +6,13 @@ import { redirect } from "next/navigation"; import { get2FARedirect } from "@/lib/server/2fa"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null) { return redirect("/login"); } diff --git a/app/recovery-code/page.tsx b/app/recovery-code/page.tsx index 40a4cbd..088e9c7 100644 --- a/app/recovery-code/page.tsx +++ b/app/recovery-code/page.tsx @@ -6,12 +6,13 @@ import { getUserRecoverCode } from "@/lib/server/user"; import { redirect } from "next/navigation"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null) { return redirect("/login"); } diff --git a/app/reset-password/2fa/passkey/actions.ts b/app/reset-password/2fa/passkey/actions.ts index 5f4e51f..ed41cb7 100644 --- a/app/reset-password/2fa/passkey/actions.ts +++ b/app/reset-password/2fa/passkey/actions.ts @@ -20,13 +20,14 @@ import { globalPOSTRateLimit } from "@/lib/server/request"; import type { AuthenticatorData, ClientData } from "@oslojs/webauthn"; export async function verify2FAWithPasskeyAction(data: unknown): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { error: "Too many requests" }; } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null || user === null) { return { error: "Not authenticated" diff --git a/app/reset-password/2fa/passkey/page.tsx b/app/reset-password/2fa/passkey/page.tsx index 7e500c0..17d3e14 100644 --- a/app/reset-password/2fa/passkey/page.tsx +++ b/app/reset-password/2fa/passkey/page.tsx @@ -8,12 +8,13 @@ import { getPasswordReset2FARedirect } from "@/lib/server/2fa"; import { encodeBase64 } from "@oslojs/encoding"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null) { return redirect("/forgot-password"); diff --git a/app/reset-password/2fa/recovery-code/actions.ts b/app/reset-password/2fa/recovery-code/actions.ts index 194e806..79d784d 100644 --- a/app/reset-password/2fa/recovery-code/actions.ts +++ b/app/reset-password/2fa/recovery-code/actions.ts @@ -9,13 +9,14 @@ export async function verifyPasswordReset2FAWithRecoveryCodeAction( _prev: ActionResult, formData: FormData ): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null) { return { message: "Not authenticated" diff --git a/app/reset-password/2fa/recovery-code/components.tsx b/app/reset-password/2fa/recovery-code/components.tsx index ba622d9..8441361 100644 --- a/app/reset-password/2fa/recovery-code/components.tsx +++ b/app/reset-password/2fa/recovery-code/components.tsx @@ -1,6 +1,6 @@ "use client"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { verifyPasswordReset2FAWithRecoveryCodeAction } from "./actions"; const initialPasswordResetRecoveryCodeState = { @@ -8,7 +8,7 @@ const initialPasswordResetRecoveryCodeState = { }; export function PasswordResetRecoveryCodeForm() { - const [state, action] = useFormState( + const [state, action] = useActionState( verifyPasswordReset2FAWithRecoveryCodeAction, initialPasswordResetRecoveryCodeState ); diff --git a/app/reset-password/2fa/recovery-code/page.tsx b/app/reset-password/2fa/recovery-code/page.tsx index 914a05d..138aab9 100644 --- a/app/reset-password/2fa/recovery-code/page.tsx +++ b/app/reset-password/2fa/recovery-code/page.tsx @@ -4,12 +4,13 @@ import { getCurrentPasswordResetSession } from "@/lib/server/password-reset"; import { redirect } from "next/navigation"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null) { return redirect("/forgot-password"); diff --git a/app/reset-password/2fa/route.ts b/app/reset-password/2fa/route.ts index 36e621d..07c8b36 100644 --- a/app/reset-password/2fa/route.ts +++ b/app/reset-password/2fa/route.ts @@ -3,12 +3,13 @@ import { getCurrentPasswordResetSession } from "@/lib/server/password-reset"; import { globalGETRateLimit } from "@/lib/server/request"; export async function GET() { - if (!globalGETRateLimit()) { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return new Response("Too many requests", { status: 429 }); } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null) { return new Response(null, { status: 302, diff --git a/app/reset-password/2fa/security-key/actions.ts b/app/reset-password/2fa/security-key/actions.ts index 08a9ea8..6a7f66f 100644 --- a/app/reset-password/2fa/security-key/actions.ts +++ b/app/reset-password/2fa/security-key/actions.ts @@ -20,13 +20,14 @@ import { globalPOSTRateLimit } from "@/lib/server/request"; import type { AuthenticatorData, ClientData } from "@oslojs/webauthn"; export async function verify2FAWithSecurityKeyAction(data: unknown): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { error: "Too many requests" }; } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null || user === null) { return { error: "Not authenticated" diff --git a/app/reset-password/2fa/security-key/page.tsx b/app/reset-password/2fa/security-key/page.tsx index 057eb8c..b2496ab 100644 --- a/app/reset-password/2fa/security-key/page.tsx +++ b/app/reset-password/2fa/security-key/page.tsx @@ -8,12 +8,13 @@ import { getPasswordReset2FARedirect } from "@/lib/server/2fa"; import { encodeBase64 } from "@oslojs/encoding"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null) { return redirect("/forgot-password"); diff --git a/app/reset-password/2fa/totp/actions.ts b/app/reset-password/2fa/totp/actions.ts index 391d0e9..9ca4f99 100644 --- a/app/reset-password/2fa/totp/actions.ts +++ b/app/reset-password/2fa/totp/actions.ts @@ -10,13 +10,14 @@ export async function verifyPasswordReset2FAWithTOTPAction( _prev: ActionResult, formData: FormData ): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null) { return { message: "Not authenticated" diff --git a/app/reset-password/2fa/totp/components.tsx b/app/reset-password/2fa/totp/components.tsx index 63e5d05..376d713 100644 --- a/app/reset-password/2fa/totp/components.tsx +++ b/app/reset-password/2fa/totp/components.tsx @@ -1,6 +1,6 @@ "use client"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { verifyPasswordReset2FAWithTOTPAction } from "./actions"; const initialPasswordResetTOTPState = { @@ -8,7 +8,7 @@ const initialPasswordResetTOTPState = { }; export function PasswordResetTOTPForm() { - const [state, action] = useFormState(verifyPasswordReset2FAWithTOTPAction, initialPasswordResetTOTPState); + const [state, action] = useActionState(verifyPasswordReset2FAWithTOTPAction, initialPasswordResetTOTPState); return ( diff --git a/app/reset-password/2fa/totp/page.tsx b/app/reset-password/2fa/totp/page.tsx index 6a49878..64d6258 100644 --- a/app/reset-password/2fa/totp/page.tsx +++ b/app/reset-password/2fa/totp/page.tsx @@ -6,12 +6,13 @@ import { redirect } from "next/navigation"; import { getPasswordReset2FARedirect } from "@/lib/server/2fa"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null) { return redirect("/forgot-password"); diff --git a/app/reset-password/actions.ts b/app/reset-password/actions.ts index ef14255..9885f72 100644 --- a/app/reset-password/actions.ts +++ b/app/reset-password/actions.ts @@ -19,13 +19,14 @@ import { globalPOSTRateLimit } from "@/lib/server/request"; import type { SessionFlags } from "@/lib/server/session"; export async function resetPasswordAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session: passwordResetSession, user } = getCurrentPasswordResetSession(); + const { session: passwordResetSession, user } = await getCurrentPasswordResetSession(); if (passwordResetSession === null) { return { message: "Not authenticated" @@ -64,8 +65,8 @@ export async function resetPasswordAction(_prev: ActionResult, formData: FormDat }; const sessionToken = generateSessionToken(); const session = createSession(sessionToken, user.id, sessionFlags); - setSessionTokenCookie(sessionToken, session.expiresAt); - deletePasswordResetSessionTokenCookie(); + await setSessionTokenCookie(sessionToken, session.expiresAt); + await deletePasswordResetSessionTokenCookie(); return redirect("/"); } diff --git a/app/reset-password/components.tsx b/app/reset-password/components.tsx index b1951bd..601629f 100644 --- a/app/reset-password/components.tsx +++ b/app/reset-password/components.tsx @@ -1,6 +1,6 @@ "use client"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { resetPasswordAction } from "./actions"; const initialPasswordResetState = { @@ -8,7 +8,7 @@ const initialPasswordResetState = { }; export function PasswordResetForm() { - const [state, action] = useFormState(resetPasswordAction, initialPasswordResetState); + const [state, action] = useActionState(resetPasswordAction, initialPasswordResetState); return ( diff --git a/app/reset-password/page.tsx b/app/reset-password/page.tsx index ae088f2..308d01a 100644 --- a/app/reset-password/page.tsx +++ b/app/reset-password/page.tsx @@ -4,12 +4,13 @@ import { getCurrentPasswordResetSession } from "@/lib/server/password-reset"; import { redirect } from "next/navigation"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentPasswordResetSession(); + const { session, user } = await getCurrentPasswordResetSession(); if (session === null) { return redirect("/forgot-password"); } diff --git a/app/reset-password/verify-email/actions.ts b/app/reset-password/verify-email/actions.ts index 41e6666..2be899f 100644 --- a/app/reset-password/verify-email/actions.ts +++ b/app/reset-password/verify-email/actions.ts @@ -9,13 +9,14 @@ import { globalPOSTRateLimit } from "@/lib/server/request"; const emailVerificationBucket = new ExpiringTokenBucket(5, 60 * 30); export async function verifyPasswordResetEmailAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session } = getCurrentPasswordResetSession(); + const { session } = await getCurrentPasswordResetSession(); if (session === null) { return { message: "Not authenticated" diff --git a/app/reset-password/verify-email/components.tsx b/app/reset-password/verify-email/components.tsx index bc2b391..47cadc6 100644 --- a/app/reset-password/verify-email/components.tsx +++ b/app/reset-password/verify-email/components.tsx @@ -1,6 +1,6 @@ "use client"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { verifyPasswordResetEmailAction } from "./actions"; const initialPasswordResetEmailVerificationState = { @@ -8,7 +8,7 @@ const initialPasswordResetEmailVerificationState = { }; export function PasswordResetEmailVerificationForm() { - const [state, action] = useFormState(verifyPasswordResetEmailAction, initialPasswordResetEmailVerificationState); + const [state, action] = useActionState(verifyPasswordResetEmailAction, initialPasswordResetEmailVerificationState); return ( diff --git a/app/reset-password/verify-email/page.tsx b/app/reset-password/verify-email/page.tsx index 3376242..7e96177 100644 --- a/app/reset-password/verify-email/page.tsx +++ b/app/reset-password/verify-email/page.tsx @@ -4,12 +4,13 @@ import { getCurrentPasswordResetSession } from "@/lib/server/password-reset"; import { redirect } from "next/navigation"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session } = getCurrentPasswordResetSession(); + const { session } = await getCurrentPasswordResetSession(); if (session === null) { return redirect("/forgot-password"); } diff --git a/app/settings/actions.ts b/app/settings/actions.ts index 9144763..ee841fa 100644 --- a/app/settings/actions.ts +++ b/app/settings/actions.ts @@ -28,13 +28,14 @@ import type { SessionFlags } from "@/lib/server/session"; const passwordUpdateBucket = new ExpiringTokenBucket(5, 60 * 30); export async function updatePasswordAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null) { return { message: "Not authenticated" @@ -85,20 +86,21 @@ export async function updatePasswordAction(_prev: ActionResult, formData: FormDa twoFactorVerified: session.twoFactorVerified }; const newSession = createSession(sessionToken, user.id, sessionFlags); - setSessionTokenCookie(sessionToken, newSession.expiresAt); + await setSessionTokenCookie(sessionToken, newSession.expiresAt); return { message: "Updated password" }; } export async function updateEmailAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null) { return { message: "Not authenticated" @@ -142,18 +144,19 @@ export async function updateEmailAction(_prev: ActionResult, formData: FormData) } const verificationRequest = createEmailVerificationRequest(user.id, email); sendVerificationEmail(verificationRequest.email, verificationRequest.code); - setEmailVerificationRequestCookie(verificationRequest); + await setEmailVerificationRequestCookie(verificationRequest); return redirect("/verify-email"); } export async function disconnectTOTPAction(): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null || user === null) { return { message: "Not authenticated" @@ -181,13 +184,14 @@ export async function disconnectTOTPAction(): Promise { } export async function deletePasskeyAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null || user === null) { return { message: "Not authenticated" @@ -229,13 +233,14 @@ export async function deletePasskeyAction(_prev: ActionResult, formData: FormDat } export async function deleteSecurityKeyAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null || user === null) { return { message: "Not authenticated" @@ -278,14 +283,15 @@ export async function deleteSecurityKeyAction(_prev: ActionResult, formData: For } export async function regenerateRecoveryCodeAction(): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { error: "Too many requests", recoveryCode: null }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null || user === null) { return { error: "Not authenticated", diff --git a/app/settings/components.tsx b/app/settings/components.tsx index 87afd46..eac0d18 100644 --- a/app/settings/components.tsx +++ b/app/settings/components.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useActionState, useState } from "react"; import { deletePasskeyAction, deleteSecurityKeyAction, @@ -9,14 +9,13 @@ import { updateEmailAction, updatePasswordAction } from "./actions"; -import { useFormState } from "react-dom"; const initialUpdatePasswordState = { message: "" }; export function UpdatePasswordForm() { - const [state, action] = useFormState(updatePasswordAction, initialUpdatePasswordState); + const [state, action] = useActionState(updatePasswordAction, initialUpdatePasswordState); return ( @@ -37,7 +36,7 @@ const initialUpdateFormState = { }; export function UpdateEmailForm() { - const [state, action] = useFormState(updateEmailAction, initialUpdateFormState); + const [state, action] = useActionState(updateEmailAction, initialUpdateFormState); return ( @@ -55,7 +54,7 @@ const initialDisconnectTOTPState = { }; export function DisconnectTOTPButton() { - const [state, formAction] = useFormState(disconnectTOTPAction, initialDisconnectTOTPState); + const [state, formAction] = useActionState(disconnectTOTPAction, initialDisconnectTOTPState); return ( @@ -69,7 +68,7 @@ const initialPasskeyState = { }; export function PasskeyCredentialListItem(props: { encodedId: string; name: string }) { - const [state, formAction] = useFormState(deletePasskeyAction, initialPasskeyState); + const [state, formAction] = useActionState(deletePasskeyAction, initialPasskeyState); return (
  • {props.name}

    @@ -87,7 +86,7 @@ const initialSecurityKeyState = { }; export function SecurityKeyCredentialListItem(props: { encodedId: string; name: string }) { - const [state, formAction] = useFormState(deleteSecurityKeyAction, initialSecurityKeyState); + const [state, formAction] = useActionState(deleteSecurityKeyAction, initialSecurityKeyState); return (
  • {props.name}

    diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 05eca02..f618658 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -16,12 +16,13 @@ import { getUserPasskeyCredentials, getUserSecurityKeyCredentials } from "@/lib/ import { encodeBase64 } from "@oslojs/encoding"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null) { return redirect("/login"); } diff --git a/app/signup/actions.ts b/app/signup/actions.ts index a365a62..c6dcee7 100644 --- a/app/signup/actions.ts +++ b/app/signup/actions.ts @@ -19,14 +19,16 @@ import type { SessionFlags } from "@/lib/server/session"; const ipBucket = new RefillingTokenBucket(3, 10); export async function signupAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } // TODO: Assumes X-Forwarded-For is always included. - const clientIP = headers().get("X-Forwarded-For"); + const headersList = await headers(); + const clientIP = headersList.get("X-Forwarded-For"); if (clientIP !== null && !ipBucket.check(clientIP, 1)) { return { message: "Too many requests" @@ -76,14 +78,14 @@ export async function signupAction(_prev: ActionResult, formData: FormData): Pro const user = await createUser(email, username, password); const emailVerificationRequest = createEmailVerificationRequest(user.id, user.email); sendVerificationEmail(emailVerificationRequest.email, emailVerificationRequest.code); - setEmailVerificationRequestCookie(emailVerificationRequest); + await setEmailVerificationRequestCookie(emailVerificationRequest); const sessionFlags: SessionFlags = { twoFactorVerified: false }; const sessionToken = generateSessionToken(); const session = createSession(sessionToken, user.id, sessionFlags); - setSessionTokenCookie(sessionToken, session.expiresAt); + await setSessionTokenCookie(sessionToken, session.expiresAt); return redirect("/2fa/setup"); } diff --git a/app/signup/components.tsx b/app/signup/components.tsx index 64433c0..91e595c 100644 --- a/app/signup/components.tsx +++ b/app/signup/components.tsx @@ -1,14 +1,14 @@ "use client"; import { signupAction } from "./actions"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; const initialState = { message: "" }; export function SignUpForm() { - const [state, action] = useFormState(signupAction, initialState); + const [state, action] = useActionState(signupAction, initialState); return ( diff --git a/app/signup/page.tsx b/app/signup/page.tsx index 948ee8f..41820eb 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -6,12 +6,13 @@ import { redirect } from "next/navigation"; import { get2FARedirect } from "@/lib/server/2fa"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session !== null) { if (!user.emailVerified) { return redirect("/verify-email"); diff --git a/app/verify-email/actions.ts b/app/verify-email/actions.ts index 9492eb4..d76e10d 100644 --- a/app/verify-email/actions.ts +++ b/app/verify-email/actions.ts @@ -19,13 +19,14 @@ import { globalPOSTRateLimit } from "@/lib/server/request"; const bucket = new ExpiringTokenBucket(5, 60 * 30); export async function verifyEmailAction(_prev: ActionResult, formData: FormData): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null) { return { message: "Not authenticated" @@ -42,7 +43,7 @@ export async function verifyEmailAction(_prev: ActionResult, formData: FormData) }; } - let verificationRequest = getCurrentUserEmailVerificationRequest(); + let verificationRequest = await getCurrentUserEmailVerificationRequest(); if (verificationRequest === null) { return { message: "Not authenticated" @@ -79,7 +80,7 @@ export async function verifyEmailAction(_prev: ActionResult, formData: FormData) deleteUserEmailVerificationRequest(user.id); invalidateUserPasswordResetSessions(user.id); updateUserEmailAndSetEmailAsVerified(user.id, verificationRequest.email); - deleteEmailVerificationRequestCookie(); + await deleteEmailVerificationRequestCookie(); if (!user.registered2FA) { return redirect("/2fa/setup"); } @@ -87,13 +88,14 @@ export async function verifyEmailAction(_prev: ActionResult, formData: FormData) } export async function resendEmailVerificationCodeAction(): Promise { - if (!globalPOSTRateLimit()) { + const canPerformRequest = await globalPOSTRateLimit(); + if (!canPerformRequest) { return { message: "Too many requests" }; } - const { session, user } = getCurrentSession(); + const { session, user } = await getCurrentSession(); if (session === null) { return { message: "Not authenticated" @@ -109,7 +111,7 @@ export async function resendEmailVerificationCodeAction(): Promise message: "Too many requests" }; } - let verificationRequest = getCurrentUserEmailVerificationRequest(); + let verificationRequest = await getCurrentUserEmailVerificationRequest(); if (verificationRequest === null) { if (user.emailVerified) { return { @@ -131,7 +133,7 @@ export async function resendEmailVerificationCodeAction(): Promise verificationRequest = createEmailVerificationRequest(user.id, verificationRequest.email); } sendVerificationEmail(verificationRequest.email, verificationRequest.code); - setEmailVerificationRequestCookie(verificationRequest); + await setEmailVerificationRequestCookie(verificationRequest); return { message: "A new code was sent to your inbox." }; diff --git a/app/verify-email/components.tsx b/app/verify-email/components.tsx index 693e504..7c2b680 100644 --- a/app/verify-email/components.tsx +++ b/app/verify-email/components.tsx @@ -1,14 +1,14 @@ "use client"; import { resendEmailVerificationCodeAction, verifyEmailAction } from "./actions"; -import { useFormState } from "react-dom"; +import { useActionState } from "react"; const emailVerificationInitialState = { message: "" }; export function EmailVerificationForm() { - const [state, action] = useFormState(verifyEmailAction, emailVerificationInitialState); + const [state, action] = useActionState(verifyEmailAction, emailVerificationInitialState); return ( @@ -24,7 +24,7 @@ const resendEmailInitialState = { }; export function ResendEmailVerificationCodeForm() { - const [state, action] = useFormState(resendEmailVerificationCodeAction, resendEmailInitialState); + const [state, action] = useActionState(resendEmailVerificationCodeAction, resendEmailInitialState); return ( diff --git a/app/verify-email/page.tsx b/app/verify-email/page.tsx index 3a31ae9..a0d8a05 100644 --- a/app/verify-email/page.tsx +++ b/app/verify-email/page.tsx @@ -6,19 +6,20 @@ import { redirect } from "next/navigation"; import { getCurrentUserEmailVerificationRequest } from "@/lib/server/email-verification"; import { globalGETRateLimit } from "@/lib/server/request"; -export default function Page() { - if (!globalGETRateLimit()) { +export default async function Page() { + const canPerformRequest = await globalGETRateLimit(); + if (!canPerformRequest) { return "Too many requests"; } - const { user } = getCurrentSession(); + const { user } = await getCurrentSession(); if (user === null) { return redirect("/redirect"); } // TODO: Ideally we'd sent a new verification email automatically if the previous one is expired, // but we can't set cookies inside server components. - const verificationRequest = getCurrentUserEmailVerificationRequest(); + const verificationRequest = await getCurrentUserEmailVerificationRequest(); if (verificationRequest === null && user.emailVerified) { return redirect("/"); } diff --git a/lib/server/email-verification.ts b/lib/server/email-verification.ts index e94ebb0..c6ef0c5 100644 --- a/lib/server/email-verification.ts +++ b/lib/server/email-verification.ts @@ -55,8 +55,9 @@ export function sendVerificationEmail(email: string, code: string): void { console.log(`To ${email}: Your verification code is ${code}`); } -export function setEmailVerificationRequestCookie(request: EmailVerificationRequest): void { - cookies().set("email_verification", request.id, { +export async function setEmailVerificationRequestCookie(request: EmailVerificationRequest): Promise { + const cookieStore = await cookies(); + cookieStore.set("email_verification", request.id, { httpOnly: true, path: "/", secure: process.env.NODE_ENV === "production", @@ -65,8 +66,9 @@ export function setEmailVerificationRequestCookie(request: EmailVerificationRequ }); } -export function deleteEmailVerificationRequestCookie(): void { - cookies().set("email_verification", "", { +export async function deleteEmailVerificationRequestCookie(): Promise { + const cookieStore = await cookies(); + cookieStore.set("email_verification", "", { httpOnly: true, path: "/", secure: process.env.NODE_ENV === "production", @@ -75,18 +77,19 @@ export function deleteEmailVerificationRequestCookie(): void { }); } -export const getCurrentUserEmailVerificationRequest = cache(() => { - const { user } = getCurrentSession(); +export const getCurrentUserEmailVerificationRequest = cache(async () => { + const { user } = await getCurrentSession(); if (user === null) { return null; } - const id = cookies().get("email_verification")?.value ?? null; + const cookieStore = await cookies(); + const id = cookieStore.get("email_verification")?.value ?? null; if (id === null) { return null; } const request = getUserEmailVerificationRequest(user.id, id); if (request === null) { - deleteEmailVerificationRequestCookie(); + await deleteEmailVerificationRequestCookie(); } return request; }); diff --git a/lib/server/password-reset.ts b/lib/server/password-reset.ts index 5421f34..97ada35 100644 --- a/lib/server/password-reset.ts +++ b/lib/server/password-reset.ts @@ -84,20 +84,22 @@ export function invalidateUserPasswordResetSessions(userId: number): void { db.execute("DELETE FROM password_reset_session WHERE user_id = ?", [userId]); } -export const getCurrentPasswordResetSession = cache(() => { - const token = cookies().get("password_reset_session")?.value ?? null; +export const getCurrentPasswordResetSession = cache(async () => { + const cookieStore = await cookies(); + const token = cookieStore.get("password_reset_session")?.value ?? null; if (token === null) { return { session: null, user: null }; } const result = validatePasswordResetSessionToken(token); if (result.session === null) { - deletePasswordResetSessionTokenCookie(); + await deletePasswordResetSessionTokenCookie(); } return result; }); -export function setPasswordResetSessionTokenCookie(token: string, expiresAt: Date): void { - cookies().set("password_reset_session", token, { +export async function setPasswordResetSessionTokenCookie(token: string, expiresAt: Date): Promise { + const cookieStore = await cookies(); + cookieStore.set("password_reset_session", token, { expires: expiresAt, sameSite: "lax", httpOnly: true, @@ -106,8 +108,9 @@ export function setPasswordResetSessionTokenCookie(token: string, expiresAt: Dat }); } -export function deletePasswordResetSessionTokenCookie(): void { - cookies().set("password_reset_session", "", { +export async function deletePasswordResetSessionTokenCookie(): Promise { + const cookieStore = await cookies(); + cookieStore.set("password_reset_session", "", { maxAge: 0, sameSite: "lax", httpOnly: true, diff --git a/lib/server/request.ts b/lib/server/request.ts index 19aebf1..fdba69e 100644 --- a/lib/server/request.ts +++ b/lib/server/request.ts @@ -3,18 +3,20 @@ import { RefillingTokenBucket } from "./rate-limit"; export const globalBucket = new RefillingTokenBucket(100, 1); -export function globalGETRateLimit(): boolean { +export async function globalGETRateLimit(): Promise { // Note: Assumes X-Forwarded-For will always be defined. - const clientIP = headers().get("X-Forwarded-For"); + const headersList = await headers(); + const clientIP = headersList.get("X-Forwarded-For"); if (clientIP === null) { return true; } return globalBucket.consume(clientIP, 1); } -export function globalPOSTRateLimit(): boolean { +export async function globalPOSTRateLimit(): Promise { // Note: Assumes X-Forwarded-For will always be defined. - const clientIP = headers().get("X-Forwarded-For"); + const headersList = await headers(); + const clientIP = headersList.get("X-Forwarded-For"); if (clientIP === null) { return true; } diff --git a/lib/server/session.ts b/lib/server/session.ts index ccc2309..b34a0d0 100644 --- a/lib/server/session.ts +++ b/lib/server/session.ts @@ -56,8 +56,9 @@ WHERE session.id = ? return { session, user }; } -export const getCurrentSession = cache((): SessionValidationResult => { - const token = cookies().get("session")?.value ?? null; +export const getCurrentSession = cache(async (): Promise => { + const cookieStore = await cookies(); + const token = cookieStore.get("session")?.value ?? null; if (token === null) { return { session: null, user: null }; } @@ -73,8 +74,9 @@ export function invalidateUserSessions(userId: number): void { db.execute("DELETE FROM session WHERE user_id = ?", [userId]); } -export function setSessionTokenCookie(token: string, expiresAt: Date): void { - cookies().set("session", token, { +export async function setSessionTokenCookie(token: string, expiresAt: Date): Promise { + const cookieStore = await cookies(); + cookieStore.set("session", token, { httpOnly: true, path: "/", secure: process.env.NODE_ENV === "production", @@ -83,8 +85,9 @@ export function setSessionTokenCookie(token: string, expiresAt: Date): void { }); } -export function deleteSessionTokenCookie(): void { - cookies().set("session", "", { +export async function deleteSessionTokenCookie(): Promise { + const cookieStore = await cookies(); + cookieStore.set("session", "", { httpOnly: true, path: "/", secure: process.env.NODE_ENV === "production", diff --git a/next.config.mjs b/next.config.mjs index 74e0b7b..899ac8e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,8 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - serverComponentsExternalPackages: ["@node-rs/argon2"] - } + serverExternalPackages: ["@node-rs/argon2"] }; export default nextConfig; diff --git a/package.json b/package.json index fb82e52..b606b86 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbopack", "build": "next build", "start": "next start", "lint": "next lint", @@ -19,20 +19,21 @@ "@pilcrowjs/db-query": "^0.0.2", "@pilcrowjs/object-parser": "^0.0.4", "better-sqlite3": "^11.3.0", - "next": "14.2.14", - "react": "^18", - "react-dom": "^18", + "next": "15.0.3", + "react": "19.0.0-rc-66855b96-20241106", + "react-dom": "19.0.0-rc-66855b96-20241106", "uqr": "^0.1.2" }, "devDependencies": { "@types/better-sqlite3": "^7.6.11", "@types/node": "^20", - "@types/react": "^18", - "@types/react-dom": "^18", + "@types/react": "npm:types-react@19.0.0-rc.1", + "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "eslint": "^8", - "eslint-config-next": "14.2.14", + "eslint-config-next": "15.0.3", "eslint-config-prettier": "^9.1.0", "prettier": "^3.3.3", "typescript": "^5" - } + }, + "packageManager": "pnpm@9.14.2+sha512.6e2baf77d06b9362294152c851c4f278ede37ab1eba3a55fda317a4a17b209f4dbb973fb250a77abc463a341fcb1f17f17cfa24091c4eb319cda0d9b84278387" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9328dfc..be002b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,14 +35,14 @@ importers: specifier: ^11.3.0 version: 11.3.0 next: - specifier: 14.2.14 - version: 14.2.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 15.0.3 + version: 15.0.3(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) react: - specifier: ^18 - version: 18.3.1 + specifier: 19.0.0-rc-66855b96-20241106 + version: 19.0.0-rc-66855b96-20241106 react-dom: - specifier: ^18 - version: 18.3.1(react@18.3.1) + specifier: 19.0.0-rc-66855b96-20241106 + version: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) uqr: specifier: ^0.1.2 version: 0.1.2 @@ -54,17 +54,17 @@ importers: specifier: ^20 version: 20.16.10 "@types/react": - specifier: ^18 - version: 18.3.11 + specifier: npm:types-react@19.0.0-rc.1 + version: types-react@19.0.0-rc.1 "@types/react-dom": - specifier: ^18 - version: 18.3.0 + specifier: npm:types-react-dom@19.0.0-rc.1 + version: types-react-dom@19.0.0-rc.1 eslint: specifier: ^8 version: 8.57.1 eslint-config-next: - specifier: 14.2.14 - version: 14.2.14(eslint@8.57.1)(typescript@5.6.2) + specifier: 15.0.3 + version: 15.0.3(eslint@8.57.1)(typescript@5.6.2) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.1) @@ -126,82 +126,194 @@ packages: { integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== } deprecated: Use @eslint/object-schema instead - "@isaacs/cliui@8.0.2": + "@img/sharp-darwin-arm64@0.33.5": resolution: - { integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== } - engines: { node: ">=12" } + { integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [darwin] + + "@img/sharp-darwin-x64@0.33.5": + resolution: + { integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-darwin-arm64@1.0.4": + resolution: + { integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== } + cpu: [arm64] + os: [darwin] + + "@img/sharp-libvips-darwin-x64@1.0.4": + resolution: + { integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-linux-arm64@1.0.4": + resolution: + { integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== } + cpu: [arm64] + os: [linux] + + "@img/sharp-libvips-linux-arm@1.0.5": + resolution: + { integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== } + cpu: [arm] + os: [linux] + + "@img/sharp-libvips-linux-s390x@1.0.4": + resolution: + { integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== } + cpu: [s390x] + os: [linux] + + "@img/sharp-libvips-linux-x64@1.0.4": + resolution: + { integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== } + cpu: [x64] + os: [linux] + + "@img/sharp-libvips-linuxmusl-arm64@1.0.4": + resolution: + { integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== } + cpu: [arm64] + os: [linux] + + "@img/sharp-libvips-linuxmusl-x64@1.0.4": + resolution: + { integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== } + cpu: [x64] + os: [linux] + + "@img/sharp-linux-arm64@0.33.5": + resolution: + { integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + + "@img/sharp-linux-arm@0.33.5": + resolution: + { integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm] + os: [linux] + + "@img/sharp-linux-s390x@0.33.5": + resolution: + { integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [s390x] + os: [linux] + + "@img/sharp-linux-x64@0.33.5": + resolution: + { integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + + "@img/sharp-linuxmusl-arm64@0.33.5": + resolution: + { integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + + "@img/sharp-linuxmusl-x64@0.33.5": + resolution: + { integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + + "@img/sharp-wasm32@0.33.5": + resolution: + { integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [wasm32] + + "@img/sharp-win32-ia32@0.33.5": + resolution: + { integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ia32] + os: [win32] + + "@img/sharp-win32-x64@0.33.5": + resolution: + { integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [win32] "@napi-rs/wasm-runtime@0.2.5": resolution: { integrity: sha512-kwUxR7J9WLutBbulqg1dfOrMTwhMdXLdcGUhcbCcGwnPLt3gz19uHVdwH1syKVDbE022ZS2vZxOWflFLS0YTjw== } - "@next/env@14.2.14": + "@next/env@15.0.3": resolution: - { integrity: sha512-/0hWQfiaD5//LvGNgc8PjvyqV50vGK0cADYzaoOOGN8fxzBn3iAiaq3S0tCRnFBldq0LVveLcxCTi41ZoYgAgg== } + { integrity: sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA== } - "@next/eslint-plugin-next@14.2.14": + "@next/eslint-plugin-next@15.0.3": resolution: - { integrity: sha512-kV+OsZ56xhj0rnTn6HegyTGkoa16Mxjrpk7pjWumyB2P8JVQb8S9qtkjy/ye0GnTr4JWtWG4x/2qN40lKZ3iVQ== } + { integrity: sha512-3Ln/nHq2V+v8uIaxCR6YfYo7ceRgZNXfTd3yW1ukTaFbO+/I8jNakrjYWODvG9BuR2v5kgVtH/C8r0i11quOgw== } - "@next/swc-darwin-arm64@14.2.14": + "@next/swc-darwin-arm64@15.0.3": resolution: - { integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw== } + { integrity: sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw== } engines: { node: ">= 10" } cpu: [arm64] os: [darwin] - "@next/swc-darwin-x64@14.2.14": + "@next/swc-darwin-x64@15.0.3": resolution: - { integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww== } + { integrity: sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw== } engines: { node: ">= 10" } cpu: [x64] os: [darwin] - "@next/swc-linux-arm64-gnu@14.2.14": + "@next/swc-linux-arm64-gnu@15.0.3": resolution: - { integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg== } + { integrity: sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw== } engines: { node: ">= 10" } cpu: [arm64] os: [linux] - "@next/swc-linux-arm64-musl@14.2.14": + "@next/swc-linux-arm64-musl@15.0.3": resolution: - { integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A== } + { integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA== } engines: { node: ">= 10" } cpu: [arm64] os: [linux] - "@next/swc-linux-x64-gnu@14.2.14": + "@next/swc-linux-x64-gnu@15.0.3": resolution: - { integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w== } + { integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w== } engines: { node: ">= 10" } cpu: [x64] os: [linux] - "@next/swc-linux-x64-musl@14.2.14": + "@next/swc-linux-x64-musl@15.0.3": resolution: - { integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ== } + { integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA== } engines: { node: ">= 10" } cpu: [x64] os: [linux] - "@next/swc-win32-arm64-msvc@14.2.14": + "@next/swc-win32-arm64-msvc@15.0.3": resolution: - { integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A== } + { integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ== } engines: { node: ">= 10" } cpu: [arm64] os: [win32] - "@next/swc-win32-ia32-msvc@14.2.14": - resolution: - { integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ== } - engines: { node: ">= 10" } - cpu: [ia32] - os: [win32] - - "@next/swc-win32-x64-msvc@14.2.14": + "@next/swc-win32-x64-msvc@15.0.3": resolution: - { integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg== } + { integrity: sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA== } engines: { node: ">= 10" } cpu: [x64] os: [win32] @@ -372,11 +484,6 @@ packages: resolution: { integrity: sha512-mBy3FMv2lvl/sZX/q03wvl3Km8FWg7kbrqQ/qMxK49uZcBssD76Js5k+o7VuCDJI8SNvsrbIX8y6vclx7bWeSg== } - "@pkgjs/parseargs@0.11.0": - resolution: - { integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== } - engines: { node: ">=14" } - "@rtsao/scc@1.1.0": resolution: { integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== } @@ -389,9 +496,9 @@ packages: resolution: { integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== } - "@swc/helpers@0.5.5": + "@swc/helpers@0.5.13": resolution: - { integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A== } + { integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w== } "@tybys/wasm-util@0.9.0": resolution: @@ -413,13 +520,9 @@ packages: resolution: { integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA== } - "@types/react-dom@18.3.0": - resolution: - { integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== } - - "@types/react@18.3.11": + "@types/react@18.3.12": resolution: - { integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ== } + { integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw== } "@typescript-eslint/eslint-plugin@8.8.0": resolution: @@ -511,21 +614,11 @@ packages: { integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== } engines: { node: ">=8" } - ansi-regex@6.1.0: - resolution: - { integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== } - engines: { node: ">=12" } - ansi-styles@4.3.0: resolution: { integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== } engines: { node: ">=8" } - ansi-styles@6.2.1: - resolution: - { integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== } - engines: { node: ">=12" } - argparse@2.0.1: resolution: { integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } @@ -671,6 +764,15 @@ packages: resolution: { integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== } + color-string@1.9.1: + resolution: + { integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== } + + color@4.2.3: + resolution: + { integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== } + engines: { node: ">=12.5.0" } + concat-map@0.0.1: resolution: { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } @@ -766,14 +868,6 @@ packages: { integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== } engines: { node: ">=6.0.0" } - eastasianwidth@0.2.0: - resolution: - { integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== } - - emoji-regex@8.0.0: - resolution: - { integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== } - emoji-regex@9.2.2: resolution: { integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== } @@ -835,11 +929,11 @@ packages: { integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } engines: { node: ">=10" } - eslint-config-next@14.2.14: + eslint-config-next@15.0.3: resolution: - { integrity: sha512-TXwyjGICAlWC9O0OufS3koTsBKQH8l1xt3SY/aDuvtKHIwjTHplJKWVb1WOEX0OsDaxGbFXmfD2EY1sNfG0Y/w== } + { integrity: sha512-IGP2DdQQrgjcr4mwFPve4DrCqo7CVVez1WoYY47XwKSrYO4hC0Dlb+iJA60i0YfICOzgNADIb8r28BpQ5Zs0wg== } peerDependencies: - eslint: ^7.23.0 || ^8.0.0 + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: ">=3.3.1" peerDependenciesMeta: typescript: @@ -910,12 +1004,12 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - eslint-plugin-react-hooks@4.6.2: + eslint-plugin-react-hooks@5.0.0: resolution: - { integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== } + { integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw== } engines: { node: ">=10" } peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 eslint-plugin-react@7.37.1: resolution: @@ -975,6 +1069,11 @@ packages: resolution: { integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== } + fast-glob@3.3.1: + resolution: + { integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== } + engines: { node: ">=8.6.0" } + fast-glob@3.3.2: resolution: { integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== } @@ -1024,11 +1123,6 @@ packages: resolution: { integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== } - foreground-child@3.3.0: - resolution: - { integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== } - engines: { node: ">=14" } - fs-constants@1.0.0: resolution: { integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== } @@ -1078,12 +1172,6 @@ packages: { integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== } engines: { node: ">=10.13.0" } - glob@10.3.10: - resolution: - { integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== } - engines: { node: ">=16 || 14 >=14.17" } - hasBin: true - glob@7.2.3: resolution: { integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== } @@ -1191,6 +1279,10 @@ packages: { integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== } engines: { node: ">= 0.4" } + is-arrayish@0.3.2: + resolution: + { integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== } + is-async-function@2.0.0: resolution: { integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== } @@ -1238,11 +1330,6 @@ packages: resolution: { integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== } - is-fullwidth-code-point@3.0.0: - resolution: - { integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== } - engines: { node: ">=8" } - is-generator-function@1.0.10: resolution: { integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== } @@ -1334,11 +1421,6 @@ packages: resolution: { integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== } - jackspeak@2.3.6: - resolution: - { integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== } - engines: { node: ">=14" } - js-tokens@4.0.0: resolution: { integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== } @@ -1402,10 +1484,6 @@ packages: { integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== } hasBin: true - lru-cache@10.4.3: - resolution: - { integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== } - merge2@1.4.1: resolution: { integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== } @@ -1434,11 +1512,6 @@ packages: resolution: { integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== } - minipass@7.1.2: - resolution: - { integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== } - engines: { node: ">=16 || 14 >=14.17" } - mkdirp-classic@0.5.3: resolution: { integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== } @@ -1461,22 +1534,25 @@ packages: resolution: { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } - next@14.2.14: + next@15.0.3: resolution: - { integrity: sha512-Q1coZG17MW0Ly5x76shJ4dkC23woLAhhnDnw+DfTc7EpZSGuWrlsZ3bZaO8t6u1Yu8FVfhkqJE+U8GC7E0GLPQ== } - engines: { node: ">=18.17.0" } + { integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw== } + engines: { node: ^18.18.0 || ^19.8.0 || >= 20.0.0 } hasBin: true peerDependencies: "@opentelemetry/api": ^1.1.0 "@playwright/test": ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 + babel-plugin-react-compiler: "*" + react: ^18.2.0 || 19.0.0-rc-66855b96-20241106 + react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106 sass: ^1.3.0 peerDependenciesMeta: "@opentelemetry/api": optional: true "@playwright/test": optional: true + babel-plugin-react-compiler: + optional: true sass: optional: true @@ -1573,11 +1649,6 @@ packages: resolution: { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== } - path-scurry@1.11.1: - resolution: - { integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== } - engines: { node: ">=16 || 14 >=14.18" } - picocolors@1.1.0: resolution: { integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== } @@ -1636,19 +1707,19 @@ packages: { integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== } hasBin: true - react-dom@18.3.1: + react-dom@19.0.0-rc-66855b96-20241106: resolution: - { integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== } + { integrity: sha512-D25vdaytZ1wFIRiwNU98NPQ/upS2P8Co4/oNoa02PzHbh8deWdepjm5qwZM/46OdSiGv4WSWwxP55RO9obqJEQ== } peerDependencies: - react: ^18.3.1 + react: 19.0.0-rc-66855b96-20241106 react-is@16.13.1: resolution: { integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== } - react@18.3.1: + react@19.0.0-rc-66855b96-20241106: resolution: - { integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== } + { integrity: sha512-klH7xkT71SxRCx4hb1hly5FJB21Hz0ACyxbXYAECEqssUjtJeFUAaI2U1DgJAzkGEnvEm3DkxuBchMC/9K4ipg== } engines: { node: ">=0.10.0" } readable-stream@3.6.2: @@ -1714,9 +1785,9 @@ packages: { integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== } engines: { node: ">= 0.4" } - scheduler@0.23.2: + scheduler@0.25.0-rc-66855b96-20241106: resolution: - { integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== } + { integrity: sha512-HQXp/Mnp/MMRSXMQF7urNFla+gmtXW/Gr1KliuR0iboTit4KvZRY8KYaq5ccCTAOJiUqQh2rE2F3wgUekmgdlA== } semver@6.3.1: resolution: @@ -1739,6 +1810,11 @@ packages: { integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== } engines: { node: ">= 0.4" } + sharp@0.33.5: + resolution: + { integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + shebang-command@2.0.0: resolution: { integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== } @@ -1754,11 +1830,6 @@ packages: { integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== } engines: { node: ">= 0.4" } - signal-exit@4.1.0: - resolution: - { integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== } - engines: { node: ">=14" } - simple-concat@1.0.1: resolution: { integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== } @@ -1767,6 +1838,10 @@ packages: resolution: { integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== } + simple-swizzle@0.2.2: + resolution: + { integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== } + source-map-js@1.2.1: resolution: { integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== } @@ -1782,16 +1857,6 @@ packages: { integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== } engines: { node: ">=10.0.0" } - string-width@4.2.3: - resolution: - { integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== } - engines: { node: ">=8" } - - string-width@5.1.2: - resolution: - { integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== } - engines: { node: ">=12" } - string.prototype.includes@2.0.0: resolution: { integrity: sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg== } @@ -1828,11 +1893,6 @@ packages: { integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== } engines: { node: ">=8" } - strip-ansi@7.1.0: - resolution: - { integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== } - engines: { node: ">=12" } - strip-bom@3.0.0: resolution: { integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== } @@ -1848,14 +1908,14 @@ packages: { integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== } engines: { node: ">=8" } - styled-jsx@5.1.1: + styled-jsx@5.1.6: resolution: - { integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw== } + { integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA== } engines: { node: ">= 12.0.0" } peerDependencies: "@babel/core": "*" babel-plugin-macros: "*" - react: ">= 16.8.0 || 17.x.x || ^18.0.0-0" + react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" peerDependenciesMeta: "@babel/core": optional: true @@ -1944,6 +2004,14 @@ packages: { integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== } engines: { node: ">= 0.4" } + types-react-dom@19.0.0-rc.1: + resolution: + { integrity: sha512-VSLZJl8VXCD0fAWp7DUTFUDCcZ8DVXOQmjhJMD03odgeFmu14ZQJHCXeETm3BEAhJqfgJaFkLnGkQv88sRx0fQ== } + + types-react@19.0.0-rc.1: + resolution: + { integrity: sha512-RshndUfqTW6K3STLPis8BtAYCGOkMbtvYsi90gmVNDZBXUyUc5juf2PE9LfS/JmOlUIRO8cWTS/1MTnmhjDqyQ== } + typescript@5.6.2: resolution: { integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== } @@ -2000,16 +2068,6 @@ packages: { integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== } engines: { node: ">=0.10.0" } - wrap-ansi@7.0.0: - resolution: - { integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== } - engines: { node: ">=10" } - - wrap-ansi@8.1.0: - resolution: - { integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== } - engines: { node: ">=12" } - wrappy@1.0.2: resolution: { integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== } @@ -2071,14 +2129,80 @@ snapshots: "@humanwhocodes/object-schema@2.0.3": {} - "@isaacs/cliui@8.0.2": + "@img/sharp-darwin-arm64@0.33.5": + optionalDependencies: + "@img/sharp-libvips-darwin-arm64": 1.0.4 + optional: true + + "@img/sharp-darwin-x64@0.33.5": + optionalDependencies: + "@img/sharp-libvips-darwin-x64": 1.0.4 + optional: true + + "@img/sharp-libvips-darwin-arm64@1.0.4": + optional: true + + "@img/sharp-libvips-darwin-x64@1.0.4": + optional: true + + "@img/sharp-libvips-linux-arm64@1.0.4": + optional: true + + "@img/sharp-libvips-linux-arm@1.0.5": + optional: true + + "@img/sharp-libvips-linux-s390x@1.0.4": + optional: true + + "@img/sharp-libvips-linux-x64@1.0.4": + optional: true + + "@img/sharp-libvips-linuxmusl-arm64@1.0.4": + optional: true + + "@img/sharp-libvips-linuxmusl-x64@1.0.4": + optional: true + + "@img/sharp-linux-arm64@0.33.5": + optionalDependencies: + "@img/sharp-libvips-linux-arm64": 1.0.4 + optional: true + + "@img/sharp-linux-arm@0.33.5": + optionalDependencies: + "@img/sharp-libvips-linux-arm": 1.0.5 + optional: true + + "@img/sharp-linux-s390x@0.33.5": + optionalDependencies: + "@img/sharp-libvips-linux-s390x": 1.0.4 + optional: true + + "@img/sharp-linux-x64@0.33.5": + optionalDependencies: + "@img/sharp-libvips-linux-x64": 1.0.4 + optional: true + + "@img/sharp-linuxmusl-arm64@0.33.5": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64": 1.0.4 + optional: true + + "@img/sharp-linuxmusl-x64@0.33.5": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64": 1.0.4 + optional: true + + "@img/sharp-wasm32@0.33.5": dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + "@emnapi/runtime": 1.3.0 + optional: true + + "@img/sharp-win32-ia32@0.33.5": + optional: true + + "@img/sharp-win32-x64@0.33.5": + optional: true "@napi-rs/wasm-runtime@0.2.5": dependencies: @@ -2087,37 +2211,34 @@ snapshots: "@tybys/wasm-util": 0.9.0 optional: true - "@next/env@14.2.14": {} + "@next/env@15.0.3": {} - "@next/eslint-plugin-next@14.2.14": + "@next/eslint-plugin-next@15.0.3": dependencies: - glob: 10.3.10 + fast-glob: 3.3.1 - "@next/swc-darwin-arm64@14.2.14": + "@next/swc-darwin-arm64@15.0.3": optional: true - "@next/swc-darwin-x64@14.2.14": + "@next/swc-darwin-x64@15.0.3": optional: true - "@next/swc-linux-arm64-gnu@14.2.14": + "@next/swc-linux-arm64-gnu@15.0.3": optional: true - "@next/swc-linux-arm64-musl@14.2.14": + "@next/swc-linux-arm64-musl@15.0.3": optional: true - "@next/swc-linux-x64-gnu@14.2.14": + "@next/swc-linux-x64-gnu@15.0.3": optional: true - "@next/swc-linux-x64-musl@14.2.14": + "@next/swc-linux-x64-musl@15.0.3": optional: true - "@next/swc-win32-arm64-msvc@14.2.14": + "@next/swc-win32-arm64-msvc@15.0.3": optional: true - "@next/swc-win32-ia32-msvc@14.2.14": - optional: true - - "@next/swc-win32-x64-msvc@14.2.14": + "@next/swc-win32-x64-msvc@15.0.3": optional: true "@node-rs/argon2-android-arm-eabi@2.0.0": @@ -2237,18 +2358,14 @@ snapshots: "@pilcrowjs/object-parser@0.0.4": {} - "@pkgjs/parseargs@0.11.0": - optional: true - "@rtsao/scc@1.1.0": {} "@rushstack/eslint-patch@1.10.4": {} "@swc/counter@0.1.3": {} - "@swc/helpers@0.5.5": + "@swc/helpers@0.5.13": dependencies: - "@swc/counter": 0.1.3 tslib: 2.7.0 "@tybys/wasm-util@0.9.0": @@ -2268,11 +2385,7 @@ snapshots: "@types/prop-types@15.7.13": {} - "@types/react-dom@18.3.0": - dependencies: - "@types/react": 18.3.11 - - "@types/react@18.3.11": + "@types/react@18.3.12": dependencies: "@types/prop-types": 15.7.13 csstype: 3.1.3 @@ -2375,14 +2488,10 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.1.0: {} - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.1: {} - argparse@2.0.1: {} aria-query@5.1.3: @@ -2532,6 +2641,18 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + concat-map@0.0.1: {} cross-spawn@7.0.3: @@ -2621,10 +2742,6 @@ snapshots: dependencies: esutils: 2.0.3 - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} end-of-stream@1.4.4: @@ -2742,19 +2859,19 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@14.2.14(eslint@8.57.1)(typescript@5.6.2): + eslint-config-next@15.0.3(eslint@8.57.1)(typescript@5.6.2): dependencies: - "@next/eslint-plugin-next": 14.2.14 + "@next/eslint-plugin-next": 15.0.3 "@rushstack/eslint-patch": 1.10.4 "@typescript-eslint/eslint-plugin": 8.8.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2) "@typescript-eslint/parser": 8.8.0(eslint@8.57.1)(typescript@5.6.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) + eslint-plugin-react-hooks: 5.0.0(eslint@8.57.1) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: @@ -2774,37 +2891,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: "@nolyfill/is-core-module": 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - "@typescript-eslint/parser" - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: "@typescript-eslint/parser": 8.8.0(eslint@8.57.1)(typescript@5.6.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: "@rtsao/scc": 1.1.0 array-includes: 3.1.8 @@ -2815,7 +2932,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -2853,7 +2970,7 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.0 - eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): + eslint-plugin-react-hooks@5.0.0(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -2951,6 +3068,14 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-glob@3.3.1: + dependencies: + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-glob@3.3.2: dependencies: "@nodelib/fs.stat": 2.0.5 @@ -2994,11 +3119,6 @@ snapshots: dependencies: is-callable: 1.2.7 - foreground-child@3.3.0: - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - fs-constants@1.0.0: {} fs.realpath@1.0.0: {} @@ -3042,14 +3162,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.3.10: - dependencies: - foreground-child: 3.3.0 - jackspeak: 2.3.6 - minimatch: 9.0.5 - minipass: 7.1.2 - path-scurry: 1.11.1 - glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -3132,6 +3244,9 @@ snapshots: call-bind: 1.0.7 get-intrinsic: 1.2.4 + is-arrayish@0.3.2: + optional: true + is-async-function@2.0.0: dependencies: has-tostringtag: 1.0.2 @@ -3169,8 +3284,6 @@ snapshots: dependencies: call-bind: 1.0.7 - is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.2 @@ -3237,12 +3350,6 @@ snapshots: reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 - jackspeak@2.3.6: - dependencies: - "@isaacs/cliui": 8.0.2 - optionalDependencies: - "@pkgjs/parseargs": 0.11.0 - js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -3291,8 +3398,6 @@ snapshots: dependencies: js-tokens: 4.0.0 - lru-cache@10.4.3: {} - merge2@1.4.1: {} micromatch@4.0.8: @@ -3312,8 +3417,6 @@ snapshots: minimist@1.2.8: {} - minipass@7.1.2: {} - mkdirp-classic@0.5.3: {} ms@2.1.3: {} @@ -3324,27 +3427,27 @@ snapshots: natural-compare@1.4.0: {} - next@14.2.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.0.3(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106): dependencies: - "@next/env": 14.2.14 - "@swc/helpers": 0.5.5 + "@next/env": 15.0.3 + "@swc/counter": 0.1.3 + "@swc/helpers": 0.5.13 busboy: 1.6.0 caniuse-lite: 1.0.30001667 - graceful-fs: 4.2.11 postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(react@18.3.1) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + styled-jsx: 5.1.6(react@19.0.0-rc-66855b96-20241106) optionalDependencies: - "@next/swc-darwin-arm64": 14.2.14 - "@next/swc-darwin-x64": 14.2.14 - "@next/swc-linux-arm64-gnu": 14.2.14 - "@next/swc-linux-arm64-musl": 14.2.14 - "@next/swc-linux-x64-gnu": 14.2.14 - "@next/swc-linux-x64-musl": 14.2.14 - "@next/swc-win32-arm64-msvc": 14.2.14 - "@next/swc-win32-ia32-msvc": 14.2.14 - "@next/swc-win32-x64-msvc": 14.2.14 + "@next/swc-darwin-arm64": 15.0.3 + "@next/swc-darwin-x64": 15.0.3 + "@next/swc-linux-arm64-gnu": 15.0.3 + "@next/swc-linux-arm64-musl": 15.0.3 + "@next/swc-linux-x64-gnu": 15.0.3 + "@next/swc-linux-x64-musl": 15.0.3 + "@next/swc-win32-arm64-msvc": 15.0.3 + "@next/swc-win32-x64-msvc": 15.0.3 + sharp: 0.33.5 transitivePeerDependencies: - "@babel/core" - babel-plugin-macros @@ -3429,11 +3532,6 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - picocolors@1.1.0: {} picomatch@2.3.1: {} @@ -3487,17 +3585,14 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dom@18.3.1(react@18.3.1): + react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.0.0-rc-66855b96-20241106 + scheduler: 0.25.0-rc-66855b96-20241106 react-is@16.13.1: {} - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + react@19.0.0-rc-66855b96-20241106: {} readable-stream@3.6.2: dependencies: @@ -3563,9 +3658,7 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.25.0-rc-66855b96-20241106: {} semver@6.3.1: {} @@ -3587,6 +3680,33 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.3 + optionalDependencies: + "@img/sharp-darwin-arm64": 0.33.5 + "@img/sharp-darwin-x64": 0.33.5 + "@img/sharp-libvips-darwin-arm64": 1.0.4 + "@img/sharp-libvips-darwin-x64": 1.0.4 + "@img/sharp-libvips-linux-arm": 1.0.5 + "@img/sharp-libvips-linux-arm64": 1.0.4 + "@img/sharp-libvips-linux-s390x": 1.0.4 + "@img/sharp-libvips-linux-x64": 1.0.4 + "@img/sharp-libvips-linuxmusl-arm64": 1.0.4 + "@img/sharp-libvips-linuxmusl-x64": 1.0.4 + "@img/sharp-linux-arm": 0.33.5 + "@img/sharp-linux-arm64": 0.33.5 + "@img/sharp-linux-s390x": 0.33.5 + "@img/sharp-linux-x64": 0.33.5 + "@img/sharp-linuxmusl-arm64": 0.33.5 + "@img/sharp-linuxmusl-x64": 0.33.5 + "@img/sharp-wasm32": 0.33.5 + "@img/sharp-win32-ia32": 0.33.5 + "@img/sharp-win32-x64": 0.33.5 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3600,8 +3720,6 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.2 - signal-exit@4.1.0: {} - simple-concat@1.0.1: {} simple-get@4.0.1: @@ -3610,6 +3728,11 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + source-map-js@1.2.1: {} stop-iteration-iterator@1.0.0: @@ -3618,18 +3741,6 @@ snapshots: streamsearch@1.1.0: {} - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - string.prototype.includes@2.0.0: dependencies: define-properties: 1.2.1 @@ -3682,20 +3793,16 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.1.0 - strip-bom@3.0.0: {} strip-json-comments@2.0.1: {} strip-json-comments@3.1.1: {} - styled-jsx@5.1.1(react@18.3.1): + styled-jsx@5.1.6(react@19.0.0-rc-66855b96-20241106): dependencies: client-only: 0.0.1 - react: 18.3.1 + react: 19.0.0-rc-66855b96-20241106 supports-color@7.2.0: dependencies: @@ -3781,6 +3888,14 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 + types-react-dom@19.0.0-rc.1: + dependencies: + "@types/react": 18.3.12 + + types-react@19.0.0-rc.1: + dependencies: + csstype: 3.1.3 + typescript@5.6.2: {} unbox-primitive@1.0.2: @@ -3844,18 +3959,6 @@ snapshots: word-wrap@1.2.5: {} - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - wrappy@1.0.2: {} yocto-queue@0.1.0: {} From 8d406ae1ecc9de7822a36c4c0f853441a8405ff9 Mon Sep 17 00:00:00 2001 From: Jason Gerbes Date: Mon, 25 Nov 2024 10:38:39 +1300 Subject: [PATCH 2/2] next.config.ts --- next.config.mjs => next.config.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename next.config.mjs => next.config.ts (50%) diff --git a/next.config.mjs b/next.config.ts similarity index 50% rename from next.config.mjs rename to next.config.ts index 899ac8e..aa3bb8e 100644 --- a/next.config.mjs +++ b/next.config.ts @@ -1,5 +1,6 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { serverExternalPackages: ["@node-rs/argon2"] };