Skip to content

Phase 2: ShareTarget Encryption Integration #173

@kevalyq

Description

@kevalyq

📌 Parent Epic

#143 (Client-Side File Encryption for Zero-Knowledge Architecture)

🎯 Goal

Integrate file encryption into Share Target flow with progress indicators, error handling, and Secret selector UI.


📋 Implementation Tasks

1. Integration Tests (Write FIRST)

File: src/pages/ShareTarget.test.tsx (extend existing)

  • Test: Encrypt files before adding to IndexedDB queue
  • Test: Show encryption progress indicator
  • Test: Handle encryption errors gracefully
  • Test: Not expose encryption keys in console/errors
  • Test: Update uploadState to 'encrypted' after encryption
  • Test: Store encrypted blob in IndexedDB
  • Test: Calculate checksums correctly

2. ShareTarget Component Updates

File: src/pages/ShareTarget.tsx (extend)

  • Add Secret selector dropdown
  • Add "Encrypt & Save" button
  • Implement handleEncryptAndSave() function
    • Get Secret's master key from API
    • Derive file-specific key
    • Calculate original checksum
    • Encrypt file
    • Calculate encrypted checksum
    • Add to IndexedDB queue
  • Add encryption progress indicators (per-file)
  • Add error display for encryption failures
  • Handle edge cases (no secret selected, crypto not supported)

3. Secret API Integration

File: src/services/secretApi.ts (new)

  • getSecretMasterKey(secretId: string): Promise<CryptoKey>
    • Fetch Secret from backend
    • Extract encryption key
    • Import as CryptoKey
  • Error handling for API failures

4. FileQueue Hook Updates

File: src/hooks/useFileQueue.ts (extend)

  • Extend FileQueueEntry type for encrypted blobs
  • Update addToQueue() to accept encrypted data
  • Add uploadState field ('pending', 'encrypting', 'encrypted', 'uploading', 'failed', 'completed')

5. UI Components

File: src/components/EncryptionProgress.tsx (new)

  • Progress bar component
  • Percentage display
  • File name display
  • Status indicator (encrypting, encrypted, failed)

File: src/components/SecretSelector.tsx (new)

  • Dropdown for Secret selection
  • Fetch Secrets from API
  • Display Secret title + icon
  • "Create new Secret" option

✅ Acceptance Criteria

  • Files encrypted before IndexedDB storage
  • Progress indicators work for each file
  • Error handling graceful (user-friendly messages)
  • No keys logged to console (security)
  • Secret selector functional
  • "Encrypt & Save" button works
  • Multiple files handled correctly
  • All tests passing (≥10 new tests)
  • Code coverage ≥80% for new code
  • TypeScript strict mode clean
  • ESLint clean (no warnings)
  • REUSE compliant (SPDX headers)
  • Accessibility (a11y) compliant

🧪 Testing Strategy

describe('ShareTarget - File Encryption', () => {
  it('should encrypt files before adding to IndexedDB queue', async () => {
    const mockFile = new File(['secret data'], 'confidential.pdf');
    render(<ShareTarget />);
    await shareFiles([mockFile]);
    const secretSelector = screen.getByLabelText('Select Secret');
    fireEvent.change(secretSelector, { target: { value: 'secret-123' } });
    const saveButton = screen.getByRole('button', { name: /encrypt & save/i });
    fireEvent.click(saveButton);
    await waitFor(() => {
      expect(screen.getByText(/encrypted/i)).toBeInTheDocument();
    });
    const queueEntry = await fileQueueDB.get(mockFile.name);
    expect(queueEntry.encryptedBlob).toBeDefined();
    expect(queueEntry.uploadState).toBe('encrypted');
  });

  it('should show encryption progress indicator', async () => { ... });
  it('should handle encryption errors gracefully', async () => { ... });
  it('should not expose encryption keys in console/errors', async () => { ... });
});

🔗 Dependencies


📝 Technical Notes

Secret API Integration

// Fetch Secret and extract key
export async function getSecretMasterKey(secretId: string): Promise<CryptoKey> {
  const response = await fetch(`/api/v1/secrets/${secretId}`, {
    headers: getAuthHeaders()
  });
  if (!response.ok) throw new Error('Failed to fetch Secret');
  
  const secret = await response.json();
  
  // Import master key as CryptoKey
  const keyData = base64ToArrayBuffer(secret.encryptionKey);
  return crypto.subtle.importKey(
    'raw',
    keyData,
    { name: 'HKDF' },
    false, // Non-extractable
    ['deriveBits', 'deriveKey']
  );
}

Progress Tracking

// Track encryption progress per file
const [encryptionProgress, setEncryptionProgress] = useState<Map<string, number>>(new Map());

// Update progress
setEncryptionProgress(prev => new Map(prev).set(file.name, 25)); // 25%
setEncryptionProgress(prev => new Map(prev).set(file.name, 50)); // 50%
setEncryptionProgress(prev => new Map(prev).set(file.name, 75)); // 75%
setEncryptionProgress(prev => new Map(prev).set(file.name, 100)); // 100%

🎨 UI/UX Design

┌─────────────────────────────────────────┐
│ Share to SecPal                         │
├─────────────────────────────────────────┤
│ Files Shared:                           │
│ • confidential.pdf (1.2 MB)             │
│ • medical-record.png (500 KB)           │
│                                         │
│ Select Secret:                          │
│ ┌─────────────────────────────────┐     │
│ │ [Select a Secret...] ▼          │     │
│ └─────────────────────────────────┘     │
│                                         │
│ [🔐 Encrypt & Save to Secret]           │
│                                         │
│ Encryption Progress:                    │
│ confidential.pdf: ████████░░ 75%        │
│ medical-record.png: ██████████ 100% ✓   │
└─────────────────────────────────────────┘

🚀 Branch Strategy

# Create feature branch FROM Phase 1 branch
git checkout feat/crypto-utilities-phase1
git checkout -b feat/sharetarget-encryption-phase2

# Follow TDD cycle
git commit -S -m "test: add ShareTarget encryption integration tests (Phase 2.1)"
git commit -S -m "feat: add Secret selector component (Phase 2.2)"
git commit -S -m "feat: integrate encryption into ShareTarget (Phase 2.3)"
git commit -S -m "feat: add encryption progress indicators (Phase 2.4)"

📏 PR Linking Instructions

When creating the PR for this sub-issue, use this in your PR description:

Fixes #TBD
Part of: #143
Depends on: #TBD (Phase 1)

⚠️ Important:

  • Wait for Phase 1 PR to be merged before starting
  • Do NOT use Fixes #143 - this is not the last sub-issue
  • PR size: ~400-500 LOC (UI + integration + tests)

Type: Sub-Issue (Phase 2/5)
Priority: High
Estimated Effort: 2 days
Sprint: Encryption Integration

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions