Custom Fields Tutorial
This tutorial shows how to add custom encrypted fields to SecureHealth, including schema updates, encryption configuration, and testing.
Prerequisitesβ
- SecureHealth installation
- MongoDB Atlas cluster with Queryable Encryption
- Understanding of MongoDB encryption types
Step 1: Plan Field Encryptionβ
Determine the encryption type for each field:
- Deterministic (D): For searchable fields (names, IDs)
- Range (R): For date/numeric queries
- Standard (S): For maximum security (SSN, notes)
Step 2: Update Encryption Schemaβ
Add new fields to config/encryption-schema.json:
{
"securehealth.patients": {
"bsonType": "object",
"encryptMetadata": {
"keyId": "/keyId",
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
},
"properties": {
"patientId": {
"encrypt": {
"bsonType": "string",
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
}
},
"customField1": {
"encrypt": {
"bsonType": "string",
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
}
},
"customField2": {
"encrypt": {
"bsonType": "date",
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
}
},
"customField3": {
"encrypt": {
"bsonType": "string",
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
}
}
}
}
}
Step 3: Update Entityβ
Add new fields to the Patient entity:
<?php
namespace App\Entity;
class Patient
{
// ... existing fields
private ?string $customField1 = null;
private ?DateTime $customField2 = null;
private ?string $customField3 = null;
public function getCustomField1(): ?string
{
return $this->customField1;
}
public function setCustomField1(?string $customField1): self
{
$this->customField1 = $customField1;
return $this;
}
public function getCustomField2(): ?DateTime
{
return $this->customField2;
}
public function setCustomField2(?DateTime $customField2): self
{
$this->customField2 = $customField2;
return $this;
}
public function getCustomField3(): ?string
{
return $this->customField3;
}
public function setCustomField3(?string $customField3): self
{
$this->customField3 = $customField3;
return $this;
}
}
Step 4: Update API Endpointsβ
Add new fields to API responses:
<?php
namespace App\Controller;
class PatientController extends AbstractController
{
#[Route('/api/patients/{id}/custom-fields', methods: ['GET'])]
#[IsGranted('ROLE_USER')]
public function getCustomFields(Patient $patient): JsonResponse
{
$this->denyAccessUnlessGranted('VIEW', $patient);
$customFields = [
'customField1' => $patient->getCustomField1(),
'customField2' => $patient->getCustomField2(),
'customField3' => $patient->getCustomField3()
];
return $this->json($customFields);
}
#[Route('/api/patients/{id}/custom-fields', methods: ['PUT'])]
#[IsGranted('ROLE_DOCTOR')]
public function updateCustomFields(Patient $patient, Request $request): JsonResponse
{
$this->denyAccessUnlessGranted('EDIT', $patient);
$data = json_decode($request->getContent(), true);
if (isset($data['customField1'])) {
$patient->setCustomField1($data['customField1']);
}
if (isset($data['customField2'])) {
$patient->setCustomField2(new DateTime($data['customField2']));
}
if (isset($data['customField3'])) {
$patient->setCustomField3($data['customField3']);
}
$this->patientRepository->save($patient);
return $this->json(['message' => 'Custom fields updated successfully']);
}
}
Step 5: Update Data Filteringβ
Modify the PatientDataFilter to include custom fields:
<?php
namespace App\Service;
class PatientDataFilter
{
public function filterPatientData(Patient $patient, User $user): array
{
$baseData = [
'id' => $patient->getId(),
'patientId' => $patient->getPatientId(),
'firstName' => $patient->getFirstName(),
'lastName' => $patient->getLastName(),
'dateOfBirth' => $patient->getDateOfBirth(),
];
$role = $user->getRoles()[0];
switch ($role) {
case 'ROLE_ADMIN':
return $this->getAdminData($patient, $baseData);
case 'ROLE_DOCTOR':
return $this->getDoctorData($patient, $baseData);
// ... other roles
}
}
private function getAdminData(Patient $patient, array $baseData): array
{
return array_merge($baseData, [
'customField1' => $patient->getCustomField1(),
'customField2' => $patient->getCustomField2(),
'customField3' => $patient->getCustomField3(),
// ... other fields
]);
}
}
Step 6: Create Database Indexesβ
Add indexes for the new encrypted fields:
// Create indexes for encrypted fields
db.patients.createIndex({ "customField1": 1 })
db.patients.createIndex({ "customField2": 1 })
db.patients.createIndex({ "customField1": 1, "customField2": 1 })
Step 7: Test Custom Fieldsβ
Create test cases:
<?php
namespace App\Tests\Service;
class PatientServiceTest extends TestCase
{
public function testCustomFieldsEncryption(): void
{
$patient = new Patient();
$patient->setPatientId('PAT-12345');
$patient->setCustomField1('Searchable Value');
$patient->setCustomField2(new DateTime('2024-01-15'));
$patient->setCustomField3('Sensitive Information');
$this->patientService->save($patient);
$savedPatient = $this->patientService->findByPatientId('PAT-12345');
$this->assertEquals('Searchable Value', $savedPatient->getCustomField1());
$this->assertEquals('2024-01-15', $savedPatient->getCustomField2()->format('Y-m-d'));
$this->assertEquals('Sensitive Information', $savedPatient->getCustomField3());
}
public function testCustomFieldsSearch(): void
{
$patients = $this->patientService->searchPatients(['customField1' => 'Searchable Value']);
$this->assertCount(1, $patients);
$this->assertEquals('PAT-12345', $patients[0]->getPatientId());
}
}
Step 8: Update Frontendβ
Add custom fields to the UI:
class PatientForm {
renderCustomFields(patient) {
return `
<div class="custom-fields">
<h3>Custom Fields</h3>
<div class="field">
<label>Custom Field 1 (Searchable)</label>
<input type="text" name="customField1" value="${patient.customField1 || ''}">
</div>
<div class="field">
<label>Custom Field 2 (Date)</label>
<input type="date" name="customField2" value="${patient.customField2 || ''}">
</div>
<div class="field">
<label>Custom Field 3 (Sensitive)</label>
<textarea name="customField3">${patient.customField3 || ''}</textarea>
</div>
</div>
`;
}
}
Troubleshootingβ
Common Issuesβ
Encryption Errors
- Verify schema configuration
- Check field types match
- Ensure proper key configuration
Search Issues
- Use deterministic encryption for searchable fields
- Create appropriate indexes
- Test search functionality
Performance Issues
- Optimize field types
- Use appropriate encryption algorithms
- Monitor query performance
Next Stepsβ
- Audit Customization Tutorial - Custom audit logging
- Integration Examples Tutorial - Real-world integrations
- Reference - Common issues and solutions