Connection Encryption
Overview
Section titled “Overview”Comprehensive encryption/decryption has been implemented for all connection types in the WorkspaceDO. This ensures sensitive credentials are encrypted at rest in the database and only decrypted when needed.
Supported Connection Types
Section titled “Supported Connection Types”All connection auth types now have full encryption/decryption support:
-
OAuth2 - Meta Ads, Google Ads, etc.
- Encrypts:
access_token,refresh_token - Plaintext:
expires_at,scopes,token_type
- Encrypts:
-
API Key - CallRail, Stripe, etc.
- Encrypts:
api_key - Plaintext:
server_prefix,api_version
- Encrypts:
-
Basic Auth - Legacy systems
- Encrypts:
password - Plaintext:
username
- Encrypts:
-
JWT - Token-based auth
- Encrypts:
token - Plaintext:
expires_at
- Encrypts:
-
Webhook Secret - Webhook validation
- Encrypts:
secret - Plaintext:
endpoint_url
- Encrypts:
Implementation Details
Section titled “Implementation Details”Core Encryption Utilities (packages/honeygrid-types/utils/encryption.ts)
Section titled “Core Encryption Utilities (packages/honeygrid-types/utils/encryption.ts)”Added type-specific encryption/decryption functions:
encryptOAuth2AuthData()/decryptOAuth2AuthData()encryptApiKeyAuthData()/decryptApiKeyAuthData()encryptBasicAuthData()/decryptBasicAuthData()encryptJWTAuthData()/decryptJWTAuthData()encryptWebhookSecretAuthData()/decryptWebhookSecretAuthData()
Generic helpers that route to the correct type:
encryptAuthData(authType, plainAuthData, encryptionKey)decryptAuthData(authType, encryptedAuthData, encryptionKey)
WorkspaceDO Helper Methods (apps/fullstack/src/worker/api/durable-objects/WorkspaceDO.ts)
Section titled “WorkspaceDO Helper Methods (apps/fullstack/src/worker/api/durable-objects/WorkspaceDO.ts)”Generic Methods (all auth types)
Section titled “Generic Methods (all auth types)”Get decrypted auth data:
const { auth_type, auth_data, platform } = await workspace.getConnectionAuthData(connectionId)// Returns fully decrypted auth data based on auth_typeSet encrypted auth data:
await workspace.setConnectionAuthData(connectionId, 'api_key', { api_key: 'my-plaintext-key', server_prefix: 'us19',})// Automatically encrypts api_key before storingOAuth2-Specific Methods (Meta, Google Ads)
Section titled “OAuth2-Specific Methods (Meta, Google Ads)”Get tokens:
const { access_token, refresh_token, expires_at } = await workspace.getConnectionOAuth2Tokens(connectionId)// Returns decrypted access_token and refresh_tokenSet tokens:
await workspace.setConnectionOAuth2Tokens(connectionId, { access_token: 'new-token', refresh_token: 'new-refresh', expires_at: '2024-12-31T23:59:59Z', scopes: ['scope1', 'scope2'],})// Encrypts both tokens before storingAPI Key-Specific Methods (CallRail)
Section titled “API Key-Specific Methods (CallRail)”Get API key:
const apiKey = await workspace.getConnectionApiKey(connectionId)// Returns decrypted API key stringSet API key:
await workspace.setConnectionApiKey(connectionId, 'my-api-key', { server_prefix: 'us19', api_version: '3.0',})// Encrypts API key before storingBackward Compatible Methods
Section titled “Backward Compatible Methods”Legacy methods still work (OAuth2 only):
getConnectionAccessToken()- gets OAuth2 access_token onlysetConnectionAccessToken()- sets OAuth2 access_token onlyrefreshConnectionAccessToken()- alias for setConnectionAccessToken
Meta Account Token Helpers
Section titled “Meta Account Token Helpers”Separate helpers for Meta accounts table (not generic connections):
getMetaAccountAccessToken()- decrypt Meta account access_tokensetMetaAccountAccessToken()- encrypt Meta account access_tokenrefreshMetaAccountAccessToken()- alias for set
Usage Examples
Section titled “Usage Examples”CallRail Connection (API Key)
Section titled “CallRail Connection (API Key)”// Creating a CallRail connectionconst connection = await workspace.createConnection({ name: 'CallRail Account', platform: 'callrail', auth_type: 'api_key', status: 'active',})
// Store encrypted API keyawait workspace.setConnectionApiKey(connection.id, 'my-callrail-api-key-123')
// Later: retrieve decrypted API keyconst apiKey = await workspace.getConnectionApiKey(connection.id)// apiKey = 'my-callrail-api-key-123' (decrypted)
// Or get all auth dataconst { auth_data } = await workspace.getConnectionAuthData(connection.id)// auth_data = { api_key: 'my-callrail-api-key-123' } (decrypted)Meta Connection (OAuth2)
Section titled “Meta Connection (OAuth2)”// Creating a Meta connectionconst connection = await workspace.createConnection({ name: 'Meta Business', platform: 'meta_messaging', auth_type: 'oauth2', status: 'pending',})
// Store encrypted OAuth2 tokensawait workspace.setConnectionOAuth2Tokens(connection.id, { access_token: 'EAABsb...', refresh_token: '1//04...', expires_at: '2024-12-31T23:59:59Z', scopes: ['pages_messaging', 'instagram_basic'],})
// Later: retrieve decrypted tokensconst { access_token, refresh_token } = await workspace.getConnectionOAuth2Tokens(connection.id)
// Or use generic methodconst { auth_data } = await workspace.getConnectionAuthData(connection.id)// auth_data = { access_token: '...', refresh_token: '...', ... } (all decrypted)Stripe Connection (API Key with version)
Section titled “Stripe Connection (API Key with version)”const connection = await workspace.createConnection({ name: 'Stripe Account', platform: 'stripe', auth_type: 'api_key', status: 'active',})
await workspace.setConnectionApiKey(connection.id, 'sk_live_abc123...', { api_version: '2023-10-16',})
const apiKey = await workspace.getConnectionApiKey(connection.id)// apiKey = 'sk_live_abc123...' (decrypted)Security Notes
Section titled “Security Notes”- Encryption at Rest: All sensitive credentials are encrypted before being stored in the database
- Decryption on Demand: Credentials are only decrypted when explicitly requested
- AES-GCM: Uses Web Crypto API with AES-GCM encryption (256-bit keys)
- Environment Key: Requires
ENCRYPTION_KEYenvironment variable (32-byte base64-encoded key) - Field-Specific: Only sensitive fields are encrypted (tokens, keys, passwords); metadata stays plaintext for queries
Migration Path
Section titled “Migration Path”Existing connections with plaintext credentials should be re-encrypted:
// For each connection:const connection = await workspace.getConnection(id)const plainAuthData = connection.auth_data
// Re-encrypt using the appropriate methodawait workspace.setConnectionAuthData(connection.id, connection.auth_type, plainAuthData)Testing
Section titled “Testing”Type checking passes with no errors:
cd apps/fullstackpnpm check # ✓ No TypeScript errorsFiles Modified
Section titled “Files Modified”packages/honeygrid-types/utils/encryption.ts- Added auth-specific encryption helperspackages/honeygrid-types/index.ts- Exported new encryption functionsapps/fullstack/src/worker/api/durable-objects/WorkspaceDO.ts- Added helper methodspackages/honeygrid-types/validators-and-types/ConnectionTypes.ts- Removed unused type guards
Cleanup
Section titled “Cleanup”Removed unused type guard functions that were replaced by the encryption utilities:
- ❌
isEncrypted()- No longer needed - ❌
isOAuth2AuthData()- Replaced bydecryptAuthData() - ❌
isApiKeyAuthData()- Replaced bydecryptAuthData() - ❌
isBasicAuthData()- Replaced bydecryptAuthData() - ❌
isJWTAuthData()- Replaced bydecryptAuthData() - ❌
isWebhookSecretAuthData()- Replaced bydecryptAuthData()
These were never used in the codebase and became unnecessary with the new encryption helper approach that routes based on auth_type string values.