diff --git a/src/encryption.ts b/src/encryption.ts index 7472382..3323d9d 100644 --- a/src/encryption.ts +++ b/src/encryption.ts @@ -81,7 +81,12 @@ export function encryptOnWrite( if (!fieldConfig.hash) { console.warn(warnings.whereConnectClauseNoHash(operation, path)) } else { - const hash = hashString(clearText, fieldConfig.hash) + const fieldConfigHash = fieldConfig.hash + // If clearText is a list, hash each value + const hash: string | string[] = Array.isArray(clearText) + ? clearText.map(value => hashString(value, fieldConfigHash)) + : hashString(clearText, fieldConfigHash) + debug.encryption( `Swapping encrypted search of ${model}.${field} with hash search under ${fieldConfig.hash.targetField} (hash: ${hash})` ) @@ -90,6 +95,14 @@ export function encryptOnWrite( return } } + // Encrypting list values is not yet supported + if (typeof clearText !== 'string') { + debug.encryption( + `Encrypting type ${typeof clearText} is not supported.` + ) + return + } + if (isOrderBy(path, field, clearText)) { // Remove unsupported orderBy clause on encrypted text // (makes no sense to sort ciphertext nor to encrypt 'asc' | 'desc') @@ -176,6 +189,14 @@ export function decryptOnRead( field }) { try { + // Decrypting list values is not yet supported + if (typeof cipherText !== 'string') { + debug.decryption( + `Decrypting type ${cipherText} is not supported.` + ) + return + } + if (!parseCloakedString(cipherText)) { return } @@ -212,8 +233,8 @@ function rewriteHashedFieldPath( hashField: string ) { const items = path.split('.').reverse() - // Special case for `where field equals or not` clause - if (items.includes('where') && items[1] === field && ['equals', 'not'].includes(items[0])) { + // Special case for `where field equals, not or in` clause + if (items.includes('where') && items[1] === field && ['equals', 'not', 'in'].includes(items[0])) { items[1] = hashField return items.reverse().join('.') } diff --git a/src/tests/integration.test.ts b/src/tests/integration.test.ts index a5ea969..44961bd 100644 --- a/src/tests/integration.test.ts +++ b/src/tests/integration.test.ts @@ -486,4 +486,25 @@ describe.each(clients)('integration ($type)', ({ client }) => { createHash('sha256').update(longNameUser.name).digest('hex') ) }, 15_000) // storing 4 MiB into the DB is a bit slow + + test('query entries in list', async () => { + await client.user.create({ + data: { + name: 'Test User 1', + email: 'test_user_in_1@example.com' + } + }) + await client.user.create({ + data: { + name: 'Test User 2', + email: 'test_user_in_2@example.com' + } + }) + + const foundUserCount = await client.user.count({ + where: { name: { in: ['Test User 1', 'Test User 2'] } } + }) + + expect(foundUserCount).toBe(2) + }) }) diff --git a/src/visitor.ts b/src/visitor.ts index 8efe60b..c965873 100644 --- a/src/visitor.ts +++ b/src/visitor.ts @@ -10,7 +10,7 @@ interface VisitorState { export interface TargetField { path: string - value: string + value: string | string[] model: string field: string fieldConfig: FieldConfiguration @@ -46,7 +46,9 @@ const makeVisitor = ( if ( type === 'object' && key in model.fields && - typeof (node as any)?.[specialSubField] === 'string' + // Used for where: { field: in: []} queries + (typeof (node as any)?.[specialSubField] === 'string' || + Array.isArray((node as any)?.[specialSubField])) ) { const value: string = (node as any)[specialSubField] const targetField: TargetField = { @@ -84,7 +86,7 @@ export function visitInputTargetFields< ) { traverseTree( params.args, - makeVisitor(models, visitor, ['equals', 'set', 'not'], debug.encryption), + makeVisitor(models, visitor, ['equals', 'set', 'not', 'in'], debug.encryption), { currentModel: params.model! }