# MongoDB Schema Design Decision Summary **Date**: 2026-02-14 **Decision**: **Zero-Knowledge Encryption for All Sensitive Data + Metadata** --- ## Core Principle **ALL sensitive data AND metadata must be encrypted client-side before reaching MongoDB.** ### Example: Blood Pressure Reading **Before encryption** (client-side): ```javascript { value: "120/80", type: "blood_pressure", unit: "mmHg", date: "2026-02-14T10:30:00Z" } ``` **After encryption** (stored in MongoDB): ```javascript { healthDataId: "health-123", userId: "user-456", profileId: "profile-789", familyId: "family-012", // Encrypted (value + metadata) healthData: [ { encrypted: true, data: "a1b2c3d4...", iv: "e5f6g7h8...", authTag: "i9j0k1l2..." } ], // Metadata (plaintext) createdAt: ISODate("2026-02-14T10:30:00Z"), updatedAt: ISODate("2026-02-14T10:30:00Z"), dataSource: "healthKit" } ``` --- ## Collections Summary | Collection | Purpose | Encrypted Fields | Plaintext Fields | |-----------|---------|------------------|-----------------| | **users** | Authentication | encryptedRecoveryPhrase | userId, email, passwordHash, tokenVersion, familyId, familyRole, permissions | | **families** | Family structure | familyName, familyMetadata | familyId, members[*].userId, members[*].profileId, members[*].role | | **profiles** | Person profiles | profileName, profileMetadata | profileId, userId, familyId, profileType | | **health_data** | Health records | healthData[*] (value + metadata) | healthDataId, userId, profileId, familyId, createdAt, updatedAt, dataSource | | **lab_results** | Lab tests | labData (value + metadata), labMetadata | labResultId, userId, profileId, familyId, createdAt, updatedAt, dataSource | | **medications** | Medication tracking | medicationData (value + metadata), reminderSchedule | medicationId, userId, profileId, familyId, active, createdAt, updatedAt | | **appointments** | Medical appointments | appointmentData (value + metadata), reminderSettings | appointmentId, userId, profileId, familyId, createdAt, updatedAt | | **shares** | Shared data | encryptedData (share-specific password) | shareId, userId, documentId, collectionName, createdAt, expiresAt, accessCount, isRevoked | | **refresh_tokens** | JWT tokens | None | jti, userId, createdAt, expiresAt, revoked | --- ## Encryption Strategy ### Client-Side Encryption **Encryption Flow**: 1. User enters health data 2. Client derives encryption key from password (PBKDF2) 3. Client encrypts health data (AES-256-GCM) 4. Client sends encrypted data to server 5. Server stores encrypted data in MongoDB 6. Server NEVER decrypts data ### What Must Be Encrypted - ✅ **Health data values** (e.g., "120/80") - ✅ **Health data metadata** (e.g., "blood_pressure", "mmHg") - ✅ **Lab test results** (e.g., "cholesterol", "200", "LabCorp") - ✅ **Medication data** (e.g., "Aspirin", "100mg", "daily") - ✅ **Appointment data** (e.g., "checkup", "Dr. Smith") - ✅ **Profile data** (e.g., "John Doe", "1990-01-01") - ✅ **Family data** (e.g., "Smith Family", "123 Main St") ### What Can Be Plaintext - ✅ **User IDs** (userId, profileId, familyId) - for queries - ✅ **Email addresses** - for authentication - ✅ **Dates** (createdAt, updatedAt) - for sorting - ✅ **Data sources** (healthKit, googleFit) - for analytics - ✅ **Tags** (cardio, daily) - for client-side search --- ## Privacy-Preserving Queries ### 1. Plaintext Queries (Recommended) **Query by plaintext fields only**: ```javascript const healthData = await db.health_data.find({ userId: 'user-123', // Plaintext ✅ profileId: 'profile-456', // Plaintext ✅ familyId: 'family-789' // Plaintext ✅ }).toArray(); // Client decrypts healthData[i].healthData[j] ``` ### 2. Tagging System (Encrypted Search) **Client adds searchable tags to encrypted data**: ```javascript const healthData = await db.health_data.find({ userId: 'user-123', tags: { $in: ['cardio', 'daily'] } // Plaintext tags ✅ }).toArray(); // Client decrypts healthData[i].healthData[j] ``` ### 3. Date Range Queries (Plaintext Dates) **Store dates as plaintext** (for range queries): ```javascript const healthData = await db.health_data.find({ userId: 'user-123', date: { $gte: ISODate("2026-02-01"), $lte: ISODate("2026-02-28") } }).toArray(); // Client decrypts healthData[i].healthData[j] ``` --- ## Technology Stack ### Backend (Axum + MongoDB) - **Axum 0.7.x**: Web framework - **MongoDB 6.0+**: Database - **Rust**: Server language ### Client (React Native + React) - **AES-256-GCM**: Encryption algorithm - **PBKDF2**: Key derivation function - **Crypto API**: Node.js crypto / react-native-quick-crypto --- ## Implementation Timeline - **Week 1**: Create MongoDB indexes - **Week 1-2**: Implement client-side encryption (React Native + React) - **Week 2-3**: Implement server-side API (Axum + MongoDB) - **Week 3**: Test encryption flow - **Week 3-4**: Test data migration (key rotation) - **Week 4**: Test privacy-preserving queries - **Week 4-5**: Performance testing **Total**: 4-5 weeks --- ## Next Steps 1. Create MongoDB indexes for all collections 2. Implement client-side encryption (React Native + React) 3. Implement server-side API (Axum + MongoDB) 4. Test encryption flow (end-to-end) 5. Test data migration (key rotation) 6. Test privacy-preserving queries 7. Performance testing 8. Create API documentation --- ## References - [Comprehensive MongoDB Schema Research](./2026-02-14-mongodb-schema-design-research.md) - [Normogen Encryption Guide](../encryption.md) - [JWT Authentication Research](./2026-02-14-jwt-authentication-research.md) - [Technology Stack Decisions](./2026-02-14-tech-stack-decision.md)