From d1fb6d355e975c1d1d63085d30600af6e8fbf671 Mon Sep 17 00:00:00 2001 From: Martin Domajnko <35891136+martines3000@users.noreply.github.com> Date: Sat, 2 Mar 2024 21:05:26 +0100 Subject: [PATCH] feat: migrate planetscale to supabase (#572) --- .changeset/twenty-turtles-approve.md | 5 + packages/dapp/.env.example | 4 +- packages/dapp/package.json | 3 - packages/dapp/prisma/schema.prisma | 15 - .../page.tsx | 8 +- packages/dapp/src/app/[locale]/app/layout.tsx | 4 +- .../src/app/api/encrypted-session/route.ts | 118 ++++++ .../src/app/api/qr-code-session/[id]/route.ts | 151 ------- .../src/components/AppBottomBar/index.tsx | 2 +- .../ConnectionModal/CreateConnectionModal.tsx | 90 ++++- .../ChooseDeviceView.tsx | 0 .../ConnectDeviceView.tsx | 110 ++--- .../CredentialView.tsx | 10 +- .../ScanQRCodeView.tsx | 65 ++- .../StartFlowView/CredentialOfferView.tsx | 8 +- .../StartFlowView/OIDCAuthView.tsx | 8 +- .../StartFlowView/PolygonAuthView.tsx | 8 +- .../StartFlowView/index.tsx | 10 +- .../index.tsx | 72 ++-- .../EncryptedSessionProvider/index.tsx | 202 ++++++++++ .../dapp/src/components/MenuPopover/index.tsx | 2 +- .../QRCodeSessionProvider/index.tsx | 193 --------- packages/dapp/src/messages/en.json | 12 +- .../dapp/src/stores/encryptedSessionStore.ts | 67 ++++ packages/dapp/src/stores/index.ts | 2 +- packages/dapp/src/stores/sessionStore.ts | 56 --- packages/dapp/src/utils/prisma.ts | 9 - .../dapp/src/utils/supabase/database.types.ts | 32 ++ pnpm-lock.yaml | 377 +++++++++++++----- 29 files changed, 960 insertions(+), 683 deletions(-) create mode 100644 .changeset/twenty-turtles-approve.md delete mode 100644 packages/dapp/prisma/schema.prisma rename packages/dapp/src/app/[locale]/app/(public)/{qr-code-session => encrypted-session}/page.tsx (63%) create mode 100644 packages/dapp/src/app/api/encrypted-session/route.ts delete mode 100644 packages/dapp/src/app/api/qr-code-session/[id]/route.ts rename packages/dapp/src/components/{QRSessionDisplay => EncryptedSessionDisplay}/ChooseDeviceView.tsx (100%) rename packages/dapp/src/components/{QRSessionDisplay => EncryptedSessionDisplay}/ConnectDeviceView.tsx (65%) rename packages/dapp/src/components/{QRSessionDisplay => EncryptedSessionDisplay}/CredentialView.tsx (95%) rename packages/dapp/src/components/{QRSessionDisplay => EncryptedSessionDisplay}/ScanQRCodeView.tsx (79%) rename packages/dapp/src/components/{QRSessionDisplay => EncryptedSessionDisplay}/StartFlowView/CredentialOfferView.tsx (95%) rename packages/dapp/src/components/{QRSessionDisplay => EncryptedSessionDisplay}/StartFlowView/OIDCAuthView.tsx (94%) rename packages/dapp/src/components/{QRSessionDisplay => EncryptedSessionDisplay}/StartFlowView/PolygonAuthView.tsx (94%) rename packages/dapp/src/components/{QRSessionDisplay => EncryptedSessionDisplay}/StartFlowView/index.tsx (86%) rename packages/dapp/src/components/{QRSessionDisplay => EncryptedSessionDisplay}/index.tsx (85%) create mode 100644 packages/dapp/src/components/EncryptedSessionProvider/index.tsx delete mode 100644 packages/dapp/src/components/QRCodeSessionProvider/index.tsx create mode 100644 packages/dapp/src/stores/encryptedSessionStore.ts delete mode 100644 packages/dapp/src/stores/sessionStore.ts delete mode 100644 packages/dapp/src/utils/prisma.ts diff --git a/.changeset/twenty-turtles-approve.md b/.changeset/twenty-turtles-approve.md new file mode 100644 index 000000000..c29b518f3 --- /dev/null +++ b/.changeset/twenty-turtles-approve.md @@ -0,0 +1,5 @@ +--- +'@blockchain-lab-um/dapp': minor +--- + +Migrate Planetscale to Supabase diff --git a/packages/dapp/.env.example b/packages/dapp/.env.example index 3ec79d0fc..75576437f 100644 --- a/packages/dapp/.env.example +++ b/packages/dapp/.env.example @@ -10,9 +10,7 @@ SEPOLIA_RPC_URL= IPFS_GATEWAY= POLYGON_RPC_URL= POLYGON_MUMBAI_RPC_URL= - -# Prisma -DATABASE_URL= += # Masca version NEXT_PUBLIC_MASCA_VERSION=v1.1.0 diff --git a/packages/dapp/package.json b/packages/dapp/package.json index c5976e96b..5f4b20c1e 100644 --- a/packages/dapp/package.json +++ b/packages/dapp/package.json @@ -11,7 +11,6 @@ "dev": "cross-env next dev", "dev:local": "cross-env USE_LOCAL='true' next dev", "docker:build": "docker build . -t blockchain-lab-um/dapp:latest", - "postinstall": "pnpm prisma generate --schema=./prisma/schema.prisma", "lint": "pnpm lint:next && pnpm lint:tsc && pnpm lint:prettier && pnpm lint:stylelint", "lint:fix": "next lint . --fix && prettier . --write", "lint:next": "next lint", @@ -28,7 +27,6 @@ "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", "@nextui-org/react": "^2.2.9", - "@prisma/client": "^5.7.0", "@radix-ui/react-toast": "^1.1.5", "@react-oauth/google": "^0.11.1", "@supabase/supabase-js": "^2.38.5", @@ -65,7 +63,6 @@ "next-sitemap": "^4.2.3", "next-themes": "^0.2.1", "pino-pretty": "^10.3.1", - "prisma": "^5.7.0", "qrcode.react": "^3.1.0", "qs": "^6.11.2", "react": "18.2.0", diff --git a/packages/dapp/prisma/schema.prisma b/packages/dapp/prisma/schema.prisma deleted file mode 100644 index e2ad86a3e..000000000 --- a/packages/dapp/prisma/schema.prisma +++ /dev/null @@ -1,15 +0,0 @@ -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "mysql" - url = env("DATABASE_URL") - relationMode = "prisma" -} - -model sessions { - id String @id @default(uuid()) - data String? @db.VarChar(512) - iv String? @db.VarChar(128) -} diff --git a/packages/dapp/src/app/[locale]/app/(public)/qr-code-session/page.tsx b/packages/dapp/src/app/[locale]/app/(public)/encrypted-session/page.tsx similarity index 63% rename from packages/dapp/src/app/[locale]/app/(public)/qr-code-session/page.tsx rename to packages/dapp/src/app/[locale]/app/(public)/encrypted-session/page.tsx index a32019760..4a42a6042 100644 --- a/packages/dapp/src/app/[locale]/app/(public)/qr-code-session/page.tsx +++ b/packages/dapp/src/app/[locale]/app/(public)/encrypted-session/page.tsx @@ -1,17 +1,17 @@ import { Metadata } from 'next'; -import QRCodeSessionDisplay from '@/components/QRSessionDisplay'; +import EncryptedSessionDisplay from '@/components/EncryptedSessionDisplay'; export const metadata: Metadata = { - title: 'QR Code Session', - description: 'QR Code Session for Masca.', + title: 'Encrypted Session', + description: 'Encrypted Session for Masca.', }; export default function Page() { return (
- +
); diff --git a/packages/dapp/src/app/[locale]/app/layout.tsx b/packages/dapp/src/app/[locale]/app/layout.tsx index 9b439cfbe..32af1ed85 100644 --- a/packages/dapp/src/app/[locale]/app/layout.tsx +++ b/packages/dapp/src/app/[locale]/app/layout.tsx @@ -3,8 +3,8 @@ import clsx from 'clsx'; import AppBottomBar from '@/components/AppBottomBar'; import AppNavbar from '@/components/AppNavbar'; import { CookiesProvider } from '@/components/CookiesProvider'; +import { EncryptedSessionProvider } from '@/components/EncryptedSessionProvider'; import MascaProvider from '@/components/MascaProvider'; -import QRCodeSessionProvider from '@/components/QRCodeSessionProvider'; import { SignInModal } from '@/components/SignInModal'; import ToastWrapper from '@/components/ToastWrapper'; import WagmiProviderWrapper from '@/components/WagmiProvider'; @@ -30,7 +30,7 @@ export default async function AppLayout({ - + diff --git a/packages/dapp/src/app/api/encrypted-session/route.ts b/packages/dapp/src/app/api/encrypted-session/route.ts new file mode 100644 index 000000000..2bbc5b35a --- /dev/null +++ b/packages/dapp/src/app/api/encrypted-session/route.ts @@ -0,0 +1,118 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createClient } from '@supabase/supabase-js'; +import jwt from 'jsonwebtoken'; + +import { Database } from '@/utils/supabase/database.types'; + +const CORS_HEADERS = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', +}; + +export async function GET(request: NextRequest) { + try { + const token = request.headers.get('Authorization')?.replace('Bearer ', ''); + + if (!token) { + return new NextResponse('Unauthorized', { + status: 401, + headers: { + ...CORS_HEADERS, + }, + }); + } + + const user = jwt.verify(token, process.env.SUPABASE_JWT_SECRET!) as { + sub: string; + address: string; + aud: string; + role: string; + iat: number; + exp: number; + }; + + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SECRET_KEY! + ); + + const { data: selectData, error: selectError } = await supabase + .from('encrypted_sessions') + .select('id') + .eq('user_id', user.sub); + + if (selectError) { + return new NextResponse('Internal Server Error', { + status: 500, + headers: { + ...CORS_HEADERS, + }, + }); + } + + // If session is found delete it + if (selectData.length !== 0) { + const { error: deleteError } = await supabase + .from('encrypted_sessions') + .delete() + .eq('user_id', user.sub); + + if (deleteError) { + return new NextResponse('Internal Server Error', { + status: 500, + headers: { + ...CORS_HEADERS, + }, + }); + } + } + + // Create a new session + const { data: insertData, error: insertError } = await supabase + .from('encrypted_sessions') + .insert({ + user_id: user.sub, + }) + .select() + .limit(1) + .single(); + + if (insertError || !insertData) { + return new NextResponse('Internal Server Error', { + status: 500, + headers: { + ...CORS_HEADERS, + }, + }); + } + + return NextResponse.json( + { + sessionId: insertData.id, + }, + { + status: 201, + headers: { + ...CORS_HEADERS, + }, + } + ); + } catch (error) { + return new NextResponse('Internal Server Error', { + status: 500, + headers: { + ...CORS_HEADERS, + }, + }); + } +} + +export async function OPTIONS() { + return new NextResponse(null, { + status: 200, + headers: { + ...CORS_HEADERS, + }, + }); +} diff --git a/packages/dapp/src/app/api/qr-code-session/[id]/route.ts b/packages/dapp/src/app/api/qr-code-session/[id]/route.ts deleted file mode 100644 index bbd60a888..000000000 --- a/packages/dapp/src/app/api/qr-code-session/[id]/route.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; - -import { prisma } from '@/utils/prisma'; - -const CORS_HEADERS = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type', -}; - -export async function GET( - _: NextRequest, - { params: { id } }: { params: { id: string } } -) { - if (!id) { - return NextResponse.json( - { error_description: 'Missing sessionId parameter' }, - { - status: 400, - headers: { - ...CORS_HEADERS, - }, - } - ); - } - - // Get session from database - const session = await prisma.sessions.findUnique({ - where: { - id, - }, - }); - - if (!session) { - return NextResponse.json( - { error_description: 'Session not found' }, - { - status: 404, - headers: { - ...CORS_HEADERS, - }, - } - ); - } - - await prisma.sessions.delete({ - where: { - id, - }, - }); - - // Get session data - return NextResponse.json( - { data: session.data, iv: session.iv }, - { - status: 200, - headers: { - ...CORS_HEADERS, - }, - } - ); -} - -export async function POST( - request: NextRequest, - { params: { id } }: { params: { id: string } } -) { - try { - const jsonData = await request.json(); - - const { data, iv } = jsonData; - if (!id) { - return NextResponse.json( - { error_description: 'Missing sessionId' }, - { - status: 400, - headers: { - ...CORS_HEADERS, - }, - } - ); - } - - if (!data) { - return NextResponse.json( - { error_description: "Missing 'data' parameter" }, - { - status: 400, - headers: { - ...CORS_HEADERS, - }, - } - ); - } - - if (!iv) { - return NextResponse.json( - { error_description: "Missing 'iv' parameter" }, - { - status: 400, - headers: { - ...CORS_HEADERS, - }, - } - ); - } - - // Put session data in database - await prisma.sessions.upsert({ - where: { - id, - }, - update: { - data, - iv, - }, - create: { - id, - data, - iv, - }, - }); - - return new NextResponse(null, { - status: 200, - headers: { - ...CORS_HEADERS, - }, - }); - } catch (e) { - console.log(e); - return NextResponse.json( - { error_description: 'Bad request' }, - { - status: 400, - headers: { - ...CORS_HEADERS, - }, - } - ); - } -} - -export async function OPTIONS() { - return new NextResponse(null, { - status: 200, - headers: { - ...CORS_HEADERS, - }, - }); -} diff --git a/packages/dapp/src/components/AppBottomBar/index.tsx b/packages/dapp/src/components/AppBottomBar/index.tsx index d69b46491..1e21fe896 100644 --- a/packages/dapp/src/components/AppBottomBar/index.tsx +++ b/packages/dapp/src/components/AppBottomBar/index.tsx @@ -33,7 +33,7 @@ const OTHER_LINKS = [ }, { name: 'qr-scanner', - href: '/app/qr-code-session', + href: '/app/encrypted-session', requiresConnection: false, }, ]; diff --git a/packages/dapp/src/components/ConnectionModal/CreateConnectionModal.tsx b/packages/dapp/src/components/ConnectionModal/CreateConnectionModal.tsx index 61c16e9c9..1ebb2c66e 100644 --- a/packages/dapp/src/components/ConnectionModal/CreateConnectionModal.tsx +++ b/packages/dapp/src/components/ConnectionModal/CreateConnectionModal.tsx @@ -1,11 +1,18 @@ 'use client'; import { useEffect, useState } from 'react'; -import { Modal, ModalBody, ModalContent, ModalHeader } from '@nextui-org/react'; +import { + Modal, + ModalBody, + ModalContent, + ModalHeader, + Spinner, +} from '@nextui-org/react'; import { useTranslations } from 'next-intl'; import { QRCodeSVG } from 'qrcode.react'; -import { useSessionStore } from '@/stores'; +import { useEncryptedSessionStore, useToastStore } from '@/stores'; +import { useAuthStore } from '@/stores/authStore'; interface CreateConnectionModalProps { isOpen: boolean; @@ -17,19 +24,58 @@ const CreateConnectionModal = ({ setOpen, }: CreateConnectionModalProps) => { const t = useTranslations('CreateConnectionModal'); + + // Local state const [connectionData, setConnectionData] = useState(null); - const { request, session, changeRequest, changeSession } = useSessionStore( - (state) => ({ - request: state.request, - session: state.session, - changeRequest: state.changeRequest, - changeSession: state.changeSession, - }) - ); - const createSession = async (): Promise => { + // Global state + const { token, isSignedIn } = useAuthStore((state) => ({ + token: state.token, + isSignedIn: state.isSignedIn, + })); + + const { + changeSession, + changeSessionId, + changeConnected, + changeHasCamera, + changeDeviceType, + } = useEncryptedSessionStore((state) => ({ + changeSession: state.changeSession, + changeSessionId: state.changeSessionId, + changeConnected: state.changeConnected, + changeHasCamera: state.changeHasCamera, + changeDeviceType: state.changeDeviceType, + })); + + const createSession = async (): Promise => { + if (!token || !isSignedIn) { + return null; + } + // Create session ID - const sessionId = crypto.randomUUID(); + const createSessionResult = await fetch('/api/encrypted-session', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }); + + if (!createSessionResult.ok) { + setTimeout(() => { + useToastStore.setState({ + open: true, + title: t('session-create-failed'), + type: 'normal', + loading: true, + link: null, + }); + }, 200); + return null; + } + + const { sessionId } = await createSessionResult.json(); const key = await crypto.subtle.generateKey( { @@ -48,13 +94,13 @@ const CreateConnectionModal = ({ // Set global session data changeSession({ - sessionId, key, exp, - connected: false, - hasCamera: false, - deviceType: 'primary', }); + changeConnected(false); + changeHasCamera(false); + changeDeviceType('primary'); + changeSessionId(sessionId); // Create session return JSON.stringify({ @@ -67,12 +113,13 @@ const CreateConnectionModal = ({ useEffect(() => { if (isOpen) { createSession() - .then((data) => { - console.log(data); - setConnectionData(data); - }) + .then((data) => setConnectionData(data)) .catch(console.error); } + + return () => { + setConnectionData(null); + }; }, [isOpen]); return ( @@ -106,6 +153,9 @@ const CreateConnectionModal = ({ width={300} /> )} + {!connectionData && ( + + )} diff --git a/packages/dapp/src/components/QRSessionDisplay/ChooseDeviceView.tsx b/packages/dapp/src/components/EncryptedSessionDisplay/ChooseDeviceView.tsx similarity index 100% rename from packages/dapp/src/components/QRSessionDisplay/ChooseDeviceView.tsx rename to packages/dapp/src/components/EncryptedSessionDisplay/ChooseDeviceView.tsx diff --git a/packages/dapp/src/components/QRSessionDisplay/ConnectDeviceView.tsx b/packages/dapp/src/components/EncryptedSessionDisplay/ConnectDeviceView.tsx similarity index 65% rename from packages/dapp/src/components/QRSessionDisplay/ConnectDeviceView.tsx rename to packages/dapp/src/components/EncryptedSessionDisplay/ConnectDeviceView.tsx index 2a468e7fc..4d1fc438c 100644 --- a/packages/dapp/src/components/QRSessionDisplay/ConnectDeviceView.tsx +++ b/packages/dapp/src/components/EncryptedSessionDisplay/ConnectDeviceView.tsx @@ -1,36 +1,59 @@ import React, { useEffect, useState } from 'react'; -import { uint8ArrayToHex } from '@blockchain-lab-um/masca-connector'; +import { createClient as createSupbaseClient } from '@supabase/supabase-js'; import { useTranslations } from 'next-intl'; import { useAccount } from 'wagmi'; import Button from '@/components/Button'; import CreateConnectionModal from '@/components/ConnectionModal/CreateConnectionModal'; import ScanQRCodeModal from '@/components/ScanQRCodeModal/ScanQRCodeModal'; -import { useSessionStore, useToastStore } from '@/stores'; +import { Database } from '@/utils/supabase/database.types'; +import { useEncryptedSessionStore, useToastStore } from '@/stores'; +import { useAuthStore } from '@/stores/authStore'; export const ConnectDeviceView = () => { const t = useTranslations('ConnectDeviceView'); + + // Local state + const [isModalOpen, setIsModalOpen] = useState(false); + const [isConnectionModalOpen, setIsConnectionModalOpen] = useState(false); + + // Global state + const { isSignedIn, changeIsSignInModalOpen } = useAuthStore((state) => ({ + isSignedIn: state.isSignedIn, + changeIsSignInModalOpen: state.changeIsSignInModalOpen, + })); + const { isConnected } = useAccount(); - const { session, changeSession } = useSessionStore((state) => ({ + const { + connected, + deviceType, + hasCamera, + changeSession, + changeConnected, + changeSessionId, + } = useEncryptedSessionStore((state) => ({ session: state.session, + connected: state.connected, + deviceType: state.deviceType, + hasCamera: state.hasCamera, changeSession: state.changeSession, + changeConnected: state.changeConnected, + changeSessionId: state.changeSessionId, })); - const [isModalOpen, setIsModalOpen] = useState(false); - const [isConnectionModalOpen, setIsConnectionModalOpen] = useState(false); useEffect(() => { // Close connect QR modal if connection is established - if (session.connected && isModalOpen) { + if (connected && isModalOpen) { setIsModalOpen(false); } - }, [session.connected]); + }, [connected]); const onScanSuccessConnectionQRCode = async (decodedText: string, _: any) => { if (isConnectionModalOpen) { setIsConnectionModalOpen(false); } // Close if already connected - if (session.connected) return; + if (connected) return; try { const data = JSON.parse(decodedText); @@ -45,41 +68,27 @@ export const ConnectDeviceView = () => { ['encrypt', 'decrypt'] ); - changeSession({ - ...session, - sessionId: data.sessionId, - key: decryptionKey, - exp: data.exp, - connected: true, - }); + // Send data + const client = createSupbaseClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ); - // Encrypt data - const iv = crypto.getRandomValues(new Uint8Array(12)); + const { error } = await client + .from('encrypted_sessions') + .update({ + connected: true, + }) + .eq('id', data.sessionId); - const encodedText = new TextEncoder().encode('Created Connection'); + if (error) throw new Error('Failed to send data'); - const encryptedData = new Uint8Array( - await crypto.subtle.encrypt( - { - name: 'AES-GCM', - iv, - }, - decryptionKey, - encodedText - ) - ); - const response = await fetch(`/api/qr-code-session/${data.sessionId}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - data: uint8ArrayToHex(encryptedData), - iv: uint8ArrayToHex(iv), - }), + changeSession({ + key: decryptionKey, + exp: data.exp, }); - - if (!response.ok) throw new Error(); + changeConnected(true); + changeSessionId(data.sessionId); setTimeout(() => { useToastStore.setState({ @@ -91,6 +100,7 @@ export const ConnectDeviceView = () => { }); }, 200); } catch (e) { + console.log(e); setTimeout(() => { useToastStore.setState({ open: true, @@ -105,7 +115,7 @@ export const ConnectDeviceView = () => { return (
- {session.deviceType === 'primary' && !session.hasCamera && ( + {deviceType === 'primary' && !hasCamera && ( <> {isConnected && ( <> @@ -117,7 +127,16 @@ export const ConnectDeviceView = () => {
-
@@ -126,9 +145,9 @@ export const ConnectDeviceView = () => { {!isConnected &&
{t('connect')}
} )} - {session.hasCamera && ( + {hasCamera && ( <> - {session.deviceType === 'secondary' && ( + {deviceType === 'secondary' && ( <>
{t('start-secondary')}
@@ -138,13 +157,12 @@ export const ConnectDeviceView = () => { variant="primary" onClick={() => { // Reset session if already set + changeSessionId(null); changeSession({ - ...session, - sessionId: null, key: null, exp: null, - connected: false, }); + changeConnected(false); setIsConnectionModalOpen(true); }} > diff --git a/packages/dapp/src/components/QRSessionDisplay/CredentialView.tsx b/packages/dapp/src/components/EncryptedSessionDisplay/CredentialView.tsx similarity index 95% rename from packages/dapp/src/components/QRSessionDisplay/CredentialView.tsx rename to packages/dapp/src/components/EncryptedSessionDisplay/CredentialView.tsx index 44e1aadcd..6d6d98bfe 100644 --- a/packages/dapp/src/components/QRSessionDisplay/CredentialView.tsx +++ b/packages/dapp/src/components/EncryptedSessionDisplay/CredentialView.tsx @@ -8,7 +8,11 @@ import { useTranslations } from 'next-intl'; import Button from '@/components/Button'; import FormatedPanel from '@/components/CredentialDisplay/FormatedPanel'; import JsonPanel from '@/components/CredentialDisplay/JsonPanel'; -import { useMascaStore, useSessionStore, useToastStore } from '@/stores'; +import { + useEncryptedSessionStore, + useMascaStore, + useToastStore, +} from '@/stores'; interface CredentialViewProps { credential: VerifiableCredential; @@ -20,7 +24,9 @@ export const CredentialView = ({ scanNewCode, }: CredentialViewProps) => { const t = useTranslations('CredentialView'); - const changeRequest = useSessionStore((state) => state.changeRequest); + const changeRequest = useEncryptedSessionStore( + (state) => state.changeRequest + ); const mascaApi = useMascaStore((state) => state.mascaApi); diff --git a/packages/dapp/src/components/QRSessionDisplay/ScanQRCodeView.tsx b/packages/dapp/src/components/EncryptedSessionDisplay/ScanQRCodeView.tsx similarity index 79% rename from packages/dapp/src/components/QRSessionDisplay/ScanQRCodeView.tsx rename to packages/dapp/src/components/EncryptedSessionDisplay/ScanQRCodeView.tsx index a166944b0..a70454187 100644 --- a/packages/dapp/src/components/QRSessionDisplay/ScanQRCodeView.tsx +++ b/packages/dapp/src/components/EncryptedSessionDisplay/ScanQRCodeView.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { uint8ArrayToHex } from '@blockchain-lab-um/masca-connector'; +import { createClient as createSupbaseClient } from '@supabase/supabase-js'; import { Html5Qrcode } from 'html5-qrcode'; import { useTranslations } from 'next-intl'; import { useAccount } from 'wagmi'; @@ -7,7 +8,9 @@ import { useAccount } from 'wagmi'; import Button from '@/components/Button'; import ScanQRCodeModal from '@/components/ScanQRCodeModal/ScanQRCodeModal'; import UploadButton from '@/components/UploadButton'; -import { useSessionStore, useToastStore } from '@/stores'; +import { Database } from '@/utils/supabase/database.types'; +import { useToastStore } from '@/stores'; +import { useEncryptedSessionStore } from '@/stores/encryptedSessionStore'; import { useQRCodeStore } from '@/stores/qrCodeStore'; interface ScanQRCodeViewProps { @@ -18,22 +21,27 @@ export const ScanQRCodeView = ({ onQRCodeScanned }: ScanQRCodeViewProps) => { const t = useTranslations('ScanQRCodeView'); const { isConnected } = useAccount(); const [isQRCodeModalOpen, setIsQRCodeModalOpen] = useState(false); + const { sessionId, session, deviceType, hasCamera, isSessionConnect } = + useEncryptedSessionStore((state) => ({ + sessionId: state.sessionId, + session: state.session, + deviceType: state.deviceType, + hasCamera: state.hasCamera, + isSessionConnect: state.connected, + })); - const session = useSessionStore((state) => state.session); - - const changeRequestData = useQRCodeStore((state) => state.changeRequestData); + const changeRequestData = useQRCodeStore((state) => state.changeRequestData); // TODO: Where was this used const onScanSuccessQRCode = async (decodedText: string, _: any) => { // Same device - if (isConnected && session.deviceType === 'primary') { + if (isConnected && deviceType === 'primary') { changeRequestData(decodedText); return; } // Cross device (mobile <-> desktop) - if (!session.sessionId || !session.key || !session.exp) return; + if (!sessionId || !session.key || !session.exp) return; if (isQRCodeModalOpen) { - console.log('Closing QR Scan modal...'); setIsQRCodeModalOpen(false); } let data: string | null = null; @@ -82,34 +90,21 @@ export const ScanQRCodeView = ({ onQRCodeScanned }: ScanQRCodeViewProps) => { ); // Send data - const response = await fetch( - `/api/qr-code-session/${session.sessionId}`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - data: uint8ArrayToHex(encryptedData), - iv: uint8ArrayToHex(iv), - }), - } + const client = createSupbaseClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ); - if (!response.ok) throw new Error(); + const { error } = await client + .from('encrypted_sessions') + .update({ + data: uint8ArrayToHex(encryptedData), + iv: uint8ArrayToHex(iv), + }) + .eq('id', sessionId); - setTimeout(() => { - useToastStore.setState({ - open: true, - title: t('success'), - type: 'success', - loading: false, - link: null, - }); - }, 200); - onQRCodeScanned(); + if (error) throw new Error('Failed to send data'); } catch (e) { - console.log('error', e); setTimeout(() => { useToastStore.setState({ open: true, @@ -147,9 +142,9 @@ export const ScanQRCodeView = ({ onQRCodeScanned }: ScanQRCodeViewProps) => { return (
- {session.connected && ( + {isSessionConnect && ( <> - {session.deviceType === 'primary' && session.hasCamera && ( + {deviceType === 'primary' && hasCamera && (
{t('scan-upload')} @@ -166,7 +161,7 @@ export const ScanQRCodeView = ({ onQRCodeScanned }: ScanQRCodeViewProps) => {
)} - {session.deviceType === 'secondary' && session.hasCamera && ( + {deviceType === 'secondary' && hasCamera && (
@@ -189,7 +184,7 @@ export const ScanQRCodeView = ({ onQRCodeScanned }: ScanQRCodeViewProps) => { )} )} - {session.connected && !session.hasCamera && ( + {isSessionConnect && !hasCamera && ( <>
diff --git a/packages/dapp/src/components/QRSessionDisplay/StartFlowView/CredentialOfferView.tsx b/packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/CredentialOfferView.tsx similarity index 95% rename from packages/dapp/src/components/QRSessionDisplay/StartFlowView/CredentialOfferView.tsx rename to packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/CredentialOfferView.tsx index deda0e345..e18e8d9f5 100644 --- a/packages/dapp/src/components/QRSessionDisplay/StartFlowView/CredentialOfferView.tsx +++ b/packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/CredentialOfferView.tsx @@ -5,7 +5,11 @@ import { useTranslations } from 'next-intl'; import { useAccount } from 'wagmi'; import Button from '@/components/Button'; -import { useMascaStore, useSessionStore, useToastStore } from '@/stores'; +import { + useEncryptedSessionStore, + useMascaStore, + useToastStore, +} from '@/stores'; interface CredentialOfferViewProps { onCredentialRecieved: (recievedCredential: VerifiableCredential) => void; @@ -15,7 +19,7 @@ export const CredentialOfferView = ({ onCredentialRecieved, }: CredentialOfferViewProps) => { const t = useTranslations('CredentialOfferView'); - const { request, changeRequest } = useSessionStore((state) => ({ + const { request, changeRequest } = useEncryptedSessionStore((state) => ({ request: state.request, changeRequest: state.changeRequest, })); diff --git a/packages/dapp/src/components/QRSessionDisplay/StartFlowView/OIDCAuthView.tsx b/packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/OIDCAuthView.tsx similarity index 94% rename from packages/dapp/src/components/QRSessionDisplay/StartFlowView/OIDCAuthView.tsx rename to packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/OIDCAuthView.tsx index 3f248cc2c..0c7deb053 100644 --- a/packages/dapp/src/components/QRSessionDisplay/StartFlowView/OIDCAuthView.tsx +++ b/packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/OIDCAuthView.tsx @@ -4,7 +4,11 @@ import { useTranslations } from 'next-intl'; import { useAccount } from 'wagmi'; import Button from '@/components/Button'; -import { useMascaStore, useSessionStore, useToastStore } from '@/stores'; +import { + useEncryptedSessionStore, + useMascaStore, + useToastStore, +} from '@/stores'; interface OIDCAuthViewProps { scanNewCode: () => void; @@ -12,7 +16,7 @@ interface OIDCAuthViewProps { export const OIDCAuthView = ({ scanNewCode }: OIDCAuthViewProps) => { const t = useTranslations('OIDCAuthView'); - const { request, changeRequest } = useSessionStore((state) => ({ + const { request, changeRequest } = useEncryptedSessionStore((state) => ({ request: state.request, changeRequest: state.changeRequest, })); diff --git a/packages/dapp/src/components/QRSessionDisplay/StartFlowView/PolygonAuthView.tsx b/packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/PolygonAuthView.tsx similarity index 94% rename from packages/dapp/src/components/QRSessionDisplay/StartFlowView/PolygonAuthView.tsx rename to packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/PolygonAuthView.tsx index 56e66a2e6..e44b67ec2 100644 --- a/packages/dapp/src/components/QRSessionDisplay/StartFlowView/PolygonAuthView.tsx +++ b/packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/PolygonAuthView.tsx @@ -4,7 +4,11 @@ import { useTranslations } from 'next-intl'; import { useAccount } from 'wagmi'; import Button from '@/components/Button'; -import { useMascaStore, useSessionStore, useToastStore } from '@/stores'; +import { + useEncryptedSessionStore, + useMascaStore, + useToastStore, +} from '@/stores'; interface StartFlowViewProps { scanNewCode: () => void; @@ -12,7 +16,7 @@ interface StartFlowViewProps { export const PolygonAuthView = ({ scanNewCode }: StartFlowViewProps) => { const t = useTranslations('PolygonAuthView'); - const { request, changeRequest } = useSessionStore((state) => ({ + const { request, changeRequest } = useEncryptedSessionStore((state) => ({ request: state.request, session: state.session, changeRequest: state.changeRequest, diff --git a/packages/dapp/src/components/QRSessionDisplay/StartFlowView/index.tsx b/packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/index.tsx similarity index 86% rename from packages/dapp/src/components/QRSessionDisplay/StartFlowView/index.tsx rename to packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/index.tsx index 487e982ea..ed9578dbd 100644 --- a/packages/dapp/src/components/QRSessionDisplay/StartFlowView/index.tsx +++ b/packages/dapp/src/components/EncryptedSessionDisplay/StartFlowView/index.tsx @@ -4,7 +4,7 @@ import { useTranslations } from 'next-intl'; import { useAccount } from 'wagmi'; import Button from '@/components/Button'; -import { useSessionStore } from '@/stores'; +import { useEncryptedSessionStore } from '@/stores'; import { CredentialOfferView } from './CredentialOfferView'; import { OIDCAuthView } from './OIDCAuthView'; import { PolygonAuthView } from './PolygonAuthView'; @@ -19,16 +19,16 @@ export const StartFlowView = ({ onCredentialRecieved, }: StartFlowViewProps) => { const t = useTranslations('StartFlowView'); - const { request, session } = useSessionStore((state) => ({ + const { request, deviceType } = useEncryptedSessionStore((state) => ({ request: state.request, - session: state.session, + deviceType: state.deviceType, })); const { isConnected } = useAccount(); return (
- {isConnected && session.deviceType === 'primary' && ( + {isConnected && deviceType === 'primary' && ( <> {request.type === 'polygonAuth' && ( @@ -42,7 +42,7 @@ export const StartFlowView = ({ )} )} - {session.deviceType === 'secondary' && ( + {deviceType === 'secondary' && ( <>
{t('scanned')} diff --git a/packages/dapp/src/components/QRSessionDisplay/index.tsx b/packages/dapp/src/components/EncryptedSessionDisplay/index.tsx similarity index 85% rename from packages/dapp/src/components/QRSessionDisplay/index.tsx rename to packages/dapp/src/components/EncryptedSessionDisplay/index.tsx index 59086ec07..f32d8ac98 100644 --- a/packages/dapp/src/components/QRSessionDisplay/index.tsx +++ b/packages/dapp/src/components/EncryptedSessionDisplay/index.tsx @@ -9,15 +9,15 @@ import { useTranslations } from 'next-intl'; import { useAccount } from 'wagmi'; import Button from '@/components/Button'; -import { useSessionStore } from '@/stores'; +import { useEncryptedSessionStore } from '@/stores/encryptedSessionStore'; import { ChooseDeviceView } from './ChooseDeviceView'; import { ConnectDeviceView } from './ConnectDeviceView'; import { CredentialView } from './CredentialView'; import { ScanQRCodeView } from './ScanQRCodeView'; import { StartFlowView } from './StartFlowView'; -const QRSessionDisplay = () => { - const t = useTranslations('QRSessionDisplay'); +const EncryptedSessionDisplay = () => { + const t = useTranslations('EncryptedSessionDisplay'); const steps = useMemo( () => [ { @@ -48,20 +48,36 @@ const QRSessionDisplay = () => { const stepperInstance = useStepper({ steps }); const { isConnected } = useAccount(); - const { request, session, changeRequest, changeSession } = useSessionStore( - (state) => ({ - request: state.request, - session: state.session, - changeRequest: state.changeRequest, - changeSession: state.changeSession, - }) - ); + const { + request, + session, + + deviceType, + hasCamera, + connected, + changeRequest, + changeSession, + changeConnected, + changeDeviceType, + changeHasCamera, + } = useEncryptedSessionStore((state) => ({ + request: state.request, + session: state.session, + deviceType: state.deviceType, + hasCamera: state.hasCamera, + connected: state.connected, + changeRequest: state.changeRequest, + changeSession: state.changeSession, + changeConnected: state.changeConnected, + changeDeviceType: state.changeDeviceType, + changeHasCamera: state.changeHasCamera, + })); useEffect(() => { - if (session.connected && stepperInstance.state.currentStep === 1) { + if (connected && stepperInstance.state.currentStep === 1) { stepperInstance.setStep(2); } - }, [session.connected]); + }, [connected]); useEffect(() => { if ( @@ -77,11 +93,11 @@ const QRSessionDisplay = () => { }, [request]); useEffect(() => { - if (session.deviceType === null) { + if (deviceType === null) { stepperInstance.setStep(0); return; } - if (session.connected && session.exp && session.exp > Date.now()) { + if (connected && session.exp && session.exp > Date.now()) { stepperInstance.setStep(2); if (request.active) { stepperInstance.setStep(3); @@ -101,27 +117,21 @@ const QRSessionDisplay = () => { }; const onDeviceTypeSelected = ( - deviceType: 'primary' | 'secondary', - hasCamera: boolean + _deviceType: 'primary' | 'secondary', + _hasCamera: boolean ) => { - if (deviceType === 'primary' && hasCamera) { + if (_deviceType === 'primary' && _hasCamera) { changeSession({ - connected: true, - sessionId: null, key: null, exp: null, - deviceType, - hasCamera, }); - + changeConnected(true); + changeDeviceType(_deviceType); + changeHasCamera(_hasCamera); stepperInstance.setStep(2); } else { - changeSession({ - ...session, - deviceType, - hasCamera, - }); - + changeDeviceType(_deviceType); + changeHasCamera(_hasCamera); stepperInstance.nextStep(); } }; @@ -216,7 +226,7 @@ const QRSessionDisplay = () => { {steps[stepperInstance.state.currentStep].hasPreviousStep && (
{stepperInstance.state.currentStep === 1 && - session.connected && + connected && session.exp && session.exp > Date.now() && (
@@ -257,4 +267,4 @@ const QRSessionDisplay = () => { ); }; -export default QRSessionDisplay; +export default EncryptedSessionDisplay; diff --git a/packages/dapp/src/components/EncryptedSessionProvider/index.tsx b/packages/dapp/src/components/EncryptedSessionProvider/index.tsx new file mode 100644 index 000000000..ede1b146e --- /dev/null +++ b/packages/dapp/src/components/EncryptedSessionProvider/index.tsx @@ -0,0 +1,202 @@ +'use client'; + +import { useEffect, useMemo } from 'react'; +import { hexToUint8Array } from '@blockchain-lab-um/masca-connector'; +import { useTranslations } from 'next-intl'; +import { useAccount } from 'wagmi'; + +import { createClient } from '@/utils/supabase/client'; +import { useMascaStore, useToastStore } from '@/stores'; +import { useAuthStore } from '@/stores/authStore'; +import { useEncryptedSessionStore } from '@/stores/encryptedSessionStore'; + +export const EncryptedSessionProvider = () => { + const t = useTranslations('EncryptedSessionProvider'); + const token = useAuthStore((state) => state.token); + + const { address } = useAccount(); + + const { + sessionId, + session, + request, + deviceType, + changeConnected, + changeRequest, + changeSession, + } = useEncryptedSessionStore((state) => ({ + sessionId: state.sessionId, + session: state.session, + request: state.request, + deviceType: state.deviceType, + changeConnected: state.changeConnected, + changeRequest: state.changeRequest, + changeSession: state.changeSession, + })); + + const api = useMascaStore((state) => state.mascaApi); + + const client = useMemo(() => createClient(token ?? ''), [token]); + + // Decrypt data + const decryptData = async ({ + iv, + encryptedData, + }: { + iv: Uint8Array; + encryptedData: string; + }) => { + const decrypted = await crypto.subtle.decrypt( + { + name: 'AES-GCM', + iv, + }, + session.key!, + hexToUint8Array(encryptedData) + ); + + const decoded = new TextDecoder().decode(decrypted); + + return decoded; + }; + + const handleRequest = async (data: string) => { + if (!api) return; + if (request.active) return; + + // OIDC Credential Offer + if (data.startsWith('openid-credential-offer://')) { + changeRequest({ + active: true, + data, + type: 'credentialOffer', + finished: false, + }); + + return; + } + + // OIDC Authorization Request + if (data.startsWith('openid://')) { + changeRequest({ + active: true, + data, + type: 'oidcAuth', + finished: false, + }); + return; + } + + let jsonDecodedData; + try { + jsonDecodedData = JSON.parse(data); + if (!jsonDecodedData) throw new Error('Invalid JSON'); + + // Polygon Credential Offer + if ( + jsonDecodedData.type === + 'https://iden3-communication.io/credentials/1.0/offer' + ) { + changeRequest({ + active: true, + data, + type: 'polygonCredentialOffer', + finished: false, + }); + return; + } + + // Polygon Authorization Request + if ( + jsonDecodedData.type === + 'https://iden3-communication.io/authorization/1.0/request' + ) { + changeRequest({ + active: true, + data, + type: 'polygonAuth', + finished: false, + }); + + return; + } + } catch (e) { + console.log(e); + } + + setTimeout(() => { + useToastStore.setState({ + open: true, + title: t('unsuported'), + type: 'error', + loading: false, + link: null, + }); + }, 200); + }; + + useEffect(() => { + if (sessionId && deviceType === 'primary') { + client + .channel('realtime encrypted_sessions') + .on( + 'postgres_changes', + { event: 'UPDATE', schema: 'public', table: 'encrypted_sessions' }, + async () => { + const { data, error } = await client + .from('encrypted_sessions') + .select() + .eq('id', sessionId) + .single(); + + if (error || !data) { + useToastStore.setState({ + open: true, + title: t('fetch-failed'), + type: 'error', + loading: false, + link: null, + }); + + return; + } + + if (data.connected) { + changeConnected(true); + } + + if (!data.data || !data.iv) return; + + const decryptedData = await decryptData({ + iv: hexToUint8Array(data.iv), + encryptedData: data.data, + }); + + await handleRequest(decryptedData); + } + ) + .subscribe(); + } + }, [sessionId]); + + useEffect(() => { + if (!session.exp) return; + + if (session.exp && session.exp < Date.now()) { + changeSession({ + key: null, + exp: null, + }); + } + }, [session.exp]); + + useEffect(() => { + changeConnected(false); + changeSession({ + exp: null, + key: null, + }); + }, [address]); + + return null; +}; diff --git a/packages/dapp/src/components/MenuPopover/index.tsx b/packages/dapp/src/components/MenuPopover/index.tsx index cf0ca5a01..1cad70080 100644 --- a/packages/dapp/src/components/MenuPopover/index.tsx +++ b/packages/dapp/src/components/MenuPopover/index.tsx @@ -87,7 +87,7 @@ const INTERNAL_LINKS: LinkProps[] = [ }, { name: 'qr-scanner', - href: '/app/qr-code-session', + href: '/app/encrypted-session', icon: IconCamera, requiresConnection: false, }, diff --git a/packages/dapp/src/components/QRCodeSessionProvider/index.tsx b/packages/dapp/src/components/QRCodeSessionProvider/index.tsx deleted file mode 100644 index 3a14feb21..000000000 --- a/packages/dapp/src/components/QRCodeSessionProvider/index.tsx +++ /dev/null @@ -1,193 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; -import { hexToUint8Array } from '@blockchain-lab-um/masca-connector'; -import { useTranslations } from 'next-intl'; -import useSWR from 'swr'; -import { useAccount } from 'wagmi'; - -import { useMascaStore, useSessionStore, useToastStore } from '@/stores'; -import { useQRCodeStore } from '@/stores/qrCodeStore'; - -const fetcher = (url: string) => fetch(url).then((r) => r.json()); - -const QRCodeSessionProvider = () => { - const t = useTranslations('QRCodeSessionProvider'); - - const { request, session, changeRequest } = useSessionStore((state) => ({ - request: state.request, - session: state.session, - changeRequest: state.changeRequest, - changeSession: state.changeSession, - })); - - const requestData = useQRCodeStore((state) => state.requestData); - const { isConnected } = useAccount(); - const api = useMascaStore((state) => state.mascaApi); - - // Conditionally fetch session data - const { data } = useSWR( - () => - session.sessionId && isConnected - ? `/api/qr-code-session/${session.sessionId}` - : null, - fetcher, - { - // Refresh every 10 seconds - errorRetryInterval: 10000, - errorRetryCount: 100, - refreshInterval: 10000, - } - ); - - useEffect(() => { - if (!session.exp) return; - - if (session.exp && session.exp < Date.now()) { - useSessionStore.setState({ - session: { - sessionId: null, - key: null, - exp: null, - connected: false, - deviceType: null, - hasCamera: false, - }, - }); - } - }, [session.exp]); - - const handleNewRequest = async (_data: string) => { - if (_data === 'Created Connection') { - useSessionStore.setState({ - session: { ...session, connected: true }, - }); - return; - } - if (!isConnected) return; - if (!api) return; - if (request.active) return; - - // OIDC Credential Offer - if (_data.startsWith('openid-credential-offer://')) { - changeRequest({ - active: true, - data: _data, - type: 'credentialOffer', - finished: false, - }); - - return; - } - - // OIDC Authorization Request - if (_data.startsWith('openid://')) { - changeRequest({ - active: true, - data: _data, - type: 'oidcAuth', - finished: false, - }); - return; - } - - let jsonDecodedData; - try { - jsonDecodedData = JSON.parse(_data); - if (!jsonDecodedData) throw new Error('Invalid JSON'); - - // Polygon Credential Offer - if ( - jsonDecodedData.type === - 'https://iden3-communication.io/credentials/1.0/offer' - ) { - changeRequest({ - active: true, - data: _data, - type: 'polygonCredentialOffer', - finished: false, - }); - return; - } - - // Polygon Authorization Request - if ( - jsonDecodedData.type === - 'https://iden3-communication.io/authorization/1.0/request' - ) { - setTimeout(() => { - useToastStore.setState({ - open: true, - title: 'Polygon Authorization Request received', - type: 'info', - loading: false, - link: `/app/qr-code-session`, - }); - }, 200); - - changeRequest({ - active: true, - data: _data, - type: 'polygonAuth', - finished: false, - }); - - return; - } - } catch (e) { - console.log(e); - } - - setTimeout(() => { - useToastStore.setState({ - open: true, - title: t('unsuported'), - type: 'error', - loading: false, - link: null, - }); - }, 200); - }; - - // Decrypt received data - useEffect(() => { - if (!session.key || !data) return; - - const { data: encryptedData, iv: encodedIV } = data; - - if (data.error_descrition || !encryptedData || !encodedIV) return; - - // Data to uint8array - const iv = hexToUint8Array(encodedIV); - - // Decrypt data - const decryptData = async () => { - const decrypted = await crypto.subtle.decrypt( - { - name: 'AES-GCM', - iv, - }, - session.key!, - hexToUint8Array(encryptedData) - ); - - const decoded = new TextDecoder().decode(decrypted); - - return decoded; - }; - - decryptData() - .then(async (_data) => handleNewRequest(_data)) - .catch((e) => console.log(e)); - }, [data, session.key]); - - // New request recieved (QR Code upload) - useEffect(() => { - if (!requestData) return; - handleNewRequest(requestData).catch((e) => console.log(e)); - }, [requestData]); - - return null; -}; - -export default QRCodeSessionProvider; diff --git a/packages/dapp/src/messages/en.json b/packages/dapp/src/messages/en.json index b918fa0d2..bdf0335bd 100644 --- a/packages/dapp/src/messages/en.json +++ b/packages/dapp/src/messages/en.json @@ -24,7 +24,7 @@ "create-credential": "Create a custom credential", "documentation": "Read the Masca documentation", "get-credential": "Get your credential using the OpenID Connect protocol", - "qr-code-session": "Create session to scan QR codes using your mobile device", + "qr-code-session": "Create an encrypted session to scan QR codes using your mobile device", "qr-scanner": "Receive credentials and share presentations by scanning QR codes", "shared-presentations": "Check your shared presentations", "verify-data": "Verify credentials & presentations" @@ -123,7 +123,8 @@ }, "CreateConnectionModal": { "desc": " Scan this QR code with your mobile device to create a connection to this page.", - "title": "Connection QR Code" + "title": "Connection QR Code", + "session-create-failed": "Failed to create session. Please try again." }, "CreateCredentialDisplay": { "invalidMethod": "Polygon ID & Iden3 are not yet supported. Switch to any other DID method to create a credential!", @@ -447,10 +448,11 @@ "qr-invalid-error": "Invalid QR code", "starting-error": "Failed to start QR code scanner" }, - "QRCodeSessionProvider": { - "unsuported": "Unsuported QR code data received" + "EncryptedSessionProvider": { + "unsuported": "Unsuported QR code data received", + "fetch-failed": "Failed to fetch session. Please try again." }, - "QRSessionDisplay": { + "EncryptedSessionDisplay": { "back": "Back", "fifth": "Fifth Step", "fifth-desc": "Finish Flow", diff --git a/packages/dapp/src/stores/encryptedSessionStore.ts b/packages/dapp/src/stores/encryptedSessionStore.ts new file mode 100644 index 000000000..5ffbc2fac --- /dev/null +++ b/packages/dapp/src/stores/encryptedSessionStore.ts @@ -0,0 +1,67 @@ +import { shallow } from 'zustand/shallow'; +import { createWithEqualityFn } from 'zustand/traditional'; + +interface EncryptedRequest { + type: + | 'polygonAuth' + | 'oidcAuth' + | 'credentialOffer' + | 'polygonCredentialOffer' + | null; + data: string | null; + active: boolean; + finished: boolean; +} + +interface Session { + key: CryptoKey | null; + exp: number | null; +} + +interface EncryptedSessionStore { + sessionId: string | null; + request: EncryptedRequest; + session: Session; + connected: boolean; + deviceType: 'primary' | 'secondary' | null; + hasCamera: boolean; + + changeSessionId: (sessionId: string | null) => void; + changeRequest: (request: EncryptedRequest) => void; + changeSession: (session: Session) => void; + changeConnected: (connected: boolean) => void; + changeDeviceType: (deviceType: 'primary' | 'secondary') => void; + changeHasCamera: (hasCamera: boolean) => void; +} + +export const encryptedSessionInitialState = { + sessionId: null, + request: { + type: null, + data: null, + active: false, + finished: false, + }, + session: { + key: null, + exp: null, + }, + connected: false, + deviceType: null, + hasCamera: false, +}; + +export const useEncryptedSessionStore = + createWithEqualityFn()( + (set) => ({ + ...encryptedSessionInitialState, + changeSessionId: (sessionId: string | null) => set({ sessionId }), + changeRequest: (request: EncryptedRequest) => set({ request }), + changeSession: (session: Session) => set({ session }), + changeConnected: (connected: boolean) => set({ connected }), + changeDeviceType: (deviceType: 'primary' | 'secondary') => + set({ deviceType }), + changeHasCamera: (hasCamera: boolean) => set({ hasCamera }), + }), + shallow + ); diff --git a/packages/dapp/src/stores/index.ts b/packages/dapp/src/stores/index.ts index cbac77b12..06b1a1d82 100644 --- a/packages/dapp/src/stores/index.ts +++ b/packages/dapp/src/stores/index.ts @@ -2,4 +2,4 @@ export * from './generalStore'; export * from './snapStore'; export * from './tableStore'; export * from './toastStore'; -export * from './sessionStore'; +export * from './encryptedSessionStore'; diff --git a/packages/dapp/src/stores/sessionStore.ts b/packages/dapp/src/stores/sessionStore.ts deleted file mode 100644 index 2abab74bf..000000000 --- a/packages/dapp/src/stores/sessionStore.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { shallow } from 'zustand/shallow'; -import { createWithEqualityFn } from 'zustand/traditional'; - -interface QRRequest { - type: - | 'polygonAuth' - | 'oidcAuth' - | 'credentialOffer' - | 'polygonCredentialOffer' - | null; - data: string | null; - active: boolean; - finished: boolean; -} - -interface Session { - sessionId: string | null; - key: CryptoKey | null; - exp: number | null; - connected: boolean; - deviceType: 'primary' | 'secondary' | null; - hasCamera: boolean; -} - -interface SessionStore { - request: QRRequest; - session: Session; - changeRequest: (request: QRRequest) => void; - changeSession: (session: Session) => void; -} - -export const sessionStoreInitialState = { - request: { - type: null, - data: null, - active: false, - finished: false, - }, - session: { - sessionId: null, - key: null, - exp: null, - connected: false, - deviceType: null, - hasCamera: false, - }, -}; - -export const useSessionStore = createWithEqualityFn()( - (set) => ({ - ...sessionStoreInitialState, - changeRequest: (request: QRRequest) => set({ request }), - changeSession: (session: Session) => set({ session }), - }), - shallow -); diff --git a/packages/dapp/src/utils/prisma.ts b/packages/dapp/src/utils/prisma.ts deleted file mode 100644 index e2b960527..000000000 --- a/packages/dapp/src/utils/prisma.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -const globalForPrisma = globalThis as unknown as { - prisma: PrismaClient | undefined; -}; - -export const prisma = globalForPrisma.prisma ?? new PrismaClient(); - -if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma; diff --git a/packages/dapp/src/utils/supabase/database.types.ts b/packages/dapp/src/utils/supabase/database.types.ts index 11e46f2f8..f535b41da 100644 --- a/packages/dapp/src/utils/supabase/database.types.ts +++ b/packages/dapp/src/utils/supabase/database.types.ts @@ -173,6 +173,38 @@ export type Database = { } Relationships: [] } + encrypted_sessions: { + Row: { + connected: boolean + data: string | null + id: string + iv: string | null + user_id: string + } + Insert: { + connected?: boolean + data?: string | null + id?: string + iv?: string | null + user_id: string + } + Update: { + connected?: boolean + data?: string | null + id?: string + iv?: string | null + user_id?: string + } + Relationships: [ + { + foreignKeyName: "public_encrypted-sessions_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "users" + referencedColumns: ["id"] + } + ] + } presentations: { Row: { created_at: string diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f8174350..c19394163 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + patchedDependencies: '@ceramicnetwork/common@2.30.0': hash: uqwtqun5atcy5jjekxssfbohei @@ -84,7 +88,7 @@ importers: version: 9.0.0(eslint@8.52.0) eslint-plugin-import: specifier: ^2.29.0 - version: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + version: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0) eslint-plugin-jest: specifier: ^27.4.3 version: 27.4.3(@typescript-eslint/eslint-plugin@6.9.0)(eslint@8.52.0)(typescript@5.2.2) @@ -403,9 +407,6 @@ importers: '@nextui-org/react': specifier: ^2.2.9 version: 2.2.9(@types/react@18.2.33)(framer-motion@10.16.5)(react-dom@18.2.0)(react@18.2.0)(tailwind-variants@0.1.19)(tailwindcss@3.3.5) - '@prisma/client': - specifier: ^5.7.0 - version: 5.7.0(prisma@5.7.0) '@radix-ui/react-toast': specifier: ^1.1.5 version: 1.1.5(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) @@ -514,9 +515,6 @@ importers: pino-pretty: specifier: ^10.3.1 version: 10.3.1 - prisma: - specifier: ^5.7.0 - version: 5.7.0 qrcode.react: specifier: ^3.1.0 version: 3.1.0(react@18.2.0) @@ -4096,12 +4094,12 @@ packages: semver: 7.5.4 serve-handler: 6.1.5 shelljs: 0.8.5 - terser-webpack-plugin: 5.3.9(@swc/core@1.3.78)(esbuild@0.19.5)(webpack@5.89.0) + terser-webpack-plugin: 5.3.9(webpack@5.89.0) tslib: 2.6.2 update-notifier: 5.1.0 url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.89.0) wait-on: 6.0.1 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 webpack-bundle-analyzer: 4.9.1 webpack-dev-server: 4.15.1(webpack@5.89.0) webpack-merge: 5.10.0 @@ -4220,7 +4218,7 @@ packages: tslib: 2.6.2 unist-util-visit: 2.0.3 utility-types: 3.10.0 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 transitivePeerDependencies: - '@parcel/css' - '@swc/core' @@ -4297,7 +4295,7 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) tslib: 2.6.2 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 transitivePeerDependencies: - '@parcel/css' - '@swc/core' @@ -4917,6 +4915,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm@0.18.20: @@ -4934,6 +4933,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-x64@0.18.20: @@ -4951,6 +4951,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/darwin-arm64@0.18.20: @@ -4968,6 +4969,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-x64@0.18.20: @@ -4985,6 +4987,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-arm64@0.18.20: @@ -5002,6 +5005,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-x64@0.18.20: @@ -5019,6 +5023,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm64@0.18.20: @@ -5036,6 +5041,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm@0.18.20: @@ -5053,6 +5059,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ia32@0.18.20: @@ -5070,6 +5077,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-loong64@0.18.20: @@ -5087,6 +5095,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-mips64el@0.18.20: @@ -5104,6 +5113,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ppc64@0.18.20: @@ -5121,6 +5131,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-riscv64@0.18.20: @@ -5138,6 +5149,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-s390x@0.18.20: @@ -5155,6 +5167,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-x64@0.18.20: @@ -5172,6 +5185,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/netbsd-x64@0.18.20: @@ -5189,6 +5203,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: true optional: true /@esbuild/openbsd-x64@0.18.20: @@ -5206,6 +5221,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: true optional: true /@esbuild/sunos-x64@0.18.20: @@ -5223,6 +5239,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: true optional: true /@esbuild/win32-arm64@0.18.20: @@ -5240,6 +5257,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-ia32@0.18.20: @@ -5257,6 +5275,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-x64@0.18.20: @@ -5274,6 +5293,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@eslint-community/eslint-utils@4.4.0(eslint@8.52.0): @@ -9113,51 +9133,6 @@ packages: /@polka/url@1.0.0-next.23: resolution: {integrity: sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==} - /@prisma/client@5.7.0(prisma@5.7.0): - resolution: {integrity: sha512-cZmglCrfNbYpzUtz7HscVHl38e9CrUs31nrVoGUK1nIPXGgt8hT4jj2s657UXcNdQ/jBUxDgGmHyu2Nyrq1txg==} - engines: {node: '>=16.13'} - requiresBuild: true - peerDependencies: - prisma: '*' - peerDependenciesMeta: - prisma: - optional: true - dependencies: - prisma: 5.7.0 - dev: false - - /@prisma/debug@5.7.0: - resolution: {integrity: sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA==} - dev: false - - /@prisma/engines-version@5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9: - resolution: {integrity: sha512-V6tgRVi62jRwTm0Hglky3Scwjr/AKFBFtS+MdbsBr7UOuiu1TKLPc6xfPiyEN1+bYqjEtjxwGsHgahcJsd1rNg==} - dev: false - - /@prisma/engines@5.7.0: - resolution: {integrity: sha512-TkOMgMm60n5YgEKPn9erIvFX2/QuWnl3GBo6yTRyZKk5O5KQertXiNnrYgSLy0SpsKmhovEPQb+D4l0SzyE7XA==} - requiresBuild: true - dependencies: - '@prisma/debug': 5.7.0 - '@prisma/engines-version': 5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9 - '@prisma/fetch-engine': 5.7.0 - '@prisma/get-platform': 5.7.0 - dev: false - - /@prisma/fetch-engine@5.7.0: - resolution: {integrity: sha512-zIn/qmO+N/3FYe7/L9o+yZseIU8ivh4NdPKSkQRIHfg2QVTVMnbhGoTcecbxfVubeTp+DjcbjS0H9fCuM4W04w==} - dependencies: - '@prisma/debug': 5.7.0 - '@prisma/engines-version': 5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9 - '@prisma/get-platform': 5.7.0 - dev: false - - /@prisma/get-platform@5.7.0: - resolution: {integrity: sha512-ZeV/Op4bZsWXuw5Tg05WwRI8BlKiRFhsixPcAM+5BKYSiUZiMKIi713tfT3drBq8+T0E1arNZgYSA9QYcglWNA==} - dependencies: - '@prisma/debug': 5.7.0 - dev: false - /@radix-ui/primitive@1.0.1: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} dependencies: @@ -11329,6 +11304,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@swc/core-darwin-x64@1.3.78: @@ -11337,6 +11313,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@swc/core-linux-arm-gnueabihf@1.3.78: @@ -11345,6 +11322,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-linux-arm64-gnu@1.3.78: @@ -11353,6 +11331,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-linux-arm64-musl@1.3.78: @@ -11361,6 +11340,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-linux-x64-gnu@1.3.78: @@ -11369,6 +11349,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-linux-x64-musl@1.3.78: @@ -11377,6 +11358,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-win32-arm64-msvc@1.3.78: @@ -11385,6 +11367,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@swc/core-win32-ia32-msvc@1.3.78: @@ -11393,6 +11376,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@swc/core-win32-x64-msvc@1.3.78: @@ -11401,6 +11385,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@swc/core@1.3.78: @@ -11423,6 +11408,7 @@ packages: '@swc/core-win32-arm64-msvc': 1.3.78 '@swc/core-win32-ia32-msvc': 1.3.78 '@swc/core-win32-x64-msvc': 1.3.78 + dev: true /@swc/helpers@0.4.14: resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} @@ -13477,6 +13463,7 @@ packages: dependencies: webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack@5.89.0) + dev: true /@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.89.0): resolution: {integrity: sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==} @@ -13487,6 +13474,7 @@ packages: dependencies: webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack@5.89.0) + dev: true /@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.89.0): resolution: {integrity: sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==} @@ -13501,6 +13489,7 @@ packages: dependencies: webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack@5.89.0) + dev: true /@xmldom/xmldom@0.7.13: resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} @@ -14216,7 +14205,7 @@ packages: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /babel-plugin-apply-mdx-type-prop@1.6.22(@babel/core@7.12.9): resolution: {integrity: sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==} @@ -15881,7 +15870,7 @@ packages: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.1 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /core-js-compat@3.32.0: resolution: {integrity: sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==} @@ -16184,7 +16173,7 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.31) postcss-value-parser: 4.2.0 semver: 7.5.4 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /css-minimizer-webpack-plugin@4.2.2(clean-css@5.3.2)(webpack@5.89.0): resolution: {integrity: sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==} @@ -16218,7 +16207,7 @@ packages: schema-utils: 4.2.0 serialize-javascript: 6.0.1 source-map: 0.6.1 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -17475,6 +17464,7 @@ packages: '@esbuild/win32-arm64': 0.19.5 '@esbuild/win32-ia32': 0.19.5 '@esbuild/win32-x64': 0.19.5 + dev: true /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -17521,7 +17511,7 @@ packages: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.52.0 - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0) object.assign: 4.1.4 object.entries: 1.1.6 semver: 6.3.1 @@ -17539,7 +17529,7 @@ packages: '@typescript-eslint/parser': 6.9.0(eslint@8.52.0)(typescript@5.2.2) eslint: 8.52.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.0)(eslint@8.52.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0) dev: true /eslint-config-next@13.5.6(eslint@8.55.0)(typescript@5.3.3): @@ -17557,7 +17547,7 @@ packages: eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.55.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint@8.55.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.55.0) eslint-plugin-react: 7.33.2(eslint@8.55.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.55.0) @@ -17597,7 +17587,7 @@ packages: enhanced-resolve: 5.15.0 eslint: 8.55.0 eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.9.0)(eslint@8.55.0) fast-glob: 3.3.1 get-tsconfig: 4.7.2 is-core-module: 2.13.1 @@ -17639,7 +17629,65 @@ packages: - supports-color dev: true - /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.52.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 6.9.0(eslint@8.52.0)(typescript@5.2.2) + debug: 3.2.7 + eslint: 8.52.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.55.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 6.9.0(eslint@8.52.0)(typescript@5.2.2) + debug: 3.2.7 + eslint: 8.55.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0): resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} engines: {node: '>=4'} peerDependencies: @@ -17649,7 +17697,42 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.9.0(eslint@8.55.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.9.0(eslint@8.52.0)(typescript@5.2.2) + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.3 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.52.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.52.0) + hasown: 2.0.0 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.7 + object.groupby: 1.0.1 + object.values: 1.1.7 + semver: 6.3.1 + tsconfig-paths: 3.14.2 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.9.0)(eslint@8.55.0): + resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 6.9.0(eslint@8.52.0)(typescript@5.2.2) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 @@ -17658,7 +17741,7 @@ packages: doctrine: 2.1.0 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.55.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -18543,6 +18626,7 @@ packages: /fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} + dev: true /fastfile@0.0.20: resolution: {integrity: sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA==} @@ -18672,7 +18756,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /file-saver@2.0.5: resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} @@ -18911,7 +18995,7 @@ packages: semver: 7.5.4 tapable: 1.1.3 typescript: 5.3.3 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /form-data@3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} @@ -19810,7 +19894,7 @@ packages: lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.2.1 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /html5-qrcode@2.3.8: resolution: {integrity: sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==} @@ -20050,6 +20134,7 @@ packages: dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 + dev: true /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -20174,6 +20259,7 @@ packages: /interpret@3.1.1: resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} engines: {node: '>=10.13.0'} + dev: true /intl-messageformat@10.5.8: resolution: {integrity: sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA==} @@ -22941,7 +23027,7 @@ packages: webpack: ^5.0.0 dependencies: schema-utils: 4.2.0 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -24506,7 +24592,7 @@ packages: jiti: 1.20.0 postcss: 8.4.31 semver: 7.5.4 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 transitivePeerDependencies: - typescript @@ -25024,15 +25110,6 @@ packages: react: 18.2.0 dev: false - /prisma@5.7.0: - resolution: {integrity: sha512-0rcfXO2ErmGAtxnuTNHQT9ztL0zZheQjOI/VNJzdq87C3TlGPQtMqtM+KCwU6XtmkoEr7vbCQqA7HF9IY0ST+Q==} - engines: {node: '>=16.13'} - hasBin: true - requiresBuild: true - dependencies: - '@prisma/engines': 5.7.0 - dev: false - /prismjs@1.29.0: resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} engines: {node: '>=6'} @@ -25390,7 +25467,7 @@ packages: strip-ansi: 6.0.1 text-table: 0.2.0 typescript: 5.3.3 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 transitivePeerDependencies: - eslint - supports-color @@ -25494,7 +25571,7 @@ packages: dependencies: '@babel/runtime': 7.23.2 react-loadable: /@docusaurus/react-loadable@5.5.2(react@18.2.0) - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /react-native-securerandom@0.1.1(react-native@0.72.6): resolution: {integrity: sha512-CozcCx0lpBLevxiXEb86kwLRalBCHNjiGPlw3P7Fi27U6ZLdfjOCNRHD1LtBKcvPvI3TvkBXB3GOtLvqaYJLGw==} @@ -25901,6 +25978,7 @@ packages: engines: {node: '>= 10.13.0'} dependencies: resolve: 1.22.4 + dev: true /recursive-readdir@2.2.3: resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==} @@ -26121,6 +26199,7 @@ packages: engines: {node: '>=8'} dependencies: resolve-from: 5.0.0 + dev: true /resolve-from@3.0.0: resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} @@ -27650,7 +27729,7 @@ packages: normalize-path: 3.0.0 schema-utils: 4.2.0 stylelint: 15.11.0(typescript@5.3.3) - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 dev: true /stylelint@15.11.0(typescript@5.3.3): @@ -28067,6 +28146,78 @@ packages: serialize-javascript: 6.0.1 terser: 5.22.0 webpack: 5.89.0(@swc/core@1.3.78)(esbuild@0.19.5) + dev: true + + /terser-webpack-plugin@5.3.9(esbuild@0.18.20)(webpack@5.89.0): + resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.20 + esbuild: 0.18.20 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.1 + terser: 5.22.0 + webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + dev: true + + /terser-webpack-plugin@5.3.9(webpack@5.88.2): + resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.20 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.1 + terser: 5.22.0 + webpack: 5.88.2 + + /terser-webpack-plugin@5.3.9(webpack@5.89.0): + resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.20 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.1 + terser: 5.22.0 + webpack: 5.89.0 /terser@5.22.0: resolution: {integrity: sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==} @@ -29049,7 +29200,7 @@ packages: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /url-parse-lax@3.0.0: resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} @@ -29756,6 +29907,7 @@ packages: rechoir: 0.8.0 webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) webpack-merge: 5.9.0 + dev: true /webpack-dev-middleware@5.3.3(webpack@5.89.0): resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==} @@ -29768,7 +29920,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.2.0 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /webpack-dev-server@4.15.1(webpack@5.89.0): resolution: {integrity: sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==} @@ -29811,7 +29963,7 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 webpack-dev-middleware: 5.3.3(webpack@5.89.0) ws: 8.14.2 transitivePeerDependencies: @@ -29877,7 +30029,46 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(@swc/core@1.3.78)(esbuild@0.19.5)(webpack@5.89.0) + terser-webpack-plugin: 5.3.9(webpack@5.88.2) + watchpack: 2.4.0 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + /webpack@5.89.0: + resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.6 + '@types/estree': 1.0.3 + '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/wasm-edit': 1.11.6 + '@webassemblyjs/wasm-parser': 1.11.6 + acorn: 8.10.0 + acorn-import-assertions: 1.9.0(acorn@8.10.0) + browserslist: 4.22.1 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.15.0 + es-module-lexer: 1.3.1 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.9(webpack@5.89.0) watchpack: 2.4.0 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -29923,6 +30114,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: true /webpack@5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4): resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} @@ -29955,7 +30147,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(@swc/core@1.3.78)(esbuild@0.19.5)(webpack@5.89.0) + terser-webpack-plugin: 5.3.9(esbuild@0.18.20)(webpack@5.89.0) watchpack: 2.4.0 webpack-cli: 5.1.4(webpack@5.89.0) webpack-sources: 3.2.3 @@ -29963,6 +30155,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: true /webpackbar@5.0.2(webpack@5.89.0): resolution: {integrity: sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==} @@ -29974,7 +30167,7 @@ packages: consola: 2.15.3 pretty-time: 1.1.0 std-env: 3.4.3 - webpack: 5.89.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0 /websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} @@ -30514,7 +30707,3 @@ packages: - expo - react-native - web-streams-polyfill - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false