@@ -16,110 +16,249 @@ npm install @wataruoguchi/emmett-crypto-shredding-kysely @wataruoguchi/emmett-cr
1616
1717### 1. Run Database Migration
1818
19- Copy the migration file from [ ` database/migrations/ ` ] ( https://github.com/wataruoguchi/emmett-libs/blob/main/packages/emmett-crypto-shredding-kysely/database/migrations/1761627233034_crypto_shredding.ts ) to your migrations directory.
19+ Copy the migration file from [ ` database/migrations/ ` ] ( https://github.com/wataruoguchi/emmett-libs/blob/main/packages/emmett-crypto-shredding-kysely/database/migrations/1761627233034_crypto_shredding.ts ) to your migrations directory and run it .
2020
21- ### 2. Create Key Management and Policy Resolver
21+ ### 2. Create Encryption Policies
22+
23+ ** ⚠️ Important:** Policies must be created ** before** events can be encrypted. Create them during:
24+
25+ - ** Tenant onboarding** - When a new tenant/partition is created
26+ - ** Application setup** - As a one-time bootstrap step
27+ - ** Feature enablement** - When enabling crypto shredding for existing streams
2228
2329``` typescript
24- import {
25- createKeyManagement ,
26- createPolicyResolver
27- } from ' @wataruoguchi/emmett-crypto-shredding-kysely' ;
30+ import { createPolicies } from ' @wataruoguchi/emmett-crypto-shredding-kysely' ;
2831
29- const keyManagement = createKeyManagement (db );
30- const policyResolver = createPolicyResolver (db , logger );
32+ // Example: During tenant onboarding
33+ async function onboardTenant(tenantId : string ) {
34+ // Create tenant...
35+
36+ // Set up encryption policies for sensitive streams
37+ await createPolicies (db , [
38+ {
39+ policyId: ` ${tenantId }-generator ` ,
40+ partition: tenantId ,
41+ streamTypeClass: ' generator' , // Stream type to encrypt
42+ encryptionAlgorithm: ' AES-GCM' ,
43+ keyRotationIntervalDays: 180 ,
44+ keyScope: ' stream' , // 'stream' or 'type'
45+ },
46+ ]);
47+ }
3148```
3249
33- ### 3. Set Up Encryption Policies
50+ ** Alternative: ** Use default policies for common use cases:
3451
3552``` typescript
36- import { createDefaultPolicies , createPolicies } from ' @wataruoguchi/emmett-crypto-shredding-kysely' ;
53+ import { createDefaultPolicies } from ' @wataruoguchi/emmett-crypto-shredding-kysely' ;
3754
38- // Option 1: Use default policies
39- // Creates policies for common stream types:
40- // - user-data: AES-GCM, 180 day rotation, stream scope
41- // - audit-log: AES-GCM, 365 day rotation, stream scope
55+ // Creates policies for 'user-data' and 'audit-log' stream types
4256await createDefaultPolicies (db , ' tenant-123' );
57+ ```
58+
59+ ### 3. Create Encrypted Event Store in Your Module
60+
61+ Wire up the crypto event store in your module factory:
62+
63+ ``` typescript
64+ import {
65+ createCryptoEventStore ,
66+ createWebCryptoProvider ,
67+ type CryptoContext ,
68+ } from ' @wataruoguchi/emmett-crypto-shredding' ;
69+ import {
70+ createKeyManagement ,
71+ createPolicyResolver ,
72+ } from ' @wataruoguchi/emmett-crypto-shredding-kysely' ;
73+ import { getKyselyEventStore } from ' @wataruoguchi/emmett-event-store-kysely' ;
74+
75+ export function createGeneratorModule({ db , logger }) {
76+ const eventStore = createCryptoEventStore (
77+ getKyselyEventStore ({ db , logger }),
78+ {
79+ policy: createPolicyResolver (db , logger ),
80+ keys: createKeyManagement (db ),
81+ crypto: createWebCryptoProvider (),
82+ buildAAD : ({ partition , streamId }: CryptoContext ) =>
83+ new TextEncoder ().encode (` ${partition }:${streamId } ` ),
84+ logger ,
85+ },
86+ );
87+
88+ // Use eventStore in your event handler...
89+ return createService ({ eventStore });
90+ }
91+ ```
92+
93+ ** How it works:**
94+
95+ 1 . When events are appended, the policy resolver checks if the stream type has a policy
96+ 2 . If a policy exists, events are encrypted using the specified algorithm
97+ 3 . Keys are automatically generated and managed per the policy's ` keyScope `
98+ 4 . If no policy exists for a stream type, events are stored unencrypted
99+
100+ ## API Reference
101+
102+ ### Policy Management
43103
44- // Option 2: Create custom policies
104+ #### Creating Policies
105+
106+ ``` typescript
107+ import {
108+ createPolicies ,
109+ createDefaultPolicies ,
110+ type DatabasePolicyConfig
111+ } from ' @wataruoguchi/emmett-crypto-shredding-kysely' ;
112+
113+ // Create custom policies
45114await createPolicies (db , [
46115 {
47- policyId: ' tenant-123-user-data ' ,
116+ policyId: ' tenant-123-generator ' ,
48117 partition: ' tenant-123' ,
49- streamTypeClass: ' user-data ' ,
118+ streamTypeClass: ' generator ' ,
50119 encryptionAlgorithm: ' AES-GCM' ,
51120 keyRotationIntervalDays: 180 ,
52- keyScope: ' stream' ,
121+ keyScope: ' stream' , // 'stream' = one key per stream, 'type' = one key per stream type
53122 },
54123]);
124+
125+ // Or use defaults (creates 'user-data' and 'audit-log' policies)
126+ await createDefaultPolicies (db , ' tenant-123' );
55127```
56128
57- ### 4. Create Encrypted Event Store
129+ #### Managing Policies
58130
59131``` typescript
60- import { createCryptoEventStore } from ' @wataruoguchi/emmett-crypto-shredding' ;
61- import { getKyselyEventStore } from ' @wataruoguchi/emmett-event-store-kysely' ;
132+ import {
133+ updatePolicy ,
134+ listPolicies ,
135+ deletePolicy
136+ } from ' @wataruoguchi/emmett-crypto-shredding-kysely' ;
62137
63- const baseEventStore = getKyselyEventStore ({ db , logger });
64-
65- const cryptoEventStore = createCryptoEventStore ({
66- baseEventStore ,
67- keyManagement ,
68- policyResolver ,
69- buildAAD : (ctx ) => JSON .stringify ({
70- partition: ctx .partition ,
71- streamId: ctx .streamId ,
72- streamType: ctx .streamType ,
73- eventType: ctx .eventType ,
74- }),
75- logger ,
138+ // Update policy
139+ await updatePolicy (db , ' tenant-123-generator' , ' tenant-123' , {
140+ encryptionAlgorithm: ' AES-GCM' ,
141+ keyRotationIntervalDays: 365 ,
76142});
77- ```
78143
79- ## API Reference
144+ // List all policies for a partition
145+ const policies = await listPolicies (db , ' tenant-123' );
146+
147+ // Delete policy (stops encrypting new events for this stream type)
148+ await deletePolicy (db , ' tenant-123-generator' , ' tenant-123' );
149+ ```
80150
81151### Key Management
82152
153+ The ` createKeyManagement ` function returns a service that automatically manages encryption keys based on your policies:
154+
83155``` typescript
84- // Get or create active key
156+ import { createKeyManagement } from ' @wataruoguchi/emmett-crypto-shredding-kysely' ;
157+
158+ const keyManagement = createKeyManagement (db );
159+
160+ // Get or create active key (usually done automatically by crypto event store)
85161const key = await keyManagement .getActiveKey ({
86162 partition: ' tenant-123' ,
87- keyRef: ' user-data ' ,
163+ keyRef: ' generator-stream-123 ' , // Varies based on keyScope
88164});
89165
90- // Rotate key
166+ // Manually rotate key for a specific reference
91167await keyManagement .rotateKey ({
92168 partition: ' tenant-123' ,
93- keyRef: ' user-data ' ,
169+ keyRef: ' generator-stream-123 ' ,
94170});
95171
96- // Destroy all keys for a partition (crypto shredding)
172+ // Crypto shredding: Destroy all keys for a partition
173+ // This makes all encrypted events for this partition unrecoverable
97174await keyManagement .destroyPartitionKeys ({
98175 partition: ' tenant-123' ,
99176});
100177```
101178
102- ### Policy Management
179+ ## Common Patterns
180+
181+ ### Tenant Onboarding with Encryption
103182
104183``` typescript
105- // Create policies
106- await createPolicies (db , policies );
184+ async function createTenantWithEncryption(tenantData : { name: string }) {
185+ // 1. Create tenant
186+ const tenant = await tenantRepository .create (tenantData );
187+
188+ // 2. Set up encryption policies for sensitive streams
189+ await createPolicies (db , [
190+ {
191+ policyId: ` ${tenant .id }-user-data ` ,
192+ partition: tenant .id ,
193+ streamTypeClass: ' user-data' ,
194+ encryptionAlgorithm: ' AES-GCM' ,
195+ keyRotationIntervalDays: 180 ,
196+ keyScope: ' stream' ,
197+ },
198+ ]);
199+
200+ return tenant ;
201+ }
202+ ```
107203
108- // Update policy
109- await updatePolicy (db , policyId , partition , {
110- encryptionAlgorithm: ' AES-GCM' ,
111- keyRotationIntervalDays: 365 ,
112- });
204+ ### Selective Encryption by Stream Type
205+
206+ Only encrypt sensitive stream types. Other streams remain unencrypted for performance:
207+
208+ ``` typescript
209+ // Encrypt sensitive streams
210+ await createPolicies (db , [
211+ {
212+ policyId: ` ${tenantId }-user-data ` ,
213+ partition: tenantId ,
214+ streamTypeClass: ' user-data' , // ✅ Encrypted
215+ encryptionAlgorithm: ' AES-GCM' ,
216+ keyRotationIntervalDays: 180 ,
217+ keyScope: ' stream' ,
218+ },
219+ ]);
220+
221+ // No policy for 'cart' stream type → stored unencrypted ✓
222+ ```
113223
114- // List policies
115- const policies = await listPolicies (db , partition );
224+ ### Crypto Shredding (Right to be Forgotten)
116225
117- // Delete policy
118- await deletePolicy (db , policyId , partition );
226+ ``` typescript
227+ async function forgetTenant(tenantId : string ) {
228+ // Destroy all encryption keys for the tenant
229+ // This makes all encrypted events permanently unrecoverable
230+ await keyManagement .destroyPartitionKeys ({ partition: tenantId });
231+
232+ // Optionally: Delete unencrypted data or anonymize tenant records
233+ // ...
234+ }
119235```
120236
237+ ## Troubleshooting
238+
239+ ### Events Not Being Encrypted
240+
241+ ** Problem:** Events are stored in plaintext even though policies exist.
242+
243+ ** Checklist:**
244+
245+ 1 . ✅ Verify policy exists: ` await listPolicies(db, tenantId) `
246+ 2 . ✅ Check ` streamTypeClass ` matches your stream type exactly
247+ 3 . ✅ Ensure policy was created before appending events
248+ 4 . ✅ Verify ` createCryptoEventStore ` is being used (not just base event store)
249+
250+ ### Cannot Read Encrypted Events
251+
252+ ** Problem:** Events cannot be decrypted or appear as ` [encrypted] ` .
253+
254+ ** Possible causes:**
255+
256+ - Keys have been destroyed (crypto shredding)
257+ - Wrong partition/tenant context
258+ - Database connectivity issues with ` encryption_keys ` table
259+
121260## See Also
122261
123262- [ Crypto Shredding Core] ( ./emmett-crypto-shredding ) - Core encryption functionality
124263- [ Event Store Kysely] ( ./emmett-event-store-kysely ) - Base event store implementation
125- - [ Example Application] ( https://github.com/wataruoguchi/emmett-libs/tree/main/example ) - Complete working example
264+ - [ Example Application] ( https://github.com/wataruoguchi/emmett-libs/tree/main/example ) - Complete working example with crypto shredding
0 commit comments