47ng/prisma-field-encryption

"not" filter not works in hash fields

Closed this issue · 2 comments

This is my prisma schema, and for each patient, they have 0 - N cases.

model Patient {
  uid             String           @id
  cases           Case[]
  ...

  @@map("patient")
}

model Case {
  cid                   BigInt                 @id @default(autoincrement())
  caseNumber            String                 @default("") @map("case_number") /// @encrypted
  caseNumberHash        String?                @map("case_number_hash") /// @encryption:hash(caseNumber)
  patient               Patient?               @relation(fields: [patientId], references: [uid])
  patientId             String?                @map("patient_id")
  ...

  @@map("case")
}

If I query entries with empty caseNumber, it works correctly. However, when I query entries with non-empty caseNumber, it does not work.

# exclude empty caseNumber in table case
db.case.findMany({
  where: { caseNumber: { not: '' } },
});

I still get empty chartNumber in the response

[
  {
    "cid": 47,
    "caseNumber": "",
    "patientId": "ce0d1b78-8858-4122-8c9c-d262927b5480",
    ...
  },
  ...
]

I try to trace code, and finally find the issue would be here. When query parameter is object type, function makeVisitor uses specialSubFields ['equals', 'set'] to handle it, which does not contain not operator.

const makeVisitor = (
  models: DMMFModels,
  visitor: TargetFieldVisitorFn,
  specialSubFields: string[],
  debug: Debugger
) =>
  function visitNode(state: VisitorState, { key, type, node, path }: Item) {
    const model = models[state.currentModel]
    if (!model || !key) {
      return state
    }
    if (type === 'string' && key in model.fields) {
     ...
    }
    // Special cases: {field}.set for updates, {field}.equals for queries
    for (const specialSubField of specialSubFields) {
      if (
        type === 'object' &&
        key in model.fields &&
        typeof (node as any)?.[specialSubField] === 'string'
      ) {
        const value: string = (node as any)[specialSubField]
        const targetField: TargetField = {
          field: key,
          model: state.currentModel,
          fieldConfig: model.fields[key],
          path: [...path, specialSubField].join('.'),
          value
        }
        debug('Visiting %O', targetField)
        visitor(targetField)
        return state
      }
    }
    ...
    return state
  }

export function visitInputTargetFields<
  Models extends string,
  Actions extends string
>(
  params: MiddlewareParams<Models, Actions>,
  models: DMMFModels,
  visitor: TargetFieldVisitorFn
) {
  traverseTree(
    params.args,
    makeVisitor(models, visitor, ['equals', 'set'], debug.encryption), // <---- here
    {
      currentModel: params.model!
    }
  )
}

Solve

Should add other filters like not into specialSubField?

Yes, it seems like it should replace the empty string value to the hash of said empty string for the query to succeed.

Would you like to open a PR?

Fixed in 1.5.1 by #95, thanks @chenpercy !