From bffbe9b4e69c1671fea95ca4cf4439a0f6dbc6d2 Mon Sep 17 00:00:00 2001 From: Martin Domajnko Date: Tue, 28 Nov 2023 21:13:47 +0100 Subject: [PATCH 01/28] feat: initial supabase work, siwe, update routes --- packages/dapp/.env.example | 9 + packages/dapp/package.json | 17 + .../app/(protected)/dashboard/identities.tsx | 145 +++++ .../app/(protected)/dashboard/page.tsx | 22 + .../app/api/google/{route.tsx => route.ts} | 0 .../[id]/{route.tsx => route.ts} | 0 .../src/app/api/share/presentation/route.ts | 76 +++ packages/dapp/src/app/api/siwe/nonce/route.ts | 65 +++ .../dapp/src/app/api/siwe/verify/route.ts | 181 +++++++ packages/dapp/src/app/api/veramoSetup.ts | 64 +++ .../CheckMetaMaskCompatibility/index.tsx | 10 +- .../components/SignInWithEthereum/index.tsx | 88 +++ .../src/components/SupabaseProvider/index.tsx | 21 + packages/dapp/src/middleware.ts | 21 +- packages/dapp/src/stores/authStore.ts | 24 + packages/dapp/src/utils/supabase/client.ts | 16 + .../dapp/src/utils/supabase/database.types.ts | 123 +++++ .../dapp/src/utils/supabase/helper.types.ts | 4 + pnpm-lock.yaml | 509 ++++++++++++++++-- 19 files changed, 1328 insertions(+), 67 deletions(-) create mode 100644 packages/dapp/src/app/[locale]/app/(protected)/dashboard/identities.tsx create mode 100644 packages/dapp/src/app/[locale]/app/(protected)/dashboard/page.tsx rename packages/dapp/src/app/api/google/{route.tsx => route.ts} (100%) rename packages/dapp/src/app/api/qr-code-session/[id]/{route.tsx => route.ts} (100%) create mode 100644 packages/dapp/src/app/api/share/presentation/route.ts create mode 100644 packages/dapp/src/app/api/siwe/nonce/route.ts create mode 100644 packages/dapp/src/app/api/siwe/verify/route.ts create mode 100644 packages/dapp/src/app/api/veramoSetup.ts create mode 100644 packages/dapp/src/components/SignInWithEthereum/index.tsx create mode 100644 packages/dapp/src/components/SupabaseProvider/index.tsx create mode 100644 packages/dapp/src/stores/authStore.ts create mode 100644 packages/dapp/src/utils/supabase/client.ts create mode 100644 packages/dapp/src/utils/supabase/database.types.ts create mode 100644 packages/dapp/src/utils/supabase/helper.types.ts diff --git a/packages/dapp/.env.example b/packages/dapp/.env.example index fe377ee95..f0d6d1e6e 100644 --- a/packages/dapp/.env.example +++ b/packages/dapp/.env.example @@ -9,3 +9,12 @@ DATABASE_URL= # Masca version NEXT_PUBLIC_MASCA_VERSION=v1.0.0 + +# SupaBase Public +NEXT_PUBLIC_SUPABASE_URL=https://vfxyvzkprpeegheyapzg.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZmeHl2emtwcnBlZWdoZXlhcHpnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDExMDEwNjcsImV4cCI6MjAxNjY3NzA2N30.6hR8wsUp0grypTcvaH553CLg8Badz7CzamflDPLBeqU + +# SupaBase Private +SUPABASE_SECRET_KEY= +SUPABASE_JWT_SECRET= + diff --git a/packages/dapp/package.json b/packages/dapp/package.json index a11732dd7..eff485d0e 100644 --- a/packages/dapp/package.json +++ b/packages/dapp/package.json @@ -20,8 +20,10 @@ "start": "next start" }, "dependencies": { + "@blockchain-lab-um/did-provider-key": "1.0.7", "@blockchain-lab-um/masca-connector": "1.2.1", "@blockchain-lab-um/oidc-types": "0.0.8", + "@ethersproject/providers": "^5.7.2", "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", "@metamask/detect-provider": "^2.0.0", @@ -29,17 +31,29 @@ "@prisma/client": "^5.5.2", "@radix-ui/react-toast": "^1.1.5", "@react-oauth/google": "^0.11.1", + "@supabase/ssr": "^0.0.10", + "@supabase/supabase-js": "^2.38.5", "@tanstack/react-table": "^8.10.7", "@veramo/core": "5.5.3", + "@veramo/credential-eip712": "5.5.3", + "@veramo/credential-w3c": "5.5.3", + "@veramo/did-provider-ethr": "5.5.3", + "@veramo/did-provider-pkh": "5.5.3", + "@veramo/did-resolver": "5.5.3", "@veramo/utils": "5.5.3", "@vercel/analytics": "^1.1.1", "@vercel/og": "^0.5.20", "clsx": "^2.0.0", + "date-fns": "^2.30.0", "did-jwt-vc": "^3.2.13", + "did-resolver": "4.1.0", + "ethers": "^6.9.0", + "ethr-did-resolver": "8.1.2", "file-saver": "^2.0.5", "googleapis": "^128.0.0", "headless-stepper": "^1.9.1", "html5-qrcode": "^2.3.8", + "jsonwebtoken": "^9.0.2", "luxon": "^3.4.3", "next": "13.5.6", "next-intl": "3.0.0-beta.9", @@ -50,6 +64,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "sharp": "^0.32.6", + "siwe": "^2.1.4", "swr": "^2.2.4", "tailwind-scrollbar": "^3.0.5", "zustand": "^4.4.4" @@ -57,6 +72,7 @@ "devDependencies": { "@svgr/webpack": "^8.1.0", "@types/file-saver": "^2.0.6", + "@types/jsonwebtoken": "^9.0.5", "@types/luxon": "^3.3.3", "@types/qs": "^6.9.9", "@types/react": "18.2.33", @@ -72,6 +88,7 @@ "stylelint-config-standard-scss": "^11.0.0", "stylelint-prettier": "^4.0.2", "stylelint-webpack-plugin": "^4.1.1", + "supabase": "^1.113.3", "tailwindcss": "^3.3.5" }, "nx": { diff --git a/packages/dapp/src/app/[locale]/app/(protected)/dashboard/identities.tsx b/packages/dapp/src/app/[locale]/app/(protected)/dashboard/identities.tsx new file mode 100644 index 000000000..a7e752185 --- /dev/null +++ b/packages/dapp/src/app/[locale]/app/(protected)/dashboard/identities.tsx @@ -0,0 +1,145 @@ +'use client'; + +import { useEffect, useState } from 'react'; + +import Button from '@/components/Button'; +import { createClient } from '@/utils/supabase/client'; +import { type Tables } from '@/utils/supabase/helper.types'; +import { useAuthStore } from '@/stores/authStore'; + +// TODO: Management of identities +export const Identities = () => { + const token = useAuthStore((state) => state.token); + const [identities, setIdentities] = useState[]>([]); + + useEffect(() => { + if (!token) return; + + const client = createClient(token); + + const fetchIdentities = async () => { + const { data, error, status } = await client + .from('identities') + .select('*'); + + if (!data) return; + + setIdentities(data); + }; + + fetchIdentities().catch((error) => { + console.error(error); + }); + }, [token]); + + const handleShareCredential = async () => { + try { + const presentation = { + holder: 'did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61', + verifiableCredential: [ + '{"type":["VerifiableCredential","MascaUserCredential"],"proof":{"type":"EthereumEip712Signature2021","eip712":{"types":{"Proof":[{"name":"created","type":"string"},{"name":"proofPurpose","type":"string"},{"name":"type","type":"string"},{"name":"verificationMethod","type":"string"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"}],"CredentialSchema":[{"name":"id","type":"string"},{"name":"type","type":"string"}],"CredentialSubject":[{"name":"id","type":"string"},{"name":"type","type":"string"}],"VerifiableCredential":[{"name":"@context","type":"string[]"},{"name":"credentialSchema","type":"CredentialSchema"},{"name":"credentialSubject","type":"CredentialSubject"},{"name":"issuanceDate","type":"string"},{"name":"issuer","type":"string"},{"name":"proof","type":"Proof"},{"name":"type","type":"string[]"}]},"domain":{"name":"VerifiableCredential","chainId":1,"version":"1"},"primaryType":"VerifiableCredential"},"created":"2023-09-04T12:28:25.221Z","proofValue":"0xb29ad85e9e4041d149b5784ad593f51239a22e7cde4b4e11e1bbbc0a6b92a4fa4fbc94c181f230a2a31f41b6b9f23af21d337ce244adf75ca506f07f6b67cd841c","proofPurpose":"assertionMethod","verificationMethod":"did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61#controller"},"issuer":"did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61","@context":["https://www.w3.org/2018/credentials/v1"],"issuanceDate":"2023-09-04T12:28:25.221Z","credentialSchema":{"id":"https://beta.api.schemas.serto.id/v1/public/program-completion-certificate/1.0/json-schema.json","type":"JsonSchemaValidator2018"},"credentialSubject":{"id":"did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61","type":"Regular User"}}', + ], + type: ['VerifiablePresentation', 'Custom'], + '@context': ['https://www.w3.org/2018/credentials/v1'], + issuanceDate: '2023-11-28T14:41:54.514Z', + proof: { + verificationMethod: + 'did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61#controller', + created: '2023-11-28T14:41:54.514Z', + proofPurpose: 'assertionMethod', + type: 'EthereumEip712Signature2021', + proofValue: + '0x690c7c8c1036da74ca4c3c692541ac711bc06cbb22b9855de2bc19e955458c025e2b0658949002f7e2fc64ef70fb6b0ee597059acbcc3741eb46cf9e7bd9941a1c', + eip712: { + domain: { + chainId: 1, + name: 'VerifiablePresentation', + version: '1', + }, + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + ], + Proof: [ + { + name: 'created', + type: 'string', + }, + { + name: 'proofPurpose', + type: 'string', + }, + { + name: 'type', + type: 'string', + }, + { + name: 'verificationMethod', + type: 'string', + }, + ], + VerifiablePresentation: [ + { + name: '@context', + type: 'string[]', + }, + { + name: 'holder', + type: 'string', + }, + { + name: 'issuanceDate', + type: 'string', + }, + { + name: 'proof', + type: 'Proof', + }, + { + name: 'type', + type: 'string[]', + }, + { + name: 'verifiableCredential', + type: 'string[]', + }, + ], + }, + primaryType: 'VerifiablePresentation', + }, + }, + }; + const result = await fetch('/api/share/presentation', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ presentation }), + }); + } catch (error) { + console.error(error); + } + }; + + if (!token) return null; + + return ( +
+ +
+ ); +}; diff --git a/packages/dapp/src/app/[locale]/app/(protected)/dashboard/page.tsx b/packages/dapp/src/app/[locale]/app/(protected)/dashboard/page.tsx new file mode 100644 index 000000000..ddad9cc3a --- /dev/null +++ b/packages/dapp/src/app/[locale]/app/(protected)/dashboard/page.tsx @@ -0,0 +1,22 @@ +import { Metadata } from 'next'; + +import { SupabaseProvider } from '@/components/SupabaseProvider'; +import { Identities } from './identities'; + +export const metadata: Metadata = { + title: 'Dashboard', + description: 'Dashboard page for extra features.', +}; + +export default async function Page() { + return ( +
+
+ + {/* TODO: Shared Credential Management */} + + +
+
+ ); +} diff --git a/packages/dapp/src/app/api/google/route.tsx b/packages/dapp/src/app/api/google/route.ts similarity index 100% rename from packages/dapp/src/app/api/google/route.tsx rename to packages/dapp/src/app/api/google/route.ts diff --git a/packages/dapp/src/app/api/qr-code-session/[id]/route.tsx b/packages/dapp/src/app/api/qr-code-session/[id]/route.ts similarity index 100% rename from packages/dapp/src/app/api/qr-code-session/[id]/route.tsx rename to packages/dapp/src/app/api/qr-code-session/[id]/route.ts diff --git a/packages/dapp/src/app/api/share/presentation/route.ts b/packages/dapp/src/app/api/share/presentation/route.ts new file mode 100644 index 000000000..888263bea --- /dev/null +++ b/packages/dapp/src/app/api/share/presentation/route.ts @@ -0,0 +1,76 @@ +import { NextRequest, NextResponse } from 'next/server'; +import jwt from 'jsonwebtoken'; + +import { getAgent } from '../../veramoSetup'; + +const CORS_HEADERS = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET POST OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', +}; + +export async function POST(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 { presentation } = await request.json(); + + if (!presentation) { + return new NextResponse('Missing presentation', { + status: 400, + headers: { + ...CORS_HEADERS, + }, + }); + } + + const agent = await getAgent(); + + const { verified } = await agent.verifyPresentation({ + presentation, + }); + + console.log('verified', verified); + + return new NextResponse(null, { + status: 204, + headers: { + ...CORS_HEADERS, + }, + }); + } catch (error) { + return new NextResponse('Unauthorized', { + status: 401, + headers: { + ...CORS_HEADERS, + }, + }); + } +} + +export async function OPTIONS() { + return new NextResponse(null, { + status: 200, + headers: { + ...CORS_HEADERS, + }, + }); +} diff --git a/packages/dapp/src/app/api/siwe/nonce/route.ts b/packages/dapp/src/app/api/siwe/nonce/route.ts new file mode 100644 index 000000000..f3ba16e67 --- /dev/null +++ b/packages/dapp/src/app/api/siwe/nonce/route.ts @@ -0,0 +1,65 @@ +import { NextResponse } from 'next/server'; +import { createClient } from '@supabase/supabase-js'; +import { add, format } from 'date-fns'; + +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() { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SECRET_KEY! + ); + + // Insert a new nonce and select 1 row + const { data, error } = await supabase + .from('authorization') + .insert({ + // Expires in 5 minutes (ISO String) + expires_at: format( + add(new Date(), { minutes: 5 }), + "yyyy-MM-dd'T'HH:mm:ss.SSSxxx" + ), + }) + .select() + .limit(1) + .single(); + + if (error || !data) { + return new NextResponse('Internal server error', { + status: 500, + headers: { + ...CORS_HEADERS, + }, + }); + } + + return NextResponse.json( + { + nonce: data.nonce, + expiresAt: data.expires_at, + createdAt: data.created_at, + }, + { + headers: { + ...CORS_HEADERS, + 'Set-Cookie': `verify.session=${data.id}; Path=/; HttpOnly; Secure; SameSite=Strict;`, + }, + status: 200, + } + ); +} + +export async function OPTIONS() { + return new NextResponse(null, { + status: 200, + headers: { + ...CORS_HEADERS, + }, + }); +} diff --git a/packages/dapp/src/app/api/siwe/verify/route.ts b/packages/dapp/src/app/api/siwe/verify/route.ts new file mode 100644 index 000000000..d021c6905 --- /dev/null +++ b/packages/dapp/src/app/api/siwe/verify/route.ts @@ -0,0 +1,181 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createClient } from '@supabase/supabase-js'; +import { parse } from 'date-fns'; +import jwt from 'jsonwebtoken'; +import { SiweMessage } from 'siwe'; + +import { Database } from '@/utils/supabase/database.types'; + +const CORS_HEADERS = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', +}; + +export async function POST(request: NextRequest) { + try { + // Get session id from cookie + const sessionId = request.cookies.get('verify.session')?.value; + + if (!sessionId) { + return new NextResponse('Unauthorized', { + status: 401, + headers: { + ...CORS_HEADERS, + }, + }); + } + + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SECRET_KEY! + ); + + const { data: authorizationQueryData } = await supabase + .from('authorization') + .select() + .eq('id', sessionId) + .limit(1); + + if (!authorizationQueryData || authorizationQueryData.length === 0) { + return new NextResponse('Unauthorized', { + status: 401, + headers: { + ...CORS_HEADERS, + }, + }); + } + + const [authorizationData] = authorizationQueryData; + + console.log(authorizationData); + + console.log( + parse( + authorizationData.expires_at, + "yyyy-MM-dd'T'HH:mm:ss.SSSxxx", + new Date() + ) + ); + console.log(new Date()); + + // Check if expired + if (new Date(authorizationData.expires_at) < new Date()) { + return new NextResponse('Unauthorized', { + status: 401, + headers: { + ...CORS_HEADERS, + }, + }); + } + + const { message, signature } = await request.json(); + + console.log(message, signature); + + if (!message || !signature) { + return new NextResponse('Unauthorized', { + status: 401, + headers: { + ...CORS_HEADERS, + }, + }); + } + + const siweObject = new SiweMessage(message); + + const { data } = await siweObject.verify({ + signature, + nonce: authorizationData.nonce.replaceAll('-', ''), + }); + + const { address } = data; + + let userData; + + // Find user by address + const { data: userQueryData, error: userQueryError } = await supabase + .from('users') + .select() + .eq('address', address) + .limit(1); + + if (userQueryError) { + return new NextResponse('Failed to query user by address', { + status: 500, + headers: { + ...CORS_HEADERS, + }, + }); + } + + // If user does not exist, create a new one + if (!userQueryData || userQueryData.length === 0) { + const { data: userCreateData, error: userCreateError } = await supabase + .from('users') + .insert({ + address, + }) + .select() + .limit(1) + .single(); + + if (userCreateError || !userCreateData) { + return new NextResponse('Failed to create user', { + status: 500, + headers: { + ...CORS_HEADERS, + }, + }); + } + + userData = userCreateData; + } else { + [userData] = userQueryData; + } + + // Create a access token + const token = jwt.sign( + { + sub: userData.id, + address, + // Neded for Supabase + aud: 'authenticated', + role: 'authenticated', + }, + process.env.SUPABASE_JWT_SECRET!, + { + expiresIn: '1h', + } + ); + + // Return response and set cookie + return NextResponse.json( + { + jwt: token, + }, + { + status: 200, + headers: { + ...CORS_HEADERS, + }, + } + ); + } catch (error) { + return new NextResponse('An unexpected error occurred.', { + 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/veramoSetup.ts b/packages/dapp/src/app/api/veramoSetup.ts new file mode 100644 index 000000000..fbb0255ff --- /dev/null +++ b/packages/dapp/src/app/api/veramoSetup.ts @@ -0,0 +1,64 @@ +import { getDidKeyResolver as didKeyResolver } from '@blockchain-lab-um/did-provider-key'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { + createAgent, + type ICredentialVerifier, + type IDIDManager, + type IKeyManager, + type IResolver, + type TAgent, +} from '@veramo/core'; +import { CredentialIssuerEIP712 } from '@veramo/credential-eip712'; +import { + CredentialPlugin, + type ICredentialIssuer, +} from '@veramo/credential-w3c'; +import { getDidPkhResolver as didPkhResolver } from '@veramo/did-provider-pkh'; +import { DIDResolverPlugin } from '@veramo/did-resolver'; +import { Resolver } from 'did-resolver'; +import { getResolver as didEthrResolver } from 'ethr-did-resolver'; + +export type Agent = TAgent< + IDIDManager & IKeyManager & IResolver & ICredentialVerifier +>; + +const networks = [ + { + name: 'mainnet', + provider: new JsonRpcProvider( + 'https://mainnet.infura.io/v3/bf246ad3028f42318f2e996a7aa85bfc' + ), + }, + { + name: 'sepolia', + provider: new JsonRpcProvider( + 'https://sepolia.infura.io/v3/bf246ad3028f42318f2e996a7aa85bfc' + ), + chainId: '0xaa36a7', + }, +]; + +export const getAgent = async (): Promise => { + const agent = createAgent< + IDIDManager & + IKeyManager & + IResolver & + ICredentialIssuer & + ICredentialVerifier + >({ + plugins: [ + new CredentialPlugin(), + new CredentialIssuerEIP712(), + + new DIDResolverPlugin({ + resolver: new Resolver({ + ...didEthrResolver({ networks }), + ...didPkhResolver(), + ...didKeyResolver(), + }), + }), + ], + }); + + return agent; +}; diff --git a/packages/dapp/src/components/CheckMetaMaskCompatibility/index.tsx b/packages/dapp/src/components/CheckMetaMaskCompatibility/index.tsx index 7088f8a7c..547a0a95d 100644 --- a/packages/dapp/src/components/CheckMetaMaskCompatibility/index.tsx +++ b/packages/dapp/src/components/CheckMetaMaskCompatibility/index.tsx @@ -10,10 +10,12 @@ import detectEthereumProvider from '@metamask/detect-provider'; import { useGeneralStore, useMascaStore } from '@/stores'; -const snapId = - process.env.USE_LOCAL === 'true' - ? 'local:http://localhost:8081' - : 'npm:@blockchain-lab-um/masca'; +// const snapId = +// process.env.USE_LOCAL === 'true' +// ? 'local:http://localhost:8081' +// : 'npm:@blockchain-lab-um/masca'; + +const snapId = 'npm:@blockchain-lab-um/masca'; const CheckMetaMaskCompatibility = () => { const { changeHasMetaMask, changeHasSnaps } = useGeneralStore((state) => ({ diff --git a/packages/dapp/src/components/SignInWithEthereum/index.tsx b/packages/dapp/src/components/SignInWithEthereum/index.tsx new file mode 100644 index 000000000..8423a838c --- /dev/null +++ b/packages/dapp/src/components/SignInWithEthereum/index.tsx @@ -0,0 +1,88 @@ +'use client'; + +import { BrowserProvider } from 'ethers'; +import { SiweMessage } from 'siwe'; + +import Button from '@/components/Button'; +import { useAuthStore } from '@/stores/authStore'; + +export const SignInWithEthereum = () => { + const { changeToken, changeIsSignedIn } = useAuthStore((state) => ({ + changeToken: state.changeToken, + changeIsSignedIn: state.changeIsSignedIn, + })); + + const getNonce = async () => { + const response = await fetch('/api/siwe/nonce', { + method: 'GET', + credentials: 'include', + }); + + if (!response.ok) throw new Error('Failed to get nonce.'); + + const data = await response.json(); + + if (!data.nonce) throw new Error('Failed to get nonce.'); + + return data as { + nonce: string; + expiresAt: string; + createdAt: string; + }; + }; + + const createSiweMessage = async (address: string) => { + const { nonce } = await getNonce(); + console.log(address); + console.log(nonce); + const message = new SiweMessage({ + domain: window.location.host, + address, + statement: 'Sign in with Ethereum to masca.io', + uri: window.location.origin, + version: '1', + chainId: 1, + nonce: nonce.replaceAll('-', ''), + }); + + return message.prepareMessage(); + }; + + const handleSignInWithEthereum = async () => { + try { + const provider = new BrowserProvider(window.ethereum); + + const signer = await provider.getSigner(); + + const message = await createSiweMessage(await signer.getAddress()); + + const signature = await signer.signMessage(message); + + const response = await fetch('/api/siwe/verify', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ message, signature }), + credentials: 'include', + }); + + if (!response.ok) throw new Error('Failed to sign in.'); + + const data = await response.json(); + + if (!data.jwt) throw new Error('Failed to sign in.'); + + changeToken(data.jwt); + changeIsSignedIn(true); + } catch (error) { + console.error(error); + } + }; + + return ( + + ); +}; diff --git a/packages/dapp/src/components/SupabaseProvider/index.tsx b/packages/dapp/src/components/SupabaseProvider/index.tsx new file mode 100644 index 000000000..47b7c90b4 --- /dev/null +++ b/packages/dapp/src/components/SupabaseProvider/index.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { useAuthStore } from '@/stores/authStore'; +import { SignInWithEthereum } from '../SignInWithEthereum'; + +export const SupabaseProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const isSignedIn = useAuthStore((state) => state.isSignedIn); + + if (!isSignedIn) + return ( +
+ +
+ ); + + return children; +}; diff --git a/packages/dapp/src/middleware.ts b/packages/dapp/src/middleware.ts index afcdc0f7e..a56c81e44 100644 --- a/packages/dapp/src/middleware.ts +++ b/packages/dapp/src/middleware.ts @@ -1,12 +1,19 @@ -import createMiddleware from 'next-intl/middleware'; +import { NextRequest } from 'next/server'; +import createIntlMiddleware from 'next-intl/middleware'; -export default createMiddleware({ - // A list of all locales that are supported - locales: ['en'], +export async function middleware(request: NextRequest) { + const handleI18nRouting = createIntlMiddleware({ + // A list of all locales that are supported + locales: ['en'], - // If this locale is matched, pathnames work without a prefix (e.g. `/about`) - defaultLocale: 'en', -}); + // If this locale is matched, pathnames work without a prefix (e.g. `/about`) + defaultLocale: 'en', + }); + + const response = handleI18nRouting(request); + + return response; +} export const config = { // Skip all paths that should not be internationalized diff --git a/packages/dapp/src/stores/authStore.ts b/packages/dapp/src/stores/authStore.ts new file mode 100644 index 000000000..5b881cf67 --- /dev/null +++ b/packages/dapp/src/stores/authStore.ts @@ -0,0 +1,24 @@ +import { shallow } from 'zustand/shallow'; +import { createWithEqualityFn } from 'zustand/traditional'; + +interface AuthStore { + isSignedIn: boolean; + token: string | null; + changeToken: (token: string) => void; + changeIsSignedIn: (isSignedIn: boolean) => void; +} + +export const authStoreInitialState = { + isSignedIn: false, + token: null, +}; + +export const useAuthStore = createWithEqualityFn()( + (set) => ({ + ...authStoreInitialState, + + changeToken: (token: string) => set({ token }), + changeIsSignedIn: (isSignedIn: boolean) => set({ isSignedIn }), + }), + shallow +); diff --git a/packages/dapp/src/utils/supabase/client.ts b/packages/dapp/src/utils/supabase/client.ts new file mode 100644 index 000000000..b5df67954 --- /dev/null +++ b/packages/dapp/src/utils/supabase/client.ts @@ -0,0 +1,16 @@ +import { createClient as createSupbaseClient } from '@supabase/supabase-js'; + +import { Database } from './database.types'; + +export const createClient = (token: string) => + createSupbaseClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + global: { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + } + ); diff --git a/packages/dapp/src/utils/supabase/database.types.ts b/packages/dapp/src/utils/supabase/database.types.ts new file mode 100644 index 000000000..20029710c --- /dev/null +++ b/packages/dapp/src/utils/supabase/database.types.ts @@ -0,0 +1,123 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[]; + +export interface Database { + public: { + Tables: { + authorization: { + Row: { + created_at: string; + expires_at: string; + id: number; + nonce: string; + }; + Insert: { + created_at?: string; + expires_at: string; + id?: number; + nonce?: string; + }; + Update: { + created_at?: string; + expires_at?: string; + id?: number; + nonce?: string; + }; + Relationships: []; + }; + credentials: { + Row: { + created_at: string; + credential: Json; + expires_at: string | null; + id: string; + user_id: string; + }; + Insert: { + created_at?: string; + credential: Json; + expires_at?: string | null; + id?: string; + user_id?: string; + }; + Update: { + created_at?: string; + credential?: Json; + expires_at?: string | null; + id?: string; + user_id?: string; + }; + Relationships: [ + { + foreignKeyName: 'credentials_user_id_fkey'; + columns: ['user_id']; + isOneToOne: false; + referencedRelation: 'users'; + referencedColumns: ['id']; + }, + ]; + }; + identities: { + Row: { + addedd_at: string; + did: string; + id: string; + user_id: string; + }; + Insert: { + addedd_at?: string; + did: string; + id?: string; + user_id: string; + }; + Update: { + addedd_at?: string; + did?: string; + id?: string; + user_id?: string; + }; + Relationships: [ + { + foreignKeyName: 'identities_user_id_fkey'; + columns: ['user_id']; + isOneToOne: false; + referencedRelation: 'users'; + referencedColumns: ['id']; + }, + ]; + }; + users: { + Row: { + address: string; + id: string; + }; + Insert: { + address: string; + id?: string; + }; + Update: { + address?: string; + id?: string; + }; + Relationships: []; + }; + }; + Views: { + [_ in never]: never; + }; + Functions: { + [_ in never]: never; + }; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; +} diff --git a/packages/dapp/src/utils/supabase/helper.types.ts b/packages/dapp/src/utils/supabase/helper.types.ts new file mode 100644 index 000000000..ed10e7f34 --- /dev/null +++ b/packages/dapp/src/utils/supabase/helper.types.ts @@ -0,0 +1,4 @@ +import { Database } from './database.types'; + +export type Tables = + Database['public']['Tables'][T]['Row']; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03178c8ea..f539a8aa4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -389,12 +389,18 @@ importers: packages/dapp: dependencies: + '@blockchain-lab-um/did-provider-key': + specifier: 1.0.7 + version: 1.0.7 '@blockchain-lab-um/masca-connector': specifier: 1.2.1 version: link:../connector '@blockchain-lab-um/oidc-types': specifier: 0.0.8 version: link:../../libs/oidc/types + '@ethersproject/providers': + specifier: ^5.7.2 + version: 5.7.2 '@headlessui/react': specifier: ^1.7.17 version: 1.7.17(react-dom@18.2.0)(react@18.2.0) @@ -416,12 +422,33 @@ importers: '@react-oauth/google': specifier: ^0.11.1 version: 0.11.1(react-dom@18.2.0)(react@18.2.0) + '@supabase/ssr': + specifier: ^0.0.10 + version: 0.0.10(@supabase/supabase-js@2.39.0) + '@supabase/supabase-js': + specifier: ^2.38.5 + version: 2.39.0 '@tanstack/react-table': specifier: ^8.10.7 version: 8.10.7(react-dom@18.2.0)(react@18.2.0) '@veramo/core': specifier: 5.5.3 version: 5.5.3 + '@veramo/credential-eip712': + specifier: 5.5.3 + version: 5.5.3 + '@veramo/credential-w3c': + specifier: 5.5.3 + version: 5.5.3(expo@49.0.16)(react-native@0.72.6) + '@veramo/did-provider-ethr': + specifier: 5.5.3 + version: 5.5.3 + '@veramo/did-provider-pkh': + specifier: 5.5.3 + version: 5.5.3 + '@veramo/did-resolver': + specifier: 5.5.3 + version: 5.5.3 '@veramo/utils': specifier: 5.5.3 version: 5.5.3 @@ -434,9 +461,21 @@ importers: clsx: specifier: ^2.0.0 version: 2.0.0 + date-fns: + specifier: ^2.30.0 + version: 2.30.0 did-jwt-vc: specifier: ^3.2.13 version: 3.2.13 + did-resolver: + specifier: 4.1.0 + version: 4.1.0 + ethers: + specifier: ^6.9.0 + version: 6.9.0 + ethr-did-resolver: + specifier: 8.1.2 + version: 8.1.2 file-saver: specifier: ^2.0.5 version: 2.0.5 @@ -449,6 +488,9 @@ importers: html5-qrcode: specifier: ^2.3.8 version: 2.3.8 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 luxon: specifier: ^3.4.3 version: 3.4.3 @@ -479,6 +521,9 @@ importers: sharp: specifier: ^0.32.6 version: 0.32.6 + siwe: + specifier: ^2.1.4 + version: 2.1.4(ethers@6.9.0) swr: specifier: ^2.2.4 version: 2.2.4(react@18.2.0) @@ -495,6 +540,9 @@ importers: '@types/file-saver': specifier: ^2.0.6 version: 2.0.6 + '@types/jsonwebtoken': + specifier: ^9.0.5 + version: 9.0.5 '@types/luxon': specifier: ^3.3.3 version: 3.3.3 @@ -540,6 +588,9 @@ importers: stylelint-webpack-plugin: specifier: ^4.1.1 version: 4.1.1(stylelint@15.11.0)(webpack@5.89.0) + supabase: + specifier: ^1.113.3 + version: 1.115.4 tailwindcss: specifier: ^3.3.5 version: 3.3.5(ts-node@10.9.1) @@ -2956,6 +3007,33 @@ packages: resolution: {integrity: sha512-R524tD5VwOt3QRHr7N518nqTVR/HKgfWL4LypekcGuNQN8R4PWScvuRcRzrY39A28kLztMv+TJdiKuMNbkU1ug==} engines: {node: '>=8.9'} + /@blockchain-lab-um/did-provider-key@1.0.7: + resolution: {integrity: sha512-pkWO0llegx4mPzeiEG6ZJZekuJ/+mriGQ1MNK6Pgc4mnzxuvCBjyG+8eZpcpR98gInENfSz4bDb0lqHQ93xkHQ==} + dependencies: + '@blockchain-lab-um/utils': 1.3.6 + '@cef-ebsi/key-did-resolver': 1.1.0 + '@stablelib/ed25519': 1.0.3 + '@veramo/core': 5.4.1 + '@veramo/did-manager': 5.4.1 + did-resolver: 4.1.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /@blockchain-lab-um/utils@1.3.6: + resolution: {integrity: sha512-pQLflCwhzuV8tYtJW2DNdx0WqjrsKiH6QUGdWMTRPBIIxDKqOvHNLfxw29SG2FXX2JcfrTqNG80aX+qCdAdlfg==} + dependencies: + '@veramo/utils': 5.4.1 + did-resolver: 4.1.0 + elliptic: 6.5.4 + multiformats: 11.0.2 + secp256k1: 5.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /@cef-ebsi/ebsi-did-resolver@3.2.0: resolution: {integrity: sha512-4wM5TK5l6xRBSkOCEZRP64Xqsd0BqmvjfSpREn8fXbrpMZqOafku77YQSMzqFmOIQ62HSEi5Vf4CSzDHAmalig==} engines: {node: '>=16.10.0'} @@ -3173,7 +3251,7 @@ packages: /@changesets/apply-release-plan@6.1.4: resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.2 '@changesets/config': 2.3.1 '@changesets/get-version-range-type': 0.3.2 '@changesets/git': 2.0.0 @@ -3191,7 +3269,7 @@ packages: /@changesets/assemble-release-plan@5.2.4(patch_hash=7wd56cmfn2e53tjdtcvljkwy4u): resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.2 '@changesets/errors': 0.1.4 '@changesets/get-dependents-graph': 1.3.6 '@changesets/types': 5.2.1 @@ -3295,7 +3373,7 @@ packages: /@changesets/get-release-plan@3.0.17: resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.2 '@changesets/assemble-release-plan': 5.2.4(patch_hash=7wd56cmfn2e53tjdtcvljkwy4u) '@changesets/config': 2.3.1 '@changesets/pre': 1.0.14 @@ -3311,7 +3389,7 @@ packages: /@changesets/git@2.0.0: resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.2 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 @@ -3336,7 +3414,7 @@ packages: /@changesets/pre@1.0.14: resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.2 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 @@ -3346,7 +3424,7 @@ packages: /@changesets/read@0.5.9: resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.2 '@changesets/git': 2.0.0 '@changesets/logger': 0.0.5 '@changesets/parse': 0.3.16 @@ -3366,7 +3444,7 @@ packages: /@changesets/write@0.2.3: resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.2 '@changesets/types': 5.2.1 fs-extra: 7.0.1 human-id: 1.0.2 @@ -4706,7 +4784,7 @@ packages: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: '@babel/helper-module-imports': 7.22.15 - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.2 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.2 @@ -6188,7 +6266,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -6234,7 +6312,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -6375,7 +6453,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.5 '@types/istanbul-reports': 3.0.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 '@types/yargs': 17.0.29 chalk: 4.1.2 @@ -6440,7 +6518,7 @@ packages: /@manypkg/find-root@1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.2 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 @@ -6449,7 +6527,7 @@ packages: /@manypkg/get-packages@1.1.3: resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} dependencies: - '@babel/runtime': 7.22.11 + '@babel/runtime': 7.23.2 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -6553,6 +6631,7 @@ packages: /@metamask/eth-sig-util@6.0.1: resolution: {integrity: sha512-Lt2DC4w4Sbtbd4DCsE+vfjdWcnFHSxfSfiJpt66hfLtAbetHsvbZcwKoa46TEA3G/V48ZuS4NWvJmtaA4F8UWA==} engines: {node: '>=14.0.0'} + deprecated: This version generates invalid signatures for messages that include the value "0x" in a "bytes" field. This has been fixed in v6.0.2 and v7.0.1 dependencies: '@ethereumjs/util': 8.1.0 '@metamask/abi-utils': 1.2.0 @@ -8020,6 +8099,15 @@ packages: dependencies: apg-js: 4.2.0 + /@spruceid/siwe-parser@2.0.2: + resolution: {integrity: sha512-9WuA0ios2537cWYu39MMeH0O2KdrMKgKlOBUTWRTXQjCYu5B+mHCA0JkCbFaJ/0EjxoVIcYCXIW/DoPEpw+PqA==} + dependencies: + '@noble/hashes': 1.3.2 + apg-js: 4.2.0 + uri-js: 4.4.1 + valid-url: 1.0.9 + dev: false + /@sqltools/formatter@1.2.5: resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} dev: true @@ -8209,6 +8297,71 @@ packages: - encoding dev: false + /@supabase/functions-js@2.1.5: + resolution: {integrity: sha512-BNzC5XhCzzCaggJ8s53DP+WeHHGT/NfTsx2wUSSGKR2/ikLFQTBCDzMvGz/PxYMqRko/LwncQtKXGOYp1PkPaw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/gotrue-js@2.57.0: + resolution: {integrity: sha512-/CcAW40aPKgp9/w9WgXVUQFg1AOdvFR687ONOMjASPBuC6FsNbKlcXp4pc+rwKNtxyxDkBbR+x7zj/8g00r/Og==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/node-fetch@2.6.15: + resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==} + engines: {node: 4.x || >=6.0.0} + dependencies: + whatwg-url: 5.0.0 + dev: false + + /@supabase/postgrest-js@1.9.0: + resolution: {integrity: sha512-axP6cU69jDrLbfihJKQ6vU27tklD0gzb9idkMN363MtTXeJVt5DQNT3JnJ58JVNBdL74hgm26rAsFNvHk+tnSw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/realtime-js@2.8.4: + resolution: {integrity: sha512-5C9slLTGikHnYmAnIBOaPogAgbcNY68vnIyE6GpqIKjHElVb6LIi4clwNcjHSj4z6szuvvzj8T/+ePEgGEGekw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + '@types/phoenix': 1.6.4 + '@types/websocket': 1.0.10 + websocket: 1.0.34 + transitivePeerDependencies: + - supports-color + dev: false + + /@supabase/ssr@0.0.10(@supabase/supabase-js@2.39.0): + resolution: {integrity: sha512-eVs7+bNlff8Fd79x8K3Jbfpmf8P8QRA1Z6rUDN+fi4ReWvRBZyWOFfR6eqlsX6vTjvGgTiEqujFSkv2PYW5kbQ==} + peerDependencies: + '@supabase/supabase-js': ^2.33.1 + dependencies: + '@supabase/supabase-js': 2.39.0 + cookie: 0.5.0 + ramda: 0.29.1 + dev: false + + /@supabase/storage-js@2.5.5: + resolution: {integrity: sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/supabase-js@2.39.0: + resolution: {integrity: sha512-cYfnwWRW5rYBbPT/BNIejtRT9ULdD9PnIExQV28PZpqcqm3PLwS4f3pY7WGB01Da63VYdvktZPKuYvreqsj/Zg==} + dependencies: + '@supabase/functions-js': 2.1.5 + '@supabase/gotrue-js': 2.57.0 + '@supabase/node-fetch': 2.6.15 + '@supabase/postgrest-js': 1.9.0 + '@supabase/realtime-js': 2.8.4 + '@supabase/storage-js': 2.5.5 + transitivePeerDependencies: + - supports-color + dev: false + /@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.23.2): resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==} engines: {node: '>=10'} @@ -8876,13 +9029,13 @@ packages: /@types/bn.js@5.1.1: resolution: {integrity: sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: false /@types/bn.js@5.1.3: resolution: {integrity: sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==} dependencies: - '@types/node': 20.8.8 + '@types/node': 18.18.7 /@types/body-parser@1.19.4: resolution: {integrity: sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==} @@ -8972,7 +9125,7 @@ packages: /@types/graceful-fs@4.1.8: resolution: {integrity: sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: true /@types/hast@2.3.5: @@ -9034,6 +9187,12 @@ packages: resolution: {integrity: sha512-uo4BNABD0IVcJQjOmOq/atfxHVTniFaQEHrrGSANpWyS96ecej/85YXLvLWNe+tLshUz9/IbMbzImbKNo71WxA==} dev: true + /@types/jsonwebtoken@9.0.5: + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + dependencies: + '@types/node': 18.18.7 + dev: true + /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: @@ -9095,11 +9254,6 @@ packages: dependencies: undici-types: 5.26.5 - /@types/node@20.8.8: - resolution: {integrity: sha512-YRsdVxq6OaLfmR9Hy816IMp33xOBjfyOgUd77ehqg96CFywxAPbDbXvAsuN2KVg2HOT8Eh6uAfU+l4WffwPVrQ==} - dependencies: - undici-types: 5.25.3 - /@types/node@20.8.9: resolution: {integrity: sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==} dependencies: @@ -9124,7 +9278,11 @@ packages: /@types/pbkdf2@3.1.0: resolution: {integrity: sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==} dependencies: - '@types/node': 20.8.9 + '@types/node': 18.18.7 + + /@types/phoenix@1.6.4: + resolution: {integrity: sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA==} + dev: false /@types/prismjs@1.26.0: resolution: {integrity: sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==} @@ -9182,7 +9340,7 @@ packages: /@types/sax@1.2.4: resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 dev: false /@types/scheduler@0.16.5: @@ -9233,6 +9391,12 @@ packages: resolution: {integrity: sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==} dev: true + /@types/websocket@1.0.10: + resolution: {integrity: sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==} + dependencies: + '@types/node': 18.18.7 + dev: false + /@types/ws@8.5.8: resolution: {integrity: sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==} dependencies: @@ -9501,6 +9665,17 @@ packages: transitivePeerDependencies: - supports-color + /@veramo/core@5.4.1: + resolution: {integrity: sha512-qmd/0lYKuxZyT3N85VrRe6XwJDRqs+NRHFbWnO9T+/G2LGu1P/VBKeHNdPaLHTK4jTyfkyXTwHc2513T1Tvfjg==} + dependencies: + '@veramo/core-types': 5.5.3 + debug: 4.3.4 + events: 3.3.0 + z-schema: 6.0.1 + transitivePeerDependencies: + - supports-color + dev: false + /@veramo/core@5.5.3: resolution: {integrity: sha512-Z74AaGJB6syijEr+GgdlIf+4pXB46BDHKWEnL3ca1HOLmfCiZgpO94dl27+x+tMWBKQAGs0fEJj2yQQengre1g==} dependencies: @@ -9627,6 +9802,15 @@ packages: transitivePeerDependencies: - supports-color + /@veramo/did-manager@5.4.1: + resolution: {integrity: sha512-6Tz5elqYt/k7POT7rB6qZ/wsf6/uu5P1bRHk55eti+5u67PQF8/4/EqoLf4vxXCNk8fC+vttASpGQrCj74cnog==} + dependencies: + '@veramo/core-types': 5.5.3 + '@veramo/did-discovery': 5.5.3 + transitivePeerDependencies: + - supports-color + dev: false + /@veramo/did-manager@5.5.3: resolution: {integrity: sha512-nDmoZV0MV5fyjn0ApSh0wgTb9cSuVIRa4lUbkOG1P/7TkQMunPsQk3gw95tflEU5B/m7CClCUiKwk8qh8Mf8kw==} dependencies: @@ -9746,6 +9930,27 @@ packages: transitivePeerDependencies: - supports-color + /@veramo/utils@5.4.1: + resolution: {integrity: sha512-9TcONSD1PKxrlU3frE/Vp33wTauTsoTSLK0ubWEkzxeH9XYVgXhhe4jRBVfYS2aRz4nCNYesfWNgiX2QYSV+IQ==} + dependencies: + '@ethersproject/signing-key': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@veramo/core-types': 5.5.3 + credential-status: 2.0.6 + cross-fetch: 3.1.8(patch_hash=o7exbxzvysudd5km3yp6v3mgsi) + debug: 4.3.4 + did-jwt: 7.4.4 + did-jwt-vc: 3.2.13 + did-resolver: 4.1.0 + multiformats: 12.1.3 + uint8arrays: 4.0.6 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /@veramo/utils@5.5.3: resolution: {integrity: sha512-ZvlMiO2KB7qScfy68+ubjY/9z42u4aK8Yd0bn498cUD2emCLg94gnpi9bw0ntLR4fIarSfwT9dt5kepjGZ/nPw==} dependencies: @@ -10105,7 +10310,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: false /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} @@ -10689,7 +10893,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.2 cosmiconfig: 7.1.0 resolve: 1.22.4 dev: false @@ -11006,6 +11210,16 @@ packages: resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} dev: false + /bin-links@4.0.3: + resolution: {integrity: sha512-obsRaULtJurnfox/MDwgq6Yo9kzbv1CPTk/1/s7Z/61Lezc8IKkFCOXNeVLXz0456WRzBQmSsDWlai2tIhBsfA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + cmd-shim: 6.0.2 + npm-normalize-package-bin: 3.0.1 + read-cmd-shim: 4.0.0 + write-file-atomic: 5.0.1 + dev: true + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -11404,6 +11618,14 @@ packages: base64-js: 1.5.1 ieee754: 1.2.1 + /bufferutil@4.0.8: + resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.1 + dev: false + /builtin-status-codes@3.0.0: resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} dev: true @@ -11452,7 +11674,7 @@ packages: resolution: {integrity: sha512-rpwfAcS/CMqo0oCqDf3r9eeLgScRE3l/xHDCXhM3UyrfvIn7PrLq63uHh7yYbv8NzaZn5MVsVhIRpQ+5GZ5HyA==} engines: {node: '>=8'} dependencies: - '@noble/hashes': 1.1.5 + '@noble/hashes': 1.3.2 base-x: 4.0.0 dev: false @@ -11894,6 +12116,11 @@ packages: engines: {node: '>=6'} dev: false + /cmd-shim@6.0.2: + resolution: {integrity: sha512-+FFYbB0YLaAkhkcrjkyNLYDiOsFSfRjwjY19LXk/psmMx1z00xlCv7hhQoTGXXIKi+YXHL/iiFo8NqMVQX9nOw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -12391,7 +12618,7 @@ packages: /cross-fetch@3.1.8(patch_hash=o7exbxzvysudd5km3yp6v3mgsi): resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} dependencies: - node-fetch: 2.6.12 + node-fetch: 2.7.0 transitivePeerDependencies: - encoding patched: true @@ -12715,6 +12942,13 @@ packages: stream-transform: 2.1.3 dev: false + /d@1.0.1: + resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} + dependencies: + es5-ext: 0.10.62 + type: 1.2.0 + dev: false + /dag-jose-utils@3.0.0: resolution: {integrity: sha512-gu+XutOTy3kD8fDcA1SMjZ2U0mUOb/hxoRVZaMCizXN7Ssbc5dKOzeXQ4GquV4BdQzs3w5Y7irOpn2plFPIJfg==} dependencies: @@ -12760,7 +12994,6 @@ packages: engines: {node: '>=0.11'} dependencies: '@babel/runtime': 7.23.2 - dev: true /dayjs@1.11.10: resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} @@ -13617,6 +13850,24 @@ packages: is-date-object: 1.0.5 is-symbol: 1.0.4 + /es5-ext@0.10.62: + resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==} + engines: {node: '>=0.10'} + requiresBuild: true + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + next-tick: 1.1.0 + dev: false + + /es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-symbol: 3.1.3 + dev: false + /es6-object-assign@1.1.0: resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==} dev: true @@ -13625,6 +13876,13 @@ packages: resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} dev: false + /es6-symbol@3.1.3: + resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==} + dependencies: + d: 1.0.1 + ext: 1.7.0 + dev: false + /esbuild-loader@4.0.2(webpack@5.89.0): resolution: {integrity: sha512-kj88m0yrtTEJDeUEF+3TZsq7t9VPzQQj7UmXAzUbIaipoYSrd0UxKAcg4l9CBgP8uVoploiw+nKr8DIv6Y9gXw==} peerDependencies: @@ -14239,6 +14497,22 @@ packages: - utf-8-validate dev: false + /ethers@6.9.0: + resolution: {integrity: sha512-pmfNyQzc2mseLe91FnT2vmNaTt8dDzhxZ/xItAV7uGsF4dI4ek2ufMu3rAkgQETL/TIs0GS5A+U05g9QyWnv3Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@types/node': 18.15.13 + aes-js: 4.0.0-beta.5 + tslib: 2.4.0 + ws: 8.5.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /ethjs-unit@0.1.6: resolution: {integrity: sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==} engines: {node: '>=6.5.0', npm: '>=3'} @@ -14578,6 +14852,12 @@ packages: transitivePeerDependencies: - supports-color + /ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + dependencies: + type: 2.7.2 + dev: false + /extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -15142,7 +15422,7 @@ packages: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} dependencies: - minipass: 3.1.6 + minipass: 3.3.6 /fs-monkey@1.0.5: resolution: {integrity: sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==} @@ -15984,7 +16264,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: false /human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} @@ -16802,7 +17081,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -16899,7 +17178,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -16926,7 +17205,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.8 - '@types/node': 18.18.6 + '@types/node': 18.18.7 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -16976,7 +17255,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-util: 29.7.0 /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -17025,7 +17304,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -17120,7 +17399,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 18.18.6 + '@types/node': 18.18.7 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.11 @@ -17167,7 +17446,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.18.7 + '@types/node': 20.8.9 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -17185,7 +17464,7 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 18.18.6 + '@types/node': 18.18.7 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -17475,6 +17754,22 @@ packages: base64-js: 1.5.1 dev: false + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.5.4 + dev: false + /jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -17485,6 +17780,14 @@ packages: object.values: 1.1.7 dev: true + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + /jwa@2.0.0: resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} dependencies: @@ -17493,6 +17796,13 @@ packages: safe-buffer: 5.2.1 dev: false + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: false + /jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} dependencies: @@ -17877,9 +18187,17 @@ packages: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} dev: false + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false + /lodash.invokemap@4.6.0: resolution: {integrity: sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==} + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false + /lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} dev: false @@ -17888,9 +18206,21 @@ packages: resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} dev: true + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false + + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false + /lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false + /lodash.kebabcase@4.1.1: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} dev: true @@ -17909,6 +18239,10 @@ packages: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} dev: true + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false + /lodash.pullall@4.2.0: resolution: {integrity: sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==} @@ -18746,6 +19080,7 @@ packages: engines: {node: '>=8'} dependencies: yallist: 4.0.0 + optional: true /minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} @@ -18756,7 +19091,6 @@ packages: /minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} - optional: true /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} @@ -18767,7 +19101,7 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} dependencies: - minipass: 3.1.6 + minipass: 3.3.6 yallist: 4.0.0 /mixme@0.5.9: @@ -18971,6 +19305,10 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + dev: false + /next@13.5.6(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5): resolution: {integrity: sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==} engines: {node: '>=16.14.0'} @@ -19074,17 +19412,6 @@ packages: dependencies: lodash: 4.17.21 - /node-fetch@2.6.12: - resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - dependencies: - whatwg-url: 5.0.0 - /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -19208,6 +19535,11 @@ packages: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} + /npm-normalize-package-bin@3.0.1: + resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + /npm-package-arg@11.0.1: resolution: {integrity: sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==} engines: {node: ^16.14.0 || >=18.0.0} @@ -20817,6 +21149,10 @@ packages: ffjavascript: 0.2.56 dev: false + /ramda@0.29.1: + resolution: {integrity: sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==} + dev: false + /randexp@0.5.3: resolution: {integrity: sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==} engines: {node: '>=4'} @@ -21152,6 +21488,11 @@ packages: dependencies: pify: 2.3.0 + /read-cmd-shim@4.0.0: + resolution: {integrity: sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + /read-only-stream@2.0.0: resolution: {integrity: sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==} dependencies: @@ -22180,6 +22521,18 @@ packages: sax: 1.3.0 dev: false + /siwe@2.1.4(ethers@6.9.0): + resolution: {integrity: sha512-Dke1Qqa3mgiLm3vjqw/+SQ7dl8WV/Pfk3AlQBF94cBFydTYhztngqYrikzE3X5UTsJ6565dfVbQptszsuYZNYg==} + peerDependencies: + ethers: ^5.6.8 || ^6.0.8 + dependencies: + '@spruceid/siwe-parser': 2.0.2 + '@stablelib/random': 1.0.2 + ethers: 6.9.0 + uri-js: 4.4.1 + valid-url: 1.0.9 + dev: false + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -22993,6 +23346,20 @@ packages: resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} optional: true + /supabase@1.115.4: + resolution: {integrity: sha512-VXfz9riWHKEmbzv8ITJuqVCVl/92DLhKvUMiU9tVwCBylMWMqg0hOoh89VSZETektZOtxXk24jCQ9zrBqodzKQ==} + engines: {npm: '>=8'} + hasBin: true + requiresBuild: true + dependencies: + bin-links: 4.0.3 + https-proxy-agent: 7.0.2 + node-fetch: 3.3.2 + tar: 6.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /superstruct@1.0.3: resolution: {integrity: sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==} engines: {node: '>=14.0.0'} @@ -23216,7 +23583,6 @@ packages: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 - optional: true /temp-dir@1.0.0: resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} @@ -23790,6 +24156,14 @@ packages: media-typer: 0.3.0 mime-types: 2.1.35 + /type@1.2.0: + resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} + dev: false + + /type@2.7.2: + resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} + dev: false + /typed-array-buffer@1.0.0: resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} engines: {node: '>= 0.4'} @@ -23991,9 +24365,6 @@ packages: resolution: {integrity: sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==} dev: false - /undici-types@5.25.3: - resolution: {integrity: sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==} - /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -24294,6 +24665,14 @@ packages: dependencies: react: 18.2.0 + /utf-8-validate@5.0.10: + resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.1 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -24360,7 +24739,6 @@ packages: /valid-url@1.0.9: resolution: {integrity: sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==} - optional: true /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -24982,6 +25360,20 @@ packages: resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} engines: {node: '>=0.8.0'} + /websocket@1.0.34: + resolution: {integrity: sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==} + engines: {node: '>=4.0.0'} + dependencies: + bufferutil: 4.0.8 + debug: 2.6.9 + es5-ext: 0.10.62 + typedarray-to-buffer: 3.1.5 + utf-8-validate: 5.0.10 + yaeti: 0.0.6 + transitivePeerDependencies: + - supports-color + dev: false + /whatwg-fetch@3.6.19: resolution: {integrity: sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==} optional: true @@ -25289,6 +25681,11 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + /yaeti@0.0.6: + resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==} + engines: {node: '>=0.10.32'} + dev: false + /yallist@2.1.2: resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} dev: false From b738518b825ee884404f3b30db80def4b5ccc55e Mon Sep 17 00:00:00 2001 From: martines3000 Date: Wed, 29 Nov 2023 16:14:46 +0100 Subject: [PATCH 02/28] feat: basic share view page, add next-ui --- packages/dapp/.npmrc | 1 + packages/dapp/package.json | 2 + .../app/(protected)/dashboard/identities.tsx | 13 +- .../share-presentation/[id]/formatedView.tsx | 63 + .../(public)/share-presentation/[id]/page.tsx | 80 + .../share-presentation/[id]/tabsWrapper.tsx | 44 + .../app/(public)/share-presentation/error.tsx | 32 + .../(public)/share-presentation/not-found.tsx | 5 + .../app/api/share/presentation/[id]/route.ts | 66 + .../src/app/api/share/presentation/route.ts | 57 +- .../dapp/src/components/AppNavbar/index.tsx | 10 + .../CredentialDisplay/FormatedPanel.tsx | 68 +- .../CredentialDisplay/JsonPanel.tsx | 11 +- .../components/CredentialDisplay/index.tsx | 2 +- .../dapp/src/components/DIDDisplay/index.tsx | 27 + .../{JsonMdoal => JsonModal}/index.tsx | 0 .../QRSessionDisplay/CredentialView.tsx | 2 +- .../src/components/ThemeProvider/index.tsx | 13 +- packages/dapp/src/messages/en.json | 4 +- .../dapp/src/utils/supabase/database.types.ts | 180 +- ...tailwind.config.cjs => tailwind.config.js} | 13 +- pnpm-lock.yaml | 3924 +++++++++++++---- 22 files changed, 3719 insertions(+), 898 deletions(-) create mode 100644 packages/dapp/.npmrc create mode 100644 packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/formatedView.tsx create mode 100644 packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/page.tsx create mode 100644 packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/tabsWrapper.tsx create mode 100644 packages/dapp/src/app/[locale]/app/(public)/share-presentation/error.tsx create mode 100644 packages/dapp/src/app/[locale]/app/(public)/share-presentation/not-found.tsx create mode 100644 packages/dapp/src/app/api/share/presentation/[id]/route.ts create mode 100644 packages/dapp/src/components/DIDDisplay/index.tsx rename packages/dapp/src/components/{JsonMdoal => JsonModal}/index.tsx (100%) rename packages/dapp/{tailwind.config.cjs => tailwind.config.js} (95%) diff --git a/packages/dapp/.npmrc b/packages/dapp/.npmrc new file mode 100644 index 000000000..1778f104e --- /dev/null +++ b/packages/dapp/.npmrc @@ -0,0 +1 @@ +public-hoist-pattern[]=*@nextui-org/* diff --git a/packages/dapp/package.json b/packages/dapp/package.json index eff485d0e..0ea4d7a66 100644 --- a/packages/dapp/package.json +++ b/packages/dapp/package.json @@ -28,6 +28,7 @@ "@heroicons/react": "^2.0.18", "@metamask/detect-provider": "^2.0.0", "@metamask/providers": "13.1.0", + "@nextui-org/react": "^2.2.9", "@prisma/client": "^5.5.2", "@radix-ui/react-toast": "^1.1.5", "@react-oauth/google": "^0.11.1", @@ -50,6 +51,7 @@ "ethers": "^6.9.0", "ethr-did-resolver": "8.1.2", "file-saver": "^2.0.5", + "framer-motion": "^10.16.5", "googleapis": "^128.0.0", "headless-stepper": "^1.9.1", "html5-qrcode": "^2.3.8", diff --git a/packages/dapp/src/app/[locale]/app/(protected)/dashboard/identities.tsx b/packages/dapp/src/app/[locale]/app/(protected)/dashboard/identities.tsx index a7e752185..bf4695818 100644 --- a/packages/dapp/src/app/[locale]/app/(protected)/dashboard/identities.tsx +++ b/packages/dapp/src/app/[locale]/app/(protected)/dashboard/identities.tsx @@ -35,21 +35,22 @@ export const Identities = () => { const handleShareCredential = async () => { try { const presentation = { - holder: 'did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61', + holder: 'did:ethr:0x1:0x81b9ce991ce4c9e1b810325f4de85baaf222e041', verifiableCredential: [ - '{"type":["VerifiableCredential","MascaUserCredential"],"proof":{"type":"EthereumEip712Signature2021","eip712":{"types":{"Proof":[{"name":"created","type":"string"},{"name":"proofPurpose","type":"string"},{"name":"type","type":"string"},{"name":"verificationMethod","type":"string"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"}],"CredentialSchema":[{"name":"id","type":"string"},{"name":"type","type":"string"}],"CredentialSubject":[{"name":"id","type":"string"},{"name":"type","type":"string"}],"VerifiableCredential":[{"name":"@context","type":"string[]"},{"name":"credentialSchema","type":"CredentialSchema"},{"name":"credentialSubject","type":"CredentialSubject"},{"name":"issuanceDate","type":"string"},{"name":"issuer","type":"string"},{"name":"proof","type":"Proof"},{"name":"type","type":"string[]"}]},"domain":{"name":"VerifiableCredential","chainId":1,"version":"1"},"primaryType":"VerifiableCredential"},"created":"2023-09-04T12:28:25.221Z","proofValue":"0xb29ad85e9e4041d149b5784ad593f51239a22e7cde4b4e11e1bbbc0a6b92a4fa4fbc94c181f230a2a31f41b6b9f23af21d337ce244adf75ca506f07f6b67cd841c","proofPurpose":"assertionMethod","verificationMethod":"did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61#controller"},"issuer":"did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61","@context":["https://www.w3.org/2018/credentials/v1"],"issuanceDate":"2023-09-04T12:28:25.221Z","credentialSchema":{"id":"https://beta.api.schemas.serto.id/v1/public/program-completion-certificate/1.0/json-schema.json","type":"JsonSchemaValidator2018"},"credentialSubject":{"id":"did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61","type":"Regular User"}}', + '{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://beta.api.schemas.serto.id/v1/public/program-completion-certificate/1.0/json-schema.json","type":"JsonSchemaValidator2018"},"credentialSubject":{"id":"did:ethr:0x1:0x81b9ce991ce4c9e1b810325f4de85baaf222e041","type":"Regular Ussdfasdfsdaer"},"issuanceDate":"2023-11-29T14:09:21.387Z","issuer":"did:ethr:0x1:0x81b9ce991ce4c9e1b810325f4de85baaf222e041","proof":{"created":"2023-11-29T14:09:21.387Z","eip712":{"domain":{"chainId":1,"name":"VerifiableCredential","version":"1"},"primaryType":"VerifiableCredential","types":{"CredentialSchema":[{"name":"id","type":"string"},{"name":"type","type":"string"}],"CredentialSubject":[{"name":"id","type":"string"},{"name":"type","type":"string"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"}],"Proof":[{"name":"created","type":"string"},{"name":"proofPurpose","type":"string"},{"name":"type","type":"string"},{"name":"verificationMethod","type":"string"}],"VerifiableCredential":[{"name":"@context","type":"string[]"},{"name":"credentialSchema","type":"CredentialSchema"},{"name":"credentialSubject","type":"CredentialSubject"},{"name":"issuanceDate","type":"string"},{"name":"issuer","type":"string"},{"name":"proof","type":"Proof"},{"name":"type","type":"string[]"}]}},"proofPurpose":"assertionMethod","proofValue":"0xf4b918822cd3e5a3d0cb662cc5a512fe1afc1b19414b262a914f7e8fa6a35ef026ed1bcc7a1516e24f02d39443f48eea0e236d4d3551e5d3ae244b2587edbdb91b","type":"EthereumEip712Signature2021","verificationMethod":"did:ethr:0x1:0x81b9ce991ce4c9e1b810325f4de85baaf222e041#controller"},"type":["VerifiableCredential","MascaUserCredential"]}', + '{"type":["VerifiableCredential","MascaUserCredential"],"proof":{"type":"EthereumEip712Signature2021","eip712":{"types":{"Proof":[{"name":"created","type":"string"},{"name":"proofPurpose","type":"string"},{"name":"type","type":"string"},{"name":"verificationMethod","type":"string"}],"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"}],"CredentialSchema":[{"name":"id","type":"string"},{"name":"type","type":"string"}],"CredentialSubject":[{"name":"id","type":"string"},{"name":"type","type":"string"}],"VerifiableCredential":[{"name":"@context","type":"string[]"},{"name":"credentialSchema","type":"CredentialSchema"},{"name":"credentialSubject","type":"CredentialSubject"},{"name":"issuanceDate","type":"string"},{"name":"issuer","type":"string"},{"name":"proof","type":"Proof"},{"name":"type","type":"string[]"}]},"domain":{"name":"VerifiableCredential","chainId":1,"version":"1"},"primaryType":"VerifiableCredential"},"created":"2023-11-29T10:03:00.315Z","proofValue":"0xcb1ad947ee0c6d08a862a39bd4ec0b96e839a14aba8f874374174bd8c469d3b0676e67330cf4525b35c9e1d5707256de103c1b473ae043997a9f00dad6abefba1c","proofPurpose":"assertionMethod","verificationMethod":"did:ethr:0x1:0x81b9ce991ce4c9e1b810325f4de85baaf222e041#controller"},"issuer":"did:ethr:0x1:0x81b9ce991ce4c9e1b810325f4de85baaf222e041","@context":["https://www.w3.org/2018/credentials/v1"],"issuanceDate":"2023-11-29T10:03:00.315Z","credentialSchema":{"id":"https://beta.api.schemas.serto.id/v1/public/program-completion-certificate/1.0/json-schema.json","type":"JsonSchemaValidator2018"},"credentialSubject":{"id":"did:ethr:0x1:0x81b9ce991ce4c9e1b810325f4de85baaf222e041","type":"Regular User"}}', ], type: ['VerifiablePresentation', 'Custom'], '@context': ['https://www.w3.org/2018/credentials/v1'], - issuanceDate: '2023-11-28T14:41:54.514Z', + issuanceDate: '2023-11-29T14:09:35.264Z', proof: { verificationMethod: - 'did:ethr:0x1:0x5c97460ef76c4c26a8a82a828b74492bc8c09b61#controller', - created: '2023-11-28T14:41:54.514Z', + 'did:ethr:0x1:0x81b9ce991ce4c9e1b810325f4de85baaf222e041#controller', + created: '2023-11-29T14:09:35.264Z', proofPurpose: 'assertionMethod', type: 'EthereumEip712Signature2021', proofValue: - '0x690c7c8c1036da74ca4c3c692541ac711bc06cbb22b9855de2bc19e955458c025e2b0658949002f7e2fc64ef70fb6b0ee597059acbcc3741eb46cf9e7bd9941a1c', + '0xca87173f4059e2cb807c6e635e3701e11222c9deb827c5125f4d1329078b804335bfc37b4eca2e90c23ebda98c18d83503462a25ce9076cf1899aeb1435213021b', eip712: { domain: { chainId: 1, diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/formatedView.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/formatedView.tsx new file mode 100644 index 000000000..1ae519c68 --- /dev/null +++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/formatedView.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { usePathname, useRouter } from 'next/navigation'; +import { Pagination } from '@nextui-org/react'; +import { VerifiableCredential } from '@veramo/core'; + +import FormatedPanel from '@/components/CredentialDisplay/FormatedPanel'; +import { DIDDisplay } from '@/components/DIDDisplay'; + +export const FormatedView = ({ + credential, + holder, + issuanceDate, + page, + total, +}: { + credential: VerifiableCredential; + holder: string; + issuanceDate: string | undefined; + page: string; + total: number; +}) => { + const router = useRouter(); + const pathname = usePathname(); + return ( + <> +
+ { + const params = new URLSearchParams(window.location.search); + params.set('page', val.toString()); + params.set('view', 'normal'); + router.replace(`${pathname}?${params.toString()}`); + }} + /> +
+
+
+
+

+ Holder: +

+ +
+ {issuanceDate && ( +
+

+ Issuance date: +

+ {new Date(Date.parse(issuanceDate)).toDateString()} +
+ )} +
+ +
+ + ); +}; diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/page.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/page.tsx new file mode 100644 index 000000000..79d914eb7 --- /dev/null +++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/page.tsx @@ -0,0 +1,80 @@ +import { Metadata } from 'next'; +import { notFound } from 'next/navigation'; +import { createClient } from '@supabase/supabase-js'; +import { VerifiablePresentation } from '@veramo/core'; +import { decodeCredentialToObject } from '@veramo/utils'; + +import JsonPanel from '@/components/CredentialDisplay/JsonPanel'; +import { Database } from '@/utils/supabase/database.types'; +import { FormatedView } from './formatedView'; +import { TabWrapper } from './tabsWrapper'; + +export const metadata: Metadata = { + title: 'Share presentation', + description: '', +}; + +const getPresentation = async (id: string): Promise => { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SECRET_KEY! + ); + + const { data, error } = await supabase + .from('presentations') + .select() + .eq('id', id) + .limit(1); + + if (error) { + throw new Error('Failed to fetch presentation'); + } + + if (!data || data.length === 0) { + return notFound(); + } + + const presentation = data[0].presentation as VerifiablePresentation; + return presentation; +}; + +export default async function Page({ + params: { id }, + searchParams, +}: { + params: { id: string }; + searchParams: { + view: 'normal' | 'json'; + page: string | undefined; + }; +}) { + const presentation = await getPresentation(id); + const credentials = presentation.verifiableCredential + ? presentation.verifiableCredential.map(decodeCredentialToObject) + : []; + const page = searchParams.page ?? '1'; + const view = searchParams.view ?? 'normal'; + return ( +
+
+ + } + JsonView={ +
+ +
+ } + /> +
+
+ ); +} diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/tabsWrapper.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/tabsWrapper.tsx new file mode 100644 index 000000000..d27d310b3 --- /dev/null +++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/tabsWrapper.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { usePathname, useRouter } from 'next/navigation'; +import { Tab, Tabs } from '@nextui-org/react'; + +interface TabWrapperProps { + view: 'normal' | 'json'; + FormatedView: React.ReactNode; + JsonView: React.ReactNode; +} + +export const TabWrapper = ({ + view, + FormatedView, + JsonView, +}: TabWrapperProps) => { + const router = useRouter(); + const pathname = usePathname(); + + return ( + { + const params = new URLSearchParams(window.location.search); + + params.set('view', key.toString()); + if (key === 'json') { + params.delete('page'); + } else { + params.set('page', '1'); + } + + router.replace(`${pathname}?${params.toString()}`); + }} + > + + {FormatedView} + + + {JsonView} + + + ); +}; diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/error.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/error.tsx new file mode 100644 index 000000000..6bee7300e --- /dev/null +++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/error.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { useEffect } from 'react'; + +// TODO: Add design for this +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + // Optionally log the error to an error reporting service + console.error(error); + }, [error]); + + return ( +
+

Something went wrong!

+ +
+ ); +} diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/not-found.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/not-found.tsx new file mode 100644 index 000000000..3a79a318a --- /dev/null +++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/not-found.tsx @@ -0,0 +1,5 @@ +import BasicNotFound from '@/components/BasicNotFound'; + +export default function NotFound() { + return ; +} diff --git a/packages/dapp/src/app/api/share/presentation/[id]/route.ts b/packages/dapp/src/app/api/share/presentation/[id]/route.ts new file mode 100644 index 000000000..f637fcd84 --- /dev/null +++ b/packages/dapp/src/app/api/share/presentation/[id]/route.ts @@ -0,0 +1,66 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createClient } from '@supabase/supabase-js'; + +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( + _: NextRequest, + { params: { id } }: { params: { id: string } } +) { + try { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SECRET_KEY! + ); + + const { data, error } = await supabase + .from('presentations') + .select() + .eq('id', id) + .limit(1) + .single(); + + if (error || !data) { + return new NextResponse('Presentation not found', { + status: 404, + headers: { + ...CORS_HEADERS, + }, + }); + } + + return NextResponse.json( + { + presentation: data.presentation, + }, + { + status: 200, + 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/share/presentation/route.ts b/packages/dapp/src/app/api/share/presentation/route.ts index 888263bea..ca23ce664 100644 --- a/packages/dapp/src/app/api/share/presentation/route.ts +++ b/packages/dapp/src/app/api/share/presentation/route.ts @@ -1,11 +1,13 @@ import { NextRequest, NextResponse } from 'next/server'; +import { createClient } from '@supabase/supabase-js'; import jwt from 'jsonwebtoken'; +import { Database } from '@/utils/supabase/database.types'; import { getAgent } from '../../veramoSetup'; const CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET POST OPTIONS', + 'Access-Control-Allow-Methods': 'POST OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }; @@ -48,17 +50,54 @@ export async function POST(request: NextRequest) { presentation, }); - console.log('verified', verified); + if (!verified) { + return new NextResponse('Presentation not valid', { + status: 400, + headers: { + ...CORS_HEADERS, + }, + }); + } - return new NextResponse(null, { - status: 204, - headers: { - ...CORS_HEADERS, + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SECRET_KEY! + ); + + const { data, error } = await supabase + .from('presentations') + .insert({ + user_id: user.sub, + presentation, + created_at: new Date().toISOString(), + }) + .select() + .limit(1) + .single(); + + if (error || !data) { + return new NextResponse('Internal Server Error', { + status: 500, + headers: { + ...CORS_HEADERS, + }, + }); + } + + return NextResponse.json( + { + presentationId: data.id, }, - }); + { + status: 201, + headers: { + ...CORS_HEADERS, + }, + } + ); } catch (error) { - return new NextResponse('Unauthorized', { - status: 401, + return new NextResponse('Internal Server Error', { + status: 500, headers: { ...CORS_HEADERS, }, diff --git a/packages/dapp/src/components/AppNavbar/index.tsx b/packages/dapp/src/components/AppNavbar/index.tsx index 9eebee719..6385ef38f 100644 --- a/packages/dapp/src/components/AppNavbar/index.tsx +++ b/packages/dapp/src/components/AppNavbar/index.tsx @@ -22,6 +22,16 @@ const MAIN_LINKS = [ href: '/app/settings', requiresConnection: true, }, + { + name: 'test', + href: '/app/dashboard', + requiresConnection: true, + }, + { + name: 'sp', + href: '/app/share-presentation/87043304-8cd5-4fad-9072-527f459656f6', + requiresConnection: false, + }, ]; export default function AppNavbar() { diff --git a/packages/dapp/src/components/CredentialDisplay/FormatedPanel.tsx b/packages/dapp/src/components/CredentialDisplay/FormatedPanel.tsx index 38ba8330a..94f75d279 100644 --- a/packages/dapp/src/components/CredentialDisplay/FormatedPanel.tsx +++ b/packages/dapp/src/components/CredentialDisplay/FormatedPanel.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Fragment, useMemo, useState } from 'react'; import { CheckCircleIcon, @@ -8,40 +10,15 @@ import { VerifiableCredential } from '@veramo/core'; import clsx from 'clsx'; import { useTranslations } from 'next-intl'; +import { DIDDisplay } from '@/components/DIDDisplay'; +import JsonModal from '@/components/JsonModal'; import Tooltip from '@/components/Tooltip'; import { convertTypes, copyToClipboard } from '@/utils/string'; -import JsonModal from '../JsonMdoal'; interface FormatedPanelProps { credential: VerifiableCredential; } -const DIDDisplay = ({ did }: { did: string }) => { - const t = useTranslations('DIDDisplay'); - return ( -
-

- DID: -

- -
- ); -}; - const AddressDisplay = ({ address }: { address: string }) => { const t = useTranslations('AddressDisplay'); return ( @@ -92,7 +69,21 @@ const CredentialSubject = ({ {Object.entries(data).map(([key, value]: [string, any]) => ( {(() => { - if (key === 'id') return ; + if (key === 'id') { + return ( + <> +
+

+ DID: +

+
+ +
+
+ + ); + } + if (key === 'address') return ; const isObject = !( @@ -185,13 +176,20 @@ const FormatedPanel = ({ credential }: FormatedPanelProps) => {

{t('issuer')}

- +
+

+ DID: +

+
+ +
+

diff --git a/packages/dapp/src/components/CredentialDisplay/JsonPanel.tsx b/packages/dapp/src/components/CredentialDisplay/JsonPanel.tsx index 3441d4846..ddfad5b55 100644 --- a/packages/dapp/src/components/CredentialDisplay/JsonPanel.tsx +++ b/packages/dapp/src/components/CredentialDisplay/JsonPanel.tsx @@ -1,22 +1,23 @@ +'use client'; + import { DocumentDuplicateIcon } from '@heroicons/react/24/outline'; -import { VerifiableCredential } from '@veramo/core'; import { copyToClipboard } from '@/utils/string'; interface JsonPanelProps { - credential: VerifiableCredential; + data: Record; } -const JsonPanel = ({ credential }: JsonPanelProps) => ( +const JsonPanel = ({ data }: JsonPanelProps) => (