Files
obsidian-mcp-server/docs/plans/2025-10-26-crypto-compatibility.md

8.5 KiB

Cross-Environment Crypto Compatibility Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Fix crypto API compatibility so tests pass in Node.js environment while maintaining production behavior in Electron.

Architecture: Create crypto-adapter utility that detects environment and provides unified access to Web Crypto API (window.crypto in browser, crypto.webcrypto in Node.js).

Tech Stack: TypeScript, Jest, Node.js crypto.webcrypto, Web Crypto API


Task 1: Create crypto-adapter utility with tests (TDD)

Files:

  • Create: tests/crypto-adapter.test.ts
  • Create: src/utils/crypto-adapter.ts

Step 1: Write the failing test

Create tests/crypto-adapter.test.ts:

import { getCryptoRandomValues } from '../src/utils/crypto-adapter';

describe('crypto-adapter', () => {
	describe('getCryptoRandomValues', () => {
		it('should fill Uint8Array with random values', () => {
			const array = new Uint8Array(32);
			const result = getCryptoRandomValues(array);

			expect(result).toBe(array);
			expect(result.length).toBe(32);
			// Verify not all zeros (extremely unlikely with true random)
			const hasNonZero = Array.from(result).some(val => val !== 0);
			expect(hasNonZero).toBe(true);
		});

		it('should produce different values on subsequent calls', () => {
			const array1 = new Uint8Array(32);
			const array2 = new Uint8Array(32);

			getCryptoRandomValues(array1);
			getCryptoRandomValues(array2);

			// Arrays should be different (extremely unlikely to be identical)
			const identical = Array.from(array1).every((val, idx) => val === array2[idx]);
			expect(identical).toBe(false);
		});

		it('should preserve array type', () => {
			const uint8 = new Uint8Array(16);
			const uint16 = new Uint16Array(8);
			const uint32 = new Uint32Array(4);

			const result8 = getCryptoRandomValues(uint8);
			const result16 = getCryptoRandomValues(uint16);
			const result32 = getCryptoRandomValues(uint32);

			expect(result8).toBeInstanceOf(Uint8Array);
			expect(result16).toBeInstanceOf(Uint16Array);
			expect(result32).toBeInstanceOf(Uint32Array);
		});

		it('should work with different array lengths', () => {
			const small = new Uint8Array(8);
			const medium = new Uint8Array(32);
			const large = new Uint8Array(128);

			getCryptoRandomValues(small);
			getCryptoRandomValues(medium);
			getCryptoRandomValues(large);

			expect(small.every(val => val >= 0 && val <= 255)).toBe(true);
			expect(medium.every(val => val >= 0 && val <= 255)).toBe(true);
			expect(large.every(val => val >= 0 && val <= 255)).toBe(true);
		});
	});
});

Step 2: Run test to verify it fails

Run: npm test -- crypto-adapter.test.ts

Expected: FAIL with "Cannot find module '../src/utils/crypto-adapter'"

Step 3: Write minimal implementation

Create src/utils/crypto-adapter.ts:

/**
 * Cross-environment crypto adapter
 * Provides unified access to cryptographically secure random number generation
 * Works in both browser/Electron (window.crypto) and Node.js (crypto.webcrypto)
 */

/**
 * Gets the appropriate Crypto interface for the current environment
 * @returns Crypto interface with getRandomValues method
 * @throws Error if no crypto API is available
 */
function getCrypto(): Crypto {
	// Browser/Electron environment
	if (typeof window !== 'undefined' && window.crypto) {
		return window.crypto;
	}

	// Node.js environment (15+) - uses Web Crypto API standard
	if (typeof global !== 'undefined') {
		const nodeCrypto = require('crypto');
		if (nodeCrypto.webcrypto) {
			return nodeCrypto.webcrypto;
		}
	}

	throw new Error('No Web Crypto API available in this environment');
}

/**
 * Fills a typed array with cryptographically secure random values
 * @param array TypedArray to fill with random values
 * @returns The same array filled with random values
 */
export function getCryptoRandomValues<T extends ArrayBufferView>(array: T): T {
	return getCrypto().getRandomValues(array);
}

Step 4: Run test to verify it passes

Run: npm test -- crypto-adapter.test.ts

Expected: PASS (all 4 tests passing)

Step 5: Commit

git add tests/crypto-adapter.test.ts src/utils/crypto-adapter.ts
git commit -m "feat: add cross-environment crypto adapter

- Create getCryptoRandomValues() utility
- Support both window.crypto (browser/Electron) and crypto.webcrypto (Node.js)
- Add comprehensive test coverage for adapter functionality"

Task 2: Update auth-utils to use crypto-adapter

Files:

  • Modify: src/utils/auth-utils.ts:1-23
  • Test: tests/main-migration.test.ts (existing tests should pass)

Step 1: Verify existing tests fail with current implementation

Run: npm test -- main-migration.test.ts

Expected: FAIL with "ReferenceError: crypto is not defined"

Step 2: Update auth-utils.ts to use crypto-adapter

Modify src/utils/auth-utils.ts:

/**
 * Utility functions for authentication and API key management
 */

import { getCryptoRandomValues } from './crypto-adapter';

/**
 * Generates a cryptographically secure random API key
 * @param length Length of the API key (default: 32 characters)
 * @returns A random API key string
 */
export function generateApiKey(length: number = 32): string {
	const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
	const values = new Uint8Array(length);

	// Use cross-environment crypto adapter
	getCryptoRandomValues(values);

	let result = '';
	for (let i = 0; i < length; i++) {
		result += charset[values[i] % charset.length];
	}

	return result;
}

/**
 * Validates API key strength
 * @param apiKey The API key to validate
 * @returns Object with isValid flag and optional error message
 */
export function validateApiKey(apiKey: string): { isValid: boolean; error?: string } {
	if (!apiKey || apiKey.trim() === '') {
		return { isValid: false, error: 'API key cannot be empty' };
	}

	if (apiKey.length < 16) {
		return { isValid: false, error: 'API key must be at least 16 characters long' };
	}

	return { isValid: true };
}

Step 3: Run existing migration tests to verify they pass

Run: npm test -- main-migration.test.ts

Expected: PASS (all tests in main-migration.test.ts passing)

Step 4: Run all tests to ensure no regressions

Run: npm test

Expected: PASS (all 709+ tests passing, no failures)

Step 5: Commit

git add src/utils/auth-utils.ts
git commit -m "fix: use crypto-adapter in generateApiKey

- Replace direct crypto.getRandomValues with getCryptoRandomValues
- Fixes Node.js test environment compatibility
- Maintains production behavior in Electron"

Task 3: Verify fix and run full test suite

Files:

  • None (verification only)

Step 1: Run full test suite

Run: npm test

Expected: All tests pass (should be 713 tests: 709 existing + 4 new crypto-adapter tests)

Step 2: Verify test coverage meets thresholds

Run: npm run test:coverage

Expected:

  • Lines: ≥97%
  • Statements: ≥97%
  • Branches: ≥92%
  • Functions: ≥96%

Coverage should include new crypto-adapter.ts file.

Step 3: Run type checking

Run: npm run build

Expected: No TypeScript errors, build completes successfully

Step 4: Document verification in commit message if needed

If all checks pass, the implementation is complete. No additional commit needed unless documentation updates are required.


Completion Checklist

  • crypto-adapter.ts created with full test coverage
  • auth-utils.ts updated to use crypto-adapter
  • All existing tests pass (main-migration.test.ts)
  • New crypto-adapter tests pass (4 tests)
  • Full test suite passes (713 tests)
  • Coverage thresholds met
  • TypeScript build succeeds
  • Two commits created with descriptive messages

Expected Outcome

After completing all tasks:

  1. Tests run successfully in Node.js environment (no crypto errors)
  2. Production code unchanged in behavior (still uses window.crypto in Electron)
  3. Clean abstraction for future crypto operations
  4. Full test coverage maintained
  5. Ready for code review and PR creation

Notes for Engineer

  • Environment detection: The adapter checks typeof window first (browser/Electron), then typeof global (Node.js)
  • Web Crypto API standard: Both environments use the same API (getRandomValues), just accessed differently
  • Node.js requirement: Requires Node.js 15+ for crypto.webcrypto support
  • Type safety: TypeScript generic <T extends ArrayBufferView> preserves array type through the call
  • No mocking needed: Tests use real crypto functions in Node.js via crypto.webcrypto