2c586351fc
Removes the @shared Vite-alias workaround in userIdentity.test.ts so tsc can resolve the import without an extra paths entry.
86 lines
3.7 KiB
TypeScript
86 lines
3.7 KiB
TypeScript
import { describe, expect, it, beforeEach, vi, beforeAll } from 'vitest';
|
|
|
|
import { setCryptoBackend } from '@chat-app/shared/crypto';
|
|
import { makeWasmTestBackend } from '@chat-app/shared/crypto/testBackend';
|
|
|
|
beforeAll(async () => { setCryptoBackend(await makeWasmTestBackend()); });
|
|
|
|
const memStore: Record<string, Uint8Array> = {};
|
|
const fakeStore = {
|
|
getSecret: async (k: string) => memStore[k] ?? null,
|
|
setSecret: async (k: string, v: Uint8Array) => { memStore[k] = v; },
|
|
removeSecret: async (k: string) => { delete memStore[k]; },
|
|
};
|
|
|
|
vi.mock('./secretStore', () => ({
|
|
devLocalSecretStore: fakeStore,
|
|
setSecretStoreUser: vi.fn(),
|
|
isEncryptedVaultActive: () => false,
|
|
}));
|
|
|
|
vi.mock('./supabase', () => {
|
|
const rpcResponses = new Map<string, { data: unknown; error: unknown }>();
|
|
const rpc = vi.fn((name: string, _params: unknown) =>
|
|
Promise.resolve(rpcResponses.get(name) ?? { data: null, error: null }),
|
|
);
|
|
const supabase = {
|
|
auth: { getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'me' } }, error: null }) },
|
|
rpc,
|
|
from: vi.fn(() => ({
|
|
select: () => ({ in: () => Promise.resolve({ data: [], error: null }) }),
|
|
})),
|
|
};
|
|
return { supabase, __setRpcResponse: (n: string, r: { data: unknown; error: unknown }) => rpcResponses.set(n, r) };
|
|
});
|
|
|
|
describe('userIdentity', () => {
|
|
beforeEach(() => { for (const k of Object.keys(memStore)) delete memStore[k]; });
|
|
|
|
it('setupNewUserIdentity uploads blob, caches private key in store', async () => {
|
|
const supabaseMod = await import('./supabase');
|
|
(supabaseMod as unknown as { __setRpcResponse: (n: string, r: { data: unknown; error: unknown }) => void })
|
|
.__setRpcResponse('reset_user_key', { data: 0, error: null });
|
|
const { setupNewUserIdentity, cachedUserKey } = await import('./userIdentity');
|
|
const result = await setupNewUserIdentity({ userId: 'me', pin: '123456', withRecovery: true });
|
|
expect(result.publicKey).toHaveLength(32);
|
|
expect(result.recoveryCode).toMatch(/^[A-Z2-9]{6}-[A-Z2-9]{6}-[A-Z2-9]{6}-[A-Z2-9]{6}$/);
|
|
const cached = await cachedUserKey('me');
|
|
expect(cached).toBeInstanceOf(Uint8Array);
|
|
expect(cached?.length).toBe(32);
|
|
});
|
|
|
|
it('loadOrUnlockUserKey unlocks with correct PIN, throws on wrong PIN', async () => {
|
|
const { setupNewUserIdentity, clearUserKeyCache, loadOrUnlockUserKey } =
|
|
await import('./userIdentity');
|
|
const supabaseMod = await import('./supabase');
|
|
const setRpc = (supabaseMod as unknown as { __setRpcResponse: (n: string, r: { data: unknown; error: unknown }) => void }).__setRpcResponse;
|
|
setRpc('reset_user_key', { data: 0, error: null });
|
|
|
|
await setupNewUserIdentity({ userId: 'me', pin: '123456', withRecovery: false });
|
|
const rpc = (supabaseMod.supabase as unknown as { rpc: ReturnType<typeof vi.fn> }).rpc;
|
|
const lastCall = rpc.mock.calls[rpc.mock.calls.length - 1]!;
|
|
const params = lastCall[1] as Record<string, string>;
|
|
|
|
setRpc('try_unlock_user_key', {
|
|
data: {
|
|
exists: true, locked: false,
|
|
sealed_private_key: params.p_sealed_private_b64,
|
|
salt: params.p_salt_b64,
|
|
kdf_params: params.p_kdf_params,
|
|
recovery_sealed_private_key: null, recovery_salt: null,
|
|
failed_attempts: 0, failed_recovery_attempts: 0,
|
|
recovery_locked_until: null, key_version: 1,
|
|
},
|
|
error: null,
|
|
});
|
|
setRpc('record_pin_attempt', { data: { failed_attempts: 0, locked_until: null }, error: null });
|
|
|
|
await clearUserKeyCache('me');
|
|
const ok = await loadOrUnlockUserKey({ userId: 'me', pin: '123456' });
|
|
expect(ok.kind).toBe('unlocked');
|
|
|
|
await clearUserKeyCache('me');
|
|
await expect(loadOrUnlockUserKey({ userId: 'me', pin: '654321' })).rejects.toThrow();
|
|
});
|
|
});
|