diff --git a/packages/nextjs/.env.development b/packages/nextjs/.env.development index 8bec845..b981aa2 100644 --- a/packages/nextjs/.env.development +++ b/packages/nextjs/.env.development @@ -1,2 +1,3 @@ - POSTGRES_URL="postgresql://postgres:mysecretpassword@localhost:5432/postgres" +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=somereallysecretsecret \ No newline at end of file diff --git a/packages/nextjs/app/admin/page.tsx b/packages/nextjs/app/admin/page.tsx new file mode 100644 index 0000000..9ac066c --- /dev/null +++ b/packages/nextjs/app/admin/page.tsx @@ -0,0 +1,39 @@ +import type { NextPage } from "next"; +import { getServerSession } from "next-auth"; +import { Address } from "~~/components/scaffold-eth"; +import { getAllSubmissions } from "~~/services/database/repositories/submissions"; +import { authOptions } from "~~/utils/auth"; + +const Admin: NextPage = async () => { + const session = await getServerSession(authOptions); + + if (session?.user?.role !== "admin") { + return
Access denied
; + } + + const submissions = await getAllSubmissions(); + return ( + <> +
+ {submissions.map(submission => { + return ( +
+
+

{submission.title}

+ {submission.linkToRepository && ( + + {submission.linkToRepository} + + )} +

{submission.description}

+ {submission.builder &&
} +
+
+ ); + })} +
+ + ); +}; + +export default Admin; diff --git a/packages/nextjs/app/api/auth/[...nextauth]/route.ts b/packages/nextjs/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..6238bcf --- /dev/null +++ b/packages/nextjs/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,18 @@ +import { NextRequest, NextResponse } from "next/server"; +import NextAuth from "next-auth"; +import { authOptions, providers } from "~~/utils/auth"; + +// For more information on each option (and a full list of options) go to +// https://next-auth.js.org/configuration/options +async function auth(req: NextRequest, res: NextResponse) { + const { searchParams } = new URL(req.url as string); + const isDefaultSigninPage = searchParams.get("nextauth")?.includes("signin"); + + if (isDefaultSigninPage) { + providers.pop(); + } + + return await NextAuth(req, res as unknown as { params: { nextauth: string[] } }, authOptions); +} + +export { auth as GET, auth as POST }; diff --git a/packages/nextjs/app/siwe/page.tsx b/packages/nextjs/app/siwe/page.tsx new file mode 100644 index 0000000..34fcf06 --- /dev/null +++ b/packages/nextjs/app/siwe/page.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { useConnectModal } from "@rainbow-me/rainbowkit"; +import { useAccount } from "wagmi"; +import { useAuthSession } from "~~/hooks/useAuthSession"; +import { useHandleLogin } from "~~/hooks/useHandleLogin"; + +export default function Siwe() { + const { isConnected } = useAccount(); + const { openConnectModal } = useConnectModal(); + const { handleLogin } = useHandleLogin(); + const { isAuthenticated } = useAuthSession(); + const router = useRouter(); + + useEffect(() => { + if (isAuthenticated) { + router.push("/"); + } + }, [router, isAuthenticated]); + + return ( + + ); +} diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx index 5b0355b..c1cc6c3 100644 --- a/packages/nextjs/components/Header.tsx +++ b/packages/nextjs/components/Header.tsx @@ -4,9 +4,10 @@ import React, { useCallback, useRef, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { Bars3Icon, BugAntIcon } from "@heroicons/react/24/outline"; +import { Bars3Icon, BugAntIcon, LockClosedIcon } from "@heroicons/react/24/outline"; import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth"; import { useOutsideClick } from "~~/hooks/scaffold-eth"; +import { useAuthSession } from "~~/hooks/useAuthSession"; type HeaderMenuLink = { label: string; @@ -23,6 +24,15 @@ export const menuLinks: HeaderMenuLink[] = [ label: "Submit", href: "/submit", }, + { + label: "Admin", + href: "/admin", + icon: , + }, + { + label: "Siwe", + href: "/siwe", + }, { label: "Debug Contracts", href: "/debug", @@ -32,10 +42,23 @@ export const menuLinks: HeaderMenuLink[] = [ export const HeaderMenuLinks = () => { const pathname = usePathname(); + const { isAdmin, data: session, isAuthenticated } = useAuthSession(); return ( <> {menuLinks.map(({ label, href, icon }) => { + if (!session && label !== "Siwe") { + return null; + } + + if (isAuthenticated && label === "Siwe") { + return null; + } + + if (label === "Admin" && !isAdmin) { + return null; + } + const isActive = pathname === href; return (
  • diff --git a/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx b/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx index f67c2f7..fb56289 100644 --- a/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx +++ b/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx @@ -2,7 +2,9 @@ import { useEffect, useState } from "react"; import { RainbowKitProvider, darkTheme, lightTheme } from "@rainbow-me/rainbowkit"; +import { RainbowKitSiweNextAuthProvider } from "@rainbow-me/rainbowkit-siwe-next-auth"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { SessionProvider } from "next-auth/react"; import { useTheme } from "next-themes"; import { Toaster } from "react-hot-toast"; import { WagmiProvider } from "wagmi"; @@ -47,15 +49,19 @@ export const ScaffoldEthAppWithProviders = ({ children }: { children: React.Reac return ( - - - - {children} - - + + + + + + {children} + + + + ); }; diff --git a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx index a9e3125..01a5b94 100644 --- a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx +++ b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx @@ -1,5 +1,6 @@ import { useRef, useState } from "react"; import { NetworkOptions } from "./NetworkOptions"; +import { signOut } from "next-auth/react"; import CopyToClipboard from "react-copy-to-clipboard"; import { getAddress } from "viem"; import { Address } from "viem"; @@ -125,7 +126,10 @@ export const AddressInfoDropdown = ({ diff --git a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx index aec40a9..f563ed7 100644 --- a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx +++ b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx @@ -1,14 +1,18 @@ "use client"; // @refresh reset +import { useEffect } from "react"; import { Balance } from "../Balance"; import { AddressInfoDropdown } from "./AddressInfoDropdown"; import { AddressQRCodeModal } from "./AddressQRCodeModal"; import { WrongNetworkDropdown } from "./WrongNetworkDropdown"; import { ConnectButton } from "@rainbow-me/rainbowkit"; +import { signOut } from "next-auth/react"; import { Address } from "viem"; +import { useAccount } from "wagmi"; import { useNetworkColor } from "~~/hooks/scaffold-eth"; import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; +import { useAuthSession } from "~~/hooks/useAuthSession"; import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth"; /** @@ -17,6 +21,14 @@ import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth"; export const RainbowKitCustomConnectButton = ({ fullWidth }: { fullWidth?: boolean }) => { const networkColor = useNetworkColor(); const { targetNetwork } = useTargetNetwork(); + const { address, isConnected } = useAccount(); + const { address: sessionAddress } = useAuthSession(); + + useEffect(() => { + if (isConnected && sessionAddress && sessionAddress !== address) { + signOut(); + } + }, [address, isConnected, sessionAddress]); return ( diff --git a/packages/nextjs/hooks/useAuthSession.ts b/packages/nextjs/hooks/useAuthSession.ts new file mode 100644 index 0000000..a3366fb --- /dev/null +++ b/packages/nextjs/hooks/useAuthSession.ts @@ -0,0 +1,11 @@ +import { UseSessionOptions, useSession } from "next-auth/react"; + +export const useAuthSession = (options?: UseSessionOptions) => { + const sessionData = useSession(options); + + const isAdmin = sessionData?.data?.user?.role === "admin"; + const address = sessionData?.data?.user?.address; + const isAuthenticated = sessionData.status === "authenticated"; + + return { ...sessionData, isAdmin, address, isAuthenticated }; +}; diff --git a/packages/nextjs/hooks/useHandleLogin.ts b/packages/nextjs/hooks/useHandleLogin.ts new file mode 100644 index 0000000..18c1fb7 --- /dev/null +++ b/packages/nextjs/hooks/useHandleLogin.ts @@ -0,0 +1,34 @@ +import { useCallback } from "react"; +import { getCsrfToken, signIn } from "next-auth/react"; +import { SiweMessage } from "siwe"; +import { useAccount, useSignMessage } from "wagmi"; + +export const useHandleLogin = () => { + const { signMessageAsync } = useSignMessage(); + const { address, chain } = useAccount(); + + const handleLogin = useCallback(async () => { + try { + const message = new SiweMessage({ + domain: window.location.host, + address: address, + statement: "Sign in with Ethereum to the app.", + uri: window.location.origin, + version: "1", + chainId: chain?.id, + nonce: await getCsrfToken(), + }); + const signature = await signMessageAsync({ + message: message.prepareMessage(), + }); + signIn("credentials", { + message: JSON.stringify(message), + signature, + }); + } catch (error) { + console.log(error); + } + }, [address, chain?.id, signMessageAsync]); + + return { handleLogin }; +}; diff --git a/packages/nextjs/middleware.ts b/packages/nextjs/middleware.ts new file mode 100644 index 0000000..00b6c0a --- /dev/null +++ b/packages/nextjs/middleware.ts @@ -0,0 +1,23 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; +import { getToken } from "next-auth/jwt"; + +const SIGN_IN_PAGE = "/siwe"; + +export async function middleware(request: NextRequest) { + const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET }); + + // If no token and the request is not for the sign-in page, redirect to the sign-in page + if (!token && request.nextUrl.pathname !== SIGN_IN_PAGE) { + const url = request.nextUrl.clone(); + url.pathname = SIGN_IN_PAGE; + return NextResponse.redirect(url); + } + + // If a token is found or the request is for the sign-in page, proceed as normal + return NextResponse.next(); +} + +export const config = { + matcher: "/((?!api|static|.*\\..*|_next).*)", +}; diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 5f4ebe8..7fc61bb 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -18,6 +18,7 @@ "dependencies": { "@heroicons/react": "~2.0.11", "@rainbow-me/rainbowkit": "2.1.2", + "@rainbow-me/rainbowkit-siwe-next-auth": "^0.4.1", "@tanstack/react-query": "~5.28.6", "@uniswap/sdk-core": "~4.0.1", "@uniswap/v2-sdk": "~3.0.1", @@ -26,7 +27,9 @@ "daisyui": "4.5.0", "dotenv": "^16.4.5", "drizzle-orm": "^0.32.1", + "ethers": "^6.13.1", "next": "~14.0.4", + "next-auth": "^4.24.7", "next-themes": "~0.2.1", "nprogress": "~0.2.0", "pg": "^8.12.0", @@ -35,6 +38,7 @@ "react-copy-to-clipboard": "~5.1.0", "react-dom": "~18.2.0", "react-hot-toast": "~2.4.0", + "siwe": "^2.3.2", "use-debounce": "~8.0.4", "usehooks-ts": "2.13.0", "viem": "2.17.4", diff --git a/packages/nextjs/services/database/repositories/builders.ts b/packages/nextjs/services/database/repositories/builders.ts index ad7bc2c..736d914 100644 --- a/packages/nextjs/services/database/repositories/builders.ts +++ b/packages/nextjs/services/database/repositories/builders.ts @@ -13,5 +13,5 @@ export async function getBuilderById(id: string) { } export async function createBuilder(builder: BuilderInsert) { - return await db.insert(builders).values(builder); + return await db.insert(builders).values(builder).returning({ id: builders.id, role: builders.role }); } diff --git a/packages/nextjs/types/next-auth/next-auth.d.ts b/packages/nextjs/types/next-auth/next-auth.d.ts new file mode 100644 index 0000000..4aac85b --- /dev/null +++ b/packages/nextjs/types/next-auth/next-auth.d.ts @@ -0,0 +1,23 @@ +import "next-auth"; +import { DefaultUser } from "next-auth"; +import { DefaultJWT } from "next-auth/jwt"; + +declare module "next-auth" { + export interface Session { + user: { + address?: string | null; + role?: string | null; + }; + expires: ISODateString; + } + + export interface User extends DefaultUser { + role?: string | null; + address?: string | null; + } + + export interface JWT extends DefaultJWT { + role?: string | null; + address?: string | null; + } +} diff --git a/packages/nextjs/utils/auth.ts b/packages/nextjs/utils/auth.ts new file mode 100644 index 0000000..1be6c87 --- /dev/null +++ b/packages/nextjs/utils/auth.ts @@ -0,0 +1,78 @@ +import { cookies } from "next/headers"; +import { AuthOptions, JWT, Session, User } from "next-auth"; +import CredentialsProvider from "next-auth/providers/credentials"; +import { getCsrfToken } from "next-auth/react"; +import { SiweMessage } from "siwe"; +import { createBuilder, getBuilderById } from "~~/services/database/repositories/builders"; + +export const providers = [ + CredentialsProvider({ + name: "Ethereum", + credentials: { + message: { + label: "Message", + type: "text", + placeholder: "0x0", + }, + signature: { + label: "Signature", + type: "text", + placeholder: "0x0", + }, + }, + async authorize(credentials) { + try { + const siwe = new SiweMessage(JSON.parse(credentials?.message || "{}")); + const nextAuthUrl = new URL(process.env.NEXTAUTH_URL as string); + + const result = await siwe.verify({ + signature: credentials?.signature || "", + domain: nextAuthUrl.host, + nonce: await getCsrfToken({ + req: { + headers: { + cookie: cookies().toString(), + }, + }, + }), + }); + + if (result.success) { + let user = await getBuilderById(siwe.address); + + if (!user) { + // Create a new user if don't exist + user = (await createBuilder({ id: siwe.address, role: "user" }))[0]; + } + + return { id: user.id, role: user.role }; + } + return null; + } catch (e) { + return null; + } + }, + }), +]; + +export const authOptions: AuthOptions = { + // https://next-auth.js.org/configuration/providers/oauth + providers, + session: { + strategy: "jwt", + }, + secret: process.env.NEXTAUTH_SECRET, + callbacks: { + async jwt({ token, user }: { token: JWT; user: User }) { + if (user) { + token.role = user.role; + } + return token; + }, + async session({ session, token }: { session: Session; token: JWT }) { + session.user.address = token.sub; + session.user.role = token.role; + return session; + }, + }, +} as const; diff --git a/yarn.lock b/yarn.lock index 7e4b0b9..3fdd7af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.10.1": + version: 1.10.1 + resolution: "@adraffy/ens-normalize@npm:1.10.1" + checksum: 0836f394ea256972ec19a0b5e78cb7f5bcdfd48d8a32c7478afc94dd53ae44c04d1aa2303d7f3077b4f3ac2323b1f557ab9188e8059978748fdcd83e04a80dcc + languageName: node + linkType: hard + "@alloc/quick-lru@npm:^5.2.0": version: 5.2.0 resolution: "@alloc/quick-lru@npm:5.2.0" @@ -133,6 +140,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.20.13": + version: 7.25.0 + resolution: "@babel/runtime@npm:7.25.0" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 4a2a374a58eb01aaa65c5762606e90b3a1f448e0c637d42278b6cc0b42a9f5399b5f381ba9f237ee087da2860d14dd2d1de7bddcbe18be6a3cafba97e44bed64 + languageName: node + linkType: hard + "@babel/template@npm:^7.24.7": version: 7.24.7 resolution: "@babel/template@npm:7.24.7" @@ -1887,7 +1903,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.4.0": +"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" checksum: 8ba816ae26c90764b8c42493eea383716396096c5f7ba6bea559993194f49d80a73c081f315f4c367e51bd2d5891700bcdfa816b421d24ab45b41cb03e4f3342 @@ -2261,6 +2277,13 @@ __metadata: languageName: node linkType: hard +"@panva/hkdf@npm:^1.0.2": + version: 1.2.1 + resolution: "@panva/hkdf@npm:1.2.1" + checksum: a4a9d1812f88f02bc163b365524bbaa5239cc4711e5e7be1bda68dabae1c896cf1cd12520949b0925a6910733d1afcb25ab51fd3cf06f0f69aee988fffebf56e + languageName: node + linkType: hard + "@parcel/watcher-android-arm64@npm:2.4.1": version: 2.4.1 resolution: "@parcel/watcher-android-arm64@npm:2.4.1" @@ -2420,6 +2443,18 @@ __metadata: languageName: node linkType: hard +"@rainbow-me/rainbowkit-siwe-next-auth@npm:^0.4.1": + version: 0.4.1 + resolution: "@rainbow-me/rainbowkit-siwe-next-auth@npm:0.4.1" + peerDependencies: + "@rainbow-me/rainbowkit": 2.0.x || 2.1.x + next-auth: ">=4.21.0 <5" + react: ">=18" + siwe: ^2.1.4 + checksum: ce885be3f12df968c5dc223e18bd0d21375da872e70bb2244b4524b9e01239268df3b419001912974a435ca05a628ed81847c15d5b44bd7854ca89148df175d5 + languageName: node + linkType: hard + "@rainbow-me/rainbowkit@npm:2.1.0": version: 2.1.0 resolution: "@rainbow-me/rainbowkit@npm:2.1.0" @@ -2622,6 +2657,7 @@ __metadata: dependencies: "@heroicons/react": ~2.0.11 "@rainbow-me/rainbowkit": 2.1.2 + "@rainbow-me/rainbowkit-siwe-next-auth": ^0.4.1 "@tanstack/react-query": ~5.28.6 "@trivago/prettier-plugin-sort-imports": ~4.3.0 "@types/node": ^17.0.45 @@ -2645,7 +2681,9 @@ __metadata: eslint-config-next: ~14.0.4 eslint-config-prettier: ~9.1.0 eslint-plugin-prettier: ~5.1.3 + ethers: ^6.13.1 next: ~14.0.4 + next-auth: ^4.24.7 next-themes: ~0.2.1 nprogress: ~0.2.0 pg: ^8.12.0 @@ -2656,6 +2694,7 @@ __metadata: react-copy-to-clipboard: ~5.1.0 react-dom: ~18.2.0 react-hot-toast: ~2.4.0 + siwe: ^2.3.2 tailwindcss: ~3.4.3 tsx: ^4.16.2 type-fest: ~4.6.0 @@ -2781,6 +2820,18 @@ __metadata: languageName: node linkType: hard +"@spruceid/siwe-parser@npm:^2.1.2": + version: 2.1.2 + resolution: "@spruceid/siwe-parser@npm:2.1.2" + dependencies: + "@noble/hashes": ^1.1.2 + apg-js: ^4.3.0 + uri-js: ^4.4.1 + valid-url: ^1.0.9 + checksum: feb0806ed3da945192245e6ac20108a1e4ef79184a04967bf251307e7315ca824862b016bd8e2100a739e8ee76ef768f088314601adbf3e847bb7692fcb33ac0 + languageName: node + linkType: hard + "@stablelib/aead@npm:^1.0.1": version: 1.0.1 resolution: "@stablelib/aead@npm:1.0.1" @@ -4657,6 +4708,13 @@ __metadata: languageName: node linkType: hard +"apg-js@npm:^4.3.0": + version: 4.4.0 + resolution: "apg-js@npm:4.4.0" + checksum: 81f9753ef8ec102d6ec7ceddd57a5aee2e7e238ac5877165f9ac6cb5cb8ec73ec5070e040d3af04822bc0de754699b0199c39159ed0d8453d6bb99812a326deb + languageName: node + linkType: hard + "aproba@npm:^1.0.3 || ^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -5894,6 +5952,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180 + languageName: node + linkType: hard + "copy-to-clipboard@npm:^3.3.1": version: 3.3.3 resolution: "copy-to-clipboard@npm:3.3.3" @@ -7995,6 +8060,21 @@ __metadata: languageName: node linkType: hard +"ethers@npm:^6.13.1": + version: 6.13.2 + resolution: "ethers@npm:6.13.2" + dependencies: + "@adraffy/ens-normalize": 1.10.1 + "@noble/curves": 1.2.0 + "@noble/hashes": 1.3.2 + "@types/node": 18.15.13 + aes-js: 4.0.0-beta.5 + tslib: 2.4.0 + ws: 8.17.1 + checksum: 981860c736c7ae121774ad38ea07e3611ce524a77d2fcb77db499b65afe0cbe8f344cd5204d94b68b316349ff770fd2a7d9c8b2039da41c072f98d9864099925 + languageName: node + linkType: hard + "ethers@npm:~6.10.0": version: 6.10.0 resolution: "ethers@npm:6.10.0" @@ -10028,6 +10108,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^4.15.5": + version: 4.15.9 + resolution: "jose@npm:4.15.9" + checksum: 41abe1c99baa3cf8a78ebbf93da8f8e50e417b7a26754c4afa21865d87527b8ac2baf66de2c5f6accc3f7d7158658dae7364043677236ea1d07895b040097f15 + languageName: node + linkType: hard + "js-sdsl@npm:^4.1.4": version: 4.4.2 resolution: "js-sdsl@npm:4.4.2" @@ -11157,6 +11244,31 @@ __metadata: languageName: node linkType: hard +"next-auth@npm:^4.24.7": + version: 4.24.7 + resolution: "next-auth@npm:4.24.7" + dependencies: + "@babel/runtime": ^7.20.13 + "@panva/hkdf": ^1.0.2 + cookie: ^0.5.0 + jose: ^4.15.5 + oauth: ^0.9.15 + openid-client: ^5.4.0 + preact: ^10.6.3 + preact-render-to-string: ^5.1.19 + uuid: ^8.3.2 + peerDependencies: + next: ^12.2.5 || ^13 || ^14 + nodemailer: ^6.6.5 + react: ^17.0.2 || ^18 + react-dom: ^17.0.2 || ^18 + peerDependenciesMeta: + nodemailer: + optional: true + checksum: e7849ecf86394d86f08730b96f869c9353b6cc003794fcd953db2949f1ab06a2ce4f7a67312fdb8c7b3bbe7e99a8a215f11ef4eba5ec93f6c3e8ac1954e8b3e6 + languageName: node + linkType: hard + "next-themes@npm:~0.2.1": version: 0.2.1 resolution: "next-themes@npm:0.2.1" @@ -11455,6 +11567,13 @@ __metadata: languageName: node linkType: hard +"oauth@npm:^0.9.15": + version: 0.9.15 + resolution: "oauth@npm:0.9.15" + checksum: 957c0d8d85300398dcb0e293953650c0fc3facc795bee8228238414f19f59cef5fd4ee8d17a972c142924c10c5f6ec50ef80f77f4a6cc6e3c98f9d22c027801c + languageName: node + linkType: hard + "obj-multiplex@npm:^1.0.0": version: 1.0.0 resolution: "obj-multiplex@npm:1.0.0" @@ -11473,6 +11592,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^2.2.0": + version: 2.2.0 + resolution: "object-hash@npm:2.2.0" + checksum: 55ba841e3adce9c4f1b9b46b41983eda40f854e0d01af2802d3ae18a7085a17168d6b81731d43fdf1d6bcbb3c9f9c56d22c8fea992203ad90a38d7d919bc28f1 + languageName: node + linkType: hard + "object-hash@npm:^3.0.0": version: 3.0.0 resolution: "object-hash@npm:3.0.0" @@ -11593,6 +11719,13 @@ __metadata: languageName: node linkType: hard +"oidc-token-hash@npm:^5.0.3": + version: 5.0.3 + resolution: "oidc-token-hash@npm:5.0.3" + checksum: 35fa19aea9ff2c509029ec569d74b778c8a215b92bd5e6e9bc4ebbd7ab035f44304ff02430a6397c3fb7c1d15ebfa467807ca0bcd31d06ba610b47798287d303 + languageName: node + linkType: hard + "on-exit-leak-free@npm:^0.2.0": version: 0.2.0 resolution: "on-exit-leak-free@npm:0.2.0" @@ -11647,6 +11780,18 @@ __metadata: languageName: node linkType: hard +"openid-client@npm:^5.4.0": + version: 5.6.5 + resolution: "openid-client@npm:5.6.5" + dependencies: + jose: ^4.15.5 + lru-cache: ^6.0.0 + object-hash: ^2.2.0 + oidc-token-hash: ^5.0.3 + checksum: 2240079f761173b10635ce5fefbac04b6820f54e00d588ab2afdddb6c0f0ab6568e663cf1ab6a4a2297fbdbb73e42d78b8190f91dba7e1b80d287b2127fcbc7c + languageName: node + linkType: hard + "optionator@npm:^0.8.1": version: 0.8.3 resolution: "optionator@npm:0.8.3" @@ -12347,7 +12492,18 @@ __metadata: languageName: node linkType: hard -"preact@npm:^10.16.0": +"preact-render-to-string@npm:^5.1.19": + version: 5.2.6 + resolution: "preact-render-to-string@npm:5.2.6" + dependencies: + pretty-format: ^3.8.0 + peerDependencies: + preact: ">=10" + checksum: be8d5d8fb502d422c503e68af7bcccb6facd942f3ae9a4d093ebe3f1d4f0b15c540624bdac434d53a2a8e8fb7afa4606383414e937c40933ca43445470a026ff + languageName: node + linkType: hard + +"preact@npm:^10.16.0, preact@npm:^10.6.3": version: 10.23.1 resolution: "preact@npm:10.23.1" checksum: fb7d28c7da1442ed80435444326fa2dcb563d4af05f631611fff8c08fb46a6f209d1a780aa7638a0bc2ac4d7a351f3304ff97bebfd6f5ee5bc4feb880d84a34f @@ -12402,6 +12558,13 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^3.8.0": + version: 3.8.0 + resolution: "pretty-format@npm:3.8.0" + checksum: 21a114d43ef06978f8f7f6212be4649b0b094f05d9b30e14e37550bf35c8ca24d8adbca9e5adc4cc15d9eaf7a1e7a30478a4dc37b30982bfdf0292a5b385484c + languageName: node + linkType: hard + "pretty-ms@npm:7.0.1": version: 7.0.1 resolution: "pretty-ms@npm:7.0.1" @@ -13473,6 +13636,20 @@ __metadata: languageName: node linkType: hard +"siwe@npm:^2.3.2": + version: 2.3.2 + resolution: "siwe@npm:2.3.2" + dependencies: + "@spruceid/siwe-parser": ^2.1.2 + "@stablelib/random": ^1.0.1 + uri-js: ^4.4.1 + valid-url: ^1.0.9 + peerDependencies: + ethers: ^5.6.8 || ^6.0.8 + checksum: f49356ceaf2dff256cb5379a41cd75461dcd67cc478e0f3338943aa094d4d92d35b8295e6a987afbd16fdb6c870ffb4d43adf9ff79085aaddab58084df8f188c + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -15026,7 +15203,7 @@ __metadata: languageName: node linkType: hard -"uri-js@npm:^4.2.2": +"uri-js@npm:^4.2.2, uri-js@npm:^4.4.1": version: 4.4.1 resolution: "uri-js@npm:4.4.1" dependencies: @@ -15166,6 +15343,13 @@ __metadata: languageName: node linkType: hard +"valid-url@npm:^1.0.9": + version: 1.0.9 + resolution: "valid-url@npm:1.0.9" + checksum: 3ecb030559404441c2cf104cbabab8770efb0f36d117db03d1081052ef133015a68806148ce954bb4dd0b5c42c14b709a88783c93d66b0916cb67ba771c98702 + languageName: node + linkType: hard + "valtio@npm:1.11.2": version: 1.11.2 resolution: "valtio@npm:1.11.2"