diff --git a/next-mini-app/app/layout.tsx b/next-mini-app/app/layout.tsx index dca6502..b68e653 100644 --- a/next-mini-app/app/layout.tsx +++ b/next-mini-app/app/layout.tsx @@ -1,18 +1,7 @@ import type { Metadata } from "next" -import { Geist, Geist_Mono } from "next/font/google" import "./globals.css" import { WorldAuthProvider } from 'next-world-auth/react' -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"] -}) - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"] -}) - export const metadata: Metadata = { title: "World Mini App", description: "Wallet authentication for World Mini Apps" @@ -26,9 +15,7 @@ export default function RootLayout({ return ( - + {children} diff --git a/next-mini-app/app/page.tsx b/next-mini-app/app/page.tsx index ce22e9d..f663c7b 100644 --- a/next-mini-app/app/page.tsx +++ b/next-mini-app/app/page.tsx @@ -1,25 +1,145 @@ 'use client' -import { useState } from "react" +import { useEffect, useState } from "react" import Image from "next/image" -import { Tokens } from 'next-world-auth' -import { useWorldAuth } from 'next-world-auth/react' +import QRCode from "qrcode" +import { Tokens } from "next-world-auth" +import { useWorldAuth } from "next-world-auth/react" + +const worldIdStatusLabel: Record = { + idle: "Idle", + loading: "Preparing request", + awaiting_connection: "Waiting for connection", + awaiting_confirmation: "Waiting for confirmation", + success: "Verified", + error: "Failed", +} + export default function Home() { - const { isLoading, isInstalled, isAuthenticated, session, signInWorldID, signInWallet, signOut, getLocation, pay } = useWorldAuth() + const { + isLoading, + isInstalled, + isAuthenticated, + hasStoredWorldID, + session, + worldIdStatus, + worldIdConnectUri, + worldIdError, + signInWorldID, + signInWallet, + signOut, + forgetWorldID, + getLocation, + pay, + } = useWorldAuth() const [tipAmount, setTipAmount] = useState(null) + const [worldIdQrCode, setWorldIdQrCode] = useState(null) + + useEffect(() => { + let isCancelled = false + + if (!worldIdConnectUri) { + setWorldIdQrCode(null) + return + } + + QRCode.toDataURL(worldIdConnectUri, { + width: 320, + margin: 1, + }).then((value) => { + if (!isCancelled) { + setWorldIdQrCode(value) + } + }).catch(() => { + if (!isCancelled) { + setWorldIdQrCode(null) + } + }) + + return () => { + isCancelled = true + } + }, [worldIdConnectUri]) const tip = async (amount: number) => { - const r = await pay({ amount, token: Tokens.WLD, recipient: '0x2Eb67DdFf6761bC0938e670bf1e1ed46110DDABb' }) - if(r.success) { + const result = await pay({ amount, token: Tokens.WLD, recipient: "0x2Eb67DdFf6761bC0938e670bf1e1ed46110DDABb" }) + if (result.success) { setTipAmount(amount) } } + const worldIdSession = session?.worldId + const showWorldIdFlow = Boolean( + worldIdConnectUri || + worldIdError || + worldIdStatus !== "idle" || + hasStoredWorldID + ) + + const worldIdFlow = showWorldIdFlow ? ( + + {hasStoredWorldID && !worldIdSession && ( + + A saved World ID 4 session is available for returning-user verification. + + )} + {worldIdStatus !== "idle" && !(worldIdStatus === "success" && worldIdSession) && ( + World ID status: {worldIdStatusLabel[worldIdStatus] || worldIdStatus} + )} + {worldIdError && {worldIdError}} + {worldIdQrCode && ( + + )} + {worldIdConnectUri && ( + + Open World ID connect link + + )} + {hasStoredWorldID && ( + + Forget saved World ID + + )} + + ) : null + + const worldIdDetails = worldIdSession ? ( + <> + You have authenticated with World ID 4 + Verification source: {worldIdSession.identifiers.join(", ") || "unknown"} + Orb Verification Status: {session?.isOrbVerified ? "verified ✓" : "not verified ✗"} + + Session ID: {worldIdSession.sessionId} + + + Verified at: {new Date(worldIdSession.verifiedAt).toLocaleString()} + + + Configured action: {worldIdSession.action} + + > + ) : null + return ( - + World Mini App Template - Template available on GitHub + Template available on GitHub {isLoading ? ( @@ -30,27 +150,29 @@ export default function Home() { This app is designed to be run on World App - {session && session.isAuthenticatedWorldID && <> - You have authenticated with World ID - Your unique app ID is available - Orb Verification Status: {session.isOrbVerified ? 'verified ✓' : 'not verified ✗'} - + {worldIdDetails} + Logout - >} - {!(session && session.isAuthenticatedWorldID) && <> - In a web context, it is however possible to sign in with World ID - You will need the World App to sign in with World ID - signInWorldID({})} - > - Sign in with World ID - - >} + > + ) : ( + <> + World ID 4 now uses a connect flow instead of the old redirect sign-in. + Use the button below to start the World ID 4 session flow and scan the QR code with World App. + + Sign in with World ID 4 + + > + )} + {worldIdFlow} >} {isInstalled && (isAuthenticated && session ? ( <> @@ -66,8 +188,8 @@ export default function Home() { {session.isAuthenticatedWallet && <> You have authenticated with World Wallet - Welcome {session?.user?.username}! - Your wallet address is: {session?.user?.walletAddress} + Welcome {session.user.username}! + Your wallet address is: {session.user.walletAddress} tip(0.1)} @@ -89,39 +211,36 @@ export default function Home() { Sign in with World Wallet } - {session.isAuthenticatedWorldID && <> - You have authenticated with World ID - Your unique app ID is available - Orb Verification Status: {session.isOrbVerified ? 'verified ✓' : 'not verified ✗'} - >} + {session.isAuthenticatedWorldID && worldIdDetails} {!session.isAuthenticatedWorldID && signInWorldID({})} + onClick={signInWorldID} > - Sign in with World ID + Sign in with World ID 4 } - Your location is: { - session?.extra?.location - ? (<> - latitude: {session.extra.location.latitude} - longitude: {session.extra.location.longitude} - >) - : 'unknown' - } + Your location is: { + session.extra?.location + ? (<> + latitude: {session.extra.location.latitude} + longitude: {session.extra.location.longitude} + >) + : "unknown" + } - {session?.extra?.location ? 'Update Location' : 'Get Location'} + {session.extra?.location ? "Update Location" : "Get Location"} + {worldIdFlow} > ) : ( <> - + A starter mini app in a few lines of code! You are not authenticated - pick a method to sign in @@ -135,11 +254,13 @@ export default function Home() { signInWorldID({})} + onClick={signInWorldID} > - Login with World ID + Login with World ID 4 + World ID 4 uses a session-based connect flow and can reuse a saved session ID for returning users. + {worldIdFlow} > ))} > @@ -147,4 +268,4 @@ export default function Home() { ) -} \ No newline at end of file +} diff --git a/next-mini-app/eslint.config.mjs b/next-mini-app/eslint.config.mjs index c85fb67..edbf701 100644 --- a/next-mini-app/eslint.config.mjs +++ b/next-mini-app/eslint.config.mjs @@ -10,6 +10,9 @@ const compat = new FlatCompat({ }); const eslintConfig = [ + { + ignores: [".next/**", "node_modules/**"], + }, ...compat.extends("next/core-web-vitals", "next/typescript"), ]; diff --git a/next-mini-app/package.json b/next-mini-app/package.json index ec4c38f..b08b954 100644 --- a/next-mini-app/package.json +++ b/next-mini-app/package.json @@ -4,19 +4,21 @@ "private": true, "scripts": { "dev": "next dev --turbopack", - "build": "next build", + "build": "next build --webpack", "start": "next start", - "lint": "next lint" + "lint": "eslint ." }, "dependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "next": "16.2.1", - "next-world-auth": "0.1.0" + "next-world-auth": "file:../next-world-auth", + "qrcode": "1.5.4" }, "devDependencies": { "typescript": "^5", "@types/node": "^20", + "@types/qrcode": "1.5.6", "@types/react": "^19", "@types/react-dom": "^19", "postcss": "^8", diff --git a/next-mini-app/pnpm-lock.yaml b/next-mini-app/pnpm-lock.yaml index 8f0a116..78e47ed 100644 --- a/next-mini-app/pnpm-lock.yaml +++ b/next-mini-app/pnpm-lock.yaml @@ -12,8 +12,11 @@ importers: specifier: 16.2.1 version: 16.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-world-auth: - specifier: 0.1.0 - version: 0.1.0(@types/react@19.0.8)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3)) + specifier: file:../next-world-auth + version: file:../next-world-auth(@types/react@19.0.8)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3)) + qrcode: + specifier: 1.5.4 + version: 1.5.4 react: specifier: ^19.0.0 version: 19.0.0 @@ -27,6 +30,9 @@ importers: '@types/node': specifier: ^20 version: 20.17.19 + '@types/qrcode': + specifier: 1.5.6 + version: 1.5.6 '@types/react': specifier: ^19 version: 19.0.8 @@ -360,6 +366,13 @@ packages: resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@noble/secp256k1@2.3.0': + resolution: {integrity: sha512-0TQed2gcBbIrh7Ccyw+y/uZQvbJwm7Ao4scBUxqpBCcsOlZG0O4KGfjtNAy/li4W8n1xt3dxrwJ0beZ2h2G6Kw==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -410,6 +423,9 @@ packages: '@types/node@20.17.19': resolution: {integrity: sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A==} + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + '@types/react-dom@19.0.3': resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==} peerDependencies: @@ -469,8 +485,21 @@ packages: resolution: {integrity: sha512-GqBNAI+OyG+YXoMOI9PJDlO+MeiUnclrdjaXDYYCjmXBQi+tx0rQjNJfxwS9H6jQSVPSYaa/crcgYYMJvwdS0A==} engines: {node: '>=12.4'} - '@worldcoin/minikit-js@1.9.6': - resolution: {integrity: sha512-nKPj9hQngHV0bD4/SD/XZmNAYv/zfZjptPjnuzIWouRR8AQHFpdkCPIw03QujhBG+3kUwUpsBAgYPwEk4vj2dw==} + '@worldcoin/idkit-core@4.0.16': + resolution: {integrity: sha512-slcaXhP+cF/gxWQiV7ncVjXYuyoiIii2xyeweErIeLZpIJWuUpVypSxlW6S5aYHdzi1yCHt0nkIrEKSgIj9HrQ==} + + '@worldcoin/idkit-server@1.0.0': + resolution: {integrity: sha512-ZCyXJFexxLjPtG7lxTntf6oiwF0pzoUIsVFIAAgFyXav5KmUnVlrvVoeGrgpiDFRznH2B4/Iv5xv+SkEHxOdXQ==} + engines: {node: '>=18'} + + '@worldcoin/idkit@4.0.11': + resolution: {integrity: sha512-oAmjTdCgNX3JVQHz4cZXHRKsDuSlmzcokJPAQ4pL8HWzCYLovT5a/D5MkVW8o95+dpnwgIogvsfjWg/d1LyvWg==} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + '@worldcoin/minikit-js@1.11.0': + resolution: {integrity: sha512-EkkKa0Ei4tBDq75QpyLPEac4N5wpUGX/J990LeyKuKAJWsFqtZltIxHtWPyPY+a9WKbfboW29NL7ZnEl1xg47g==} engines: {node: '>= 16'} peerDependencies: react: ^17 || ^18 || ^19 @@ -635,6 +664,10 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + caniuse-lite@1.0.30001699: resolution: {integrity: sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==} @@ -649,6 +682,9 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -707,6 +743,10 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -725,6 +765,9 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -940,6 +983,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -974,6 +1021,10 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.2.7: resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} @@ -1239,6 +1290,10 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1293,8 +1348,8 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next-world-auth@0.1.0: - resolution: {integrity: sha512-M/vIrFjYAb/UB4CozODfDl+iMTqCFkKGaoeN8zB4WM0x1uniTdRjNuy9mBS/8ZfbX+DIGQoWLSjGzi0XvwwFQw==} + next-world-auth@file:../next-world-auth: + resolution: {directory: ../next-world-auth, type: directory} next@16.2.1: resolution: {integrity: sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==} @@ -1381,14 +1436,26 @@ packages: typescript: optional: true + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1430,6 +1497,10 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -1490,6 +1561,11 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1520,6 +1596,13 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1572,6 +1655,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -1798,6 +1884,9 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which-typed-array@1.1.18: resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} engines: {node: '>= 0.4'} @@ -1811,6 +1900,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -1831,11 +1924,22 @@ packages: utf-8-validate: optional: true + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + yaml@2.7.0: resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} engines: {node: '>= 14'} hasBin: true + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2084,6 +2188,10 @@ snapshots: '@noble/hashes@1.7.1': {} + '@noble/hashes@1.8.0': {} + + '@noble/secp256k1@2.3.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2132,6 +2240,10 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 20.17.19 + '@types/react-dom@19.0.3(@types/react@19.0.8)': dependencies: '@types/react': 19.0.8 @@ -2230,7 +2342,24 @@ snapshots: - typescript - zod - '@worldcoin/minikit-js@1.9.6(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3))': + '@worldcoin/idkit-core@4.0.16': + dependencies: + '@noble/hashes': 1.8.0 + '@worldcoin/idkit-server': 1.0.0 + + '@worldcoin/idkit-server@1.0.0': + dependencies: + '@noble/hashes': 1.8.0 + '@noble/secp256k1': 2.3.0 + + '@worldcoin/idkit@4.0.11(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@worldcoin/idkit-core': 4.0.16 + qrcode: 1.5.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@worldcoin/minikit-js@1.11.0(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3))': dependencies: '@worldcoin/idkit-core': 2.0.2(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3) abitype: 1.0.8(typescript@5.7.3) @@ -2407,6 +2536,8 @@ snapshots: camelcase-css@2.0.1: {} + camelcase@5.3.1: {} + caniuse-lite@1.0.30001699: {} chalk@4.1.2: @@ -2428,6 +2559,12 @@ snapshots: client-only@0.0.1: {} + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -2476,6 +2613,8 @@ snapshots: dependencies: ms: 2.1.3 + decamelize@1.2.0: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -2495,6 +2634,8 @@ snapshots: didyoumean@1.2.2: {} + dijkstrajs@1.0.3: {} + dlv@1.1.3: {} doctrine@2.1.0: @@ -2854,6 +2995,11 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -2891,6 +3037,8 @@ snapshots: functions-have-names@1.2.3: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.2.7: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3170,6 +3318,10 @@ snapshots: lines-and-columns@1.2.4: {} + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -3215,9 +3367,11 @@ snapshots: natural-compare@1.4.0: {} - next-world-auth@0.1.0(@types/react@19.0.8)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3)): + next-world-auth@file:../next-world-auth(@types/react@19.0.8)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3)): dependencies: - '@worldcoin/minikit-js': 1.9.6(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3)) + '@worldcoin/idkit': 4.0.11(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@worldcoin/idkit-core': 4.0.16 + '@worldcoin/minikit-js': 1.11.0(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3)) next: 16.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) @@ -3336,7 +3490,7 @@ snapshots: dependencies: '@adraffy/ens-normalize': 1.11.0 '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 + '@noble/hashes': 1.8.0 '@scure/bip32': 1.6.2 '@scure/bip39': 1.5.4 abitype: 1.0.8(typescript@5.7.3) @@ -3346,14 +3500,24 @@ snapshots: transitivePeerDependencies: - zod + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} parent-module@1.0.1: @@ -3381,6 +3545,8 @@ snapshots: pirates@4.0.6: {} + pngjs@5.0.0: {} + possible-typed-array-names@1.1.0: {} postcss-import@15.1.0(postcss@8.5.2): @@ -3436,6 +3602,12 @@ snapshots: punycode@2.3.1: {} + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + queue-microtask@1.2.3: {} react-dom@19.0.0(react@19.0.0): @@ -3475,6 +3647,10 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-directory@2.1.1: {} + + require-main-filename@2.0.0: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -3525,6 +3701,8 @@ snapshots: semver@7.7.4: optional: true + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -3881,6 +4059,8 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 + which-module@2.0.1: {} + which-typed-array@1.1.18: dependencies: available-typed-arrays: 1.0.7 @@ -3896,6 +4076,12 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -3910,8 +4096,29 @@ snapshots: ws@8.18.0: {} + y18n@4.0.3: {} + yaml@2.7.0: {} + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + yocto-queue@0.1.0: {} zustand@4.5.6(@types/react@19.0.8)(react@19.0.0): diff --git a/next-world-auth/package.json b/next-world-auth/package.json index 1aceceb..2cdcaa1 100644 --- a/next-world-auth/package.json +++ b/next-world-auth/package.json @@ -14,7 +14,9 @@ "author": "gip", "license": "MIT", "dependencies": { - "@worldcoin/minikit-js": "^1.9.6", + "@worldcoin/idkit": "4.0.11", + "@worldcoin/idkit-core": "4.0.16", + "@worldcoin/minikit-js": "1.11.0", "next": "16.2.1", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/next-world-auth/pnpm-lock.yaml b/next-world-auth/pnpm-lock.yaml index 7be2ea0..d5f7caa 100644 --- a/next-world-auth/pnpm-lock.yaml +++ b/next-world-auth/pnpm-lock.yaml @@ -8,9 +8,15 @@ importers: .: dependencies: + '@worldcoin/idkit': + specifier: 4.0.11 + version: 4.0.11(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@worldcoin/idkit-core': + specifier: 4.0.16 + version: 4.0.16 '@worldcoin/minikit-js': - specifier: ^1.9.6 - version: 1.9.6(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3)) + specifier: 1.11.0 + version: 1.11.0(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3)) next: specifier: 16.2.1 version: 16.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -328,6 +334,13 @@ packages: resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@noble/secp256k1@2.3.0': + resolution: {integrity: sha512-0TQed2gcBbIrh7Ccyw+y/uZQvbJwm7Ao4scBUxqpBCcsOlZG0O4KGfjtNAy/li4W8n1xt3dxrwJ0beZ2h2G6Kw==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -415,8 +428,21 @@ packages: resolution: {integrity: sha512-GqBNAI+OyG+YXoMOI9PJDlO+MeiUnclrdjaXDYYCjmXBQi+tx0rQjNJfxwS9H6jQSVPSYaa/crcgYYMJvwdS0A==} engines: {node: '>=12.4'} - '@worldcoin/minikit-js@1.9.6': - resolution: {integrity: sha512-nKPj9hQngHV0bD4/SD/XZmNAYv/zfZjptPjnuzIWouRR8AQHFpdkCPIw03QujhBG+3kUwUpsBAgYPwEk4vj2dw==} + '@worldcoin/idkit-core@4.0.16': + resolution: {integrity: sha512-slcaXhP+cF/gxWQiV7ncVjXYuyoiIii2xyeweErIeLZpIJWuUpVypSxlW6S5aYHdzi1yCHt0nkIrEKSgIj9HrQ==} + + '@worldcoin/idkit-server@1.0.0': + resolution: {integrity: sha512-ZCyXJFexxLjPtG7lxTntf6oiwF0pzoUIsVFIAAgFyXav5KmUnVlrvVoeGrgpiDFRznH2B4/Iv5xv+SkEHxOdXQ==} + engines: {node: '>=18'} + + '@worldcoin/idkit@4.0.11': + resolution: {integrity: sha512-oAmjTdCgNX3JVQHz4cZXHRKsDuSlmzcokJPAQ4pL8HWzCYLovT5a/D5MkVW8o95+dpnwgIogvsfjWg/d1LyvWg==} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + '@worldcoin/minikit-js@1.11.0': + resolution: {integrity: sha512-EkkKa0Ei4tBDq75QpyLPEac4N5wpUGX/J990LeyKuKAJWsFqtZltIxHtWPyPY+a9WKbfboW29NL7ZnEl1xg47g==} engines: {node: '>= 16'} peerDependencies: react: ^17 || ^18 || ^19 @@ -446,6 +472,10 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -532,6 +562,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + caniuse-lite@1.0.30001699: resolution: {integrity: sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==} @@ -542,6 +576,9 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -580,6 +617,10 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -595,6 +636,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -603,6 +647,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + es-abstract@1.23.9: resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} @@ -714,6 +761,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -739,6 +790,10 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.2.7: resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} @@ -864,6 +919,10 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} @@ -962,6 +1021,10 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1076,14 +1139,26 @@ packages: typescript: optional: true + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1106,6 +1181,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -1125,6 +1204,11 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1148,6 +1232,13 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1192,6 +1283,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -1236,6 +1330,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + string.prototype.matchall@4.0.12: resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} engines: {node: '>= 0.4'} @@ -1255,6 +1353,10 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1360,6 +1462,9 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which-typed-array@1.1.18: resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} engines: {node: '>= 0.4'} @@ -1373,6 +1478,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -1385,6 +1494,17 @@ packages: utf-8-validate: optional: true + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1601,6 +1721,10 @@ snapshots: '@noble/hashes@1.7.1': {} + '@noble/hashes@1.8.0': {} + + '@noble/secp256k1@2.3.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1732,7 +1856,24 @@ snapshots: - typescript - zod - '@worldcoin/minikit-js@1.9.6(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3))': + '@worldcoin/idkit-core@4.0.16': + dependencies: + '@noble/hashes': 1.8.0 + '@worldcoin/idkit-server': 1.0.0 + + '@worldcoin/idkit-server@1.0.0': + dependencies: + '@noble/hashes': 1.8.0 + '@noble/secp256k1': 2.3.0 + + '@worldcoin/idkit@4.0.11(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@worldcoin/idkit-core': 4.0.16 + qrcode: 1.5.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@worldcoin/minikit-js@1.11.0(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3)(viem@2.23.5(typescript@5.7.3))': dependencies: '@worldcoin/idkit-core': 2.0.2(@types/react@19.0.8)(react@19.0.0)(typescript@5.7.3) abitype: 1.0.8(typescript@5.7.3) @@ -1761,6 +1902,8 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -1873,6 +2016,8 @@ snapshots: callsites@3.1.0: {} + camelcase@5.3.1: {} + caniuse-lite@1.0.30001699: {} chalk@4.1.2: @@ -1882,6 +2027,12 @@ snapshots: client-only@0.0.1: {} + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -1920,6 +2071,8 @@ snapshots: dependencies: ms: 2.1.3 + decamelize@1.2.0: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -1937,6 +2090,8 @@ snapshots: detect-libc@2.1.2: optional: true + dijkstrajs@1.0.3: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -1947,6 +2102,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + emoji-regex@8.0.0: {} + es-abstract@1.23.9: dependencies: array-buffer-byte-length: 1.0.2 @@ -2163,6 +2320,11 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -2192,6 +2354,8 @@ snapshots: functions-have-names@1.2.3: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.2.7: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2322,6 +2486,8 @@ snapshots: dependencies: call-bound: 1.0.3 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.0: dependencies: call-bound: 1.0.3 @@ -2426,6 +2592,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -2551,7 +2721,7 @@ snapshots: dependencies: '@adraffy/ens-normalize': 1.11.0 '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 + '@noble/hashes': 1.8.0 '@scure/bip32': 1.6.2 '@scure/bip39': 1.5.4 abitype: 1.0.8(typescript@5.7.3) @@ -2561,14 +2731,24 @@ snapshots: transitivePeerDependencies: - zod + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -2583,6 +2763,8 @@ snapshots: picomatch@2.3.1: {} + pngjs@5.0.0: {} + possible-typed-array-names@1.1.0: {} postcss@8.4.31: @@ -2601,6 +2783,12 @@ snapshots: punycode@2.3.1: {} + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + queue-microtask@1.2.3: {} react-dom@19.0.0(react@19.0.0): @@ -2632,6 +2820,10 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-directory@2.1.1: {} + + require-main-filename@2.0.0: {} + resolve-from@4.0.0: {} resolve@2.0.0-next.5: @@ -2674,6 +2866,8 @@ snapshots: semver@7.7.4: optional: true + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -2764,6 +2958,12 @@ snapshots: source-map-js@1.2.1: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 @@ -2808,6 +3008,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-json-comments@3.1.1: {} styled-jsx@5.1.6(react@19.0.0): @@ -2945,6 +3149,8 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 + which-module@2.0.1: {} + which-typed-array@1.1.18: dependencies: available-typed-arrays: 1.0.7 @@ -2960,8 +3166,35 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + ws@8.18.0: {} + y18n@4.0.3: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + yocto-queue@0.1.0: {} zustand@4.5.6(@types/react@19.0.8)(react@19.0.0): diff --git a/next-world-auth/src/errors.ts b/next-world-auth/src/errors.ts index a6da6f1..b99cf08 100644 --- a/next-world-auth/src/errors.ts +++ b/next-world-auth/src/errors.ts @@ -5,7 +5,11 @@ const errors = { ERR_NOT_INSTALLED: 'not installed', ERR_FAILED_EXCEPTION: 'failed due to exception', ERR_FAILED_TO_REDIRECT: 'failed to redirect', - ERR_INVALID_PARAMETERS: 'invalid parameters' + ERR_INVALID_PARAMETERS: 'invalid parameters', + ERR_WORLD_ID_CONFIG: 'world id config is missing or invalid', + ERR_WORLD_ID_TIMED_OUT: 'world id verification timed out', + ERR_WORLD_ID_FAILED: 'world id verification failed', + ERR_WORLD_ID_INVALID_RESPONSE: 'world id verification returned an invalid response' } export default errors diff --git a/next-world-auth/src/handler.ts b/next-world-auth/src/handler.ts index 4fdd5e9..8cf7e07 100644 --- a/next-world-auth/src/handler.ts +++ b/next-world-auth/src/handler.ts @@ -2,44 +2,78 @@ import { WorldAuthOptions, WorldAuthOptions0, defaultWorldAuthOptions } from './ import { NextRequest, NextResponse } from 'next/server' import { cookies } from 'next/headers' import { verifySiweMessage, getIsUserVerified, MiniAppWalletAuthSuccessPayload } from '@worldcoin/minikit-js' +import { signRequest, type IDKitResult } from '@worldcoin/idkit-core' import type { Session, User } from './types' +import type { WorldIdEnvironment, WorldIdRpContextResponse } from './world-id' +import { getWorldIdIdentifiers, isOrbVerifiedIdentifier, isWorldIdSessionResult } from './world-id' type IRequestPayload = { payload: MiniAppWalletAuthSuccessPayload nonce: string } -type WorldIdTokenClaims = { - email?: string - 'https://id.worldcoin.org/v1'?: { - verification_level?: string - } +type WorldIdVerifyResultItem = { + identifier: string + success: boolean + nullifier?: string + code?: string + detail?: string } -type WorldIdTokenResponse = { - id_token?: string +type WorldIdVerifyResponse = { + success?: boolean + results?: WorldIdVerifyResultItem[] + action?: string + nullifier?: string + created_at?: string + environment?: WorldIdEnvironment + session_id?: string + message?: string } -const decodeJwt = (token: string): WorldIdTokenClaims => { - const payload = token.split('.')[1] - if (!payload) { - throw new Error('Invalid JWT payload') - } - const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/')) - return JSON.parse(decoded) as WorldIdTokenClaims +type WorldIdServerConfig = { + appId: `app_${string}` + action: string + rpId: string + signingKey: string + environment: WorldIdEnvironment + verifyBaseUrl: string } -const getCallbackRedirectUrl = (req: NextRequest, error?: string) => { - const redirectUrl = new URL('/', process.env.WLD_SERVER || req.nextUrl.origin) - if (error) { - redirectUrl.searchParams.set('error', error) - } - return redirectUrl +const getWorldIdEnvironment = (): WorldIdEnvironment => { + return process.env.WLD_WORLD_ID_ENVIRONMENT === 'staging' ? 'staging' : 'production' } -const redirectCallbackError = async (req: NextRequest, options: WorldAuthOptions0, error: string) => { - await deleteSession(options) - return NextResponse.redirect(getCallbackRedirectUrl(req, error)) +const getWorldIdServerConfig = (): { config: WorldIdServerConfig | null; error?: string } => { + const appId = process.env.NEXT_PUBLIC_WLD_APP_ID || process.env.NEXT_PUBLIC_WLD_CLIENT_ID + const rpId = process.env.WLD_RP_ID + const action = process.env.WLD_WORLD_ID_ACTION + const signingKey = process.env.WLD_RP_SIGNING_KEY + const missing: string[] = [] + + if (!appId) missing.push('NEXT_PUBLIC_WLD_APP_ID') + if (!rpId) missing.push('WLD_RP_ID') + if (!action) missing.push('WLD_WORLD_ID_ACTION') + if (!signingKey) missing.push('WLD_RP_SIGNING_KEY') + + if (missing.length > 0) { + return { config: null, error: `Missing World ID 4 env vars: ${missing.join(', ')}` } + } + + const environment = getWorldIdEnvironment() + + return { + config: { + appId: appId as `app_${string}`, + action: action!, + rpId: rpId!, + signingKey: signingKey!, + environment, + verifyBaseUrl: environment === 'staging' + ? 'https://staging-developer.worldcoin.org' + : 'https://developer.world.org', + }, + } } const deleteSession = async (options: WorldAuthOptions0): Promise => { @@ -56,6 +90,7 @@ const setSession = (options: WorldAuthOptions0) => async (session: Session | nul cookieStore.set(options.cookieSessionName, JSON.stringify(session), { secure: true, httpOnly: true, + maxAge: options.sessionMaxAge, }) } else { cookieStore.delete(options.cookieSessionName) @@ -65,7 +100,7 @@ const setSession = (options: WorldAuthOptions0) => async (session: Session | nul const updateSession = (options: WorldAuthOptions0) => async (session: Session): Promise => { const session0 = await getSession(options)() let session1: Session - if(!session0) { + if (!session0) { session1 = session } else { const user1 = { ...session0.user, ...session.user } @@ -74,14 +109,14 @@ const updateSession = (options: WorldAuthOptions0) => async (session: Session): isAuthenticatedWorldID: session0.isAuthenticatedWorldID || session.isAuthenticatedWorldID, isOrbVerified: session0.isOrbVerified || session.isOrbVerified, user: user1, - extra: { ...session0.extra, ...session.extra } + extra: { ...session0.extra, ...session.extra }, + worldId: session.worldId || session0.worldId, } } await setSession(options)(session1) return session1 } - const augmentSession = (options: WorldAuthOptions0) => async (key: string, data: object | null): Promise => { const cookieStore = await cookies() const body = cookieStore.get(options.cookieSessionName) @@ -89,7 +124,7 @@ const augmentSession = (options: WorldAuthOptions0) => async (key: string, data: if (session && session.user) { const extra = session.extra || {} let session1: Session - if(data) { + if (data) { session1 = { ...session, extra: { ...extra, [key]: data } } } else { delete extra[key] @@ -117,8 +152,8 @@ const completeSiwe = (options: WorldAuthOptions) => async (req: NextRequest) => const { payload, nonce, user } = (await req.json()) as (IRequestPayload & { user: User }) if (nonce !== (await cookies()).get(options0.cookieNonceName)?.value) { - await deleteSession(options0) - return NextResponse.json({ + await deleteSession(options0) + return NextResponse.json({ status: 'error', isValid: false, message: 'Invalid nonce', @@ -130,7 +165,7 @@ const completeSiwe = (options: WorldAuthOptions) => async (req: NextRequest) => getIsUserVerified(user.walletAddress!) ]) - if(!validMessage.isValid) { + if (!validMessage.isValid) { await deleteSession(options0) return NextResponse.json(null) } @@ -139,14 +174,14 @@ const completeSiwe = (options: WorldAuthOptions) => async (req: NextRequest) => isAuthenticatedWallet: true, isAuthenticatedWorldID: false, isOrbVerified: isUserOrbVerified, - user : { + user: { ...user, }, extra: {} } const session1 = await updateSession(options0)(session) - if(options.callbacks?.onSignIn) { - await options.callbacks.onSignIn(session.user) + if (options.callbacks?.onSignIn) { + await options.callbacks.onSignIn(session1.user) } return NextResponse.json(session1) } catch { @@ -155,6 +190,96 @@ const completeSiwe = (options: WorldAuthOptions) => async (req: NextRequest) => } } +const getWorldIdRpContext = async () => { + const { config, error } = getWorldIdServerConfig() + if (!config) { + return NextResponse.json({ success: false, error }, { status: 500 }) + } + + const rpSignature = signRequest(config.action, config.signingKey) + + const response: WorldIdRpContextResponse = { + appId: config.appId, + action: config.action, + environment: config.environment, + rpContext: { + rp_id: config.rpId, + nonce: rpSignature.nonce, + created_at: rpSignature.createdAt, + expires_at: rpSignature.expiresAt, + signature: rpSignature.sig, + }, + } + + return NextResponse.json(response) +} + +const verifyWorldId = (options: WorldAuthOptions0) => async (req: NextRequest) => { + const { config, error } = getWorldIdServerConfig() + if (!config) { + return NextResponse.json({ success: false, error }, { status: 500 }) + } + + const idkitResponse = (await req.json()) as IDKitResult + const verifyResponse = await fetch(`${config.verifyBaseUrl}/api/v4/verify/${config.rpId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(idkitResponse), + }) + + let payload: WorldIdVerifyResponse | null = null + try { + payload = await verifyResponse.json() as WorldIdVerifyResponse + } catch { + payload = null + } + + if (!verifyResponse.ok || !payload?.success) { + return NextResponse.json(payload || { success: false, error: 'World ID verification failed' }, { + status: verifyResponse.ok ? 400 : verifyResponse.status, + }) + } + + const resultIdentifiers = payload.results + ?.filter((result) => result.success) + .map((result) => result.identifier) || [] + const responseIdentifiers = idkitResponse.responses.map((response) => response.identifier) + const identifiers = getWorldIdIdentifiers([...resultIdentifiers, ...responseIdentifiers]) + const sessionId = payload.session_id || (isWorldIdSessionResult(idkitResponse) ? idkitResponse.session_id : undefined) + + if (!sessionId) { + return NextResponse.json({ + success: false, + error: 'World ID verification succeeded but no session_id was returned', + verify: payload, + }, { status: 502 }) + } + + const session: Session = { + isAuthenticatedWallet: false, + isAuthenticatedWorldID: true, + isOrbVerified: identifiers.some(isOrbVerifiedIdentifier), + user: {}, + extra: {}, + worldId: { + sessionId, + identifiers, + action: config.action, + verifiedAt: payload.created_at || new Date().toISOString(), + protocolVersion: '4.0', + }, + } + + const session1 = await updateSession(options)(session) + if (options.callbacks?.onSignIn) { + await options.callbacks.onSignIn(session1.user) + } + + return NextResponse.json(session1) +} + // eslint-disable-next-line @typescript-eslint/no-unused-vars const session = (options: WorldAuthOptions0) => async (req: NextRequest) => { const session = await getSession(options)() @@ -168,74 +293,14 @@ export const handler = (options: WorldAuthOptions) => async (req: NextRequest): const options0: WorldAuthOptions0 = { ...defaultWorldAuthOptions, ...options } switch (req.nextUrl.pathname) { - case '/api/auth/callback': - // World ID callback - if (req.method === 'GET') { - const code = req.nextUrl.searchParams.get('code') - if (!code) { - return NextResponse.json({ status: 'error', message: 'No code provided' }, { status: 400 }) - } - - const data = new URLSearchParams() - data.append('code', code) - data.append('grant_type', 'authorization_code') - const redirectUri = process.env.NEXT_PUBLIC_WLD_REDIRECT_URI - if (!redirectUri) { - return redirectCallbackError(req, options0, 'world-id-config') - } - data.append('redirect_uri', redirectUri) - - const clientId = process.env.WLD_CLIENT_ID - const clientSecret = process.env.WLD_CLIENT_SECRET - if (!clientId || !clientSecret) { - return redirectCallbackError(req, options0, 'world-id-config') - } - let idToken: string | undefined - try { - const res = await fetch('https://id.worldcoin.org/token', { - method: 'POST', - headers: { - Authorization: `Basic ${btoa(`${clientId}:${clientSecret}`)}`, - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: data, - }) - if (!res.ok) { - return redirectCallbackError(req, options0, 'world-id-token') - } - const tokenResponse = (await res.json()) as WorldIdTokenResponse - idToken = tokenResponse.id_token - } catch { - return redirectCallbackError(req, options0, 'world-id-token') - } - if (!idToken) { - return redirectCallbackError(req, options0, 'world-id-token') - } - let decoded: WorldIdTokenClaims - try { - decoded = decodeJwt(idToken) - } catch { - return redirectCallbackError(req, options0, 'world-id-token') - } - const { email } = decoded - if (!email) { - return redirectCallbackError(req, options0, 'world-id-token') - } - const verificationLevel = decoded['https://id.worldcoin.org/v1']?.verification_level - const session: Session = { - isAuthenticatedWallet: false, - isAuthenticatedWorldID: true, - isOrbVerified: verificationLevel === 'orb', - user: { - appWorldID: email - }, - extra: {} - } - await updateSession(options0)(session) - if(options.callbacks?.onSignIn) { - await options.callbacks.onSignIn(session.user) - } - return NextResponse.redirect(getCallbackRedirectUrl(req)) + case '/api/miniauth/worldid/rp-context': + if (req.method === 'POST') { + return getWorldIdRpContext() + } + break + case '/api/miniauth/worldid/verify': + if (req.method === 'POST') { + return verifyWorldId(options0)(req) } break case '/api/miniauth/nonce': @@ -253,29 +318,28 @@ export const handler = (options: WorldAuthOptions) => async (req: NextRequest): break case '/api/miniauth/session': if (req.method === 'GET') { - const s = await session(options0)(req) - return s + return session(options0)(req) } break - case '/api/miniauth/logout': - if (req.method === 'POST') { - const session = await deleteSession(options0) - if(options.callbacks?.onSignOut && session && session?.user) { - await options.callbacks.onSignOut(session?.user) - } - return NextResponse.json({ success: true }) + case '/api/miniauth/logout': + if (req.method === 'POST') { + const session = await deleteSession(options0) + if (options.callbacks?.onSignOut && session && session.user) { + await options.callbacks.onSignOut(session.user) } - break - case '/api/miniauth/augment': - if (req.method === 'POST') { - const { key, data } = await req.json() - if (typeof key === 'string' && key.length > 0 && (data === null || typeof data === 'object')) { - const session = await augmentSession(options0)(key, data) - return NextResponse.json(session) - } - return NextResponse.json({ status: 'error' }, { status: 400 }) - } - break + return NextResponse.json({ success: true }) + } + break + case '/api/miniauth/augment': + if (req.method === 'POST') { + const { key, data } = await req.json() + if (typeof key === 'string' && key.length > 0 && (data === null || typeof data === 'object')) { + const session = await augmentSession(options0)(key, data) + return NextResponse.json(session) + } + return NextResponse.json({ status: 'error' }, { status: 400 }) + } + break default: break } diff --git a/next-world-auth/src/index.ts b/next-world-auth/src/index.ts index 325b4af..ca2edd9 100644 --- a/next-world-auth/src/index.ts +++ b/next-world-auth/src/index.ts @@ -2,7 +2,7 @@ import { handler, getSession } from './handler' import { WorldAuthOptions, defaultWorldAuthOptions } from './options' export type { WorldAuthOptions } -export type { Session, User, MyLocation } from './types' +export type { Session, User, MyLocation, WorldIdSession, WorldIdStatus } from './types' export { getSession as getServerSession } diff --git a/next-world-auth/src/react.tsx b/next-world-auth/src/react.tsx index 2bca03c..ecc7f61 100644 --- a/next-world-auth/src/react.tsx +++ b/next-world-auth/src/react.tsx @@ -2,26 +2,37 @@ import React, { ReactNode, createContext, useContext, useEffect, useState } from 'react' import { MiniKit, Tokens, tokenToDecimals } from '@worldcoin/minikit-js' +import type { IDKitRequest, IDKitResult } from '@worldcoin/idkit-core' import { WorldAuthOptions, WorldAuthOptions0, defaultWorldAuthOptions } from './options' -import type { Session, MyLocation } from './types' +import type { Session, MyLocation, WorldIdStatus } from './types' +import { createWorldIdSessionRequest, type WorldIdRpContextResponse } from './world-id' import errors from './errors' +type ActionSuccess = { success: true } +type ActionFailure = { success: false; error: string } +type ActionResult = ActionSuccess | ActionFailure + type WorldAuthContextType = { // State isLoading: boolean - isInitialized: boolean // Has the provider been initialized? - isInstalled: boolean // Are we running in the World App? - isAuthenticated: boolean // Session should not be null if isAuthenticated is true - session: Session | null + isInitialized: boolean + isInstalled: boolean + isAuthenticated: boolean + hasStoredWorldID: boolean + session: Session | null + worldIdStatus: WorldIdStatus + worldIdConnectUri: string | null + worldIdError: string | null // Actions - signInWorldID: (state: { state?: string, nonce?: string }) => Promise<{ success: false, error: string }> - signInWallet: () => Promise<{ success: boolean }> + signInWorldID: () => Promise + signInWallet: () => Promise<{ success: boolean; error?: string }> signOut: () => Promise<{ success: boolean }> + forgetWorldID: () => Promise<{ success: boolean }> augmentSession: (key: string, data: object | null) => Promise<{ success: boolean }> getLocation: () => Promise<{ success: boolean; latitude?: number; longitude?: number; error?: string }> pay: ({ amount, token, recipient }: { amount: number, token: Tokens, recipient: string }) => Promise<{ success: boolean, finalPayload: object | null }> - minikit: MiniKit | null + minikit: typeof MiniKit | null } const initialContext: WorldAuthContextType = { @@ -29,33 +40,145 @@ const initialContext: WorldAuthContextType = { isInitialized: false, isInstalled: false, isAuthenticated: false, + hasStoredWorldID: false, session: null, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - signInWorldID: async (state: { state?: string | null, nonce?: string } | null) => ({ success: false, error: errors.ERR_NOT_IMPLEMENTED }), + worldIdStatus: 'idle', + worldIdConnectUri: null, + worldIdError: null, + signInWorldID: async () => ({ success: false, error: errors.ERR_NOT_IMPLEMENTED }), signInWallet: async () => ({ success: false }), signOut: async () => ({ success: false }), + forgetWorldID: async () => ({ success: false }), augmentSession: async () => ({ success: false }), getLocation: async () => ({ success: false }), pay: async () => ({ success: false, finalPayload: null }), minikit: null } +const WORLD_ID_POLL_INTERVAL_MS = 1000 +const WORLD_ID_POLL_TIMEOUT_MS = 2 * 60 * 1000 + const WorldAuthContext = createContext(initialContext) +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + +const getPublicWorldIdAppId = () => { + const appId = process.env.NEXT_PUBLIC_WLD_APP_ID || process.env.NEXT_PUBLIC_WLD_CLIENT_ID + return appId || null +} + +const getWorldIdStorageKey = (options: WorldAuthOptions0) => `${options.cookieSessionName}:worldid-session-id` + +const readStoredWorldIdSessionId = (options: WorldAuthOptions0) => { + if (typeof window === 'undefined') { + return null + } + try { + return window.localStorage.getItem(getWorldIdStorageKey(options)) + } catch { + return null + } +} + +const writeStoredWorldIdSessionId = (options: WorldAuthOptions0, sessionId: string) => { + if (typeof window === 'undefined') { + return + } + try { + window.localStorage.setItem(getWorldIdStorageKey(options), sessionId) + } catch { + // Ignore local storage errors. + } +} + +const clearStoredWorldIdSessionId = (options: WorldAuthOptions0) => { + if (typeof window === 'undefined') { + return + } + try { + window.localStorage.removeItem(getWorldIdStorageKey(options)) + } catch { + // Ignore local storage errors. + } +} + +const getErrorMessageFromUnknown = (error: unknown, fallback: string) => { + if (error instanceof Error && error.message) { + return error.message + } + return fallback +} + +const getErrorMessageFromResponse = async (response: Response, fallback: string) => { + try { + const payload = await response.json() as { error?: string; message?: string } + return payload.error || payload.message || fallback + } catch { + return fallback + } +} + +const isConfirmedWorldIdResult = (result: IDKitResult): result is IDKitResult & { session_id: string } => { + return 'session_id' in result && typeof result.session_id === 'string' && result.session_id.length > 0 +} + export const WorldAuthProvider = ({ options, children }: { options?: WorldAuthOptions; children: ReactNode }) => { const options0: WorldAuthOptions0 = { ...defaultWorldAuthOptions, ...options || {} } const [authState, setAuthState] = useState(initialContext) + const syncStoredWorldIdState = (session: Session | null) => { + if (session?.worldId?.sessionId) { + writeStoredWorldIdSessionId(options0, session.worldId.sessionId) + return true + } + return Boolean(readStoredWorldIdSessionId(options0)) + } + + const waitForWorldIdCompletion = async (request: IDKitRequest): Promise<{ success: true; result: IDKitResult } | { success: false; error: string }> => { + const startedAt = Date.now() + + while ((Date.now() - startedAt) < WORLD_ID_POLL_TIMEOUT_MS) { + const status = await request.pollOnce() + + if (status.type === 'confirmed' && status.result) { + return { success: true, result: status.result } + } + + if (status.type === 'failed') { + return { success: false, error: status.error || errors.ERR_WORLD_ID_FAILED } + } + + setAuthState((prev) => ({ + ...prev, + worldIdStatus: status.type === 'waiting_for_connection' + ? 'awaiting_connection' + : 'awaiting_confirmation', + worldIdConnectUri: request.connectorURI || null, + })) + + await sleep(WORLD_ID_POLL_INTERVAL_MS) + } + + return { success: false, error: errors.ERR_WORLD_ID_TIMED_OUT } + } + useEffect(() => { - const init = async () => { + const init = () => { try { - await MiniKit.install(process.env.NEXT_PUBLIC_WLD_CLIENT_ID) + MiniKit.install(getPublicWorldIdAppId() || undefined) const installed = MiniKit.isInstalled() - console.log('MiniKit installed:', installed) // Debugging log - setAuthState(prev => ({ ...prev, isInstalled: installed, isInitialized: true })) - } catch (error) { - console.error('MiniKit installation failed:', error) - setAuthState(prev => ({ ...prev, isInitialized: true })) + setAuthState(prev => ({ + ...prev, + isInstalled: installed, + isInitialized: true, + hasStoredWorldID: Boolean(readStoredWorldIdSessionId(options0)), + })) + } catch { + setAuthState(prev => ({ + ...prev, + isInitialized: true, + hasStoredWorldID: Boolean(readStoredWorldIdSessionId(options0)), + })) } } init() @@ -63,107 +186,241 @@ export const WorldAuthProvider = ({ options, children }: { options?: WorldAuthOp useEffect(() => { const checkSession = async () => { - if (authState.isInitialized) { - const res = await fetch(`/api/miniauth/session`) - if(res.ok) { - const s = await res.json() - if(s) { + if (!authState.isInitialized) { + return + } + + const hasStoredWorldID = Boolean(readStoredWorldIdSessionId(options0)) + + try { + const res = await fetch('/api/miniauth/session') + if (res.ok) { + const session = await res.json() as Session | null + const nextHasStoredWorldID = syncStoredWorldIdState(session) || hasStoredWorldID + + if (session) { setAuthState(prev => ({ ...prev, isAuthenticated: true, isLoading: false, - session: s, - })) - } else { - setAuthState(prev => ({ - ...prev, - isAuthenticated: false, - isLoading: false, - session: null, + hasStoredWorldID: nextHasStoredWorldID, + session, })) + return } - } else { - setAuthState(prev => ({ - ...prev, - isAuthenticated: false, - isLoading: false, - session: null, - })) } + } catch { + // Fall through to the unauthenticated state. } + + setAuthState(prev => ({ + ...prev, + isAuthenticated: false, + isLoading: false, + hasStoredWorldID, + session: null, + })) } + checkSession() - }, [authState.isInstalled, authState.isInitialized]) + }, [authState.isInitialized]) const augmentSession = async (key: string, data: object | null): Promise<{ success: boolean }> => { - const res = await fetch(`/api/miniauth/augment`, { + const res = await fetch('/api/miniauth/augment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key, data }) }) - if(res.ok) { - const s = await res.json() - setAuthState(prev => ({ ...prev, session: s })) + if (res.ok) { + const session = await res.json() as Session | null + setAuthState(prev => ({ + ...prev, + hasStoredWorldID: syncStoredWorldIdState(session), + session, + })) } return { success: res.ok } } + const forgetWorldID = async () => { + clearStoredWorldIdSessionId(options0) + setAuthState(prev => ({ + ...prev, + hasStoredWorldID: false, + worldIdConnectUri: null, + worldIdError: null, + worldIdStatus: 'idle', + })) + return { success: true } + } + const signOut = async () => { setAuthState(prev => ({ ...prev, isLoading: true, - isAuthenticated: false, - session: null + isAuthenticated: false, + session: null, + worldIdConnectUri: null, + worldIdError: null, + worldIdStatus: 'idle', })) - // Delete the session cookie + document.cookie = `${options0.cookieSessionName}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT` + let success = false try { - const res = await fetch(`/api/miniauth/logout`, { + const res = await fetch('/api/miniauth/logout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }) success = res.ok } catch { - // Pass + // Ignore logout fetch errors. } + setAuthState(prev => ({ ...prev, - isLoading: false + isLoading: false, + hasStoredWorldID: Boolean(readStoredWorldIdSessionId(options0)), })) + return { success } } - const signInWorldID = async (params: { state?: string, nonce?: string } | null = null): Promise<{ success: false, error: string }> => { + const signInWorldID = async (): Promise => { setAuthState(prev => ({ ...prev, isLoading: true, + worldIdStatus: 'loading', + worldIdConnectUri: null, + worldIdError: null, })) + try { - const nonce = params?.nonce || crypto.randomUUID() - const urlParams = new URLSearchParams({ - redirect_uri: process.env.NEXT_PUBLIC_WLD_REDIRECT_URI || '', - response_type: 'code', - response_mode: 'query', - nonce, - scope: 'openid profile email', - client_id: process.env.NEXT_PUBLIC_WLD_CLIENT_ID || '' + const rpContextResponse = await fetch('/api/miniauth/worldid/rp-context', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({}), }) - if(params?.state) { - urlParams.append('state', params.state) + + if (!rpContextResponse.ok) { + const error = await getErrorMessageFromResponse(rpContextResponse, errors.ERR_WORLD_ID_CONFIG) + setAuthState(prev => ({ + ...prev, + isLoading: false, + worldIdStatus: 'error', + worldIdError: error, + })) + return { success: false, error } } - const location = `https://id.worldcoin.org/authorize?${urlParams.toString()}` - window.location.href = location - // Dead - client will redirect - } catch { - // Pass + + const worldIdConfig = await rpContextResponse.json() as WorldIdRpContextResponse + const appId = worldIdConfig.appId || getPublicWorldIdAppId() + if (!appId) { + setAuthState(prev => ({ + ...prev, + isLoading: false, + worldIdStatus: 'error', + worldIdError: errors.ERR_WORLD_ID_CONFIG, + })) + return { success: false, error: errors.ERR_WORLD_ID_CONFIG } + } + + const storedSessionId = readStoredWorldIdSessionId(options0) || undefined + const request = await createWorldIdSessionRequest({ + app_id: appId, + rp_context: worldIdConfig.rpContext, + environment: worldIdConfig.environment, + }, worldIdConfig.action, storedSessionId) + + const connectorURI = request.connectorURI || null + setAuthState(prev => ({ + ...prev, + worldIdStatus: connectorURI ? 'awaiting_connection' : 'awaiting_confirmation', + worldIdConnectUri: connectorURI, + })) + + if (authState.isInstalled && connectorURI) { + window.location.href = connectorURI + } + + const completion = await waitForWorldIdCompletion(request) + if (!completion.success) { + setAuthState(prev => ({ + ...prev, + isLoading: false, + worldIdStatus: 'error', + worldIdConnectUri: null, + worldIdError: completion.error, + })) + return { success: false, error: completion.error } + } + + if (!isConfirmedWorldIdResult(completion.result)) { + setAuthState(prev => ({ + ...prev, + isLoading: false, + worldIdStatus: 'error', + worldIdConnectUri: null, + worldIdError: errors.ERR_WORLD_ID_INVALID_RESPONSE, + })) + return { success: false, error: errors.ERR_WORLD_ID_INVALID_RESPONSE } + } + + const verifyResponse = await fetch('/api/miniauth/worldid/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(completion.result), + }) + + if (!verifyResponse.ok) { + const error = await getErrorMessageFromResponse(verifyResponse, errors.ERR_WORLD_ID_FAILED) + setAuthState(prev => ({ + ...prev, + isLoading: false, + worldIdStatus: 'error', + worldIdConnectUri: null, + worldIdError: error, + })) + return { success: false, error } + } + + const session = await verifyResponse.json() as Session | null + if (!session?.worldId?.sessionId) { + setAuthState(prev => ({ + ...prev, + isLoading: false, + worldIdStatus: 'error', + worldIdConnectUri: null, + worldIdError: errors.ERR_WORLD_ID_INVALID_RESPONSE, + })) + return { success: false, error: errors.ERR_WORLD_ID_INVALID_RESPONSE } + } + + writeStoredWorldIdSessionId(options0, session.worldId.sessionId) + setAuthState(prev => ({ + ...prev, + isAuthenticated: true, + isLoading: false, + hasStoredWorldID: true, + session, + worldIdStatus: 'success', + worldIdConnectUri: null, + worldIdError: null, + })) + return { success: true } + } catch (error) { + const message = getErrorMessageFromUnknown(error, errors.ERR_FAILED_EXCEPTION) + setAuthState(prev => ({ + ...prev, + isLoading: false, + worldIdStatus: 'error', + worldIdConnectUri: null, + worldIdError: message, + })) + return { success: false, error: message } } - setAuthState(prev => ({ - ...prev, - isLoading: false, - })) - return { success: false, error: errors.ERR_FAILED_TO_REDIRECT } } const signInWallet = async () => { @@ -176,7 +433,7 @@ export const WorldAuthProvider = ({ options, children }: { options?: WorldAuthOp isLoading: true, })) try { - const res = await fetch(`/api/miniauth/nonce`) + const res = await fetch('/api/miniauth/nonce') const { nonce } = await res.json() const { finalPayload } = await MiniKit.commandsAsync.walletAuth({ @@ -204,13 +461,14 @@ export const WorldAuthProvider = ({ options, children }: { options?: WorldAuthOp }), }) - const session = await response.json() + const session = await response.json() as Session | null if (session) { setAuthState(prev => ({ ...prev, isAuthenticated: true, isLoading: false, + hasStoredWorldID: syncStoredWorldIdState(session), session, })) return { success: true } @@ -219,16 +477,16 @@ export const WorldAuthProvider = ({ options, children }: { options?: WorldAuthOp setAuthState(prev => ({ ...prev, isAuthenticated: false, - session: null, isLoading: false, + session: null, })) return { success: false, error: 'sign-in failed' } } catch { setAuthState(prev => ({ ...prev, isAuthenticated: false, - session: null, isLoading: false, + session: null, })) return { success: false, error: errors.ERR_FAILED_EXCEPTION } } @@ -247,13 +505,13 @@ export const WorldAuthProvider = ({ options, children }: { options?: WorldAuthOp const getLocation = async (force = false): Promise => { - const validUntil = new Date(2099, 0, 1).toISOString() // TODO: should that expire? + const validUntil = new Date(2099, 0, 1).toISOString() if (!authState.session) { return { success: false, error: 'not authenticated', validUntil } } try { - const location = authState.session?.extra?.location as MyLocation | undefined - const now = new Date(); + const location = authState.session.extra?.location as MyLocation | undefined + const now = new Date() if ( location && typeof location.latitude === 'number' && @@ -266,7 +524,7 @@ export const WorldAuthProvider = ({ options, children }: { options?: WorldAuthOp latitude: location.latitude, longitude: location.longitude, validUntil: location.validUntil, - }; + } } const position = await new Promise((resolve, reject) => { @@ -276,44 +534,38 @@ export const WorldAuthProvider = ({ options, children }: { options?: WorldAuthOp } navigator.geolocation.getCurrentPosition(resolve, reject) }) - - const validUntil = new Date(now.getTime() + options0.locationMaxAge * 1000).toISOString() + const validUntil0 = new Date(now.getTime() + options0.locationMaxAge * 1000).toISOString() const locationData = { success: true, latitude: position.coords.latitude, longitude: position.coords.longitude, - validUntil, + validUntil: validUntil0, } - await augmentSession('location', locationData) - - return { - success: true, - latitude: position.coords.latitude, - longitude: position.coords.longitude, - validUntil, - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - const errorMessage = error?.message || 'Unknown error' + return locationData + } catch { const locationData = { success: false, - error: errorMessage, - validUntil - } - - await augmentSession('location', locationData) - - return { - success: false, - error: errorMessage, + error: 'failed to get location', validUntil, } + await augmentSession('location', locationData) + return locationData } } return ( - + {children} ) @@ -325,4 +577,4 @@ export const useWorldAuth = () => { throw new Error('useWorldAuth must be used within a WorldAuthProvider') } return context -} \ No newline at end of file +} diff --git a/next-world-auth/src/types.ts b/next-world-auth/src/types.ts index a6fb156..d33e77f 100644 --- a/next-world-auth/src/types.ts +++ b/next-world-auth/src/types.ts @@ -1,4 +1,22 @@ +export type WorldIdProtocolVersion = '4.0' + +export type WorldIdSession = { + sessionId: string + identifiers: string[] + action: string + verifiedAt: string + protocolVersion: WorldIdProtocolVersion +} + +export type WorldIdStatus = + | 'idle' + | 'loading' + | 'awaiting_connection' + | 'awaiting_confirmation' + | 'success' + | 'error' + // Session export type Session = { isAuthenticatedWallet: boolean @@ -6,11 +24,13 @@ export type Session = { isOrbVerified: boolean user: User extra: Extra + worldId?: WorldIdSession } export type User = { walletAddress?: string username?: string + // Deprecated: World ID 4 uses `session.worldId.sessionId` for continuity. appWorldID?: string } diff --git a/next-world-auth/src/world-id.ts b/next-world-auth/src/world-id.ts new file mode 100644 index 0000000..b7aa843 --- /dev/null +++ b/next-world-auth/src/world-id.ts @@ -0,0 +1,75 @@ +import { IDKit, type ConstraintNode, type IDKitRequest, type IDKitResult, type IDKitSessionConfig, type RpContext } from '@worldcoin/idkit-core' + +export const WORLD_ID_CREDENTIALS = ['proof_of_human', 'passport', 'mnc', 'face'] as const + +export type WorldIdEnvironment = 'production' | 'staging' + +export type WorldIdRpContextResponse = { + appId: `app_${string}` + action: string + environment: WorldIdEnvironment + rpContext: RpContext +} + +type RuntimeSessionBuilderConfig = { + type: 'request' | 'session' | 'proveSession' + app_id: `app_${string}` + action?: string + session_id?: string + rp_context: RpContext + action_description?: string + bridge_url?: string + return_to?: string + allow_legacy_proofs?: boolean + override_connect_base_url?: string + environment?: WorldIdEnvironment +} + +type RuntimeSessionBuilder = { + config: RuntimeSessionBuilderConfig + constraints: (constraints: ConstraintNode) => Promise +} + +export const WORLD_ID_CONSTRAINTS: ConstraintNode = { + enumerate: WORLD_ID_CREDENTIALS.map((type) => ({ type })), +} + +export const createWorldIdSessionRequest = ( + config: IDKitSessionConfig, + bootstrapAction: string, + sessionId?: string +) => { + const builder = IDKit.request({ + app_id: config.app_id, + action: bootstrapAction, + rp_context: config.rp_context, + allow_legacy_proofs: false, + action_description: config.action_description, + bridge_url: config.bridge_url, + return_to: config.return_to, + override_connect_base_url: config.override_connect_base_url, + environment: config.environment, + }) as unknown as RuntimeSessionBuilder + + builder.config = { + type: sessionId ? 'proveSession' : 'session', + app_id: config.app_id, + session_id: sessionId, + rp_context: config.rp_context, + action_description: config.action_description, + bridge_url: config.bridge_url, + return_to: config.return_to, + override_connect_base_url: config.override_connect_base_url, + environment: config.environment, + } + + return builder.constraints(WORLD_ID_CONSTRAINTS) +} + +export const getWorldIdIdentifiers = (identifiers: string[]) => [...new Set(identifiers.filter(Boolean))] + +export const isWorldIdSessionResult = (result: IDKitResult): result is IDKitResult & { session_id: string } => { + return 'session_id' in result && typeof result.session_id === 'string' && result.session_id.length > 0 +} + +export const isOrbVerifiedIdentifier = (identifier: string) => identifier === 'proof_of_human' || identifier === 'orb'