@korajs/auth 1.0.0-beta.12 → 1.0.0-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +52 -47
  2. package/dist/{create-org-session-RsDj9cl4.d.cts → create-org-session-ChFdulEM.d.cts} +211 -17
  3. package/dist/{create-org-session-RsDj9cl4.d.ts → create-org-session-ChFdulEM.d.ts} +211 -17
  4. package/dist/index.cjs +645 -150
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +27 -9
  7. package/dist/index.d.ts +27 -9
  8. package/dist/index.js +644 -150
  9. package/dist/index.js.map +1 -1
  10. package/dist/{operation-encryptor-DRmKNWpF.d.cts → operation-encryptor-DDdlb9bm.d.cts} +16 -0
  11. package/dist/{operation-encryptor-DRmKNWpF.d.ts → operation-encryptor-DDdlb9bm.d.ts} +16 -0
  12. package/dist/react.d.cts +2 -2
  13. package/dist/react.d.ts +2 -2
  14. package/dist/server.cjs +2880 -1667
  15. package/dist/server.cjs.map +1 -1
  16. package/dist/server.d.cts +810 -169
  17. package/dist/server.d.ts +810 -169
  18. package/dist/server.js +2848 -1646
  19. package/dist/server.js.map +1 -1
  20. package/dist/svelte.cjs +2 -2
  21. package/dist/svelte.cjs.map +1 -1
  22. package/dist/svelte.d.cts +2 -2
  23. package/dist/svelte.d.ts +2 -2
  24. package/dist/svelte.js +2 -2
  25. package/dist/svelte.js.map +1 -1
  26. package/dist/vue.d.cts +1 -1
  27. package/dist/vue.d.ts +1 -1
  28. package/package.json +7 -7
  29. package/src/admin/admin-api.ts +327 -0
  30. package/src/admin/audit-log.ts +324 -0
  31. package/src/admin/webhooks.ts +576 -0
  32. package/src/bindings/create-auth-session.ts +184 -0
  33. package/src/bindings/create-org-session.ts +130 -0
  34. package/src/client/auth-client.ts +1592 -0
  35. package/src/client/auth-sync.ts +213 -0
  36. package/src/client/device-session.ts +104 -0
  37. package/src/client/org-client.ts +399 -0
  38. package/src/client/quickstart.ts +108 -0
  39. package/src/client/storage.ts +94 -0
  40. package/src/device/device-identity.ts +330 -0
  41. package/src/device/device-store.ts +379 -0
  42. package/src/encryption/auto-lock.ts +170 -0
  43. package/src/encryption/database-encryption.ts +265 -0
  44. package/src/encryption/key-derivation.ts +149 -0
  45. package/src/encryption/operation-encryptor.ts +361 -0
  46. package/src/index.ts +132 -0
  47. package/src/mfa/totp.ts +826 -0
  48. package/src/org/org-routes.ts +758 -0
  49. package/src/org/org-store.ts +490 -0
  50. package/src/org/org-types.ts +230 -0
  51. package/src/passkey/passkey-client.ts +597 -0
  52. package/src/passkey/passkey-server.ts +779 -0
  53. package/src/postgres/ensure-schema.ts +65 -0
  54. package/src/provider/adapter.ts +246 -0
  55. package/src/provider/built-in/auth-routes.ts +1313 -0
  56. package/src/provider/built-in/email-verification.ts +303 -0
  57. package/src/provider/built-in/password-hash.ts +118 -0
  58. package/src/provider/built-in/password-reset.ts +416 -0
  59. package/src/provider/built-in/postgres-user-store.ts +365 -0
  60. package/src/provider/built-in/quickstart-server.ts +760 -0
  61. package/src/provider/built-in/sqlite-user-store.ts +335 -0
  62. package/src/provider/built-in/sync-scopes.ts +85 -0
  63. package/src/provider/built-in/user-store.ts +465 -0
  64. package/src/provider/external/clerk-adapter.ts +157 -0
  65. package/src/provider/external/external-jwt-provider.ts +491 -0
  66. package/src/provider/external/supabase-adapter.ts +163 -0
  67. package/src/provider/oauth/linked-identity-store.ts +108 -0
  68. package/src/provider/oauth/oauth-flow.ts +550 -0
  69. package/src/provider/oauth/oauth-types.ts +184 -0
  70. package/src/provider/oauth/postgres-oauth-store.ts +296 -0
  71. package/src/provider/oauth/sqlite-oauth-store.ts +285 -0
  72. package/src/rbac/rbac-engine.ts +323 -0
  73. package/src/rbac/rbac-types.ts +210 -0
  74. package/src/rbac/scope-resolver.ts +140 -0
  75. package/src/react/AuthProvider.tsx +97 -0
  76. package/src/react/OrgProvider.tsx +41 -0
  77. package/src/react/auth-context.ts +26 -0
  78. package/src/react/hooks.ts +110 -0
  79. package/src/react/org-hooks.ts +214 -0
  80. package/src/react.ts +26 -0
  81. package/src/server.ts +338 -0
  82. package/src/session/session.ts +401 -0
  83. package/src/svelte/auth-context.ts +50 -0
  84. package/src/svelte/org-context.ts +32 -0
  85. package/src/svelte/org-hooks.ts +201 -0
  86. package/src/svelte/use-auth.ts +115 -0
  87. package/src/svelte.ts +25 -0
  88. package/src/tokens/encrypted-token-store.ts +360 -0
  89. package/src/tokens/jwt.ts +236 -0
  90. package/src/tokens/postgres-token-revocation-store.ts +140 -0
  91. package/src/tokens/sqlite-token-revocation-store.ts +121 -0
  92. package/src/tokens/token-manager.ts +821 -0
  93. package/src/tokens/token-store.ts +192 -0
  94. package/src/types.ts +394 -0
  95. package/src/vue/auth-context.ts +10 -0
  96. package/src/vue/auth-provider-types.ts +5 -0
  97. package/src/vue/auth-provider.ts +76 -0
  98. package/src/vue/org-hooks.ts +193 -0
  99. package/src/vue/org-provider.ts +49 -0
  100. package/src/vue/use-auth.ts +139 -0
  101. package/src/vue.ts +10 -0
@@ -0,0 +1,361 @@
1
+ import { KoraError } from '@korajs/core'
2
+ import type { Operation } from '@korajs/core'
3
+ import { decryptData, encryptData } from './database-encryption'
4
+
5
+ // ============================================================================
6
+ // Constants
7
+ // ============================================================================
8
+
9
+ /** Marker field indicating that an operation's data has been encrypted. */
10
+ const ENCRYPTED_MARKER = '__kora_encrypted' as const
11
+
12
+ /** Current encryption envelope version for forward compatibility. */
13
+ const ENCRYPTION_VERSION = 1
14
+
15
+ // ============================================================================
16
+ // Types
17
+ // ============================================================================
18
+
19
+ /**
20
+ * The envelope structure stored in an operation's `data` or `previousData`
21
+ * field when encrypted. The original field contents are replaced with this
22
+ * envelope, which the server relays opaquely.
23
+ */
24
+ interface EncryptedFieldEnvelope {
25
+ /** Marker flag — always true. Used to detect encrypted fields. */
26
+ [ENCRYPTED_MARKER]: true
27
+ /** Base64url-encoded AES-256-GCM ciphertext */
28
+ ciphertext: string
29
+ /** Base64url-encoded 12-byte initialization vector */
30
+ iv: string
31
+ /** Algorithm identifier for forward compatibility */
32
+ algorithm: 'AES-256-GCM'
33
+ /** Envelope version for schema evolution */
34
+ version: number
35
+ }
36
+
37
+ /**
38
+ * Configuration for the operation encryptor.
39
+ */
40
+ export interface OperationEncryptorConfig {
41
+ /**
42
+ * The AES-256-GCM CryptoKey used to encrypt and decrypt operation data.
43
+ *
44
+ * This can be:
45
+ * - A key derived from a user passphrase via `deriveEncryptionKey`
46
+ * - A randomly generated key from `generateEncryptionKey`
47
+ * - A key imported from raw bytes via `importKey`
48
+ *
49
+ * All devices that need to read each other's operations must share
50
+ * the same encryption key (or keys, if key rotation is implemented).
51
+ */
52
+ key: CryptoKey
53
+ }
54
+
55
+ // ============================================================================
56
+ // Errors
57
+ // ============================================================================
58
+
59
+ /**
60
+ * Thrown when operation encryption or decryption fails.
61
+ */
62
+ export class OperationEncryptionError extends KoraError {
63
+ constructor(message: string, context?: Record<string, unknown>) {
64
+ super(message, 'OPERATION_ENCRYPTION_ERROR', context)
65
+ this.name = 'OperationEncryptionError'
66
+ }
67
+ }
68
+
69
+ // ============================================================================
70
+ // Base64url encoding helpers
71
+ // ============================================================================
72
+
73
+ function toBase64Url(bytes: Uint8Array): string {
74
+ let binary = ''
75
+ for (let i = 0; i < bytes.length; i++) {
76
+ binary += String.fromCharCode(bytes[i] as number)
77
+ }
78
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
79
+ }
80
+
81
+ function fromBase64Url(str: string): Uint8Array {
82
+ let base64 = str.replace(/-/g, '+').replace(/_/g, '/')
83
+ const paddingNeeded = (4 - (base64.length % 4)) % 4
84
+ base64 += '='.repeat(paddingNeeded)
85
+
86
+ const binary = atob(base64)
87
+ const bytes = new Uint8Array(binary.length)
88
+ for (let i = 0; i < binary.length; i++) {
89
+ bytes[i] = binary.charCodeAt(i)
90
+ }
91
+ return bytes
92
+ }
93
+
94
+ // ============================================================================
95
+ // Implementation
96
+ // ============================================================================
97
+
98
+ /**
99
+ * Encrypts and decrypts the `data` and `previousData` fields of Kora operations.
100
+ *
101
+ * This provides end-to-end encryption for sync: the server relays operations
102
+ * without being able to read the user's data. Only metadata needed for sync
103
+ * orchestration (id, nodeId, collection, timestamp, causalDeps, etc.) remains
104
+ * in cleartext so the server can route, deduplicate, and order operations.
105
+ *
106
+ * **How it works:**
107
+ * 1. `encryptOperation` replaces `data` and `previousData` with encrypted envelopes
108
+ * containing base64url-encoded AES-256-GCM ciphertext
109
+ * 2. The encrypted operation is sent through the normal sync pipeline
110
+ * 3. The server stores and relays the operation without modification
111
+ * 4. `decryptOperation` on receiving clients restores the original field values
112
+ *
113
+ * **What stays in cleartext (needed for sync):**
114
+ * - `id` (content-addressed hash — used for deduplication)
115
+ * - `nodeId`, `sequenceNumber` (version vectors)
116
+ * - `timestamp` (causal ordering)
117
+ * - `type`, `collection`, `recordId` (routing and storage)
118
+ * - `causalDeps` (dependency tracking)
119
+ * - `schemaVersion` (migration)
120
+ *
121
+ * **What gets encrypted (user data):**
122
+ * - `data` (record field values for inserts/updates)
123
+ * - `previousData` (previous field values for 3-way merge)
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * import { OperationEncryptor, generateEncryptionKey } from '@korajs/auth'
128
+ *
129
+ * const key = await generateEncryptionKey()
130
+ * const encryptor = new OperationEncryptor({ key })
131
+ *
132
+ * // Before sending via sync
133
+ * const encrypted = await encryptor.encryptOperation(operation)
134
+ * syncEngine.send(encrypted)
135
+ *
136
+ * // After receiving from sync
137
+ * const decrypted = await encryptor.decryptOperation(receivedOp)
138
+ * store.apply(decrypted)
139
+ * ```
140
+ */
141
+ export class OperationEncryptor {
142
+ private readonly key: CryptoKey
143
+
144
+ constructor(config: OperationEncryptorConfig) {
145
+ this.key = config.key
146
+ }
147
+
148
+ /**
149
+ * Encrypt an operation's data fields.
150
+ *
151
+ * Returns a new Operation with `data` and `previousData` replaced by
152
+ * encrypted envelopes. The original operation is not mutated.
153
+ *
154
+ * If `data` or `previousData` is null (e.g., delete operations),
155
+ * that field remains null — there is nothing to encrypt.
156
+ *
157
+ * @param operation - The operation to encrypt
158
+ * @returns A new operation with encrypted data fields
159
+ * @throws {OperationEncryptionError} If encryption fails
160
+ */
161
+ async encryptOperation(operation: Operation): Promise<Operation> {
162
+ const [encryptedData, encryptedPreviousData] = await Promise.all([
163
+ this.encryptField(operation.data, operation.id, 'data'),
164
+ this.encryptField(operation.previousData, operation.id, 'previousData'),
165
+ ])
166
+
167
+ return {
168
+ ...operation,
169
+ data: encryptedData,
170
+ previousData: encryptedPreviousData,
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Decrypt an operation's data fields.
176
+ *
177
+ * Returns a new Operation with the original `data` and `previousData`
178
+ * restored from their encrypted envelopes. The original operation is
179
+ * not mutated.
180
+ *
181
+ * If a field is null or not encrypted (no marker), it passes through unchanged.
182
+ * This enables mixed plaintext/encrypted operations during migration.
183
+ *
184
+ * @param operation - The operation to decrypt
185
+ * @returns A new operation with decrypted data fields
186
+ * @throws {OperationEncryptionError} If decryption fails (wrong key, tampered data)
187
+ */
188
+ async decryptOperation(operation: Operation): Promise<Operation> {
189
+ const [decryptedData, decryptedPreviousData] = await Promise.all([
190
+ this.decryptField(operation.data, operation.id, 'data'),
191
+ this.decryptField(operation.previousData, operation.id, 'previousData'),
192
+ ])
193
+
194
+ return {
195
+ ...operation,
196
+ data: decryptedData,
197
+ previousData: decryptedPreviousData,
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Check if an operation's data fields are encrypted.
203
+ *
204
+ * Returns true if either `data` or `previousData` contains an encrypted
205
+ * envelope marker. Useful for determining whether decryption is needed
206
+ * before applying an operation.
207
+ *
208
+ * @param operation - The operation to check
209
+ * @returns true if any data field is encrypted
210
+ */
211
+ isEncrypted(operation: Operation): boolean {
212
+ return isEncryptedEnvelope(operation.data) || isEncryptedEnvelope(operation.previousData)
213
+ }
214
+
215
+ /**
216
+ * Encrypt a batch of operations.
217
+ *
218
+ * Convenience method for encrypting multiple operations at once.
219
+ * Operations are encrypted in parallel for performance.
220
+ *
221
+ * @param operations - The operations to encrypt
222
+ * @returns New operations with encrypted data fields
223
+ */
224
+ async encryptBatch(operations: Operation[]): Promise<Operation[]> {
225
+ return Promise.all(operations.map((op) => this.encryptOperation(op)))
226
+ }
227
+
228
+ /**
229
+ * Decrypt a batch of operations.
230
+ *
231
+ * Convenience method for decrypting multiple operations at once.
232
+ * Operations are decrypted in parallel for performance.
233
+ *
234
+ * @param operations - The operations to decrypt
235
+ * @returns New operations with decrypted data fields
236
+ */
237
+ async decryptBatch(operations: Operation[]): Promise<Operation[]> {
238
+ return Promise.all(operations.map((op) => this.decryptOperation(op)))
239
+ }
240
+
241
+ // --- Private helpers ---
242
+
243
+ private async encryptField(
244
+ field: Record<string, unknown> | null,
245
+ operationId: string,
246
+ fieldName: string,
247
+ ): Promise<Record<string, unknown> | null> {
248
+ if (field === null) {
249
+ return null
250
+ }
251
+
252
+ const plaintext = new TextEncoder().encode(JSON.stringify(field))
253
+
254
+ try {
255
+ const { ciphertext, iv } = await encryptData(this.key, plaintext)
256
+
257
+ const envelope: EncryptedFieldEnvelope = {
258
+ [ENCRYPTED_MARKER]: true,
259
+ ciphertext: toBase64Url(ciphertext),
260
+ iv: toBase64Url(iv),
261
+ algorithm: 'AES-256-GCM',
262
+ version: ENCRYPTION_VERSION,
263
+ }
264
+
265
+ return envelope as unknown as Record<string, unknown>
266
+ } catch (cause) {
267
+ if (cause instanceof OperationEncryptionError) {
268
+ throw cause
269
+ }
270
+ throw new OperationEncryptionError(`Failed to encrypt operation ${fieldName} field.`, {
271
+ operationId,
272
+ fieldName,
273
+ cause: cause instanceof Error ? cause.message : String(cause),
274
+ })
275
+ }
276
+ }
277
+
278
+ private async decryptField(
279
+ field: Record<string, unknown> | null,
280
+ operationId: string,
281
+ fieldName: string,
282
+ ): Promise<Record<string, unknown> | null> {
283
+ if (field === null) {
284
+ return null
285
+ }
286
+
287
+ // Pass through unencrypted fields (backward compatibility)
288
+ if (!isEncryptedEnvelope(field)) {
289
+ return field
290
+ }
291
+
292
+ const envelope = field as unknown as EncryptedFieldEnvelope
293
+
294
+ if (envelope.version > ENCRYPTION_VERSION) {
295
+ throw new OperationEncryptionError(
296
+ `Encrypted field uses version ${envelope.version}, but this client only supports version ${ENCRYPTION_VERSION}. Update your @korajs/auth package to decrypt this operation.`,
297
+ { operationId, fieldName, version: envelope.version },
298
+ )
299
+ }
300
+
301
+ try {
302
+ const ciphertext = fromBase64Url(envelope.ciphertext)
303
+ const iv = fromBase64Url(envelope.iv)
304
+
305
+ const plaintextBytes = await decryptData(this.key, ciphertext, iv)
306
+ const json = new TextDecoder().decode(plaintextBytes)
307
+ const parsed: unknown = JSON.parse(json)
308
+
309
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
310
+ throw new OperationEncryptionError(`Decrypted ${fieldName} is not a valid record object.`, {
311
+ operationId,
312
+ fieldName,
313
+ })
314
+ }
315
+
316
+ return parsed as Record<string, unknown>
317
+ } catch (cause) {
318
+ if (cause instanceof OperationEncryptionError) {
319
+ throw cause
320
+ }
321
+ throw new OperationEncryptionError(
322
+ `Failed to decrypt operation ${fieldName} field. This may indicate a wrong encryption key or tampered data.`,
323
+ {
324
+ operationId,
325
+ fieldName,
326
+ cause: cause instanceof Error ? cause.message : String(cause),
327
+ },
328
+ )
329
+ }
330
+ }
331
+ }
332
+
333
+ // ============================================================================
334
+ // Utility functions
335
+ // ============================================================================
336
+
337
+ /**
338
+ * Check if a field value is an encrypted envelope.
339
+ *
340
+ * This is a standalone utility function that can be used without constructing
341
+ * an OperationEncryptor instance. Useful for routing logic that needs to
342
+ * detect encrypted operations.
343
+ *
344
+ * @param field - An operation's `data` or `previousData` field
345
+ * @returns true if the field is an encrypted envelope
346
+ */
347
+ export function isEncryptedField(field: Record<string, unknown> | null): boolean {
348
+ return isEncryptedEnvelope(field)
349
+ }
350
+
351
+ function isEncryptedEnvelope(field: Record<string, unknown> | null): boolean {
352
+ if (field === null || typeof field !== 'object') {
353
+ return false
354
+ }
355
+ return (
356
+ field[ENCRYPTED_MARKER] === true &&
357
+ typeof field.ciphertext === 'string' &&
358
+ typeof field.iv === 'string' &&
359
+ field.algorithm === 'AES-256-GCM'
360
+ )
361
+ }
package/src/index.ts ADDED
@@ -0,0 +1,132 @@
1
+ // @korajs/auth — public API
2
+ // Every export here is a public API commitment. Be explicit.
3
+
4
+ // === Client ===
5
+ export { AuthClient, AuthError, MfaRequiredError } from './client/auth-client'
6
+ export type {
7
+ AuthClientConfig,
8
+ AuthClientSession,
9
+ AuthSessionStatus,
10
+ AuthTokenStorage,
11
+ AuthUser,
12
+ AuthState,
13
+ LinkedOAuthAccount,
14
+ OAuthAuthorizationOptions,
15
+ OAuthAuthorizationResult,
16
+ OAuthCallbackParams,
17
+ } from './client/auth-client'
18
+ export { createKoraAuth } from './client/quickstart'
19
+ export type { CreateKoraAuthOptions } from './client/quickstart'
20
+ export { createKoraAuthSync } from './client/auth-sync'
21
+ export type {
22
+ AuthSyncClient,
23
+ CreateKoraAuthSyncOptions,
24
+ KoraAuthSyncBinding,
25
+ } from './client/auth-sync'
26
+ export type { AuthSyncBinding } from '@korajs/core/bindings'
27
+ export { createAuthSession } from './bindings/create-auth-session'
28
+ export type { AuthSession, AuthSessionSnapshot } from './bindings/create-auth-session'
29
+ export { createOrgSession, checkOrgPermission } from './bindings/create-org-session'
30
+ export type { OrgSession, OrgSnapshot } from './bindings/create-org-session'
31
+ export { AuthDeviceIdentityError, createPersistentDeviceIdentity } from './client/device-session'
32
+ export type {
33
+ AuthDeviceIdentity,
34
+ AuthDeviceIdentityProvider,
35
+ PersistentDeviceIdentityOptions,
36
+ } from './client/device-session'
37
+ export {
38
+ createAuthTokenStorage,
39
+ createMemoryAuthTokenStorage,
40
+ createWebStorageAuthTokenStorage,
41
+ } from './client/storage'
42
+ export type { AuthKeyValueStorage, AuthTokenStorageOptions } from './client/storage'
43
+
44
+ // === Organization Client ===
45
+ export { OrgClient, OrgClientError } from './client/org-client'
46
+ export type {
47
+ OrgClientConfig,
48
+ ClientOrganization,
49
+ ClientMembership,
50
+ ClientInvitation,
51
+ } from './client/org-client'
52
+
53
+ // === Token Types ===
54
+ export type {
55
+ AuthTokens,
56
+ TokenPayload,
57
+ TokenType,
58
+ AuthConfig,
59
+ AuthStatus,
60
+ AuthEvent,
61
+ AuthEventType,
62
+ } from './types'
63
+
64
+ // === Device Identity ===
65
+ export {
66
+ generateDeviceKeyPair,
67
+ exportPublicKeyJwk,
68
+ signChallenge,
69
+ verifyChallenge,
70
+ computePublicKeyThumbprint,
71
+ toBase64Url,
72
+ fromBase64Url,
73
+ CryptoUnavailableError,
74
+ DeviceIdentityError,
75
+ } from './device/device-identity'
76
+
77
+ // === Device Key Store (persistent storage for device key pairs) ===
78
+ export {
79
+ createDeviceKeyStore,
80
+ IndexedDBDeviceKeyStore,
81
+ InMemoryDeviceKeyStore,
82
+ DeviceKeyStoreError,
83
+ } from './device/device-store'
84
+ export type { DeviceKeyStore } from './device/device-store'
85
+
86
+ // === Token Store (client-side persistence) ===
87
+ export { TokenStore } from './tokens/token-store'
88
+
89
+ // === Encrypted Token Store (AES-256-GCM encrypted localStorage) ===
90
+ export { EncryptedTokenStore, EncryptedTokenStoreError } from './tokens/encrypted-token-store'
91
+ export type { EncryptedTokenStoreConfig } from './tokens/encrypted-token-store'
92
+
93
+ // === Passkey / WebAuthn (client-side credential creation and assertion) ===
94
+ export {
95
+ isPasskeySupported,
96
+ isPlatformAuthenticatorAvailable,
97
+ createPasskeyCredential,
98
+ authenticateWithPasskey,
99
+ PasskeyError,
100
+ PasskeyUnsupportedError,
101
+ } from './passkey/passkey-client'
102
+ export type {
103
+ PasskeyRegistrationResponse,
104
+ PasskeyAuthenticationResponse,
105
+ } from './passkey/passkey-client'
106
+
107
+ // === Encryption (Phase 2: local data protection) ===
108
+ export {
109
+ generateEncryptionKey,
110
+ encryptData,
111
+ decryptData,
112
+ exportKey,
113
+ importKey,
114
+ EncryptionError,
115
+ } from './encryption/database-encryption'
116
+
117
+ export {
118
+ deriveEncryptionKey,
119
+ generateSalt,
120
+ KeyDerivationError,
121
+ } from './encryption/key-derivation'
122
+
123
+ export { AutoLockManager } from './encryption/auto-lock'
124
+ export type { AutoLockConfig } from './encryption/auto-lock'
125
+
126
+ // === E2E Operation Encryption (encrypt data fields for sync) ===
127
+ export {
128
+ OperationEncryptor,
129
+ OperationEncryptionError,
130
+ isEncryptedField,
131
+ } from './encryption/operation-encryptor'
132
+ export type { OperationEncryptorConfig } from './encryption/operation-encryptor'