Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for filtering encrypted field in list of values #136

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions src/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@ export function encryptOnWrite<Models extends string, Actions extends string>(
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})`
)
Expand All @@ -90,6 +95,14 @@ export function encryptOnWrite<Models extends string, Actions extends string>(
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')
Expand Down Expand Up @@ -176,6 +189,14 @@ export function decryptOnRead<Models extends string, Actions extends string>(
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
}
Expand Down Expand Up @@ -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('.')
}
Expand Down
21 changes: 21 additions & 0 deletions src/tests/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '[email protected]'
}
})
await client.user.create({
data: {
name: 'Test User 2',
email: '[email protected]'
}
})

const foundUserCount = await client.user.count({
where: { name: { in: ['Test User 1', 'Test User 2'] } }
})

expect(foundUserCount).toBe(2)
})
})
8 changes: 5 additions & 3 deletions src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface VisitorState {

export interface TargetField {
path: string
value: string
value: string | string[]
model: string
field: string
fieldConfig: FieldConfiguration
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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!
}
Expand Down
Loading