From 427d17401c7d6bac35f8221d8132c18b399e85b0 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Fri, 27 Jun 2025 17:33:06 -0300 Subject: [PATCH 1/4] fix: inboxes and app keys --- apps/connect/src/routes/authenticate.tsx | 10 ++++++---- packages/hypergraph-react/src/HypergraphAppContext.tsx | 6 +++--- .../src/hooks/useExternalSpaceInbox.ts | 4 ++-- .../hypergraph-react/src/hooks/useOwnSpaceInbox.ts | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/connect/src/routes/authenticate.tsx b/apps/connect/src/routes/authenticate.tsx index cc849b15..7c783e16 100644 --- a/apps/connect/src/routes/authenticate.tsx +++ b/apps/connect/src/routes/authenticate.tsx @@ -407,8 +407,8 @@ function AuthenticateComponent() { appId: state.appInfo.appId, address: newAppIdentity.address, accountAddress, - signaturePublicKey: newAppIdentity.signaturePublicKey, - encryptionPublicKey: newAppIdentity.encryptionPublicKey, + signaturePublicKey: keys.signaturePublicKey, + encryptionPublicKey: keys.encryptionPublicKey, ciphertext, nonce, accountProof, @@ -424,6 +424,8 @@ function AuthenticateComponent() { body: JSON.stringify(message), }); const appIdentityResponse = await response.json(); + // TODO: All apps are essentially using the same keys, we should change to using + // the newly created app identity keys, but that requires changing a lot of the verification logic in the server and HypergraphAppContext await encryptSpacesAndRedirect({ accountAddress, appIdentity: { @@ -432,8 +434,8 @@ function AuthenticateComponent() { accountAddress, encryptionPrivateKey: keys.encryptionPrivateKey, signaturePrivateKey: keys.signaturePrivateKey, - encryptionPublicKey: newAppIdentity.encryptionPublicKey, - signaturePublicKey: newAppIdentity.signaturePublicKey, + encryptionPublicKey: keys.encryptionPublicKey, + signaturePublicKey: keys.signaturePublicKey, sessionToken: appIdentityResponse.appIdentity.sessionToken, sessionTokenExpires: new Date(appIdentityResponse.appIdentity.sessionTokenExpires), permissionId, diff --git a/packages/hypergraph-react/src/HypergraphAppContext.tsx b/packages/hypergraph-react/src/HypergraphAppContext.tsx index ae62f7c7..3a23518f 100644 --- a/packages/hypergraph-react/src/HypergraphAppContext.tsx +++ b/packages/hypergraph-react/src/HypergraphAppContext.tsx @@ -627,7 +627,7 @@ export function HypergraphAppProvider({ } const inboxCreator = Inboxes.recoverAccountInboxCreatorKey(response.inbox); if (inboxCreator !== identity.signaturePublicKey) { - console.error('Invalid inbox creator', response.inbox); + console.error('Invalid inbox creator', response.inbox, inboxCreator, identity.signaturePublicKey); return; } @@ -1019,7 +1019,7 @@ export function HypergraphAppProvider({ } const message = await Inboxes.createSpaceInboxCreationMessage({ author: { - accountAddress: identity.address, + accountAddress: identity.accountAddress, signaturePublicKey, encryptionPublicKey, signaturePrivateKey, @@ -1118,7 +1118,7 @@ export function HypergraphAppProvider({ throw new Error('Missing keys'); } const message = await Inboxes.createAccountInboxCreationMessage({ - accountAddress: identity.address, + accountAddress: identity.accountAddress, isPublic, authPolicy, encryptionPublicKey, diff --git a/packages/hypergraph-react/src/hooks/useExternalSpaceInbox.ts b/packages/hypergraph-react/src/hooks/useExternalSpaceInbox.ts index ee8b40b0..2d1bad11 100644 --- a/packages/hypergraph-react/src/hooks/useExternalSpaceInbox.ts +++ b/packages/hypergraph-react/src/hooks/useExternalSpaceInbox.ts @@ -51,8 +51,8 @@ export function useExternalSpaceInbox({ let authorAccountAddress: string | null = null; let signaturePrivateKey: string | null = null; - if (identity?.address && inbox.authPolicy !== 'anonymous') { - authorAccountAddress = identity.address; + if (identity?.accountAddress && inbox.authPolicy !== 'anonymous') { + authorAccountAddress = identity.accountAddress; signaturePrivateKey = identity.signaturePrivateKey; } else if (inbox.authPolicy === 'requires_auth') { throw new Error('Cannot send message to a required auth inbox without an identity'); diff --git a/packages/hypergraph-react/src/hooks/useOwnSpaceInbox.ts b/packages/hypergraph-react/src/hooks/useOwnSpaceInbox.ts index 99d0f032..98c213a3 100644 --- a/packages/hypergraph-react/src/hooks/useOwnSpaceInbox.ts +++ b/packages/hypergraph-react/src/hooks/useOwnSpaceInbox.ts @@ -104,8 +104,8 @@ export function useOwnSpaceInbox({ let authorAccountAddress: string | null = null; let signaturePrivateKey: string | null = null; - if (identity?.address && ownInbox.authPolicy !== 'anonymous') { - authorAccountAddress = identity.address; + if (identity?.accountAddress && ownInbox.authPolicy !== 'anonymous') { + authorAccountAddress = identity.accountAddress; signaturePrivateKey = identity.signaturePrivateKey; } else if (ownInbox.authPolicy === 'requires_auth') { throw new Error('Cannot send message to a required auth inbox without an identity'); From 11e2fac6c7f6dbd5da389418318dab78fe89298a Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Fri, 27 Jun 2025 16:19:43 -0300 Subject: [PATCH 2/4] fix: use the app identity keys everywhere --- apps/connect/src/routes/authenticate.tsx | 26 +-- apps/events/src/Boot.tsx | 6 +- apps/events/src/routes/login.lazy.tsx | 1 - .../next-example/src/components/providers.tsx | 6 +- apps/server/src/handlers/applySpaceEvent.ts | 7 +- apps/server/src/handlers/create-space.ts | 6 +- apps/server/src/handlers/getAccountInbox.ts | 4 +- .../src/handlers/getAppOrConnectIdentity.ts | 82 ++++++++++ apps/server/src/handlers/getSpace.ts | 4 +- apps/server/src/index.ts | 95 ++++++++--- .../src/HypergraphAppContext.tsx | 69 +++++--- .../src/hooks/useExternalAccountInbox.ts | 2 +- packages/hypergraph/src/connect/login.ts | 2 +- .../src/identity/get-verified-identity.ts | 16 +- .../src/inboxes/message-validation.ts | 4 + packages/hypergraph/src/messages/types.ts | 1 + .../src/space-events/apply-event.ts | 7 +- packages/hypergraph/src/store.ts | 44 ++++- .../hypergraph/test/inboxes/inboxes.test.ts | 151 ++++++++++++++---- .../space-events/accept-invitation.test.ts | 12 +- .../test/space-events/apply-event.test.ts | 12 +- .../test/space-events/create-space.test.ts | 8 +- .../test/space-events/delete-space.test.ts | 12 +- 23 files changed, 447 insertions(+), 130 deletions(-) create mode 100644 apps/server/src/handlers/getAppOrConnectIdentity.ts diff --git a/apps/connect/src/routes/authenticate.tsx b/apps/connect/src/routes/authenticate.tsx index 7c783e16..45173869 100644 --- a/apps/connect/src/routes/authenticate.tsx +++ b/apps/connect/src/routes/authenticate.tsx @@ -261,7 +261,7 @@ function AuthenticateComponent() { id: keyData.id, ciphertext: Utils.bytesToHex(keyBox.keyBoxCiphertext), nonce: Utils.bytesToHex(keyBox.keyBoxNonce), - authorPublicKey: appIdentity.encryptionPublicKey, + authorPublicKey: keys.encryptionPublicKey, accountAddress: accountAddress, }; }); @@ -388,27 +388,33 @@ function AuthenticateComponent() { rpcUrl: import.meta.env.VITE_HYPERGRAPH_RPC_URL, }); + const appIdentityKeys = { + encryptionPrivateKey: newAppIdentity.encryptionPrivateKey, + encryptionPublicKey: newAppIdentity.encryptionPublicKey, + signaturePrivateKey: newAppIdentity.signaturePrivateKey, + signaturePublicKey: newAppIdentity.signaturePublicKey, + }; console.log('encrypting app identity'); const { ciphertext, nonce } = await Connect.encryptAppIdentity( signer, newAppIdentity.address, newAppIdentity.addressPrivateKey, permissionId, - keys, + appIdentityKeys, ); console.log('proving ownership'); const { accountProof, keyProof } = await Identity.proveIdentityOwnership( smartAccountClient, accountAddress, - keys, + appIdentityKeys, ); const message: Messages.RequestConnectCreateAppIdentity = { appId: state.appInfo.appId, address: newAppIdentity.address, accountAddress, - signaturePublicKey: keys.signaturePublicKey, - encryptionPublicKey: keys.encryptionPublicKey, + signaturePublicKey: newAppIdentity.signaturePublicKey, + encryptionPublicKey: newAppIdentity.encryptionPublicKey, ciphertext, nonce, accountProof, @@ -424,18 +430,16 @@ function AuthenticateComponent() { body: JSON.stringify(message), }); const appIdentityResponse = await response.json(); - // TODO: All apps are essentially using the same keys, we should change to using - // the newly created app identity keys, but that requires changing a lot of the verification logic in the server and HypergraphAppContext await encryptSpacesAndRedirect({ accountAddress, appIdentity: { address: newAppIdentity.address, addressPrivateKey: newAppIdentity.addressPrivateKey, accountAddress, - encryptionPrivateKey: keys.encryptionPrivateKey, - signaturePrivateKey: keys.signaturePrivateKey, - encryptionPublicKey: keys.encryptionPublicKey, - signaturePublicKey: keys.signaturePublicKey, + encryptionPrivateKey: newAppIdentity.encryptionPrivateKey, + signaturePrivateKey: newAppIdentity.signaturePrivateKey, + encryptionPublicKey: newAppIdentity.encryptionPublicKey, + signaturePublicKey: newAppIdentity.signaturePublicKey, sessionToken: appIdentityResponse.appIdentity.sessionToken, sessionTokenExpires: new Date(appIdentityResponse.appIdentity.sessionTokenExpires), permissionId, diff --git a/apps/events/src/Boot.tsx b/apps/events/src/Boot.tsx index 3cfd05e5..16f06508 100644 --- a/apps/events/src/Boot.tsx +++ b/apps/events/src/Boot.tsx @@ -15,7 +15,11 @@ declare module '@tanstack/react-router' { export function Boot() { return ( - + ); diff --git a/apps/events/src/routes/login.lazy.tsx b/apps/events/src/routes/login.lazy.tsx index 98091bee..589f77dc 100644 --- a/apps/events/src/routes/login.lazy.tsx +++ b/apps/events/src/routes/login.lazy.tsx @@ -16,7 +16,6 @@ function Login() { storage: localStorage, connectUrl: 'http://localhost:5180', successUrl: `${window.location.origin}/authenticate-success`, - appId: '93bb8907-085a-4a0e-83dd-62b0dc98e793', redirectFn: (url: URL) => { window.location.href = url.toString(); }, diff --git a/apps/next-example/src/components/providers.tsx b/apps/next-example/src/components/providers.tsx index 332613b7..d632d7d6 100644 --- a/apps/next-example/src/components/providers.tsx +++ b/apps/next-example/src/components/providers.tsx @@ -7,7 +7,11 @@ export default function Providers({ children }: { children: React.ReactNode }) { const storage = typeof window !== 'undefined' ? window.localStorage : (undefined as unknown as Storage); return ( - + {children} ); diff --git a/apps/server/src/handlers/applySpaceEvent.ts b/apps/server/src/handlers/applySpaceEvent.ts index 956fe317..f8800528 100644 --- a/apps/server/src/handlers/applySpaceEvent.ts +++ b/apps/server/src/handlers/applySpaceEvent.ts @@ -4,7 +4,7 @@ import type { Messages } from '@graphprotocol/hypergraph'; import { Identity, SpaceEvents } from '@graphprotocol/hypergraph'; import { prisma } from '../prisma.js'; -import { getConnectIdentity } from './getConnectIdentity.js'; +import { getAppOrConnectIdentity } from './getAppOrConnectIdentity.js'; type Params = { accountAddress: string; @@ -40,7 +40,7 @@ export async function applySpaceEvent({ accountAddress, spaceId, event, keyBoxes orderBy: { counter: 'desc' }, }); - const getVerifiedIdentity = (accountAddressToFetch: string) => { + const getVerifiedIdentity = (accountAddressToFetch: string, publicKey: string) => { console.log('getVerifiedIdentity', accountAddressToFetch, accountAddress); // applySpaceEvent is only allowed to be called by the account that is applying the event if (accountAddressToFetch !== accountAddress) { @@ -49,7 +49,8 @@ export async function applySpaceEvent({ accountAddress, spaceId, event, keyBoxes return Effect.gen(function* () { const identity = yield* Effect.tryPromise({ - try: () => getConnectIdentity({ accountAddress: accountAddressToFetch }), + try: () => + getAppOrConnectIdentity({ accountAddress: accountAddressToFetch, signaturePublicKey: publicKey, spaceId }), catch: () => new Identity.InvalidIdentityError(), }); return identity; diff --git a/apps/server/src/handlers/create-space.ts b/apps/server/src/handlers/create-space.ts index 75efbc69..57535e47 100644 --- a/apps/server/src/handlers/create-space.ts +++ b/apps/server/src/handlers/create-space.ts @@ -5,7 +5,7 @@ import type { Messages } from '@graphprotocol/hypergraph'; import { Identity, SpaceEvents } from '@graphprotocol/hypergraph'; import { prisma } from '../prisma.js'; -import { getConnectIdentity } from './getConnectIdentity.js'; +import { getAppOrConnectIdentity } from './getAppOrConnectIdentity.js'; type Params = { accountAddress: string; @@ -26,7 +26,7 @@ export const createSpace = async ({ infoSignatureRecovery, name, }: Params) => { - const getVerifiedIdentity = (accountAddressToFetch: string) => { + const getVerifiedIdentity = (accountAddressToFetch: string, publicKey: string) => { // applySpaceEvent is only allowed to be called by the account that is applying the event if (accountAddressToFetch !== accountAddress) { return Effect.fail(new Identity.InvalidIdentityError()); @@ -34,7 +34,7 @@ export const createSpace = async ({ return Effect.gen(function* () { const identity = yield* Effect.tryPromise({ - try: () => getConnectIdentity({ accountAddress: accountAddressToFetch }), + try: () => getAppOrConnectIdentity({ accountAddress: accountAddressToFetch, signaturePublicKey: publicKey }), catch: () => new Identity.InvalidIdentityError(), }); return identity; diff --git a/apps/server/src/handlers/getAccountInbox.ts b/apps/server/src/handlers/getAccountInbox.ts index 5acadcef..154170ea 100644 --- a/apps/server/src/handlers/getAccountInbox.ts +++ b/apps/server/src/handlers/getAccountInbox.ts @@ -8,7 +8,7 @@ export async function getAccountInbox({ accountAddress, inboxId }: { accountAddr id: true, account: { select: { - id: true, + address: true, }, }, isPublic: true, @@ -24,7 +24,7 @@ export async function getAccountInbox({ accountAddress, inboxId }: { accountAddr return { inboxId: inbox.id, - accountAddress: inbox.account.id, + accountAddress: inbox.account.address, isPublic: inbox.isPublic, authPolicy: inbox.authPolicy as Inboxes.InboxSenderAuthPolicy, encryptionPublicKey: inbox.encryptionPublicKey, diff --git a/apps/server/src/handlers/getAppOrConnectIdentity.ts b/apps/server/src/handlers/getAppOrConnectIdentity.ts new file mode 100644 index 00000000..18f8ee7f --- /dev/null +++ b/apps/server/src/handlers/getAppOrConnectIdentity.ts @@ -0,0 +1,82 @@ +import { prisma } from '../prisma.js'; + +type Params = + | { + accountAddress: string; + signaturePublicKey: string; + spaceId?: string; + } + | { + accountAddress: string; + appId: string; + spaceId?: string; + }; + +export type GetIdentityResult = { + accountAddress: string; + ciphertext: string; + nonce: string; + signaturePublicKey: string; + encryptionPublicKey: string; + accountProof: string; + keyProof: string; + appId: string | null; +}; + +export const getAppOrConnectIdentity = async (params: Params): Promise => { + if (!('appId' in params)) { + const where: { address: string; connectSignaturePublicKey?: string } = { address: params.accountAddress }; + if ('signaturePublicKey' in params) { + where.connectSignaturePublicKey = params.signaturePublicKey; + } + const account = await prisma.account.findFirst({ + where, + }); + if (account) { + return { + accountAddress: account.address, + ciphertext: account.connectCiphertext, + nonce: account.connectNonce, + signaturePublicKey: account.connectSignaturePublicKey, + encryptionPublicKey: account.connectEncryptionPublicKey, + accountProof: account.connectAccountProof, + keyProof: account.connectKeyProof, + appId: null, + }; + } + } + const appWhere: { + accountAddress: string; + appId?: string; + signaturePublicKey?: string; + spaces?: { some: { id: string } }; + } = { + accountAddress: params.accountAddress, + }; + if ('signaturePublicKey' in params) { + appWhere.signaturePublicKey = params.signaturePublicKey; + } + if ('appId' in params) { + appWhere.appId = params.appId; + } + if (params.spaceId) { + appWhere.spaces = { some: { id: params.spaceId } }; + } + + const appIdentity = await prisma.appIdentity.findFirst({ + where: appWhere, + }); + if (appIdentity) { + return { + accountAddress: appIdentity.accountAddress, + ciphertext: appIdentity.ciphertext, + nonce: appIdentity.nonce, + signaturePublicKey: appIdentity.signaturePublicKey, + encryptionPublicKey: appIdentity.encryptionPublicKey, + accountProof: appIdentity.accountProof, + keyProof: appIdentity.keyProof, + appId: appIdentity.appId, + }; + } + throw new Error('Identity not found'); +}; diff --git a/apps/server/src/handlers/getSpace.ts b/apps/server/src/handlers/getSpace.ts index ae1cd653..98a15dd5 100644 --- a/apps/server/src/handlers/getSpace.ts +++ b/apps/server/src/handlers/getSpace.ts @@ -4,9 +4,10 @@ import { prisma } from '../prisma.js'; type Params = { spaceId: string; accountAddress: string; + appIdentityAddress: string; }; -export const getSpace = async ({ spaceId, accountAddress }: Params) => { +export const getSpace = async ({ spaceId, accountAddress, appIdentityAddress }: Params) => { const space = await prisma.space.findUniqueOrThrow({ where: { id: spaceId, @@ -27,6 +28,7 @@ export const getSpace = async ({ spaceId, accountAddress }: Params) => { keyBoxes: { where: { accountAddress, + appIdentityAddress, }, select: { nonce: true, diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 1e100b91..842c6850 100755 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -17,6 +17,7 @@ import { createUpdate } from './handlers/createUpdate.js'; import { findAppIdentity } from './handlers/find-app-identity.js'; import { getAppIdentityBySessionToken } from './handlers/get-app-identity-by-session-token.js'; import { getAccountInbox } from './handlers/getAccountInbox.js'; +import { getAppOrConnectIdentity } from './handlers/getAppOrConnectIdentity.js'; import { type GetIdentityResult, getConnectIdentity } from './handlers/getConnectIdentity.js'; import { getLatestAccountInboxMessages } from './handlers/getLatestAccountInboxMessages.js'; import { getLatestSpaceInboxMessages } from './handlers/getLatestSpaceInboxMessages.js'; @@ -169,7 +170,7 @@ app.post('/connect/add-app-identity-to-spaces', async (req, res) => { }); res.status(200).json({ space }); } catch (error) { - console.error('Error creating space:', error); + console.error('Error adding identity to spaces:', error); if (error instanceof Error && error.message === 'No Privy ID token provided') { res.status(401).json({ message: 'Unauthorized' }); } else if (error instanceof Error && error.message === 'Missing Privy configuration') { @@ -318,6 +319,20 @@ app.post('/connect/app-identity', async (req, res) => { res.status(401).send('Unauthorized'); return; } + if ( + !Identity.verifyIdentityOwnership( + accountAddress, + message.signaturePublicKey, + message.accountProof, + message.keyProof, + CHAIN, + RPC_URL, + ) + ) { + console.log('Ownership proof is invalid'); + res.status(401).send('Unauthorized'); + return; + } const sessionToken = bytesToHex(randomBytes(32)); const sessionTokenExpires = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30); // 30 days const appIdentity = await createAppIdentity({ @@ -366,21 +381,54 @@ app.get('/whoami', async (req, res) => { } }); +app.get('/connect/identity', async (req, res) => { + console.log('GET connect/identity'); + const accountAddress = req.query.accountAddress as string; + if (!accountAddress) { + res.status(400).send('No accountAddress'); + return; + } + try { + const identity = await getConnectIdentity({ accountAddress }); + const outgoingMessage: Messages.ResponseIdentity = { + accountAddress, + signaturePublicKey: identity.signaturePublicKey, + encryptionPublicKey: identity.encryptionPublicKey, + accountProof: identity.accountProof, + keyProof: identity.keyProof, + }; + res.status(200).send(outgoingMessage); + } catch (error) { + const outgoingMessage: Messages.ResponseIdentityNotFoundError = { + accountAddress, + }; + res.status(404).send(outgoingMessage); + } +}); + app.get('/identity', async (req, res) => { console.log('GET identity'); const accountAddress = req.query.accountAddress as string; + const signaturePublicKey = req.query.signaturePublicKey as string; + const appId = req.query.appId as string; if (!accountAddress) { res.status(400).send('No accountAddress'); return; } + if (!signaturePublicKey && !appId) { + res.status(400).send('No signaturePublicKey or appId'); + return; + } try { - const identity = await getConnectIdentity({ accountAddress }); + const params = signaturePublicKey ? { accountAddress, signaturePublicKey } : { accountAddress, appId }; + const identity = await getAppOrConnectIdentity(params); const outgoingMessage: Messages.ResponseIdentity = { accountAddress, signaturePublicKey: identity.signaturePublicKey, encryptionPublicKey: identity.encryptionPublicKey, accountProof: identity.accountProof, keyProof: identity.keyProof, + appId: identity.appId ?? undefined, }; res.status(200).send(outgoingMessage); } catch (error) { @@ -471,7 +519,10 @@ app.post('/spaces/:spaceId/inboxes/:inboxId/messages', async (req, res) => { // Check if this public key corresponds to a user's identity let authorIdentity: GetIdentityResult; try { - authorIdentity = await getConnectIdentity({ connectSignaturePublicKey: authorPublicKey }); + authorIdentity = await getAppOrConnectIdentity({ + accountAddress: message.authorAccountAddress, + signaturePublicKey: authorPublicKey, + }); } catch (error) { res.status(403).send({ error: 'Not authorized to post to this inbox' }); return; @@ -569,7 +620,10 @@ app.post('/accounts/:accountAddress/inboxes/:inboxId/messages', async (req, res) // Check if this public key corresponds to a user's identity let authorIdentity: GetIdentityResult; try { - authorIdentity = await getConnectIdentity({ connectSignaturePublicKey: authorPublicKey }); + authorIdentity = await getAppOrConnectIdentity({ + accountAddress: message.authorAccountAddress, + signaturePublicKey: authorPublicKey, + }); } catch (error) { res.status(403).send({ error: 'Not authorized to post to this inbox' }); return; @@ -735,7 +789,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req const data = result.right; switch (data.type) { case 'subscribe-space': { - const space = await getSpace({ accountAddress, spaceId: data.id }); + const space = await getSpace({ accountAddress, spaceId: data.id, appIdentityAddress }); const outgoingMessage: Messages.ResponseSpace = { ...space, type: 'space', @@ -760,19 +814,15 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req break; } case 'create-space-event': { - const getVerifiedIdentity = (accountAddressToFetch: string) => { - console.log( - 'TODO getVerifiedIdentity should work for app identities', - accountAddressToFetch, - accountAddress, - ); + const getVerifiedIdentity = (accountAddressToFetch: string, publicKey: string) => { if (accountAddressToFetch !== accountAddress) { return Effect.fail(new Identity.InvalidIdentityError()); } return Effect.gen(function* () { const identity = yield* Effect.tryPromise({ - try: () => getConnectIdentity({ accountAddress: accountAddressToFetch }), + try: () => + getAppOrConnectIdentity({ accountAddress: accountAddressToFetch, signaturePublicKey: publicKey }), catch: () => new Identity.InvalidIdentityError(), }); return identity; @@ -796,7 +846,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req infoSignatureRecovery: 0, name: data.name, }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: space.id }); + const spaceWithEvents = await getSpace({ accountAddress, spaceId: space.id, appIdentityAddress }); const outgoingMessage: Messages.ResponseSpace = { ...spaceWithEvents, type: 'space', @@ -816,8 +866,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req event: data.event, keyBoxes: data.keyBoxes.map((keyBox) => keyBox), }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId }); - // TODO send back confirmation instead of the entire space + const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId, appIdentityAddress }); const outgoingMessage: Messages.ResponseSpace = { ...spaceWithEvents, type: 'space', @@ -843,7 +892,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req } case 'accept-invitation-event': { await applySpaceEvent({ accountAddress, spaceId: data.spaceId, event: data.event, keyBoxes: [] }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId }); + const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId, appIdentityAddress }); const outgoingMessage: Messages.ResponseSpace = { ...spaceWithEvents, type: 'space', @@ -854,7 +903,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req } case 'create-space-inbox-event': { await applySpaceEvent({ accountAddress, spaceId: data.spaceId, event: data.event, keyBoxes: [] }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId }); + const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId, appIdentityAddress }); // TODO send back confirmation instead of the entire space const outgoingMessage: Messages.ResponseSpace = { ...spaceWithEvents, @@ -871,7 +920,10 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req throw new Error('Invalid accountAddress'); } const signer = Inboxes.recoverAccountInboxCreatorKey(data); - const signerAccount = await getConnectIdentity({ connectSignaturePublicKey: signer }); + const signerAccount = await getAppOrConnectIdentity({ + accountAddress: data.accountAddress, + signaturePublicKey: signer, + }); if (signerAccount.accountAddress !== accountAddress) { throw new Error('Invalid signature'); } @@ -888,7 +940,7 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req case 'get-latest-space-inbox-messages': { try { // Check that the user has access to this space - await getSpace({ accountAddress, spaceId: data.spaceId }); + await getSpace({ accountAddress, spaceId: data.spaceId, appIdentityAddress }); const messages = await getLatestSpaceInboxMessages({ inboxId: data.inboxId, since: data.since, @@ -941,7 +993,10 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req // Check that the update was signed by a valid identity // belonging to this accountAddress const signer = Messages.recoverUpdateMessageSigner(data); - const identity = await getConnectIdentity({ connectSignaturePublicKey: signer }); + const identity = await getAppOrConnectIdentity({ + accountAddress: data.accountAddress, + signaturePublicKey: signer, + }); if (identity.accountAddress !== accountAddress) { throw new Error('Invalid signature'); } diff --git a/packages/hypergraph-react/src/HypergraphAppContext.tsx b/packages/hypergraph-react/src/HypergraphAppContext.tsx index 3a23518f..cda378bc 100644 --- a/packages/hypergraph-react/src/HypergraphAppContext.tsx +++ b/packages/hypergraph-react/src/HypergraphAppContext.tsx @@ -10,6 +10,7 @@ import { uuid } from '@automerge/automerge/slim'; import { Graph } from '@graphprotocol/grc-20'; import { Connect, + type ConnectCallbackResult, Identity, type InboxMessageStorageEntry, Inboxes, @@ -92,7 +93,11 @@ export type HypergraphAppCtx = { acceptInvitation(params: Readonly<{ invitation: Messages.Invitation }>): Promise; subscribeToSpace(params: Readonly<{ spaceId: string }>): void; inviteToSpace(params: Readonly<{ space: SpaceStorageEntry; invitee: { accountAddress: Address } }>): Promise; - getVerifiedIdentity(accountAddress: string): Promise<{ + getVerifiedIdentity( + accountAddress: string, + publicKey: string | null, + appId: string | null, + ): Promise<{ accountAddress: string; encryptionPublicKey: string; signaturePublicKey: string; @@ -108,7 +113,6 @@ export type HypergraphAppCtx = { redirectToConnect(params: { storage: Identity.Storage; successUrl: string; - appId: string; connectUrl: string; redirectFn: (url: URL) => void; }): void; @@ -220,6 +224,7 @@ export type HypergraphAppProviderProps = Readonly<{ chainId?: number; children: ReactNode; mapping: Mapping; + appId: string; }>; const mockStorage = { @@ -239,6 +244,7 @@ export function HypergraphAppProvider({ storage = typeof window !== 'undefined' ? localStorage : mockStorage, syncServerUri = 'https://hypergraph.fly.dev', chainId = Connect.GEO_TESTNET.id, + appId, children, mapping, }: HypergraphAppProviderProps) { @@ -376,6 +382,8 @@ export function HypergraphAppProvider({ }); const authorIdentity = await Identity.getVerifiedIdentity( update.accountAddress, + signer, + null, syncServerUri, CHAIN, RPC_URL, @@ -411,10 +419,10 @@ export function HypergraphAppProvider({ }); }; - const getVerifiedIdentity = (accountAddress: string) => { + const getVerifiedIdentityForEvent = (accountAddress: string, publicKey: string) => { return Effect.gen(function* () { const identity = yield* Effect.tryPromise({ - try: () => Identity.getVerifiedIdentity(accountAddress, syncServerUri, CHAIN, RPC_URL), + try: () => Identity.getVerifiedIdentity(accountAddress, publicKey, null, syncServerUri, CHAIN, RPC_URL), catch: () => new Identity.InvalidIdentityError(), }); return identity; @@ -443,7 +451,9 @@ export function HypergraphAppProvider({ for (const event of response.events) { // Not sure why but type inference doesn't work here const applyEventResult: Exit.Exit = - await Effect.runPromiseExit(SpaceEvents.applyEvent({ state, event, getVerifiedIdentity })); + await Effect.runPromiseExit( + SpaceEvents.applyEvent({ state, event, getVerifiedIdentity: getVerifiedIdentityForEvent }), + ); if (Exit.isSuccess(applyEventResult)) { state = applyEventResult.value; } else { @@ -518,7 +528,7 @@ export function HypergraphAppProvider({ const updateId = uuid(); const messageToSend = Messages.signedUpdateMessage({ - accountAddress: identity.address, + accountAddress: identity.accountAddress, updateId, spaceId: space.id, message: lastLocalChange, @@ -549,7 +559,11 @@ export function HypergraphAppProvider({ } const applyEventResult = await Effect.runPromiseExit( - SpaceEvents.applyEvent({ event: response.event, state: space.state, getVerifiedIdentity }), + SpaceEvents.applyEvent({ + event: response.event, + state: space.state, + getVerifiedIdentity: getVerifiedIdentityForEvent, + }), ); if (Exit.isSuccess(applyEventResult)) { store.send({ @@ -704,7 +718,7 @@ export function HypergraphAppProvider({ const isValid = await Inboxes.validateAccountInboxMessage( response.message, inbox, - identity.address, + identity.accountAddress, syncServerUri, CHAIN, RPC_URL, @@ -774,7 +788,7 @@ export function HypergraphAppProvider({ return Inboxes.validateAccountInboxMessage( message, inbox, - identity.address, + identity.accountAddress, syncServerUri, CHAIN, RPC_URL, @@ -930,7 +944,7 @@ export function HypergraphAppProvider({ const spaceEvent = await Effect.runPromise( SpaceEvents.createSpace({ author: { - accountAddress: identity.address, + accountAddress: identity.accountAddress, encryptionPublicKey, signaturePrivateKey, signaturePublicKey, @@ -948,7 +962,7 @@ export function HypergraphAppProvider({ event: spaceEvent, spaceId: spaceEvent.transaction.id, keyBox: { - accountAddress: identity.address, + accountAddress: identity.accountAddress, ciphertext: Utils.bytesToHex(result.keyBoxCiphertext), nonce: Utils.bytesToHex(result.keyBoxNonce), authorPublicKey: encryptionPublicKey, @@ -1223,7 +1237,7 @@ export function HypergraphAppProvider({ const spaceEvent = await Effect.runPromiseExit( SpaceEvents.acceptInvitation({ author: { - accountAddress: identity.address, + accountAddress: identity.accountAddress, signaturePublicKey, encryptionPublicKey, signaturePrivateKey, @@ -1290,11 +1304,18 @@ export function HypergraphAppProvider({ console.error('No state found for space'); return; } - const inviteeWithKeys = await Identity.getVerifiedIdentity(invitee.accountAddress, syncServerUri, CHAIN, RPC_URL); + const inviteeWithKeys = await Identity.getVerifiedIdentity( + invitee.accountAddress, + null, + appId, + syncServerUri, + CHAIN, + RPC_URL, + ); const spaceEvent = await Effect.runPromiseExit( SpaceEvents.createInvitation({ author: { - accountAddress: identity.address, + accountAddress: identity.accountAddress, signaturePublicKey, encryptionPublicKey, signaturePrivateKey, @@ -1331,14 +1352,15 @@ export function HypergraphAppProvider({ }; websocketConnection?.send(Messages.serialize(message)); }, - [identity, websocketConnection, syncServerUri], + [identity, websocketConnection, syncServerUri, appId], ); - const getVerifiedIdentity = useCallback( - (accountAddress: string) => { - return Identity.getVerifiedIdentity(accountAddress, syncServerUri, CHAIN, RPC_URL); + const getVerifiedIdentityForContext = useCallback( + (accountAddress: string, publicKey: string | null, inputAppId: string | null) => { + const appIdToUse = inputAppId ?? (publicKey ? null : appId); + return Identity.getVerifiedIdentity(accountAddress, publicKey, appIdToUse, syncServerUri, CHAIN, RPC_URL); }, - [syncServerUri], + [syncServerUri, appId], ); const ensureSpaceInboxForContext = useCallback( @@ -1395,11 +1417,10 @@ export function HypergraphAppProvider({ (params: { storage: Identity.Storage; successUrl: string; - appId: string; connectUrl: string; redirectFn: (url: URL) => void; }) => { - const { storage, successUrl, redirectFn, appId, connectUrl } = params; + const { storage, successUrl, redirectFn, connectUrl } = params; const { url, nonce, expiry, secretKey, publicKey } = Connect.createAuthUrl({ connectUrl: `${connectUrl}/authenticate`, redirectUrl: successUrl, @@ -1411,7 +1432,7 @@ export function HypergraphAppProvider({ storage.setItem('geo-connect-auth-public-key', publicKey); redirectFn(url); }, - [], + [appId], ); const processConnectAuthSuccessForContext = useCallback( @@ -1427,7 +1448,7 @@ export function HypergraphAppProvider({ } try { - const parsedAuthParams = Effect.runSync( + const parsedAuthParams: ConnectCallbackResult = Effect.runSync( Connect.parseCallbackParams({ ciphertext, nonce, @@ -1499,7 +1520,7 @@ export function HypergraphAppProvider({ listInvitations, acceptInvitation: acceptInvitationForContext, subscribeToSpace, - getVerifiedIdentity, + getVerifiedIdentity: getVerifiedIdentityForContext, inviteToSpace, isConnecting, isLoadingSpaces, diff --git a/packages/hypergraph-react/src/hooks/useExternalAccountInbox.ts b/packages/hypergraph-react/src/hooks/useExternalAccountInbox.ts index f9c01ded..55d96226 100644 --- a/packages/hypergraph-react/src/hooks/useExternalAccountInbox.ts +++ b/packages/hypergraph-react/src/hooks/useExternalAccountInbox.ts @@ -46,7 +46,7 @@ export function useExternalAccountInbox(accountAddress: string, inboxId: string) let authorAccountAddress: string | null = null; let signaturePrivateKey: string | null = null; if (identity?.address && inbox.authPolicy !== 'anonymous') { - authorAccountAddress = identity.address; + authorAccountAddress = identity.accountAddress; signaturePrivateKey = identity.signaturePrivateKey; } diff --git a/packages/hypergraph/src/connect/login.ts b/packages/hypergraph/src/connect/login.ts index 4dc01328..c8213c27 100644 --- a/packages/hypergraph/src/connect/login.ts +++ b/packages/hypergraph/src/connect/login.ts @@ -19,7 +19,7 @@ import { import type { IdentityKeys, Signer, Storage } from './types.js'; export async function identityExists(accountAddress: string, syncServerUri: string) { - const res = await fetch(new URL(`/identity?accountAddress=${accountAddress}`, syncServerUri), { + const res = await fetch(new URL(`/connect/identity?accountAddress=${accountAddress}`, syncServerUri), { method: 'GET', }); return res.status === 200; diff --git a/packages/hypergraph/src/identity/get-verified-identity.ts b/packages/hypergraph/src/identity/get-verified-identity.ts index 60bbb9ba..e9ca5665 100644 --- a/packages/hypergraph/src/identity/get-verified-identity.ts +++ b/packages/hypergraph/src/identity/get-verified-identity.ts @@ -6,6 +6,8 @@ import { verifyIdentityOwnership } from './prove-ownership.js'; export const getVerifiedIdentity = async ( accountAddress: string, + signaturePublicKey: string | null, + appId: string | null, syncServerUri: string, chain: Chain, rpcUrl: string, @@ -14,8 +16,16 @@ export const getVerifiedIdentity = async ( encryptionPublicKey: string; signaturePublicKey: string; }> => { + if (signaturePublicKey && appId) { + throw new Error('Cannot specify both signaturePublicKey and appId'); + } const storeState = store.getSnapshot(); - const identity = storeState.context.identities[accountAddress]; + const identity = storeState.context.identities[accountAddress]?.find((identity) => { + if (signaturePublicKey) { + return identity.signaturePublicKey === signaturePublicKey; + } + return identity.appId === appId; + }); if (identity) { return { accountAddress, @@ -23,7 +33,8 @@ export const getVerifiedIdentity = async ( signaturePublicKey: identity.signaturePublicKey, }; } - const res = await fetch(`${syncServerUri}/identity?accountAddress=${accountAddress}`); + const query = signaturePublicKey ? `&signaturePublicKey=${signaturePublicKey}` : `&appId=${appId}`; + const res = await fetch(`${syncServerUri}/identity?accountAddress=${accountAddress}${query}`); if (res.status !== 200) { throw new Error('Failed to fetch identity'); } @@ -49,6 +60,7 @@ export const getVerifiedIdentity = async ( signaturePublicKey: resDecoded.signaturePublicKey, accountProof: resDecoded.accountProof, keyProof: resDecoded.keyProof, + appId: resDecoded.appId ?? null, }); return { accountAddress: resDecoded.accountAddress, diff --git a/packages/hypergraph/src/inboxes/message-validation.ts b/packages/hypergraph/src/inboxes/message-validation.ts index 7c0dd643..8090d481 100644 --- a/packages/hypergraph/src/inboxes/message-validation.ts +++ b/packages/hypergraph/src/inboxes/message-validation.ts @@ -24,6 +24,8 @@ export const validateSpaceInboxMessage = async ( const signer = recoverSpaceInboxMessageSigner(message, spaceId, inbox.inboxId); const verifiedIdentity = await Identity.getVerifiedIdentity( message.authorAccountAddress, + signer, + null, syncServerUri, chain, rpcUrl, @@ -62,6 +64,8 @@ export const validateAccountInboxMessage = async ( const signer = recoverAccountInboxMessageSigner(message, accountAddress, inbox.inboxId); const verifiedIdentity = await Identity.getVerifiedIdentity( message.authorAccountAddress, + signer, + null, syncServerUri, chain, rpcUrl, diff --git a/packages/hypergraph/src/messages/types.ts b/packages/hypergraph/src/messages/types.ts index 05f6b0bd..5adbf3b3 100644 --- a/packages/hypergraph/src/messages/types.ts +++ b/packages/hypergraph/src/messages/types.ts @@ -450,6 +450,7 @@ export const ResponseIdentity = Schema.Struct({ encryptionPublicKey: Schema.String, accountProof: Schema.String, keyProof: Schema.String, + appId: Schema.optional(Schema.String), }); export type ResponseIdentity = Schema.Schema.Type; diff --git a/packages/hypergraph/src/space-events/apply-event.ts b/packages/hypergraph/src/space-events/apply-event.ts index 0af19056..cbf1cefd 100644 --- a/packages/hypergraph/src/space-events/apply-event.ts +++ b/packages/hypergraph/src/space-events/apply-event.ts @@ -18,7 +18,10 @@ import { type Params = { state: SpaceState | undefined; event: SpaceEvent; - getVerifiedIdentity: (accountAddress: string) => Effect.Effect; + getVerifiedIdentity: ( + accountAddress: string, + publicKey: string, + ) => Effect.Effect; }; const decodeSpaceEvent = Schema.decodeUnknownEither(SpaceEvent); @@ -50,7 +53,7 @@ export const applyEvent = ({ const authorPublicKey = `0x${signatureInstance.recoverPublicKey(sha256(encodedTransaction)).toHex()}`; return Effect.gen(function* () { - const identity = yield* getVerifiedIdentity(event.author.accountAddress); + const identity = yield* getVerifiedIdentity(event.author.accountAddress, authorPublicKey); if (authorPublicKey !== identity.signaturePublicKey) { yield* Effect.fail(new VerifySignatureError()); } diff --git a/packages/hypergraph/src/store.ts b/packages/hypergraph/src/store.ts index c873995c..11fc1026 100644 --- a/packages/hypergraph/src/store.ts +++ b/packages/hypergraph/src/store.ts @@ -64,7 +64,8 @@ interface StoreContext { signaturePublicKey: string; accountProof: string; keyProof: string; - }; + appId: string | null; + }[]; }; authenticated: boolean; identity: PrivateAppIdentity | null; @@ -104,6 +105,7 @@ type StoreEvent = signaturePublicKey: string; accountProof: string; keyProof: string; + appId: string | null; } | { type: 'setSpaceInbox'; @@ -291,18 +293,46 @@ export const store: Store = create signaturePublicKey: string; accountProof: string; keyProof: string; + appId: string | null; }, ) => { + const existingIdentity = context.identities[event.accountAddress]?.find( + (identity) => identity.signaturePublicKey === event.signaturePublicKey, + ); + if (existingIdentity) { + return context; + } + if (context.identities[event.accountAddress]) { + return { + ...context, + identities: { + ...context.identities, + [event.accountAddress]: [ + ...context.identities[event.accountAddress], + { + encryptionPublicKey: event.encryptionPublicKey, + signaturePublicKey: event.signaturePublicKey, + accountProof: event.accountProof, + keyProof: event.keyProof, + appId: event.appId, + }, + ], + }, + }; + } return { ...context, identities: { ...context.identities, - [event.accountAddress]: { - encryptionPublicKey: event.encryptionPublicKey, - signaturePublicKey: event.signaturePublicKey, - accountProof: event.accountProof, - keyProof: event.keyProof, - }, + [event.accountAddress]: [ + { + encryptionPublicKey: event.encryptionPublicKey, + signaturePublicKey: event.signaturePublicKey, + accountProof: event.accountProof, + keyProof: event.keyProof, + appId: event.appId, + }, + ], }, }; }, diff --git a/packages/hypergraph/test/inboxes/inboxes.test.ts b/packages/hypergraph/test/inboxes/inboxes.test.ts index 4b55f6e3..3e4f0c53 100644 --- a/packages/hypergraph/test/inboxes/inboxes.test.ts +++ b/packages/hypergraph/test/inboxes/inboxes.test.ts @@ -351,11 +351,13 @@ describe('inboxes', () => { vi.clearAllMocks(); }); - vi.spyOn(Identity, 'getVerifiedIdentity').mockImplementation(async (accountAddress: string) => ({ - accountAddress, - signaturePublicKey: bytesToHex(signaturePublicKey), - encryptionPublicKey: bytesToHex(encryptionPublicKey), - })); + vi.spyOn(Identity, 'getVerifiedIdentity').mockImplementation( + async (accountAddress: string, publicKey: string | null) => ({ + accountAddress, + signaturePublicKey: publicKey ?? '', + encryptionPublicKey: bytesToHex(encryptionPublicKey), + }), + ); it.skip('should validate a properly signed space inbox message', async () => { const spaceId = generateId(); @@ -382,7 +384,11 @@ describe('inboxes', () => { }; const isValid = await validateSpaceInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, spaceId, 'https://sync.example.com', @@ -391,7 +397,11 @@ describe('inboxes', () => { ); expect(isValid).toBe(true); - expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith(testParams.accountAddress, 'https://sync.example.com'); + expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith( + testParams.accountAddress, + bytesToHex(signaturePublicKey), + 'https://sync.example.com', + ); }); it('should reject unsigned messages for RequiresAuth inboxes', async () => { @@ -411,7 +421,11 @@ describe('inboxes', () => { }; const isValid = await validateSpaceInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, generateId(), 'https://sync.example.com', @@ -477,7 +491,11 @@ describe('inboxes', () => { }; const isValid = await validateSpaceInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, spaceId, 'https://sync.example.com', @@ -517,7 +535,18 @@ describe('inboxes', () => { }; await expect( - validateSpaceInboxMessage(message, inbox, spaceId, 'https://sync.example.com', CHAIN, RPC_URL), + validateSpaceInboxMessage( + { + ...message, + id: generateId(), + createdAt: new Date(), + }, + inbox, + spaceId, + 'https://sync.example.com', + CHAIN, + RPC_URL, + ), ).rejects.toThrow('Failed to verify identity'); }); @@ -546,7 +575,11 @@ describe('inboxes', () => { }; const isValid = await validateSpaceInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, spaceId, 'https://sync.example.com', @@ -555,7 +588,11 @@ describe('inboxes', () => { ); expect(isValid).toBe(true); - expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith(testParams.accountAddress, 'https://sync.example.com'); + expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith( + testParams.accountAddress, + bytesToHex(signaturePublicKey), + 'https://sync.example.com', + ); }); it('should accept unsigned messages on inboxes with optional auth', async () => { @@ -575,7 +612,11 @@ describe('inboxes', () => { }; const isValid = await validateSpaceInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, generateId(), 'https://sync.example.com', @@ -634,7 +675,11 @@ describe('inboxes', () => { }; const isValid = await validateSpaceInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, spaceId, 'https://sync.example.com', @@ -643,7 +688,11 @@ describe('inboxes', () => { ); expect(isValid).toBe(false); - expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith(testParams.accountAddress, 'https://sync.example.com'); + expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith( + testParams.accountAddress, + bytesToHex(signaturePublicKey), + 'https://sync.example.com', + ); }); }); @@ -651,11 +700,13 @@ describe('inboxes', () => { beforeEach(() => { vi.clearAllMocks(); - vi.spyOn(Identity, 'getVerifiedIdentity').mockImplementation(async (accountAddress: string) => ({ - accountAddress, - signaturePublicKey: bytesToHex(signaturePublicKey), - encryptionPublicKey: bytesToHex(encryptionPublicKey), - })); + vi.spyOn(Identity, 'getVerifiedIdentity').mockImplementation( + async (accountAddress: string, publicKey: string | null) => ({ + accountAddress, + signaturePublicKey: publicKey ?? '', + encryptionPublicKey: bytesToHex(encryptionPublicKey), + }), + ); }); it.skip('should validate a properly signed account inbox message', async () => { @@ -682,7 +733,11 @@ describe('inboxes', () => { }; const isValid = await validateAccountInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, accountAddress, 'https://sync.example.com', @@ -691,7 +746,11 @@ describe('inboxes', () => { ); expect(isValid).toBe(true); - expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith(testParams.accountAddress, 'https://sync.example.com'); + expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith( + testParams.accountAddress, + bytesToHex(signaturePublicKey), + 'https://sync.example.com', + ); }); it('should reject unsigned messages for RequiresAuth inboxes', async () => { @@ -713,7 +772,11 @@ describe('inboxes', () => { }; const isValid = await validateAccountInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, accountAddress, 'https://sync.example.com', @@ -771,7 +834,11 @@ describe('inboxes', () => { }; const isValid = await validateAccountInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, accountAddress, 'https://sync.example.com', @@ -780,7 +847,11 @@ describe('inboxes', () => { ); expect(isValid).toBe(false); - expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith(testParams.accountAddress, 'https://sync.example.com'); + expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith( + testParams.accountAddress, + bytesToHex(signaturePublicKey), + 'https://sync.example.com', + ); }); it('should accept unsigned messages for Anonymous inboxes', async () => { @@ -802,7 +873,11 @@ describe('inboxes', () => { }; const isValid = await validateAccountInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, accountAddress, 'https://sync.example.com', @@ -838,7 +913,11 @@ describe('inboxes', () => { }; const isValid = await validateAccountInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, accountAddress, 'https://sync.example.com', @@ -874,7 +953,11 @@ describe('inboxes', () => { }; const isValid = await validateAccountInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, accountAddress, 'https://sync.example.com', @@ -883,7 +966,11 @@ describe('inboxes', () => { ); expect(isValid).toBe(true); - expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith(testParams.accountAddress, 'https://sync.example.com'); + expect(Identity.getVerifiedIdentity).toHaveBeenCalledWith( + testParams.accountAddress, + bytesToHex(signaturePublicKey), + 'https://sync.example.com', + ); }); it('should accept unsigned messages on inboxes with optional auth', async () => { @@ -904,7 +991,11 @@ describe('inboxes', () => { }; const isValid = await validateAccountInboxMessage( - message, + { + ...message, + id: generateId(), + createdAt: new Date(), + }, inbox, accountAddress, 'https://sync.example.com', diff --git a/packages/hypergraph/test/space-events/accept-invitation.test.ts b/packages/hypergraph/test/space-events/accept-invitation.test.ts index b08fe2fd..a46c1073 100644 --- a/packages/hypergraph/test/space-events/accept-invitation.test.ts +++ b/packages/hypergraph/test/space-events/accept-invitation.test.ts @@ -1,6 +1,7 @@ import { Effect } from 'effect'; import { expect, it } from 'vitest'; +import { InvalidIdentityError, type PublicIdentity } from '../../src/identity/types.js'; import { acceptInvitation } from '../../src/space-events/accept-invitation.js'; import { applyEvent } from '../../src/space-events/apply-event.js'; import { createInvitation } from '../../src/space-events/create-invitation.js'; @@ -20,11 +21,14 @@ const invitee = { encryptionPublicKey: 'encryption', }; -const getVerifiedIdentity = (accountAddress: string) => { - if (accountAddress === author.accountAddress) { - return Effect.succeed(author); +const getVerifiedIdentity = (accountAddress: string, publicKey: string) => { + if (accountAddress === author.accountAddress && publicKey === author.signaturePublicKey) { + return Effect.succeed(author as PublicIdentity); } - return Effect.succeed(invitee); + if (accountAddress === invitee.accountAddress && publicKey === invitee.signaturePublicKey) { + return Effect.succeed(invitee as PublicIdentity); + } + return Effect.fail(new InvalidIdentityError()); }; it('should accept an invitation', async () => { diff --git a/packages/hypergraph/test/space-events/apply-event.test.ts b/packages/hypergraph/test/space-events/apply-event.test.ts index a6a8ad86..39c788ce 100644 --- a/packages/hypergraph/test/space-events/apply-event.test.ts +++ b/packages/hypergraph/test/space-events/apply-event.test.ts @@ -5,7 +5,7 @@ import { expect, it } from 'vitest'; import { canonicalize } from '../../src/utils/jsc.js'; import { stringToUint8Array } from '../../src/utils/stringToUint8Array.js'; -import { InvalidIdentityError } from '../../src/identity/types.js'; +import { InvalidIdentityError, type PublicIdentity } from '../../src/identity/types.js'; import { applyEvent } from '../../src/space-events/apply-event.js'; import { createInvitation } from '../../src/space-events/create-invitation.js'; import { createSpace } from '../../src/space-events/create-space.js'; @@ -25,12 +25,12 @@ const invitee = { encryptionPublicKey: 'encryption', }; -const getVerifiedIdentity = (accountAddress: string) => { - if (accountAddress === author.accountAddress) { - return Effect.succeed(author); +const getVerifiedIdentity = (accountAddress: string, publicKey: string) => { + if (accountAddress === author.accountAddress && publicKey === author.signaturePublicKey) { + return Effect.succeed(author as PublicIdentity); } - if (accountAddress === invitee.accountAddress) { - return Effect.succeed(invitee); + if (accountAddress === invitee.accountAddress && publicKey === invitee.signaturePublicKey) { + return Effect.succeed(invitee as PublicIdentity); } return Effect.fail(new InvalidIdentityError()); }; diff --git a/packages/hypergraph/test/space-events/create-space.test.ts b/packages/hypergraph/test/space-events/create-space.test.ts index fd135abb..4264c18a 100644 --- a/packages/hypergraph/test/space-events/create-space.test.ts +++ b/packages/hypergraph/test/space-events/create-space.test.ts @@ -1,7 +1,7 @@ import { Effect } from 'effect'; import { expect, it } from 'vitest'; -import { InvalidIdentityError } from '../../src/identity/types.js'; +import { InvalidIdentityError, type PublicIdentity } from '../../src/identity/types.js'; import { applyEvent } from '../../src/space-events/apply-event.js'; import { createSpace } from '../../src/space-events/create-space.js'; @@ -13,9 +13,9 @@ it('should create a space state', async () => { encryptionPublicKey: 'encryption', }; - const getVerifiedIdentity = (accountAddress: string) => { - if (accountAddress === author.accountAddress) { - return Effect.succeed(author); + const getVerifiedIdentity = (accountAddress: string, publicKey: string) => { + if (accountAddress === author.accountAddress && publicKey === author.signaturePublicKey) { + return Effect.succeed(author as PublicIdentity); } return Effect.fail(new InvalidIdentityError()); }; diff --git a/packages/hypergraph/test/space-events/delete-space.test.ts b/packages/hypergraph/test/space-events/delete-space.test.ts index 62bdf3a8..e321b9c3 100644 --- a/packages/hypergraph/test/space-events/delete-space.test.ts +++ b/packages/hypergraph/test/space-events/delete-space.test.ts @@ -1,7 +1,7 @@ import { Cause, Effect, Exit } from 'effect'; import { expect, it } from 'vitest'; -import { InvalidIdentityError } from '../../src/identity/types.js'; +import { InvalidIdentityError, type PublicIdentity } from '../../src/identity/types.js'; import { acceptInvitation } from '../../src/space-events/accept-invitation.js'; import { applyEvent } from '../../src/space-events/apply-event.js'; import { createInvitation } from '../../src/space-events/create-invitation.js'; @@ -23,12 +23,12 @@ const invitee = { encryptionPublicKey: 'encryption', }; -const getVerifiedIdentity = (accountAddress: string) => { - if (accountAddress === author.accountAddress) { - return Effect.succeed(author); +const getVerifiedIdentity = (accountAddress: string, publicKey: string) => { + if (accountAddress === author.accountAddress && publicKey === author.signaturePublicKey) { + return Effect.succeed(author as PublicIdentity); } - if (accountAddress === invitee.accountAddress) { - return Effect.succeed(invitee); + if (accountAddress === invitee.accountAddress && publicKey === invitee.signaturePublicKey) { + return Effect.succeed(invitee as PublicIdentity); } return Effect.fail(new InvalidIdentityError()); }; From c09034017101dbb901aa5254ec0a719c0f6b783b Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Mon, 14 Jul 2025 16:27:22 -0300 Subject: [PATCH 3/4] fix: update pnpm lock --- pnpm-lock.yaml | 210 ++++++++----------------------------------------- 1 file changed, 33 insertions(+), 177 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd40a030..6bbe92ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -513,31 +513,31 @@ importers: specifier: ^1.1.12 version: 1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tailwindcss/vite': - specifier: ^4.1.11 + specifier: ^4.1.8 version: 4.1.11(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.1)(tsx@4.20.3)(yaml@2.7.0)) '@tanstack/react-form': - specifier: ^1.12.4 - version: 1.12.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^1.12.1 + version: 1.14.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-query': - specifier: ^5.81.5 - version: 5.81.5(react@19.1.0) + specifier: ^5.79.2 + version: 5.83.0(react@19.1.0) '@tanstack/react-query-devtools': - specifier: ^5.81.5 - version: 5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0) + specifier: ^5.79.2 + version: 5.83.0(@tanstack/react-query@5.83.0(react@19.1.0))(react@19.1.0) '@tanstack/react-router': - specifier: ^1.123.2 - version: 1.123.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^1.120.15 + version: 1.127.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-router-devtools': - specifier: ^1.123.2 - version: 1.123.2(@tanstack/react-router@1.123.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.127.0)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.5)(tiny-invariant@1.3.3) + specifier: 1.120.15 + version: 1.120.15(@tanstack/react-router@1.127.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.127.0)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3) better-sqlite3: - specifier: ^12.2.0 - version: 12.2.0 + specifier: ^11.10.0 + version: 11.10.0 date-fns: specifier: ^4.1.0 version: 4.1.0 effect: - specifier: ^3.16.12 + specifier: ^3.16.10 version: 3.16.12 graphql: specifier: ^16.11.0 @@ -558,10 +558,10 @@ importers: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) shiki: - specifier: ^3.7.0 + specifier: ^3.4.2 version: 3.7.0 tailwindcss: - specifier: ^4.1.11 + specifier: ^4.1.8 version: 4.1.11 docs: @@ -4781,9 +4781,6 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 - '@tanstack/form-core@1.12.4': - resolution: {integrity: sha512-BhfNI5sEjI68Im1Vqezf9w68fJL4EB80cqW5w0zb/MV1erHHsXNRwLGmljF88VnCx1t/xd4fmF0D08wNajBauQ==} - '@tanstack/form-core@1.14.0': resolution: {integrity: sha512-uAOW3IxkT/Cmy8JlznK8S/LSpvtHjpUQi2wyuPqVfJ04y95WuV90SO+VKtb9TrNp51QLrrTFBR8tMEuzqp5wmA==} @@ -4798,27 +4795,12 @@ packages: '@tanstack/query-core@5.75.5': resolution: {integrity: sha512-kPDOxtoMn2Ycycb76Givx2fi+2pzo98F9ifHL/NFiahEDpDwSVW6o12PRuQ0lQnBOunhRG5etatAhQij91M3MQ==} - '@tanstack/query-core@5.81.5': - resolution: {integrity: sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q==} - '@tanstack/query-core@5.83.0': resolution: {integrity: sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==} '@tanstack/query-devtools@5.81.2': resolution: {integrity: sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==} - '@tanstack/react-form@1.12.4': - resolution: {integrity: sha512-MsWHTTUl1Db7tcawbREEMjUtnjK1wC9HnwEITFFhO6e9jN4vR8gb7qRM6TDKg0tkBf42fd5jhEI5qCYA8Sl2pQ==} - peerDependencies: - '@tanstack/react-start': ^1.112.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - vinxi: ^0.5.0 - peerDependenciesMeta: - '@tanstack/react-start': - optional: true - vinxi: - optional: true - '@tanstack/react-form@1.14.1': resolution: {integrity: sha512-Ioja3zcLZj082OdCH6pFNv15fD4UTfnJgKIXxY7Iumio8EcYLXSuxzanqNWewFvftshUFHknSEa7QtyOAkFs0Q==} peerDependencies: @@ -4831,12 +4813,6 @@ packages: vinxi: optional: true - '@tanstack/react-query-devtools@5.81.5': - resolution: {integrity: sha512-lCGMu4RX0uGnlrlLeSckBfnW/UV+KMlTBVqa97cwK7Z2ED5JKnZRSjNXwoma6sQBTJrcULvzgx2K6jEPvNUpDw==} - peerDependencies: - '@tanstack/react-query': ^5.81.5 - react: ^18 || ^19 - '@tanstack/react-query-devtools@5.83.0': resolution: {integrity: sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==} peerDependencies: @@ -4848,29 +4824,24 @@ packages: peerDependencies: react: ^18 || ^19 - '@tanstack/react-query@5.81.5': - resolution: {integrity: sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw==} - peerDependencies: - react: ^18 || ^19 - '@tanstack/react-query@5.83.0': resolution: {integrity: sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==} peerDependencies: react: ^18 || ^19 - '@tanstack/react-router-devtools@1.122.0': - resolution: {integrity: sha512-UClUnO+PFvX5Ddlhhfkgo/7qSRTM/a/jsT7V5dz9LrSaPCsh7oLQgm6lj1Yu8U9GfpCQBLzWzbsvmYAdpOa/TQ==} + '@tanstack/react-router-devtools@1.120.15': + resolution: {integrity: sha512-5KcUXc3fkiLo/6Y56gOM3JqmYXG1ElIH2iyUWuG5IlcegLrpXhu4OBQ+8Q4+62CD0OKy0ifUDyemrCOAEOfCvw==} engines: {node: '>=12'} peerDependencies: - '@tanstack/react-router': ^1.122.0 + '@tanstack/react-router': ^1.120.15 react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router-devtools@1.123.2': - resolution: {integrity: sha512-NRVxcBzT+c4Kxunn31WiyOUXslmJQDpzHjOFsmzMykMw2A5rh5gNZlbUpXOSy5OMXMHAJOcmJzxe1rr7/WcHmQ==} + '@tanstack/react-router-devtools@1.122.0': + resolution: {integrity: sha512-UClUnO+PFvX5Ddlhhfkgo/7qSRTM/a/jsT7V5dz9LrSaPCsh7oLQgm6lj1Yu8U9GfpCQBLzWzbsvmYAdpOa/TQ==} engines: {node: '>=12'} peerDependencies: - '@tanstack/react-router': ^1.123.2 + '@tanstack/react-router': ^1.122.0 react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' @@ -4889,13 +4860,6 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.123.2': - resolution: {integrity: sha512-IaFcVdK1kf/KyH43gR9osk9Zp9ms7cE0xz8iqeRQIH94rNDY67aJZkYgT5WKmoaPOkVokNii38TSehe2sNYkdw==} - engines: {node: '>=12'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.127.1': resolution: {integrity: sha512-6Ofe9VxvmuGmaJ1qUBSOsCimsPHlq4nx75EC8JhIwnkc95AQaFCZce7mTp++qECVf6HlkE5El12YwpeTz8wnpQ==} engines: {node: '>=12'} @@ -4909,12 +4873,6 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-store@0.7.1': - resolution: {integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-store@0.7.3': resolution: {integrity: sha512-3Dnqtbw9P2P0gw8uUM8WP2fFfg8XMDSZCTsywRPZe/XqqYW8PGkXKZTvP0AHkE4mpqP9Y43GpOg9vwO44azu6Q==} peerDependencies: @@ -4937,10 +4895,6 @@ packages: resolution: {integrity: sha512-3dZYP5cCq3jJYgnRDzKR3w4sYzrXP5sw1st303ye87VV26r31I8UaIuUEs7kiJaxgWBvqHglWCiygBWQODZXVw==} engines: {node: '>=12'} - '@tanstack/router-core@1.123.2': - resolution: {integrity: sha512-k1GTymZ2CBOX9SDiVHlqtgrNiid8fSKGjAofLToMAoLgqESg1knuIsYSeivmPkVbeSSdKkwF2uqsrJqNRIp/TQ==} - engines: {node: '>=12'} - '@tanstack/router-core@1.127.0': resolution: {integrity: sha512-hHgbtLOAnN61LFqBrE2bq3mctRLfXvJefBlTFakZJavSoMEniX6bMQ5ZMDwMtpo57Hbyzx2rTD4yZfYu74Eydg==} engines: {node: '>=12'} @@ -4957,18 +4911,6 @@ packages: csstype: optional: true - '@tanstack/router-devtools-core@1.123.2': - resolution: {integrity: sha512-BV16eDbZVp7fyYSkNP5jkjd9T5/LslPA8XkUIrxl56B/znqqYgFGNxL1aDh6BL7B9rYu0OqU2GTeXIhoxJklkw==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/router-core': ^1.123.2 - csstype: ^3.0.10 - solid-js: '>=1.9.5' - tiny-invariant: ^1.3.3 - peerDependenciesMeta: - csstype: - optional: true - '@tanstack/router-devtools-core@1.127.0': resolution: {integrity: sha512-K/UFaru0sVonaRoqQFUoNiqDt4AvXLxcRd2+9HjbGSC1xckAUNEEAcVl7jwQfERg89e9IVfHiPOIuDdQFjFBtA==} engines: {node: '>=12'} @@ -5047,9 +4989,6 @@ packages: '@tanstack/store@0.7.0': resolution: {integrity: sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==} - '@tanstack/store@0.7.1': - resolution: {integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==} - '@tanstack/store@0.7.2': resolution: {integrity: sha512-RP80Z30BYiPX2Pyo0Nyw4s1SJFH2jyM6f9i3HfX4pA+gm5jsnYryscdq2aIQLnL4TaGuQMO+zXmN9nh1Qck+Pg==} @@ -10711,22 +10650,12 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - seroval-plugins@1.2.1: - resolution: {integrity: sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==} - engines: {node: '>=10'} - peerDependencies: - seroval: ^1.0 - seroval-plugins@1.3.2: resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==} engines: {node: '>=10'} peerDependencies: seroval: ^1.0 - seroval@1.2.1: - resolution: {integrity: sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==} - engines: {node: '>=10'} - seroval@1.3.2: resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} engines: {node: '>=10'} @@ -17918,10 +17847,6 @@ snapshots: tailwindcss: 4.1.5 vite: 6.3.5(@types/node@22.15.15)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.1)(tsx@4.20.3)(yaml@2.7.0) - '@tanstack/form-core@1.12.4': - dependencies: - '@tanstack/store': 0.7.1 - '@tanstack/form-core@1.14.0': dependencies: '@tanstack/store': 0.7.2 @@ -17932,22 +17857,10 @@ snapshots: '@tanstack/query-core@5.75.5': {} - '@tanstack/query-core@5.81.5': {} - '@tanstack/query-core@5.83.0': {} '@tanstack/query-devtools@5.81.2': {} - '@tanstack/react-form@1.12.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@tanstack/form-core': 1.12.4 - '@tanstack/react-store': 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - decode-formdata: 0.9.0 - devalue: 5.1.1 - react: 19.1.0 - transitivePeerDependencies: - - react-dom - '@tanstack/react-form@1.14.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/form-core': 1.14.0 @@ -17958,12 +17871,6 @@ snapshots: transitivePeerDependencies: - react-dom - '@tanstack/react-query-devtools@5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0)': - dependencies: - '@tanstack/query-devtools': 5.81.2 - '@tanstack/react-query': 5.81.5(react@19.1.0) - react: 19.1.0 - '@tanstack/react-query-devtools@5.83.0(@tanstack/react-query@5.83.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/query-devtools': 5.81.2 @@ -17975,32 +17882,27 @@ snapshots: '@tanstack/query-core': 5.75.5 react: 19.1.0 - '@tanstack/react-query@5.81.5(react@19.1.0)': - dependencies: - '@tanstack/query-core': 5.81.5 - react: 19.1.0 - '@tanstack/react-query@5.83.0(react@19.1.0)': dependencies: '@tanstack/query-core': 5.83.0 react: 19.1.0 - '@tanstack/react-router-devtools@1.122.0(@tanstack/react-router@1.120.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.127.0)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.5)(tiny-invariant@1.3.3)': + '@tanstack/react-router-devtools@1.120.15(@tanstack/react-router@1.127.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.127.0)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)': dependencies: - '@tanstack/react-router': 1.120.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/router-devtools-core': 1.122.0(@tanstack/router-core@1.127.0)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) + '@tanstack/react-router': 1.127.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/router-devtools-core': 1.127.0(@tanstack/router-core@1.127.0)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + solid-js: 1.9.5 transitivePeerDependencies: - '@tanstack/router-core' - csstype - - solid-js - tiny-invariant - '@tanstack/react-router-devtools@1.123.2(@tanstack/react-router@1.123.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.127.0)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.5)(tiny-invariant@1.3.3)': + '@tanstack/react-router-devtools@1.122.0(@tanstack/react-router@1.120.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.127.0)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.5)(tiny-invariant@1.3.3)': dependencies: - '@tanstack/react-router': 1.123.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/router-devtools-core': 1.123.2(@tanstack/router-core@1.127.0)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) + '@tanstack/react-router': 1.120.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/router-devtools-core': 1.122.0(@tanstack/router-core@1.127.0)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) transitivePeerDependencies: @@ -18032,22 +17934,10 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-router@1.123.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@tanstack/history': 1.121.34 - '@tanstack/react-store': 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/router-core': 1.123.2 - isbot: 5.1.28 - jsesc: 3.1.0 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - '@tanstack/react-router@1.127.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/history': 1.121.34 - '@tanstack/react-store': 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-store': 0.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/router-core': 1.127.0 isbot: 5.1.28 jsesc: 3.1.0 @@ -18063,13 +17953,6 @@ snapshots: react-dom: 19.1.0(react@19.1.0) use-sync-external-store: 1.4.0(react@19.1.0) - '@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@tanstack/store': 0.7.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - use-sync-external-store: 1.5.0(react@19.1.0) - '@tanstack/react-store@0.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/store': 0.7.2 @@ -18095,19 +17978,10 @@ snapshots: '@tanstack/store': 0.7.0 tiny-invariant: 1.3.3 - '@tanstack/router-core@1.123.2': - dependencies: - '@tanstack/history': 1.121.34 - '@tanstack/store': 0.7.1 - cookie-es: 1.2.2 - jsesc: 3.1.0 - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - '@tanstack/router-core@1.127.0': dependencies: '@tanstack/history': 1.121.34 - '@tanstack/store': 0.7.1 + '@tanstack/store': 0.7.2 cookie-es: 1.2.2 jsesc: 3.1.0 seroval: 1.3.2 @@ -18125,16 +17999,6 @@ snapshots: optionalDependencies: csstype: 3.1.3 - '@tanstack/router-devtools-core@1.123.2(@tanstack/router-core@1.127.0)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': - dependencies: - '@tanstack/router-core': 1.127.0 - clsx: 2.1.1 - goober: 2.1.16(csstype@3.1.3) - solid-js: 1.9.5 - tiny-invariant: 1.3.3 - optionalDependencies: - csstype: 3.1.3 - '@tanstack/router-devtools-core@1.127.0(@tanstack/router-core@1.127.0)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': dependencies: '@tanstack/router-core': 1.127.0 @@ -18236,8 +18100,6 @@ snapshots: '@tanstack/store@0.7.0': {} - '@tanstack/store@0.7.1': {} - '@tanstack/store@0.7.2': {} '@tanstack/virtual-core@3.13.6': {} @@ -25364,16 +25226,10 @@ snapshots: dependencies: randombytes: 2.1.0 - seroval-plugins@1.2.1(seroval@1.2.1): - dependencies: - seroval: 1.2.1 - seroval-plugins@1.3.2(seroval@1.3.2): dependencies: seroval: 1.3.2 - seroval@1.2.1: {} - seroval@1.3.2: {} serve-handler@6.1.6: @@ -25614,8 +25470,8 @@ snapshots: solid-js@1.9.5: dependencies: csstype: 3.1.3 - seroval: 1.2.1 - seroval-plugins: 1.2.1(seroval@1.2.1) + seroval: 1.3.2 + seroval-plugins: 1.3.2(seroval@1.3.2) sonic-boom@2.8.0: dependencies: From ff21fe56da0862e1974a447ea5405f24e3574c51 Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Tue, 22 Jul 2025 14:32:28 +0200 Subject: [PATCH 4/4] improve get-verified-identity --- packages/hypergraph/src/identity/get-verified-identity.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/hypergraph/src/identity/get-verified-identity.ts b/packages/hypergraph/src/identity/get-verified-identity.ts index e9ca5665..56456e37 100644 --- a/packages/hypergraph/src/identity/get-verified-identity.ts +++ b/packages/hypergraph/src/identity/get-verified-identity.ts @@ -19,6 +19,9 @@ export const getVerifiedIdentity = async ( if (signaturePublicKey && appId) { throw new Error('Cannot specify both signaturePublicKey and appId'); } + if (!signaturePublicKey && !appId) { + throw new Error('Must specify either signaturePublicKey or appId'); + } const storeState = store.getSnapshot(); const identity = storeState.context.identities[accountAddress]?.find((identity) => { if (signaturePublicKey) {