-
Notifications
You must be signed in to change notification settings - Fork 3
fix(protect-dynamodb): Fixed bug when handling schema definitions #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
e33fbaf
fix(protect-dynamodb): Fixed bug when handling schema definitions wit…
calvinbrewer f75e6eb
ci(protect-dynamodb): add required dev dependency
calvinbrewer 6378ad4
chore: lock file
calvinbrewer 3e2b70e
ci(protect-dynamodb): add env for tests
calvinbrewer 2b63ee1
feat(protect-dynamodb): support protect nested schemas
calvinbrewer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@cipherstash/protect-dynamodb": minor | ||
| --- | ||
|
|
||
| Support nested protect schema in dynamodb helper functions. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@cipherstash/protect-dynamodb": minor | ||
| --- | ||
|
|
||
| Fixed bug when handling schema definitions without an equality flag. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,296 @@ | ||
| import { describe, expect, it } from 'vitest' | ||
| import 'dotenv/config' | ||
| import { describe, expect, it, beforeAll } from 'vitest' | ||
| import { protectDynamoDB } from '../src' | ||
| import { protect, csColumn, csTable, csValue } from '@cipherstash/protect' | ||
|
|
||
| const schema = csTable('dynamo_cipherstash_test', { | ||
| email: csColumn('email').equality(), | ||
| firstName: csColumn('firstName').equality(), | ||
| lastName: csColumn('lastName').equality(), | ||
| phoneNumber: csColumn('phoneNumber'), | ||
| example: { | ||
| protected: csValue('example.protected'), | ||
| deep: { | ||
| protected: csValue('example.deep.protected'), | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| describe('protect dynamodb helpers', () => { | ||
| it('should say hello', () => { | ||
| expect(true).toBe(true) | ||
| let protectClient: Awaited<ReturnType<typeof protect>> | ||
| let protectDynamo: ReturnType<typeof protectDynamoDB> | ||
|
|
||
| beforeAll(async () => { | ||
| protectClient = await protect({ | ||
| schemas: [schema], | ||
| }) | ||
|
|
||
| protectDynamo = protectDynamoDB({ | ||
| protectClient, | ||
| }) | ||
| }) | ||
|
|
||
| it('should encrypt columns', async () => { | ||
| const testData = { | ||
| id: '01ABCDEFGHIJKLMNOPQRSTUVWX', | ||
| email: 'test.user@example.com', | ||
| address: '123 Main Street', | ||
| createdAt: '2024-08-15T22:14:49.948Z', | ||
| firstName: 'John', | ||
| lastName: 'Smith', | ||
| phoneNumber: '555-555-5555', | ||
| companyName: 'Acme Corp', | ||
| batteryBrands: ['Brand1', 'Brand2'], | ||
| metadata: { role: 'admin' }, | ||
| example: { | ||
| protected: 'hello world', | ||
| notProtected: 'I am not protected', | ||
| deep: { | ||
| protected: 'deep protected', | ||
| notProtected: 'deep not protected', | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| const result = await protectDynamo.encryptModel(testData, schema) | ||
| if (result.failure) { | ||
| throw new Error(`Encryption failed: ${result.failure.message}`) | ||
| } | ||
|
|
||
| const encryptedData = result.data | ||
|
|
||
| // Verify equality columns are encrypted | ||
| expect(encryptedData).toHaveProperty('email__source') | ||
| expect(encryptedData).toHaveProperty('email__hmac') | ||
| expect(encryptedData).toHaveProperty('firstName__source') | ||
| expect(encryptedData).toHaveProperty('firstName__hmac') | ||
| expect(encryptedData).toHaveProperty('lastName__source') | ||
| expect(encryptedData).toHaveProperty('lastName__hmac') | ||
| expect(encryptedData).toHaveProperty('phoneNumber__source') | ||
| expect(encryptedData).not.toHaveProperty('phoneNumber__hmac') | ||
| expect(encryptedData.example).toHaveProperty('protected__source') | ||
| expect(encryptedData.example.deep).toHaveProperty('protected__source') | ||
|
|
||
| // Verify other fields remain unchanged | ||
| expect(encryptedData.id).toBe('01ABCDEFGHIJKLMNOPQRSTUVWX') | ||
| expect(encryptedData.address).toBe('123 Main Street') | ||
| expect(encryptedData.createdAt).toBe('2024-08-15T22:14:49.948Z') | ||
| expect(encryptedData.companyName).toBe('Acme Corp') | ||
| expect(encryptedData.batteryBrands).toEqual(['Brand1', 'Brand2']) | ||
| expect(encryptedData.example.notProtected).toBe('I am not protected') | ||
| expect(encryptedData.example.deep.notProtected).toBe('deep not protected') | ||
| expect(encryptedData.metadata).toEqual({ role: 'admin' }) | ||
| }) | ||
|
|
||
| it('should handle null and undefined values', async () => { | ||
| const testData = { | ||
| id: '01ABCDEFGHIJKLMNOPQRSTUVWX', | ||
| email: null, | ||
| firstName: undefined, | ||
| lastName: 'Smith', | ||
| phoneNumber: null, | ||
| metadata: { role: null }, | ||
| example: { | ||
| protected: null, | ||
| notProtected: 'I am not protected', | ||
| deep: { | ||
| protected: undefined, | ||
| notProtected: 'deep not protected', | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| const result = await protectDynamo.encryptModel(testData, schema) | ||
| if (result.failure) { | ||
| throw new Error(`Encryption failed: ${result.failure.message}`) | ||
| } | ||
|
|
||
| const encryptedData = result.data | ||
|
|
||
| // Verify null/undefined equality columns are handled | ||
| expect(encryptedData).toHaveProperty('lastName__source') | ||
| expect(encryptedData).toHaveProperty('lastName__hmac') | ||
|
|
||
| // Verify other fields remain unchanged | ||
| expect(encryptedData.id).toBe('01ABCDEFGHIJKLMNOPQRSTUVWX') | ||
| expect(encryptedData.phoneNumber).toBeNull() | ||
| expect(encryptedData.email).toBeNull() | ||
| expect(encryptedData.firstName).toBeUndefined() | ||
| expect(encryptedData.metadata).toEqual({ role: null }) | ||
| expect(encryptedData.example.protected).toBeNull() | ||
| expect(encryptedData.example.deep.protected).toBeUndefined() | ||
| expect(encryptedData.example.deep.notProtected).toBe('deep not protected') | ||
| }) | ||
|
|
||
| it('should handle empty strings and special characters', async () => { | ||
| const testData = { | ||
| id: '01ABCDEFGHIJKLMNOPQRSTUVWX', | ||
| email: '', | ||
| firstName: 'John!@#$%^&*()', | ||
| lastName: 'Smith ', | ||
| phoneNumber: '', | ||
| metadata: { role: 'admin!@#$%^&*()' }, | ||
| } | ||
|
|
||
| const result = await protectDynamo.encryptModel(testData, schema) | ||
| if (result.failure) { | ||
| throw new Error(`Encryption failed: ${result.failure.message}`) | ||
| } | ||
|
|
||
| const encryptedData = result.data | ||
|
|
||
| // Verify equality columns are encrypted | ||
| expect(encryptedData).toHaveProperty('email__source') | ||
| expect(encryptedData).toHaveProperty('email__hmac') | ||
| expect(encryptedData).toHaveProperty('firstName__source') | ||
| expect(encryptedData).toHaveProperty('firstName__hmac') | ||
| expect(encryptedData).toHaveProperty('lastName__source') | ||
| expect(encryptedData).toHaveProperty('lastName__hmac') | ||
| expect(encryptedData).toHaveProperty('phoneNumber__source') | ||
| expect(encryptedData).not.toHaveProperty('phoneNumber__hmac') | ||
|
|
||
| // Verify other fields remain unchanged | ||
| expect(encryptedData.id).toBe('01ABCDEFGHIJKLMNOPQRSTUVWX') | ||
| expect(encryptedData.metadata).toEqual({ role: 'admin!@#$%^&*()' }) | ||
| }) | ||
|
|
||
| it('should handle bulk encryption', async () => { | ||
| const testData = [ | ||
| { | ||
| id: '01ABCDEFGHIJKLMNOPQRSTUVWX', | ||
| email: 'test1@example.com', | ||
| firstName: 'John', | ||
| lastName: 'Smith', | ||
| phoneNumber: '555-555-5555', | ||
| }, | ||
| { | ||
| id: '02ABCDEFGHIJKLMNOPQRSTUVWX', | ||
| email: 'test2@example.com', | ||
| firstName: 'Jane', | ||
| lastName: 'Doe', | ||
| phoneNumber: '555-555-5556', | ||
| }, | ||
| ] | ||
|
|
||
| const result = await protectDynamo.bulkEncryptModels(testData, schema) | ||
| if (result.failure) { | ||
| throw new Error(`Bulk encryption failed: ${result.failure.message}`) | ||
| } | ||
|
|
||
| const encryptedData = result.data | ||
|
|
||
| // Verify both items are encrypted | ||
| expect(encryptedData).toHaveLength(2) | ||
|
|
||
| // Verify first item | ||
| expect(encryptedData[0]).toHaveProperty('email__source') | ||
| expect(encryptedData[0]).toHaveProperty('email__hmac') | ||
| expect(encryptedData[0]).toHaveProperty('firstName__source') | ||
| expect(encryptedData[0]).toHaveProperty('firstName__hmac') | ||
| expect(encryptedData[0]).toHaveProperty('lastName__source') | ||
| expect(encryptedData[0]).toHaveProperty('lastName__hmac') | ||
| expect(encryptedData[0]).toHaveProperty('phoneNumber__source') | ||
calvinbrewer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Verify second item | ||
| expect(encryptedData[1]).toHaveProperty('email__source') | ||
| expect(encryptedData[1]).toHaveProperty('email__hmac') | ||
| expect(encryptedData[1]).toHaveProperty('firstName__source') | ||
| expect(encryptedData[1]).toHaveProperty('firstName__hmac') | ||
| expect(encryptedData[1]).toHaveProperty('lastName__source') | ||
| expect(encryptedData[1]).toHaveProperty('lastName__hmac') | ||
| expect(encryptedData[1]).toHaveProperty('phoneNumber__source') | ||
| }) | ||
|
|
||
| it('should handle decryption of encrypted data', async () => { | ||
| const originalData = { | ||
| id: '01ABCDEFGHIJKLMNOPQRSTUVWX', | ||
| email: 'test.user@example.com', | ||
| firstName: 'John', | ||
| lastName: 'Smith', | ||
| phoneNumber: '555-555-5555', | ||
| example: { | ||
| protected: 'hello world', | ||
| notProtected: 'I am not protected', | ||
| deep: { | ||
| protected: 'deep protected', | ||
| notProtected: 'deep not protected', | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| // First encrypt | ||
| const encryptResult = await protectDynamo.encryptModel(originalData, schema) | ||
|
|
||
| if (encryptResult.failure) { | ||
| throw new Error(`Encryption failed: ${encryptResult.failure.message}`) | ||
| } | ||
|
|
||
| // Then decrypt | ||
| const decryptResult = await protectDynamo.decryptModel( | ||
| encryptResult.data, | ||
| schema, | ||
| ) | ||
| if (decryptResult.failure) { | ||
| throw new Error(`Decryption failed: ${decryptResult.failure.message}`) | ||
| } | ||
|
|
||
| const decryptedData = decryptResult.data | ||
|
|
||
| // Verify all fields match original data | ||
| expect(decryptedData).toEqual(originalData) | ||
| }) | ||
|
|
||
| it('should handle decryption of bulk encrypted data', async () => { | ||
| const originalData = [ | ||
| { | ||
| id: '01ABCDEFGHIJKLMNOPQRSTUVWX', | ||
| email: 'test1@example.com', | ||
| firstName: 'John', | ||
| lastName: 'Smith', | ||
| phoneNumber: '555-555-5555', | ||
| }, | ||
| { | ||
| id: '02ABCDEFGHIJKLMNOPQRSTUVWX', | ||
| email: 'test2@example.com', | ||
| firstName: 'Jane', | ||
| lastName: 'Doe', | ||
| phoneNumber: '555-555-5556', | ||
| example: { | ||
| protected: 'hello world', | ||
| notProtected: 'I am not protected', | ||
| deep: { | ||
| protected: 'deep protected', | ||
| notProtected: 'deep not protected', | ||
| }, | ||
| }, | ||
| }, | ||
| ] | ||
|
|
||
| // First encrypt | ||
| const encryptResult = await protectDynamo.bulkEncryptModels( | ||
| originalData, | ||
| schema, | ||
| ) | ||
| if (encryptResult.failure) { | ||
| throw new Error( | ||
| `Bulk encryption failed: ${encryptResult.failure.message}`, | ||
| ) | ||
| } | ||
|
|
||
| // Then decrypt | ||
| const decryptResult = await protectDynamo.bulkDecryptModels( | ||
| encryptResult.data, | ||
| schema, | ||
| ) | ||
| if (decryptResult.failure) { | ||
| throw new Error( | ||
| `Bulk decryption failed: ${decryptResult.failure.message}`, | ||
| ) | ||
| } | ||
|
|
||
| const decryptedData = decryptResult.data | ||
|
|
||
| // Verify all items match original data | ||
| expect(decryptedData).toEqual(originalData) | ||
| }) | ||
| }) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.