@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.
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 +2852 -1675
  15. package/dist/server.cjs.map +1 -1
  16. package/dist/server.d.cts +779 -168
  17. package/dist/server.d.ts +779 -168
  18. package/dist/server.js +2831 -1665
  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 +328 -0
  60. package/src/provider/built-in/quickstart-server.ts +760 -0
  61. package/src/provider/built-in/sqlite-user-store.ts +322 -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 +272 -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 +334 -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,192 @@
1
+ import type { AuthTokens } from '../types'
2
+
3
+ /** Default key used for localStorage persistence. */
4
+ const DEFAULT_STORAGE_KEY = 'kora_auth_tokens'
5
+
6
+ /**
7
+ * Minimal storage interface matching the subset of the Web Storage API
8
+ * that TokenStore needs. This allows both localStorage and in-memory
9
+ * implementations to be used interchangeably.
10
+ */
11
+ interface SimpleStorage {
12
+ getItem(key: string): string | null
13
+ setItem(key: string, value: string): void
14
+ removeItem(key: string): void
15
+ }
16
+
17
+ /**
18
+ * In-memory storage fallback used when localStorage is unavailable
19
+ * (e.g., in Node.js, SSR environments, or when storage access is denied).
20
+ */
21
+ class MemoryStorage implements SimpleStorage {
22
+ private store = new Map<string, string>()
23
+
24
+ getItem(key: string): string | null {
25
+ return this.store.get(key) ?? null
26
+ }
27
+
28
+ setItem(key: string, value: string): void {
29
+ this.store.set(key, value)
30
+ }
31
+
32
+ removeItem(key: string): void {
33
+ this.store.delete(key)
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Attempts to access localStorage. Returns null if unavailable.
39
+ *
40
+ * localStorage may be unavailable in several scenarios:
41
+ * - Node.js / server-side rendering (no window object)
42
+ * - Private browsing modes with restricted storage
43
+ * - iframe sandboxing without storage access
44
+ * - User has disabled cookies/storage in browser settings
45
+ */
46
+ function tryGetLocalStorage(): SimpleStorage | null {
47
+ try {
48
+ if (typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {
49
+ const storage = globalThis.localStorage as SimpleStorage
50
+ // Probe that read/write actually works (some browsers throw on access)
51
+ const testKey = '__kora_storage_test__'
52
+ storage.setItem(testKey, '1')
53
+ storage.removeItem(testKey)
54
+ return storage
55
+ }
56
+ } catch {
57
+ // localStorage exists but access is denied (e.g., private mode in some browsers)
58
+ }
59
+ return null
60
+ }
61
+
62
+ /**
63
+ * Client-side token storage for Kora auth tokens.
64
+ *
65
+ * Persists tokens to localStorage when available, falling back to in-memory
66
+ * storage in environments where localStorage is unavailable (Node.js, SSR,
67
+ * restricted browser contexts). Encrypted storage is planned for Phase 2.
68
+ *
69
+ * All operations are synchronous since both localStorage and in-memory
70
+ * storage are synchronous.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const store = new TokenStore()
75
+ *
76
+ * // After login
77
+ * store.saveTokens({ accessToken: '...', refreshToken: '...' })
78
+ *
79
+ * // Before API calls
80
+ * const token = store.getAccessToken()
81
+ *
82
+ * // On logout
83
+ * store.clearTokens()
84
+ * ```
85
+ */
86
+ export class TokenStore {
87
+ private readonly storageKey: string
88
+ private readonly storage: SimpleStorage
89
+
90
+ /**
91
+ * Creates a new TokenStore instance.
92
+ *
93
+ * @param storageKey - The key under which tokens are stored. Defaults to 'kora_auth_tokens'.
94
+ * Use different keys if your app runs multiple Kora instances with separate auth.
95
+ */
96
+ constructor(storageKey?: string) {
97
+ this.storageKey = storageKey ?? DEFAULT_STORAGE_KEY
98
+ this.storage = tryGetLocalStorage() ?? new MemoryStorage()
99
+ }
100
+
101
+ /**
102
+ * Save tokens to persistent storage.
103
+ *
104
+ * Overwrites any previously stored tokens. The tokens are serialized
105
+ * as JSON. Only the `accessToken`, `refreshToken`, and optional
106
+ * `deviceCredential` fields are persisted.
107
+ *
108
+ * @param tokens - The token set to store
109
+ */
110
+ saveTokens(tokens: AuthTokens): void {
111
+ const serialized: AuthTokens = {
112
+ accessToken: tokens.accessToken,
113
+ refreshToken: tokens.refreshToken,
114
+ }
115
+ if (tokens.deviceCredential !== undefined) {
116
+ serialized.deviceCredential = tokens.deviceCredential
117
+ }
118
+ this.storage.setItem(this.storageKey, JSON.stringify(serialized))
119
+ }
120
+
121
+ /**
122
+ * Load tokens from storage.
123
+ *
124
+ * @returns The stored {@link AuthTokens}, or null if no tokens have been saved
125
+ */
126
+ loadTokens(): AuthTokens | null {
127
+ const raw = this.storage.getItem(this.storageKey)
128
+ if (raw === null) {
129
+ return null
130
+ }
131
+
132
+ try {
133
+ const parsed: unknown = JSON.parse(raw)
134
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
135
+ return null
136
+ }
137
+ const record = parsed as Record<string, unknown>
138
+
139
+ // Validate required fields are present and are strings
140
+ if (typeof record.accessToken !== 'string' || typeof record.refreshToken !== 'string') {
141
+ return null
142
+ }
143
+
144
+ const tokens: AuthTokens = {
145
+ accessToken: record.accessToken,
146
+ refreshToken: record.refreshToken,
147
+ }
148
+
149
+ if (typeof record.deviceCredential === 'string') {
150
+ tokens.deviceCredential = record.deviceCredential
151
+ }
152
+
153
+ return tokens
154
+ } catch {
155
+ // Corrupted data in storage; treat as empty
156
+ return null
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Clear all stored tokens.
162
+ *
163
+ * Call this on logout to remove credentials from persistent storage.
164
+ */
165
+ clearTokens(): void {
166
+ this.storage.removeItem(this.storageKey)
167
+ }
168
+
169
+ /**
170
+ * Get the current access token.
171
+ *
172
+ * Returns the raw token string without validating expiration.
173
+ * The caller is responsible for checking whether the token is
174
+ * still valid and initiating a refresh if needed.
175
+ *
176
+ * @returns The access token string, or null if no tokens are stored
177
+ */
178
+ getAccessToken(): string | null {
179
+ const tokens = this.loadTokens()
180
+ return tokens?.accessToken ?? null
181
+ }
182
+
183
+ /**
184
+ * Get the current refresh token.
185
+ *
186
+ * @returns The refresh token string, or null if no tokens are stored
187
+ */
188
+ getRefreshToken(): string | null {
189
+ const tokens = this.loadTokens()
190
+ return tokens?.refreshToken ?? null
191
+ }
192
+ }
package/src/types.ts ADDED
@@ -0,0 +1,394 @@
1
+ import { KoraError } from '@korajs/core'
2
+
3
+ // ============================================================================
4
+ // User types
5
+ // ============================================================================
6
+
7
+ /**
8
+ * Authenticated user record.
9
+ * Represents the public-facing user identity returned by sign-in and sign-up flows.
10
+ */
11
+ export interface AuthUser {
12
+ /** Unique user identifier (UUID v7) */
13
+ id: string
14
+ /** User's email address, used as the primary login credential */
15
+ email: string
16
+ /** Display name */
17
+ name: string
18
+ /** Timestamp of user creation (milliseconds since epoch) */
19
+ createdAt: number
20
+ /** Timestamp of last profile update (milliseconds since epoch) */
21
+ updatedAt: number
22
+ }
23
+
24
+ /**
25
+ * A registered device that can operate on behalf of a user.
26
+ * Each device has its own keypair for offline credential verification.
27
+ */
28
+ export interface AuthDevice {
29
+ /** Device identifier, same as the Kora nodeId for this device */
30
+ id: string
31
+ /** The user this device belongs to */
32
+ userId: string
33
+ /** JWK-encoded public key for this device's keypair */
34
+ publicKey: string
35
+ /** Human-readable device name (e.g., "Chrome on MacBook") */
36
+ name: string
37
+ /** Timestamp when the device was first registered (milliseconds since epoch) */
38
+ registeredAt: number
39
+ /** Timestamp of last activity from this device (milliseconds since epoch) */
40
+ lastSeenAt: number
41
+ /** Whether this device is allowed to sync */
42
+ status: DeviceStatus
43
+ }
44
+
45
+ /** Device status values */
46
+ export type DeviceStatus = 'active' | 'revoked'
47
+
48
+ // ============================================================================
49
+ // Token types
50
+ // ============================================================================
51
+
52
+ /** The three kinds of tokens issued by the auth system */
53
+ export type TokenType = 'access' | 'refresh' | 'device_credential'
54
+
55
+ /**
56
+ * Base payload present in all JWT tokens.
57
+ * Fields follow standard JWT claim names.
58
+ */
59
+ export interface TokenPayload {
60
+ /** JWT ID: unique identifier for this specific token (for revocation and replay detection) */
61
+ jti: string
62
+ /** Subject: the user ID */
63
+ sub: string
64
+ /** Device ID that this token was issued to */
65
+ dev: string
66
+ /** Which kind of token this is */
67
+ type: TokenType
68
+ /** Issued-at time (seconds since epoch, per JWT spec) */
69
+ iat: number
70
+ /** Expiration time (seconds since epoch, per JWT spec) */
71
+ exp: number
72
+ /**
73
+ * Refresh-token family id. Every token minted by one sign-in and its refresh
74
+ * rotations shares it, so reuse detection can revoke exactly that family
75
+ * instead of the whole device. Absent on tokens minted before beta.13.
76
+ */
77
+ fam?: string
78
+ /**
79
+ * Issue time in milliseconds. `iat` has one-second resolution, which is too
80
+ * coarse to order a token against a revocation made in the same second.
81
+ */
82
+ iatMs?: number
83
+ /**
84
+ * Authentication methods used to obtain this session (RFC 8176), for example
85
+ * `['pwd']` or `['pwd', 'otp']`.
86
+ */
87
+ amr?: string[]
88
+ }
89
+
90
+ /**
91
+ * Short-lived token used to authenticate API requests.
92
+ * Typically lives for 15 minutes.
93
+ */
94
+ export interface AccessTokenPayload extends TokenPayload {
95
+ type: 'access'
96
+ }
97
+
98
+ /**
99
+ * Longer-lived token used to obtain new access tokens.
100
+ * Typically lives for 90 days.
101
+ */
102
+ export interface RefreshTokenPayload extends TokenPayload {
103
+ type: 'refresh'
104
+ }
105
+
106
+ /**
107
+ * Offline credential stored on the device.
108
+ * Allows the device to continue operating offline, with a mandatory
109
+ * check-in deadline by which it must reconnect to remain authorized.
110
+ */
111
+ export interface DeviceCredentialPayload extends TokenPayload {
112
+ type: 'device_credential'
113
+ /** Device public key thumbprint, binds this credential to a specific device keypair */
114
+ dpk: string
115
+ /** Timestamp (seconds since epoch) by which the device must check in with the server */
116
+ mustCheckinBy: number
117
+ }
118
+
119
+ /**
120
+ * Bundle of tokens returned after successful authentication.
121
+ */
122
+ export interface AuthTokens {
123
+ /** Short-lived access token */
124
+ accessToken: string
125
+ /** Long-lived refresh token */
126
+ refreshToken: string
127
+ /** Optional device credential for offline operation */
128
+ deviceCredential?: string
129
+ }
130
+
131
+ // ============================================================================
132
+ // Auth configuration
133
+ // ============================================================================
134
+
135
+ /** Supported auth provider types */
136
+ export type AuthProviderType = 'built-in' | 'custom'
137
+
138
+ /** Unlock mechanism for encrypted local storage */
139
+ export type UnlockMethod = 'biometric' | 'passphrase' | 'both'
140
+
141
+ /**
142
+ * Encryption settings for locally stored auth credentials.
143
+ */
144
+ export interface AuthEncryptionConfig {
145
+ /** Whether local credential encryption is enabled */
146
+ enabled: boolean
147
+ /** How the user unlocks encrypted credentials */
148
+ unlock: UnlockMethod
149
+ /** Milliseconds of inactivity before the app auto-locks. Defaults to 15 minutes. */
150
+ autoLockTimeout?: number
151
+ }
152
+
153
+ /**
154
+ * Custom lifetimes for each token type.
155
+ * All values are in milliseconds.
156
+ */
157
+ export interface TokenLifetimeConfig {
158
+ /** Access token lifetime in ms. Default: 15 minutes. */
159
+ access?: number
160
+ /** Refresh token lifetime in ms. Default: 90 days. */
161
+ refresh?: number
162
+ /** Device credential lifetime in ms. Default: 90 days. */
163
+ deviceCredential?: number
164
+ }
165
+
166
+ /**
167
+ * Configuration for the Kora auth system.
168
+ * Passed to the auth initializer to control provider, encryption, and token behavior.
169
+ */
170
+ export interface AuthConfig {
171
+ /** Which auth provider to use */
172
+ provider: AuthProviderType
173
+ /** Local encryption settings for stored credentials */
174
+ encryption?: AuthEncryptionConfig
175
+ /** Maximum time a device can operate offline without checking in (ms). Default: 30 days. */
176
+ maxOfflineDuration?: number
177
+ /** Custom token lifetimes */
178
+ tokenLifetimes?: TokenLifetimeConfig
179
+ }
180
+
181
+ // ============================================================================
182
+ // Auth state
183
+ // ============================================================================
184
+
185
+ /** Possible authentication states */
186
+ export type AuthStatus = 'authenticated' | 'unauthenticated' | 'locked'
187
+
188
+ /**
189
+ * Current state of the auth system.
190
+ * This is the value exposed to the UI layer for rendering auth-dependent views.
191
+ */
192
+ export interface AuthState {
193
+ /** Current authentication status */
194
+ status: AuthStatus
195
+ /** The authenticated user, or null if unauthenticated/locked */
196
+ user: AuthUser | null
197
+ /** The current device's ID, or null if not yet registered */
198
+ deviceId: string | null
199
+ }
200
+
201
+ // ============================================================================
202
+ // Auth events
203
+ // ============================================================================
204
+
205
+ /**
206
+ * Events emitted by the auth system.
207
+ * These integrate with Kora's event system for DevTools observability.
208
+ */
209
+ export type AuthEvent =
210
+ | { type: 'auth:signed-in'; user: AuthUser }
211
+ | { type: 'auth:signed-out' }
212
+ | { type: 'auth:locked' }
213
+ | { type: 'auth:unlocked'; user: AuthUser }
214
+ | { type: 'auth:token-refreshed' }
215
+ | { type: 'auth:device-revoked'; deviceId: string }
216
+ | { type: 'auth:permission-changed' }
217
+
218
+ /** Extract the event type string union from AuthEvent */
219
+ export type AuthEventType = AuthEvent['type']
220
+
221
+ /** Extract a specific auth event by its type */
222
+ export type AuthEventByType<T extends AuthEventType> = Extract<AuthEvent, { type: T }>
223
+
224
+ // ============================================================================
225
+ // Auth errors
226
+ // ============================================================================
227
+
228
+ /**
229
+ * Base error class for authentication-related failures.
230
+ * Extends KoraError to integrate with the framework's error handling patterns.
231
+ */
232
+ export class AuthError extends KoraError {
233
+ constructor(message: string, code: string, context?: Record<string, unknown>) {
234
+ super(message, code, context)
235
+ this.name = 'AuthError'
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Thrown when sign-in credentials are invalid (wrong email or password).
241
+ */
242
+ export class InvalidCredentialsError extends AuthError {
243
+ constructor() {
244
+ super('Invalid email or password.', 'AUTH_INVALID_CREDENTIALS')
245
+ this.name = 'InvalidCredentialsError'
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Thrown when a user tries to sign up with an email that already exists.
251
+ */
252
+ export class EmailAlreadyExistsError extends AuthError {
253
+ constructor() {
254
+ super('An account with this email already exists.', 'AUTH_EMAIL_EXISTS')
255
+ this.name = 'EmailAlreadyExistsError'
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Thrown when a token is expired, malformed, or has an invalid signature.
261
+ */
262
+ export class TokenError extends AuthError {
263
+ constructor(message: string, context?: Record<string, unknown>) {
264
+ super(message, 'AUTH_TOKEN_ERROR', context)
265
+ this.name = 'TokenError'
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Thrown when a device's credential has expired or the device has been revoked.
271
+ */
272
+ export class DeviceRevokedError extends AuthError {
273
+ constructor(deviceId: string) {
274
+ super(`Device "${deviceId}" has been revoked and can no longer sync.`, 'AUTH_DEVICE_REVOKED', {
275
+ deviceId,
276
+ })
277
+ this.name = 'DeviceRevokedError'
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Thrown when the maximum offline duration has been exceeded
283
+ * and the device must reconnect to continue operating.
284
+ */
285
+ export class OfflineExpiredError extends AuthError {
286
+ constructor(maxDuration: number, lastCheckin: number) {
287
+ const daysSinceCheckin = Math.round((Date.now() - lastCheckin) / (24 * 60 * 60 * 1000))
288
+ super(
289
+ `Device has been offline for ${daysSinceCheckin} days, exceeding the maximum offline duration. Reconnect to re-authenticate.`,
290
+ 'AUTH_OFFLINE_EXPIRED',
291
+ { maxDuration, lastCheckin, daysSinceCheckin },
292
+ )
293
+ this.name = 'OfflineExpiredError'
294
+ }
295
+ }
296
+
297
+ // ============================================================================
298
+ // Sign up / sign in params
299
+ // ============================================================================
300
+
301
+ /**
302
+ * Parameters for creating a new user account.
303
+ */
304
+ export interface SignUpParams {
305
+ /** Email address (used as login credential) */
306
+ email: string
307
+ /** Password (will be hashed before storage) */
308
+ password: string
309
+ /** Optional display name. Defaults to the local part of the email if omitted. */
310
+ name?: string
311
+ }
312
+
313
+ /**
314
+ * Parameters for signing in to an existing account.
315
+ */
316
+ export interface SignInParams {
317
+ /** Email address */
318
+ email: string
319
+ /** Password */
320
+ password: string
321
+ }
322
+
323
+ // ============================================================================
324
+ // Server-side types for the built-in provider
325
+ // ============================================================================
326
+
327
+ /**
328
+ * Parameters for creating a user record in the server-side store.
329
+ * The password has already been hashed by the time this is used.
330
+ */
331
+ export interface CreateUserParams {
332
+ /** Email address */
333
+ email: string
334
+ /** Argon2id or bcrypt hash of the password */
335
+ passwordHash: string
336
+ /** Random salt used during hashing */
337
+ salt: string
338
+ /** Display name */
339
+ name: string
340
+ }
341
+
342
+ /**
343
+ * Full user record as stored on the server, including sensitive credential fields.
344
+ * This type must NEVER be returned to the client; strip passwordHash and salt first.
345
+ */
346
+ export interface StoredUser extends AuthUser {
347
+ /** Hashed password */
348
+ passwordHash: string
349
+ /** Salt used for hashing */
350
+ salt: string
351
+ }
352
+
353
+ // ============================================================================
354
+ // Auth provider adapter interface
355
+ // ============================================================================
356
+
357
+ /**
358
+ * Adapter interface for pluggable auth providers.
359
+ * Implement this to integrate Kora auth with a custom identity provider
360
+ * (e.g., Firebase Auth, Auth0, Supabase Auth).
361
+ *
362
+ * The built-in provider implements this interface internally.
363
+ */
364
+ export interface AuthProviderAdapter {
365
+ /** Register a new user and return the user with initial tokens */
366
+ signUp(params: SignUpParams): Promise<{ user: AuthUser; tokens: AuthTokens }>
367
+ /** Authenticate an existing user and return the user with tokens */
368
+ signIn(params: SignInParams): Promise<{ user: AuthUser; tokens: AuthTokens }>
369
+ /** Exchange a refresh token for a new token set */
370
+ refreshToken(refreshToken: string): Promise<AuthTokens>
371
+ /** Validate an access token and return its payload, or null if invalid */
372
+ validateAccessToken(token: string): Promise<TokenPayload | null>
373
+ /** Revoke a device, preventing it from syncing */
374
+ revokeDevice(deviceId: string): Promise<void>
375
+ }
376
+
377
+ // ============================================================================
378
+ // Constants
379
+ // ============================================================================
380
+
381
+ /** Default access token lifetime: 15 minutes */
382
+ export const DEFAULT_ACCESS_TOKEN_LIFETIME = 15 * 60 * 1000
383
+
384
+ /** Default refresh token lifetime: 90 days */
385
+ export const DEFAULT_REFRESH_TOKEN_LIFETIME = 90 * 24 * 60 * 60 * 1000
386
+
387
+ /** Default device credential lifetime: 90 days */
388
+ export const DEFAULT_DEVICE_CREDENTIAL_LIFETIME = 90 * 24 * 60 * 60 * 1000
389
+
390
+ /** Default maximum offline duration: 30 days */
391
+ export const DEFAULT_MAX_OFFLINE_DURATION = 30 * 24 * 60 * 60 * 1000
392
+
393
+ /** Default auto-lock timeout: 15 minutes */
394
+ export const DEFAULT_AUTO_LOCK_TIMEOUT = 15 * 60 * 1000
@@ -0,0 +1,10 @@
1
+ import type { AuthClient, AuthState } from '../client/auth-client'
2
+
3
+ export interface AuthContextValue {
4
+ client: AuthClient
5
+ state: AuthState
6
+ isLoading: boolean
7
+ session: import('../bindings/create-auth-session').AuthSession
8
+ }
9
+
10
+ export const authContextKey = Symbol('korajs-auth-context')
@@ -0,0 +1,5 @@
1
+ /** Props for {@link AuthProvider}. */
2
+ export interface AuthProviderProps {
3
+ client: import('../client/auth-client').AuthClient
4
+ fallback?: import('vue').VNode | string | null
5
+ }
@@ -0,0 +1,76 @@
1
+ import type { PropType, VNode } from 'vue'
2
+ import { defineComponent, h, inject, onScopeDispose, provide, shallowRef } from 'vue'
3
+ import { type AuthSessionSnapshot, createAuthSession } from '../bindings/create-auth-session'
4
+ import type { AuthClient } from '../client/auth-client'
5
+ import { type AuthContextValue, authContextKey } from './auth-context'
6
+
7
+ export const AuthProvider = defineComponent({
8
+ name: 'AuthProvider',
9
+ props: {
10
+ client: {
11
+ type: Object as PropType<AuthClient>,
12
+ required: true,
13
+ },
14
+ fallback: {
15
+ type: [Object, String] as PropType<VNode | string | null>,
16
+ default: null,
17
+ },
18
+ },
19
+ setup(props, { slots }) {
20
+ const session = createAuthSession(props.client)
21
+ const snapshot = shallowRef<AuthSessionSnapshot>(session.getSnapshot())
22
+
23
+ const unsubscribe = session.subscribe(() => {
24
+ snapshot.value = session.getSnapshot()
25
+ })
26
+
27
+ onScopeDispose(() => {
28
+ unsubscribe()
29
+ session.destroy()
30
+ })
31
+
32
+ const contextValue: AuthContextValue = {
33
+ client: props.client,
34
+ get state() {
35
+ return snapshot.value.state
36
+ },
37
+ get isLoading() {
38
+ return snapshot.value.isLoading
39
+ },
40
+ session,
41
+ }
42
+
43
+ provide(authContextKey, contextValue)
44
+
45
+ return () => {
46
+ const current = snapshot.value
47
+
48
+ if (current.initError) {
49
+ return h(
50
+ 'div',
51
+ {
52
+ style: { color: 'red', padding: '1rem', fontFamily: 'monospace' },
53
+ role: 'alert',
54
+ },
55
+ [h('strong', null, 'Kora Auth initialization error: '), current.initError.message],
56
+ )
57
+ }
58
+
59
+ if (current.isLoading && props.fallback !== null) {
60
+ return props.fallback
61
+ }
62
+
63
+ return slots.default?.()
64
+ }
65
+ },
66
+ })
67
+
68
+ export function useAuthContext(): AuthContextValue {
69
+ const context = inject<AuthContextValue | undefined>(authContextKey)
70
+ if (!context) {
71
+ throw new Error(
72
+ 'useAuth must be used within <AuthProvider>. Wrap your app with <AuthProvider client={authClient}>.',
73
+ )
74
+ }
75
+ return context
76
+ }