Skip to content

Commit 112dc44

Browse files
committed
feat(protect): encrypt and decrpyt audit ffi calls
1 parent a66fba7 commit 112dc44

File tree

6 files changed

+128
-44
lines changed

6 files changed

+128
-44
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import 'dotenv/config'
2+
import { describe, expect, it, beforeAll } from 'vitest'
3+
import { csTable, csColumn } from '@cipherstash/schema'
4+
import { LockContext, protect } from '../src'
5+
6+
const users = csTable('users', {
7+
auditable: csColumn('auditable'),
8+
})
9+
10+
type User = {
11+
id: string
12+
auditable?: string | null
13+
}
14+
15+
let protectClient: Awaited<ReturnType<typeof protect>>
16+
17+
beforeAll(async () => {
18+
protectClient = await protect({
19+
schemas: [users],
20+
})
21+
})
22+
23+
describe('encryption and decryption', () => {
24+
it('should encrypt and decrypt a payload with audit metadata', async () => {
25+
const email = 'very_secret_data'
26+
27+
const ciphertext = await protectClient
28+
.encrypt(email, {
29+
column: users.auditable,
30+
table: users,
31+
})
32+
.audit({
33+
metadata: {
34+
sub: 'cj@cjb.io',
35+
hello: 'world',
36+
foo: 'bar',
37+
},
38+
})
39+
40+
if (ciphertext.failure) {
41+
throw new Error(`[protect]: ${ciphertext.failure.message}`)
42+
}
43+
44+
// Verify encrypted field
45+
expect(ciphertext.data).toHaveProperty('c')
46+
47+
const plaintext = await protectClient.decrypt(ciphertext.data).audit({
48+
metadata: {
49+
sub: 'cj@cjb.io',
50+
hello: 'world',
51+
foo: 'bar',
52+
},
53+
})
54+
55+
expect(plaintext).toEqual({
56+
data: email,
57+
})
58+
}, 30000)
59+
})

packages/protect/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
},
5454
"dependencies": {
5555
"@byteslice/result": "^0.2.0",
56-
"@cipherstash/protect-ffi": "0.16.0-0",
56+
"@cipherstash/protect-ffi": "0.16.0-1",
5757
"@cipherstash/schema": "workspace:*",
5858
"zod": "^3.24.2"
5959
},

packages/protect/src/ffi/operations/base-operation.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export type AuditConfig = {
55
metadata?: Record<string, unknown>
66
}
77

8+
export type AuditData = {
9+
metadata?: Record<string, unknown>
10+
}
11+
812
export abstract class ProtectOperation<T> {
913
protected auditMetadata?: Record<string, unknown>
1014

@@ -21,8 +25,10 @@ export abstract class ProtectOperation<T> {
2125
/**
2226
* Get the audit metadata for this operation.
2327
*/
24-
public getAuditMetadata(): Record<string, unknown> | undefined {
25-
return this.auditMetadata
28+
public getAuditData(): AuditData {
29+
return {
30+
metadata: this.auditMetadata,
31+
}
2632
}
2733

2834
/**

packages/protect/src/ffi/operations/decrypt.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ export class DecryptOperation extends ProtectOperation<string | null> {
2121
lockContext: LockContext,
2222
): DecryptOperationWithLockContext {
2323
const opWithLock = new DecryptOperationWithLockContext(this, lockContext)
24-
const auditMetadata = this.getAuditMetadata()
25-
if (auditMetadata) {
26-
opWithLock.audit(auditMetadata)
24+
const auditData = this.getAuditData()
25+
if (auditData) {
26+
opWithLock.audit(auditData)
2727
}
2828
return opWithLock
2929
}
@@ -39,10 +39,19 @@ export class DecryptOperation extends ProtectOperation<string | null> {
3939
return null
4040
}
4141

42+
const { metadata } = this.getAuditData()
43+
4244
logger.debug('Decrypting data WITHOUT a lock context', {
43-
auditMetadata: this.getAuditMetadata(),
45+
metadata,
4446
})
45-
return await ffiDecrypt(this.client, this.encryptedData.c)
47+
48+
return await ffiDecrypt(
49+
this.client,
50+
this.encryptedData.c,
51+
undefined,
52+
undefined,
53+
...(metadata !== undefined ? [metadata] : []),
54+
)
4655
},
4756
(error) => ({
4857
type: ProtectErrorTypes.DecryptionError,
@@ -54,12 +63,12 @@ export class DecryptOperation extends ProtectOperation<string | null> {
5463
public getOperation(): {
5564
client: Client
5665
encryptedData: EncryptedPayload
57-
auditMetadata?: Record<string, unknown>
66+
auditData?: Record<string, unknown>
5867
} {
5968
return {
6069
client: this.client,
6170
encryptedData: this.encryptedData,
62-
auditMetadata: this.getAuditMetadata(),
71+
auditData: this.getAuditData(),
6372
}
6473
}
6574
}
@@ -74,9 +83,9 @@ export class DecryptOperationWithLockContext extends ProtectOperation<
7483
super()
7584
this.operation = operation
7685
this.lockContext = lockContext
77-
const auditMetadata = operation.getAuditMetadata()
78-
if (auditMetadata) {
79-
this.audit(auditMetadata)
86+
const auditData = operation.getAuditData()
87+
if (auditData) {
88+
this.audit(auditData)
8089
}
8190
}
8291

@@ -93,8 +102,10 @@ export class DecryptOperationWithLockContext extends ProtectOperation<
93102
return null
94103
}
95104

105+
const { metadata } = this.getAuditData()
106+
96107
logger.debug('Decrypting data WITH a lock context', {
97-
auditMetadata: this.getAuditMetadata(),
108+
metadata,
98109
})
99110

100111
const context = await this.lockContext.getLockContext()
@@ -108,6 +119,7 @@ export class DecryptOperationWithLockContext extends ProtectOperation<
108119
encryptedData.c,
109120
context.data.context,
110121
context.data.ctsToken,
122+
...(metadata !== undefined ? [metadata] : []),
111123
)
112124
},
113125
(error) => ({

packages/protect/src/ffi/operations/encrypt.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,18 @@ export class EncryptOperation extends ProtectOperation<EncryptedPayload> {
5454
return null
5555
}
5656

57-
return await ffiEncrypt(this.client, {
58-
plaintext: this.plaintext,
59-
column: this.column.getName(),
60-
table: this.table.tableName,
61-
})
57+
const { metadata } = this.getAuditData()
58+
59+
return await ffiEncrypt(
60+
this.client,
61+
{
62+
plaintext: this.plaintext,
63+
column: this.column.getName(),
64+
table: this.table.tableName,
65+
},
66+
undefined,
67+
...(metadata !== undefined ? [metadata] : []),
68+
)
6269
},
6370
(error) => ({
6471
type: ProtectErrorTypes.EncryptionError,

pnpm-lock.yaml

Lines changed: 25 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)