@korajs/auth 0.1.0

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.
@@ -0,0 +1,780 @@
1
+ import { A as AuthTokens, T as TokenPayload } from './device-identity-DiwdLsUB.js';
2
+ export { f as computePublicKeyThumbprint, v as verifyChallenge } from './device-identity-DiwdLsUB.js';
3
+ import { KoraError } from '@korajs/core';
4
+
5
+ /**
6
+ * Configuration for the server-side TokenManager.
7
+ */
8
+ interface TokenManagerConfig {
9
+ /** Secret key for signing JWTs (HMAC-SHA256) */
10
+ secret: string;
11
+ /** Access token lifetime in milliseconds (default: 15 minutes) */
12
+ accessTokenLifetime?: number;
13
+ /** Refresh token lifetime in milliseconds (default: 90 days) */
14
+ refreshTokenLifetime?: number;
15
+ /** Device credential lifetime in milliseconds (default: 90 days) */
16
+ deviceCredentialLifetime?: number;
17
+ }
18
+ /**
19
+ * Server-side token manager responsible for issuing, refreshing, and validating
20
+ * Kora authentication tokens.
21
+ *
22
+ * Uses HMAC-SHA256 signed JWTs. Supports three token types:
23
+ * - **Access tokens**: Short-lived tokens for API authorization (default: 15 min)
24
+ * - **Refresh tokens**: Longer-lived tokens for obtaining new access tokens (default: 90 days)
25
+ * - **Device credentials**: Long-lived credentials bound to a device key pair (default: 90 days)
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const tokenManager = new TokenManager({ secret: 'my-signing-secret' })
30
+ *
31
+ * // Issue all tokens at once
32
+ * const tokens = tokenManager.issueTokens('user-123', 'device-456')
33
+ *
34
+ * // Validate an access token
35
+ * const payload = tokenManager.validateToken(tokens.accessToken)
36
+ *
37
+ * // Refresh when the access token expires
38
+ * const newTokens = tokenManager.refreshAccessToken(tokens.refreshToken)
39
+ * ```
40
+ */
41
+ declare class TokenManager {
42
+ private readonly secret;
43
+ private readonly accessTokenLifetime;
44
+ private readonly refreshTokenLifetime;
45
+ private readonly deviceCredentialLifetime;
46
+ constructor(config: TokenManagerConfig);
47
+ /**
48
+ * Issue a signed JWT access token.
49
+ *
50
+ * Access tokens are short-lived (default 15 minutes) and used to authorize
51
+ * API requests. When expired, use {@link refreshAccessToken} with a valid
52
+ * refresh token to obtain a new one.
53
+ *
54
+ * @param userId - The subject (user ID) to encode in the token
55
+ * @param deviceId - The device ID of the requesting device
56
+ * @returns A signed JWT string with type 'access'
57
+ */
58
+ issueAccessToken(userId: string, deviceId: string): string;
59
+ /**
60
+ * Issue a signed JWT refresh token.
61
+ *
62
+ * Refresh tokens are longer-lived (default 90 days) and used exclusively
63
+ * to obtain new access tokens via {@link refreshAccessToken}. They should
64
+ * be stored securely and never sent to resource APIs.
65
+ *
66
+ * @param userId - The subject (user ID) to encode in the token
67
+ * @param deviceId - The device ID of the requesting device
68
+ * @returns A signed JWT string with type 'refresh'
69
+ */
70
+ issueRefreshToken(userId: string, deviceId: string): string;
71
+ /**
72
+ * Issue a signed device credential token.
73
+ *
74
+ * Device credentials are long-lived tokens bound to a device's public key.
75
+ * They include a `mustCheckinBy` deadline; if the device does not check in
76
+ * before this deadline, the credential should be treated as revoked.
77
+ *
78
+ * @param userId - The subject (user ID) to encode in the token
79
+ * @param deviceId - The device ID of the requesting device
80
+ * @param publicKeyThumbprint - SHA-256 thumbprint of the device's public key
81
+ * @returns A signed JWT string with type 'device_credential'
82
+ */
83
+ issueDeviceCredential(userId: string, deviceId: string, publicKeyThumbprint: string): string;
84
+ /**
85
+ * Issue a complete set of authentication tokens.
86
+ *
87
+ * Always issues an access token and refresh token. If a `publicKeyThumbprint`
88
+ * is provided, also issues a device credential.
89
+ *
90
+ * @param userId - The subject (user ID) to encode in the tokens
91
+ * @param deviceId - The device ID of the requesting device
92
+ * @param publicKeyThumbprint - Optional SHA-256 thumbprint of the device's public key.
93
+ * When provided, a device credential is included in the returned tokens.
94
+ * @returns An {@link AuthTokens} object containing the issued tokens
95
+ */
96
+ issueTokens(userId: string, deviceId: string, publicKeyThumbprint?: string): AuthTokens;
97
+ /**
98
+ * Validate and decode a token.
99
+ *
100
+ * Verifies the HMAC-SHA256 signature and checks that the token has not expired.
101
+ * Returns null (rather than throwing) for invalid or expired tokens, so callers
102
+ * can handle authentication failure without try/catch.
103
+ *
104
+ * @param token - The JWT string to validate
105
+ * @returns The decoded {@link TokenPayload} if valid, or null if the token is
106
+ * invalid, expired, or missing required claims
107
+ */
108
+ validateToken(token: string): TokenPayload | null;
109
+ /**
110
+ * Refresh an access token using a valid refresh token.
111
+ *
112
+ * Implements **refresh token rotation**: a new refresh token is issued alongside
113
+ * the new access token, and the old refresh token should be considered consumed.
114
+ * This limits the window of vulnerability if a refresh token is compromised.
115
+ *
116
+ * Returns null if the provided token is invalid, expired, or not a refresh token.
117
+ *
118
+ * @param refreshToken - The refresh token JWT string
119
+ * @returns A new access/refresh token pair, or null if the refresh token is invalid
120
+ */
121
+ refreshAccessToken(refreshToken: string): {
122
+ accessToken: string;
123
+ refreshToken: string;
124
+ } | null;
125
+ }
126
+
127
+ /**
128
+ * A user as visible to the application layer.
129
+ * Does not include sensitive fields like password hash or salt.
130
+ */
131
+ interface AuthUser {
132
+ /** Unique user identifier (UUID v7 or crypto.randomUUID) */
133
+ id: string;
134
+ /** User's email address */
135
+ email: string;
136
+ /** User's display name */
137
+ name: string;
138
+ /** Timestamp when the user was created (milliseconds since epoch) */
139
+ createdAt: number;
140
+ }
141
+ /**
142
+ * Internal user record that includes credentials.
143
+ * Extends AuthUser with password hash and salt for verification.
144
+ */
145
+ interface StoredUser extends AuthUser {
146
+ /** Hex-encoded PBKDF2 derived key */
147
+ passwordHash: string;
148
+ /** Hex-encoded random salt used during hashing */
149
+ salt: string;
150
+ }
151
+ /**
152
+ * A device registered to a user.
153
+ */
154
+ interface AuthDevice {
155
+ /** Unique device identifier */
156
+ id: string;
157
+ /** ID of the user who owns this device */
158
+ userId: string;
159
+ /** Base64url-encoded public key (or thumbprint) for the device */
160
+ publicKey: string;
161
+ /** Human-readable device name */
162
+ name: string;
163
+ /** Whether the device has been revoked */
164
+ revoked: boolean;
165
+ /** Timestamp when the device was first registered (milliseconds since epoch) */
166
+ createdAt: number;
167
+ /** Timestamp when the device was last seen (milliseconds since epoch) */
168
+ lastSeenAt: number;
169
+ }
170
+ /**
171
+ * Thrown when a user account already exists with the given email.
172
+ */
173
+ declare class DuplicateEmailError extends KoraError {
174
+ constructor(email: string);
175
+ }
176
+ /**
177
+ * In-memory user and device store for the built-in auth provider.
178
+ *
179
+ * This is a simple implementation suitable for development and testing.
180
+ * Production applications should replace this with a database-backed store
181
+ * implementing the same interface.
182
+ *
183
+ * All methods are async to match the interface that a real database store
184
+ * would expose, even though the in-memory operations are synchronous.
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * const store = new InMemoryUserStore()
189
+ * const user = await store.createUser({
190
+ * email: 'alice@example.com',
191
+ * passwordHash: 'abc123...',
192
+ * salt: 'def456...',
193
+ * name: 'Alice',
194
+ * })
195
+ * ```
196
+ */
197
+ declare class InMemoryUserStore {
198
+ /** Users indexed by ID */
199
+ private readonly usersById;
200
+ /** Users indexed by email (lowercase) for fast lookup */
201
+ private readonly usersByEmail;
202
+ /** Devices indexed by device ID */
203
+ private readonly devicesById;
204
+ /** Device IDs indexed by user ID for fast listing */
205
+ private readonly devicesByUserId;
206
+ /**
207
+ * Create a new user account.
208
+ *
209
+ * @param params - User creation parameters
210
+ * @param params.email - The user's email address (must be unique, case-insensitive)
211
+ * @param params.passwordHash - Hex-encoded PBKDF2 derived key
212
+ * @param params.salt - Hex-encoded salt used during hashing
213
+ * @param params.name - The user's display name
214
+ * @returns The created user (without sensitive credential fields)
215
+ * @throws {DuplicateEmailError} If a user with the same email already exists
216
+ */
217
+ createUser(params: {
218
+ email: string;
219
+ passwordHash: string;
220
+ salt: string;
221
+ name: string;
222
+ }): Promise<AuthUser>;
223
+ /**
224
+ * Find a user by email address.
225
+ *
226
+ * @param email - The email to search for (case-insensitive)
227
+ * @returns The stored user record including credentials, or null if not found
228
+ */
229
+ findByEmail(email: string): Promise<StoredUser | null>;
230
+ /**
231
+ * Find a user by ID.
232
+ *
233
+ * @param id - The user ID to search for
234
+ * @returns The stored user record including credentials, or null if not found
235
+ */
236
+ findById(id: string): Promise<StoredUser | null>;
237
+ /**
238
+ * Register a device for a user.
239
+ *
240
+ * If a device with the same ID already exists and is not revoked, it is
241
+ * returned as-is (idempotent registration). If it was previously revoked,
242
+ * it is re-activated with updated details.
243
+ *
244
+ * @param params - Device registration parameters
245
+ * @param params.id - Unique device identifier
246
+ * @param params.userId - ID of the user who owns the device
247
+ * @param params.publicKey - Base64url-encoded device public key or thumbprint
248
+ * @param params.name - Human-readable device name
249
+ * @returns The registered device record
250
+ */
251
+ registerDevice(params: {
252
+ id: string;
253
+ userId: string;
254
+ publicKey: string;
255
+ name: string;
256
+ }): Promise<AuthDevice>;
257
+ /**
258
+ * Find a device by its ID.
259
+ *
260
+ * @param deviceId - The device ID to search for
261
+ * @returns The device record, or null if not found
262
+ */
263
+ findDevice(deviceId: string): Promise<AuthDevice | null>;
264
+ /**
265
+ * List all devices registered for a user.
266
+ *
267
+ * @param userId - The user ID whose devices to list
268
+ * @returns Array of device records (includes revoked devices)
269
+ */
270
+ listDevices(userId: string): Promise<AuthDevice[]>;
271
+ /**
272
+ * Revoke a device, preventing it from being used for authentication.
273
+ *
274
+ * This is a soft revoke — the device record remains but is marked as revoked.
275
+ * If the device does not exist, this is a no-op.
276
+ *
277
+ * @param deviceId - The ID of the device to revoke
278
+ */
279
+ revokeDevice(deviceId: string): Promise<void>;
280
+ /**
281
+ * Update the last-seen timestamp for a device.
282
+ *
283
+ * Called when a device authenticates or syncs to track activity.
284
+ * If the device does not exist, this is a no-op.
285
+ *
286
+ * @param deviceId - The ID of the device to update
287
+ */
288
+ touchDevice(deviceId: string): Promise<void>;
289
+ }
290
+
291
+ /**
292
+ * Configuration for building the built-in auth routes.
293
+ */
294
+ interface AuthRoutesConfig {
295
+ /** The user/device store backing the auth routes */
296
+ userStore: InMemoryUserStore;
297
+ /** The token manager for issuing and validating JWTs */
298
+ tokenManager: TokenManager;
299
+ }
300
+ /**
301
+ * Response envelope returned by all auth route handlers.
302
+ *
303
+ * Successful responses include a `data` field; failures include an `error` string.
304
+ * The `status` field maps directly to an HTTP status code.
305
+ */
306
+ interface AuthRouteResponse<T> {
307
+ /** HTTP status code */
308
+ status: number;
309
+ /** Either the success payload or an error message */
310
+ body: {
311
+ data: T;
312
+ } | {
313
+ error: string;
314
+ };
315
+ }
316
+ /**
317
+ * HTTP route handlers for the built-in Kora auth provider.
318
+ *
319
+ * These are framework-agnostic functions that accept parsed request bodies and return
320
+ * structured responses. The server package is responsible for wiring them into its
321
+ * HTTP server (e.g., mapping `POST /auth/signup` to `handleSignUp`).
322
+ *
323
+ * All handlers follow the same pattern:
324
+ * - Validate input
325
+ * - Perform the operation
326
+ * - Return `{ status, body: { data } }` on success
327
+ * - Return `{ status, body: { error } }` on failure
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * const routes = new BuiltInAuthRoutes({
332
+ * userStore: new InMemoryUserStore(),
333
+ * tokenManager: new TokenManager({ secret: 'my-secret' }),
334
+ * })
335
+ *
336
+ * // Wire into an HTTP server:
337
+ * app.post('/auth/signup', async (req, res) => {
338
+ * const result = await routes.handleSignUp(req.body)
339
+ * res.status(result.status).json(result.body)
340
+ * })
341
+ * ```
342
+ */
343
+ declare class BuiltInAuthRoutes {
344
+ private readonly userStore;
345
+ private readonly tokenManager;
346
+ constructor(config: AuthRoutesConfig);
347
+ /**
348
+ * Handle user sign-up (POST /auth/signup).
349
+ *
350
+ * Validates email format and password length, hashes the password,
351
+ * creates the user, optionally registers a device, and issues tokens.
352
+ *
353
+ * @param body - Sign-up request body
354
+ * @param body.email - The user's email address
355
+ * @param body.password - The plaintext password (min 8 characters)
356
+ * @param body.name - Optional display name (defaults to email local part)
357
+ * @param body.deviceId - Optional device ID to register
358
+ * @param body.devicePublicKey - Optional device public key (base64url)
359
+ * @returns Auth response with the created user and tokens, or an error
360
+ */
361
+ handleSignUp(body: {
362
+ email: string;
363
+ password: string;
364
+ name?: string;
365
+ deviceId?: string;
366
+ devicePublicKey?: string;
367
+ }): Promise<AuthRouteResponse<{
368
+ user: AuthUser;
369
+ tokens: AuthTokens;
370
+ }>>;
371
+ /**
372
+ * Handle user sign-in (POST /auth/signin).
373
+ *
374
+ * Looks up the user by email, verifies the password, optionally registers
375
+ * a new device, and issues tokens.
376
+ *
377
+ * @param body - Sign-in request body
378
+ * @param body.email - The user's email address
379
+ * @param body.password - The plaintext password
380
+ * @param body.deviceId - Optional device ID to register
381
+ * @param body.devicePublicKey - Optional device public key (base64url)
382
+ * @returns Auth response with the user and tokens, or an error
383
+ */
384
+ handleSignIn(body: {
385
+ email: string;
386
+ password: string;
387
+ deviceId?: string;
388
+ devicePublicKey?: string;
389
+ }): Promise<AuthRouteResponse<{
390
+ user: AuthUser;
391
+ tokens: AuthTokens;
392
+ }>>;
393
+ /**
394
+ * Handle token refresh (POST /auth/refresh).
395
+ *
396
+ * Validates the provided refresh token and issues a new token pair
397
+ * (refresh token rotation). The old refresh token should be considered
398
+ * consumed after this call.
399
+ *
400
+ * @param body - Refresh request body
401
+ * @param body.refreshToken - The current refresh token
402
+ * @returns Auth response with new tokens, or an error
403
+ */
404
+ handleRefresh(body: {
405
+ refreshToken: string;
406
+ }): Promise<AuthRouteResponse<AuthTokens>>;
407
+ /**
408
+ * Handle get-current-user (GET /auth/me).
409
+ *
410
+ * Validates the access token and returns the authenticated user's profile.
411
+ *
412
+ * @param accessToken - The JWT access token (without "Bearer " prefix)
413
+ * @returns Auth response with the user profile, or an error
414
+ */
415
+ handleGetMe(accessToken: string): Promise<AuthRouteResponse<AuthUser>>;
416
+ /**
417
+ * Handle list-devices (GET /auth/devices).
418
+ *
419
+ * Validates the access token and returns all devices registered for the user.
420
+ *
421
+ * @param accessToken - The JWT access token (without "Bearer " prefix)
422
+ * @returns Auth response with the device list, or an error
423
+ */
424
+ handleListDevices(accessToken: string): Promise<AuthRouteResponse<AuthDevice[]>>;
425
+ /**
426
+ * Handle device revocation (DELETE /auth/device/:id).
427
+ *
428
+ * Validates the access token and revokes the specified device.
429
+ * Only the device's owner can revoke it.
430
+ *
431
+ * @param accessToken - The JWT access token (without "Bearer " prefix)
432
+ * @param deviceId - The ID of the device to revoke
433
+ * @returns Auth response with success flag, or an error
434
+ */
435
+ handleRevokeDevice(accessToken: string, deviceId: string): Promise<AuthRouteResponse<{
436
+ success: boolean;
437
+ }>>;
438
+ /**
439
+ * Creates a sync server auth provider compatible with `@korajs/server`.
440
+ *
441
+ * The returned object implements the `AuthProvider` interface from
442
+ * `@korajs/server`, validating access tokens and returning an auth
443
+ * context containing the user ID and device metadata. This bridges
444
+ * the built-in auth system with the sync server's authentication layer.
445
+ *
446
+ * @returns An object with an `authenticate` method suitable for KoraSyncServer's `auth` config
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })
451
+ * const syncServer = new KoraSyncServer({
452
+ * store,
453
+ * auth: routes.toSyncAuthProvider(),
454
+ * })
455
+ * ```
456
+ */
457
+ toSyncAuthProvider(): {
458
+ authenticate(token: string): Promise<{
459
+ userId: string;
460
+ scopes?: Record<string, Record<string, unknown>>;
461
+ metadata?: Record<string, unknown>;
462
+ } | null>;
463
+ };
464
+ }
465
+
466
+ /**
467
+ * Creates a signed JWT (HS256) from a payload and secret.
468
+ *
469
+ * The token is structured as `header.payload.signature` per RFC 7519.
470
+ * Only HMAC-SHA256 is supported. The header is always `{"alg":"HS256","typ":"JWT"}`.
471
+ *
472
+ * @param payload - The claims to include in the token. Must be JSON-serializable.
473
+ * @param secret - The HMAC-SHA256 secret key used for signing.
474
+ * @returns A signed JWT string in the format `header.payload.signature`
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * const token = encodeJwt(
479
+ * { sub: 'user-123', exp: Math.floor(Date.now() / 1000) + 900 },
480
+ * 'my-secret'
481
+ * )
482
+ * ```
483
+ */
484
+ declare function encodeJwt(payload: Record<string, unknown>, secret: string): string;
485
+ /**
486
+ * Decodes a JWT without verifying its signature.
487
+ *
488
+ * Use this only when you need to inspect claims (e.g., reading `exp` to decide
489
+ * whether to attempt a refresh) and do NOT need to trust the token's authenticity.
490
+ * For trusted reads, use {@link verifyJwt} instead.
491
+ *
492
+ * @param token - The JWT string to decode
493
+ * @returns The decoded payload as a record, or null if the token is malformed
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * const claims = decodeJwt(token)
498
+ * if (claims && typeof claims.sub === 'string') {
499
+ * console.log('User:', claims.sub)
500
+ * }
501
+ * ```
502
+ */
503
+ declare function decodeJwt(token: string): Record<string, unknown> | null;
504
+ /**
505
+ * Decodes a JWT and verifies its HMAC-SHA256 signature.
506
+ *
507
+ * Returns the payload only if the signature is valid. Returns null if the token
508
+ * is malformed, has an invalid signature, or cannot be parsed.
509
+ *
510
+ * This function does NOT check expiration. Use {@link isExpired} separately
511
+ * to check the `exp` claim, allowing callers to distinguish between
512
+ * "invalid signature" (security issue) and "expired" (normal lifecycle).
513
+ *
514
+ * @param token - The JWT string to verify
515
+ * @param secret - The HMAC-SHA256 secret key that was used to sign the token
516
+ * @returns The decoded payload if the signature is valid, or null otherwise
517
+ *
518
+ * @example
519
+ * ```typescript
520
+ * const claims = verifyJwt(token, 'my-secret')
521
+ * if (claims === null) {
522
+ * throw new Error('Invalid token signature')
523
+ * }
524
+ * if (isExpired(claims)) {
525
+ * throw new Error('Token has expired')
526
+ * }
527
+ * ```
528
+ */
529
+ declare function verifyJwt(token: string, secret: string): Record<string, unknown> | null;
530
+ /**
531
+ * Checks whether a token payload has expired based on its `exp` claim.
532
+ *
533
+ * The `exp` claim is expected to be in seconds since the Unix epoch (per JWT spec).
534
+ * If the `exp` claim is missing or is not a number, the token is considered
535
+ * non-expiring and this function returns false.
536
+ *
537
+ * @param payload - An object with an optional `exp` field (seconds since epoch)
538
+ * @returns true if the token has expired, false otherwise
539
+ *
540
+ * @example
541
+ * ```typescript
542
+ * const claims = verifyJwt(token, secret)
543
+ * if (claims && isExpired(claims)) {
544
+ * // Token signature is valid but it has expired -- attempt refresh
545
+ * }
546
+ * ```
547
+ */
548
+ declare function isExpired(payload: {
549
+ exp?: number;
550
+ }): boolean;
551
+
552
+ interface HashResult {
553
+ /** Hex-encoded PBKDF2 derived key. */
554
+ hash: string;
555
+ /** Hex-encoded random salt used during hashing. */
556
+ salt: string;
557
+ }
558
+ /**
559
+ * Hashes a password using PBKDF2 with a cryptographically random salt.
560
+ *
561
+ * Uses SHA-512 with 600,000 iterations and a 32-byte random salt,
562
+ * producing a 64-byte derived key. Both the hash and salt are returned
563
+ * as hex-encoded strings for storage.
564
+ *
565
+ * @param password - The plaintext password to hash
566
+ * @returns The hex-encoded hash and salt
567
+ *
568
+ * @example
569
+ * ```typescript
570
+ * const { hash, salt } = await hashPassword('my-secret-password')
571
+ * // Store hash and salt in the database
572
+ * ```
573
+ */
574
+ declare function hashPassword(password: string): Promise<HashResult>;
575
+ /**
576
+ * Verifies a plaintext password against a stored hash and salt using
577
+ * timing-safe comparison to prevent timing attacks.
578
+ *
579
+ * Re-derives the key from the password and salt using the same PBKDF2
580
+ * parameters, then compares the result against the stored hash using
581
+ * `crypto.timingSafeEqual` to avoid leaking information through
582
+ * response timing.
583
+ *
584
+ * @param password - The plaintext password to verify
585
+ * @param hash - The hex-encoded hash to compare against
586
+ * @param salt - The hex-encoded salt that was used to produce the hash
587
+ * @returns `true` if the password matches, `false` otherwise
588
+ *
589
+ * @example
590
+ * ```typescript
591
+ * const isValid = await verifyPassword('my-secret-password', storedHash, storedSalt)
592
+ * if (isValid) {
593
+ * // Grant access
594
+ * }
595
+ * ```
596
+ */
597
+ declare function verifyPassword(password: string, hash: string, salt: string): Promise<boolean>;
598
+
599
+ /**
600
+ * Parameters for signing up a new user.
601
+ */
602
+ interface SignUpParams {
603
+ /** The user's email address */
604
+ email: string;
605
+ /** The plaintext password */
606
+ password: string;
607
+ /** Optional display name */
608
+ name?: string;
609
+ /** Optional device ID to register with the account */
610
+ deviceId?: string;
611
+ /** Optional device public key (base64url) */
612
+ devicePublicKey?: string;
613
+ }
614
+ /**
615
+ * Parameters for signing in an existing user.
616
+ */
617
+ interface SignInParams {
618
+ /** The user's email address */
619
+ email: string;
620
+ /** The plaintext password */
621
+ password: string;
622
+ /** Optional device ID to register or associate */
623
+ deviceId?: string;
624
+ /** Optional device public key (base64url) */
625
+ devicePublicKey?: string;
626
+ }
627
+ /**
628
+ * Abstraction for authentication providers.
629
+ *
630
+ * This interface allows swapping between the built-in email/password provider
631
+ * and external providers (OAuth, SAML, custom) without changing application code.
632
+ * Every provider must support the same core operations: sign up, sign in,
633
+ * token refresh, token validation, user lookup, and device management.
634
+ *
635
+ * @example
636
+ * ```typescript
637
+ * // Use the built-in provider
638
+ * const provider: AuthProviderAdapter = new BuiltInProvider({
639
+ * userStore: new InMemoryUserStore(),
640
+ * tokenManager: new TokenManager({ secret: 'my-secret' }),
641
+ * })
642
+ *
643
+ * const { user, tokens } = await provider.signUp({
644
+ * email: 'alice@example.com',
645
+ * password: 'secure-password-123',
646
+ * })
647
+ * ```
648
+ */
649
+ interface AuthProviderAdapter {
650
+ /**
651
+ * Create a new user account and issue authentication tokens.
652
+ *
653
+ * @param params - Sign-up parameters including email, password, and optional device info
654
+ * @returns The created user and tokens
655
+ * @throws If the email is already registered or input validation fails
656
+ */
657
+ signUp(params: SignUpParams): Promise<{
658
+ user: AuthUser;
659
+ tokens: AuthTokens;
660
+ }>;
661
+ /**
662
+ * Authenticate an existing user and issue tokens.
663
+ *
664
+ * @param params - Sign-in parameters including email and password
665
+ * @returns The authenticated user and tokens
666
+ * @throws If the credentials are invalid
667
+ */
668
+ signIn(params: SignInParams): Promise<{
669
+ user: AuthUser;
670
+ tokens: AuthTokens;
671
+ }>;
672
+ /**
673
+ * Exchange a refresh token for new tokens (rotation).
674
+ *
675
+ * @param refreshToken - The current refresh token
676
+ * @returns A new token pair
677
+ * @throws If the refresh token is invalid or expired
678
+ */
679
+ refreshTokens(refreshToken: string): Promise<AuthTokens>;
680
+ /**
681
+ * Validate an access token and extract identity claims.
682
+ *
683
+ * @param token - The JWT access token
684
+ * @returns User ID and device ID if valid, or null if invalid/expired
685
+ */
686
+ validateAccessToken(token: string): Promise<{
687
+ userId: string;
688
+ deviceId: string;
689
+ } | null>;
690
+ /**
691
+ * Look up a user by ID.
692
+ *
693
+ * @param userId - The user's ID
694
+ * @returns The user profile, or null if not found
695
+ */
696
+ getUser(userId: string): Promise<AuthUser | null>;
697
+ /**
698
+ * Revoke a device. Requires a valid access token for authorization.
699
+ *
700
+ * @param accessToken - The caller's access token
701
+ * @param deviceId - The ID of the device to revoke
702
+ * @throws If the token is invalid or the device does not belong to the caller
703
+ */
704
+ revokeDevice(accessToken: string, deviceId: string): Promise<void>;
705
+ /**
706
+ * List all devices for the authenticated user.
707
+ *
708
+ * @param accessToken - The caller's access token
709
+ * @returns Array of device records
710
+ * @throws If the token is invalid
711
+ */
712
+ listDevices(accessToken: string): Promise<AuthDevice[]>;
713
+ }
714
+ /**
715
+ * Error thrown by provider adapter methods when an operation fails.
716
+ * Wraps the HTTP-style status and error message from route handlers
717
+ * into an exception for use in the adapter pattern.
718
+ */
719
+ declare class AuthProviderError extends Error {
720
+ /** HTTP-style status code */
721
+ readonly status: number;
722
+ constructor(message: string, status: number);
723
+ }
724
+ /**
725
+ * Built-in authentication provider implementing email/password authentication.
726
+ *
727
+ * Wraps {@link BuiltInAuthRoutes} in the {@link AuthProviderAdapter} interface,
728
+ * converting HTTP-style responses into direct return values and exceptions.
729
+ * This is the default provider shipped with Kora and is suitable for
730
+ * applications that want simple email/password auth without external services.
731
+ *
732
+ * @example
733
+ * ```typescript
734
+ * import { BuiltInProvider } from '@korajs/auth'
735
+ * import { InMemoryUserStore } from '@korajs/auth'
736
+ * import { TokenManager } from '@korajs/auth'
737
+ *
738
+ * const provider = new BuiltInProvider({
739
+ * userStore: new InMemoryUserStore(),
740
+ * tokenManager: new TokenManager({ secret: process.env.AUTH_SECRET }),
741
+ * })
742
+ *
743
+ * const { user, tokens } = await provider.signUp({
744
+ * email: 'alice@example.com',
745
+ * password: 'strong-password-123',
746
+ * name: 'Alice',
747
+ * })
748
+ * ```
749
+ */
750
+ declare class BuiltInProvider implements AuthProviderAdapter {
751
+ private readonly routes;
752
+ private readonly tokenManager;
753
+ private readonly userStore;
754
+ constructor(config: AuthRoutesConfig);
755
+ /** @inheritdoc */
756
+ signUp(params: SignUpParams): Promise<{
757
+ user: AuthUser;
758
+ tokens: AuthTokens;
759
+ }>;
760
+ /** @inheritdoc */
761
+ signIn(params: SignInParams): Promise<{
762
+ user: AuthUser;
763
+ tokens: AuthTokens;
764
+ }>;
765
+ /** @inheritdoc */
766
+ refreshTokens(refreshToken: string): Promise<AuthTokens>;
767
+ /** @inheritdoc */
768
+ validateAccessToken(token: string): Promise<{
769
+ userId: string;
770
+ deviceId: string;
771
+ } | null>;
772
+ /** @inheritdoc */
773
+ getUser(userId: string): Promise<AuthUser | null>;
774
+ /** @inheritdoc */
775
+ revokeDevice(accessToken: string, deviceId: string): Promise<void>;
776
+ /** @inheritdoc */
777
+ listDevices(accessToken: string): Promise<AuthDevice[]>;
778
+ }
779
+
780
+ export { type AuthDevice, type AuthProviderAdapter, AuthProviderError, type AuthRouteResponse, type AuthRoutesConfig, type AuthUser, BuiltInAuthRoutes, BuiltInProvider, DuplicateEmailError, InMemoryUserStore, type SignInParams, type SignUpParams, type StoredUser, TokenManager, type TokenManagerConfig, decodeJwt, encodeJwt, hashPassword, isExpired, verifyJwt, verifyPassword };