@korajs/auth 1.0.0-beta.11 → 1.0.0-beta.13
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.
- package/README.md +52 -47
- package/dist/{create-org-session-RsDj9cl4.d.cts → create-org-session-ChFdulEM.d.cts} +211 -17
- package/dist/{create-org-session-RsDj9cl4.d.ts → create-org-session-ChFdulEM.d.ts} +211 -17
- package/dist/index.cjs +645 -150
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -9
- package/dist/index.d.ts +27 -9
- package/dist/index.js +644 -150
- package/dist/index.js.map +1 -1
- package/dist/{operation-encryptor-DRmKNWpF.d.cts → operation-encryptor-DDdlb9bm.d.cts} +16 -0
- package/dist/{operation-encryptor-DRmKNWpF.d.ts → operation-encryptor-DDdlb9bm.d.ts} +16 -0
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/server.cjs +2852 -1675
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +779 -168
- package/dist/server.d.ts +779 -168
- package/dist/server.js +2831 -1665
- package/dist/server.js.map +1 -1
- package/dist/svelte.cjs +2 -2
- package/dist/svelte.cjs.map +1 -1
- package/dist/svelte.d.cts +2 -2
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +2 -2
- package/dist/svelte.js.map +1 -1
- package/dist/vue.d.cts +1 -1
- package/dist/vue.d.ts +1 -1
- package/package.json +7 -7
- package/src/admin/admin-api.ts +327 -0
- package/src/admin/audit-log.ts +324 -0
- package/src/admin/webhooks.ts +576 -0
- package/src/bindings/create-auth-session.ts +184 -0
- package/src/bindings/create-org-session.ts +130 -0
- package/src/client/auth-client.ts +1592 -0
- package/src/client/auth-sync.ts +213 -0
- package/src/client/device-session.ts +104 -0
- package/src/client/org-client.ts +399 -0
- package/src/client/quickstart.ts +108 -0
- package/src/client/storage.ts +94 -0
- package/src/device/device-identity.ts +330 -0
- package/src/device/device-store.ts +379 -0
- package/src/encryption/auto-lock.ts +170 -0
- package/src/encryption/database-encryption.ts +265 -0
- package/src/encryption/key-derivation.ts +149 -0
- package/src/encryption/operation-encryptor.ts +361 -0
- package/src/index.ts +132 -0
- package/src/mfa/totp.ts +826 -0
- package/src/org/org-routes.ts +758 -0
- package/src/org/org-store.ts +490 -0
- package/src/org/org-types.ts +230 -0
- package/src/passkey/passkey-client.ts +597 -0
- package/src/passkey/passkey-server.ts +779 -0
- package/src/postgres/ensure-schema.ts +65 -0
- package/src/provider/adapter.ts +246 -0
- package/src/provider/built-in/auth-routes.ts +1313 -0
- package/src/provider/built-in/email-verification.ts +303 -0
- package/src/provider/built-in/password-hash.ts +118 -0
- package/src/provider/built-in/password-reset.ts +416 -0
- package/src/provider/built-in/postgres-user-store.ts +328 -0
- package/src/provider/built-in/quickstart-server.ts +760 -0
- package/src/provider/built-in/sqlite-user-store.ts +322 -0
- package/src/provider/built-in/sync-scopes.ts +85 -0
- package/src/provider/built-in/user-store.ts +465 -0
- package/src/provider/external/clerk-adapter.ts +157 -0
- package/src/provider/external/external-jwt-provider.ts +491 -0
- package/src/provider/external/supabase-adapter.ts +163 -0
- package/src/provider/oauth/linked-identity-store.ts +108 -0
- package/src/provider/oauth/oauth-flow.ts +550 -0
- package/src/provider/oauth/oauth-types.ts +184 -0
- package/src/provider/oauth/postgres-oauth-store.ts +296 -0
- package/src/provider/oauth/sqlite-oauth-store.ts +272 -0
- package/src/rbac/rbac-engine.ts +323 -0
- package/src/rbac/rbac-types.ts +210 -0
- package/src/rbac/scope-resolver.ts +140 -0
- package/src/react/AuthProvider.tsx +97 -0
- package/src/react/OrgProvider.tsx +41 -0
- package/src/react/auth-context.ts +26 -0
- package/src/react/hooks.ts +110 -0
- package/src/react/org-hooks.ts +214 -0
- package/src/react.ts +26 -0
- package/src/server.ts +334 -0
- package/src/session/session.ts +401 -0
- package/src/svelte/auth-context.ts +50 -0
- package/src/svelte/org-context.ts +32 -0
- package/src/svelte/org-hooks.ts +201 -0
- package/src/svelte/use-auth.ts +115 -0
- package/src/svelte.ts +25 -0
- package/src/tokens/encrypted-token-store.ts +360 -0
- package/src/tokens/jwt.ts +236 -0
- package/src/tokens/postgres-token-revocation-store.ts +140 -0
- package/src/tokens/sqlite-token-revocation-store.ts +121 -0
- package/src/tokens/token-manager.ts +821 -0
- package/src/tokens/token-store.ts +192 -0
- package/src/types.ts +394 -0
- package/src/vue/auth-context.ts +10 -0
- package/src/vue/auth-provider-types.ts +5 -0
- package/src/vue/auth-provider.ts +76 -0
- package/src/vue/org-hooks.ts +193 -0
- package/src/vue/org-provider.ts +49 -0
- package/src/vue/use-auth.ts +139 -0
- 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'
|