From 57ebf4b0fe98aa2b0f664e20289449e043d9ddde Mon Sep 17 00:00:00 2001 From: Gaubee Date: Wed, 18 Feb 2026 13:10:06 +0800 Subject: [PATCH] fix: validate crypto authorize pattern against target wallet --- src/services/crypto-box/executor.ts | 6 ++- .../activities/sheets/CryptoAuthorizeJob.tsx | 25 ++--------- .../crypto-authorize-pattern.test.ts | 45 +++++++++++++++++++ .../sheets/crypto-authorize-pattern.ts | 11 +++++ 4 files changed, 64 insertions(+), 23 deletions(-) create mode 100644 src/stackflow/activities/sheets/__tests__/crypto-authorize-pattern.test.ts create mode 100644 src/stackflow/activities/sheets/crypto-authorize-pattern.ts diff --git a/src/services/crypto-box/executor.ts b/src/services/crypto-box/executor.ts index 950a6532a..212f7e8db 100644 --- a/src/services/crypto-box/executor.ts +++ b/src/services/crypto-box/executor.ts @@ -18,6 +18,7 @@ import type { AsymmetricEncryptParams, SignParams, } from './types' +import { CryptoBoxErrorCodes } from './types' // 导入加密工具 import { @@ -141,7 +142,10 @@ class CryptoExecutor { if (err instanceof Error && err.message.includes('Address mismatch')) { throw err } - throw new Error(`Failed to get keypair for wallet ${walletId}: invalid patternKey or wallet not found`) + throw Object.assign( + new Error('Crypto authorization is invalid. Please re-authorize.'), + { code: CryptoBoxErrorCodes.INVALID_SESSION_SECRET } + ) } } diff --git a/src/stackflow/activities/sheets/CryptoAuthorizeJob.tsx b/src/stackflow/activities/sheets/CryptoAuthorizeJob.tsx index 08ff146e7..836b53117 100644 --- a/src/stackflow/activities/sheets/CryptoAuthorizeJob.tsx +++ b/src/stackflow/activities/sheets/CryptoAuthorizeJob.tsx @@ -13,7 +13,6 @@ import { useFlow } from '../../stackflow'; import { ActivityParamsProvider, useActivityParams } from '../../hooks'; import { MiniappSheetHeader } from '@/components/ecosystem'; import { PatternLock, patternToString } from '@/components/security/pattern-lock'; -import { walletStorageService } from '@/services/wallet-storage'; import { type CryptoAction, type TokenDuration, @@ -24,6 +23,7 @@ import { import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { walletStore } from '@/stores'; import { superjson } from '@biochain/chain-effect'; +import { verifyCryptoAuthorizePattern } from './crypto-authorize-pattern'; type CryptoAuthorizeJobParams = { /** 请求的操作权限 (superjson 字符串) */ @@ -66,27 +66,8 @@ function CryptoAuthorizeJobContent() { try { const patternKey = patternToString(nodes); - - // 验证手势密码是否正确 - const wallets = await walletStorageService.getAllWallets(); - if (wallets.length === 0) { - setError(true); - setPattern([]); - setIsVerifying(false); - return; - } - - let isValid = false; - for (const wallet of wallets) { - try { - await walletStorageService.getMnemonic(wallet.id, patternKey); - isValid = true; - break; - } catch { - // 继续尝试下一个钱包 - } - } - + // 验证手势密码必须匹配当前目标钱包,避免“任意钱包可解锁”导致后续执行失败 + const isValid = await verifyCryptoAuthorizePattern(walletId, patternKey); if (isValid && walletId) { // 发送成功事件(包含 walletId 和 selectedDuration 用于 Token 创建) const event = new CustomEvent('crypto-authorize-confirm', { diff --git a/src/stackflow/activities/sheets/__tests__/crypto-authorize-pattern.test.ts b/src/stackflow/activities/sheets/__tests__/crypto-authorize-pattern.test.ts new file mode 100644 index 000000000..03b98c3c0 --- /dev/null +++ b/src/stackflow/activities/sheets/__tests__/crypto-authorize-pattern.test.ts @@ -0,0 +1,45 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockGetMnemonic } = vi.hoisted(() => ({ + mockGetMnemonic: vi.fn(), +})) + +vi.mock('@/services/wallet-storage', () => ({ + walletStorageService: { + getMnemonic: mockGetMnemonic, + }, +})) + +import { verifyCryptoAuthorizePattern } from '../crypto-authorize-pattern' + +describe('verifyCryptoAuthorizePattern', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('returns false when walletId is missing', async () => { + const result = await verifyCryptoAuthorizePattern(undefined, '0-1-2-5-8') + expect(result).toBe(false) + expect(mockGetMnemonic).not.toHaveBeenCalled() + }) + + it('validates pattern against target wallet only', async () => { + mockGetMnemonic.mockResolvedValueOnce('mnemonic') + + const result = await verifyCryptoAuthorizePattern('wallet-target', '0-1-2-5-8') + + expect(result).toBe(true) + expect(mockGetMnemonic).toHaveBeenCalledTimes(1) + expect(mockGetMnemonic).toHaveBeenCalledWith('wallet-target', '0-1-2-5-8') + }) + + it('returns false when target wallet pattern is invalid', async () => { + mockGetMnemonic.mockRejectedValueOnce(new Error('Failed to decrypt mnemonic')) + + const result = await verifyCryptoAuthorizePattern('wallet-target', '0-1-2-5-8') + + expect(result).toBe(false) + expect(mockGetMnemonic).toHaveBeenCalledTimes(1) + expect(mockGetMnemonic).toHaveBeenCalledWith('wallet-target', '0-1-2-5-8') + }) +}) diff --git a/src/stackflow/activities/sheets/crypto-authorize-pattern.ts b/src/stackflow/activities/sheets/crypto-authorize-pattern.ts new file mode 100644 index 000000000..48aba287d --- /dev/null +++ b/src/stackflow/activities/sheets/crypto-authorize-pattern.ts @@ -0,0 +1,11 @@ +import { walletStorageService } from '@/services/wallet-storage' + +export async function verifyCryptoAuthorizePattern(walletId: string | undefined, patternKey: string): Promise { + if (!walletId) return false + try { + await walletStorageService.getMnemonic(walletId, patternKey) + return true + } catch { + return false + } +}