From b173b68b9aa59f3ce99a7927ecd0d5bf69e41083 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Thu, 27 Aug 2026 14:38:00 -0500 Subject: [PATCH 1/3] fix: devices showing as unverified when unverified --- src/app/utils/matrix-crypto.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/utils/matrix-crypto.ts b/src/app/utils/matrix-crypto.ts index 99d38b5a99..bf6accd20b 100644 --- a/src/app/utils/matrix-crypto.ts +++ b/src/app/utils/matrix-crypto.ts @@ -9,6 +9,7 @@ export const verifiedDevice = async ( if (!status) return null; - const verified = status.crossSigningVerified || status.localVerified; + const verified = + status.crossSigningVerified || status.localVerified || status.signedByOwner; return verified; }; From 0baaa91fa92d3f067d693acf16c7990eda8a209c Mon Sep 17 00:00:00 2001 From: 7w1 Date: Thu, 27 Aug 2026 14:39:52 -0500 Subject: [PATCH 2/3] formatting --- src/app/utils/matrix-crypto.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/utils/matrix-crypto.ts b/src/app/utils/matrix-crypto.ts index bf6accd20b..c68d37a2f9 100644 --- a/src/app/utils/matrix-crypto.ts +++ b/src/app/utils/matrix-crypto.ts @@ -9,7 +9,6 @@ export const verifiedDevice = async ( if (!status) return null; - const verified = - status.crossSigningVerified || status.localVerified || status.signedByOwner; + const verified = status.crossSigningVerified || status.localVerified || status.signedByOwner; return verified; }; From ecb2b43c449a67ec7babb6faaf10be12b1ff8643 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Thu, 27 Aug 2026 15:03:05 -0500 Subject: [PATCH 3/3] fix tests --- .../useDeviceVerificationStatus.test.tsx | 89 +++++++++++++------ src/app/utils/matrix-crypto.ts | 3 +- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/src/app/hooks/useDeviceVerificationStatus.test.tsx b/src/app/hooks/useDeviceVerificationStatus.test.tsx index e627e0dffa..cf02c388c4 100644 --- a/src/app/hooks/useDeviceVerificationStatus.test.tsx +++ b/src/app/hooks/useDeviceVerificationStatus.test.tsx @@ -50,13 +50,22 @@ vi.mock('@sentry/react', () => sentry); const USER_ID = '@me:example.org'; const DEVICE_ID = 'DEVICEONE'; -const getDeviceVerificationStatus = - vi.fn< - ( - userId: string, - deviceId: string - ) => Promise<{ crossSigningVerified: boolean; localVerified: boolean } | null> - >(); +const deviceVerificationStatus = (status: { + crossSigningVerified: boolean; + localVerified: boolean; + signedByOwner?: boolean; +}) => ({ signedByOwner: false, ...status }); + +const getDeviceVerificationStatus = vi.fn< + ( + userId: string, + deviceId: string + ) => Promise<{ + crossSigningVerified: boolean; + localVerified: boolean; + signedByOwner: boolean; + } | null> +>(); const crypto = { getDeviceVerificationStatus } as unknown as CryptoApi; const createWrapper = () => { @@ -71,10 +80,9 @@ const createWrapper = () => { describe('useDeviceVerificationStatus', () => { beforeEach(() => { getDeviceVerificationStatus.mockReset(); - getDeviceVerificationStatus.mockResolvedValue({ - crossSigningVerified: true, - localVerified: false, - }); + getDeviceVerificationStatus.mockResolvedValue( + deviceVerificationStatus({ crossSigningVerified: true, localVerified: false }) + ); sentry.addBreadcrumb.mockClear(); sentry.metrics.count.mockClear(); }); @@ -89,11 +97,26 @@ describe('useDeviceVerificationStatus', () => { }); it('reports a locally verified device as verified', async () => { - getDeviceVerificationStatus.mockResolvedValue({ - crossSigningVerified: false, - localVerified: true, + getDeviceVerificationStatus.mockResolvedValue( + deviceVerificationStatus({ crossSigningVerified: false, localVerified: true }) + ); + + const { result } = renderHook(() => useDeviceVerificationStatus(crypto, USER_ID, DEVICE_ID), { + wrapper: createWrapper(), }); + await waitFor(() => expect(result.current).toBe(VerificationStatus.Verified)); + }); + + it('reports an owner-signed device as verified', async () => { + getDeviceVerificationStatus.mockResolvedValue( + deviceVerificationStatus({ + crossSigningVerified: false, + localVerified: false, + signedByOwner: true, + }) + ); + const { result } = renderHook(() => useDeviceVerificationStatus(crypto, USER_ID, DEVICE_ID), { wrapper: createWrapper(), }); @@ -194,10 +217,9 @@ describe('useDeviceVerificationStatus', () => { await waitFor(() => expect(result.current).toBe(VerificationStatus.Verified)); expect(getDeviceVerificationStatus).toHaveBeenCalledTimes(1); - getDeviceVerificationStatus.mockResolvedValue({ - crossSigningVerified: false, - localVerified: false, - }); + getDeviceVerificationStatus.mockResolvedValue( + deviceVerificationStatus({ crossSigningVerified: false, localVerified: false }) + ); await act(async () => { mockMx.emit(event, ...(args as never[])); }); @@ -212,10 +234,9 @@ describe('useDeviceVerificationStatus', () => { }); await waitFor(() => expect(result.current).toBe(VerificationStatus.Verified)); - getDeviceVerificationStatus.mockResolvedValue({ - crossSigningVerified: false, - localVerified: false, - }); + getDeviceVerificationStatus.mockResolvedValue( + deviceVerificationStatus({ crossSigningVerified: false, localVerified: false }) + ); await act(async () => { mockMx.emit(CryptoEvent.KeysChanged, ...([{}] as never[])); @@ -249,9 +270,15 @@ describe('useDeviceVerificationStatus', () => { ( userId: string, deviceId: string - ) => Promise<{ crossSigningVerified: boolean; localVerified: boolean } | null> + ) => Promise<{ + crossSigningVerified: boolean; + localVerified: boolean; + signedByOwner: boolean; + } | null> >() - .mockResolvedValue({ crossSigningVerified: true, localVerified: false }); + .mockResolvedValue( + deviceVerificationStatus({ crossSigningVerified: true, localVerified: false }) + ); const crypto2 = { getDeviceVerificationStatus: getDeviceVerificationStatus2, } as unknown as CryptoApi; @@ -290,10 +317,9 @@ describe('useDeviceVerificationStatus', () => { await waitFor(() => expect(resultB.current).toBe(VerificationStatus.Verified)); expect(getDeviceVerificationStatus).toHaveBeenCalledTimes(2); - getDeviceVerificationStatus.mockResolvedValue({ - crossSigningVerified: false, - localVerified: false, - }); + getDeviceVerificationStatus.mockResolvedValue( + deviceVerificationStatus({ crossSigningVerified: false, localVerified: false }) + ); await act(async () => { mockMx.emit(CryptoEvent.UserTrustStatusChanged, ...([USER_B, {}] as never[])); @@ -335,7 +361,12 @@ describe('useUnverifiedDeviceCount', () => { it('counts only devices that are not cross-signing verified', async () => { getDeviceVerificationStatus.mockImplementation((_userId: string, deviceId: string) => - Promise.resolve({ crossSigningVerified: deviceId === 'VERIFIED', localVerified: false }) + Promise.resolve( + deviceVerificationStatus({ + crossSigningVerified: deviceId === 'VERIFIED', + localVerified: false, + }) + ) ); const { result } = renderHook( diff --git a/src/app/utils/matrix-crypto.ts b/src/app/utils/matrix-crypto.ts index c68d37a2f9..233ad0bd58 100644 --- a/src/app/utils/matrix-crypto.ts +++ b/src/app/utils/matrix-crypto.ts @@ -9,6 +9,5 @@ export const verifiedDevice = async ( if (!status) return null; - const verified = status.crossSigningVerified || status.localVerified || status.signedByOwner; - return verified; + return !!(status.crossSigningVerified || status.localVerified || status.signedByOwner); };