@luckystack/login 0.10.1 → 0.11.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.
package/dist/index.d.ts CHANGED
@@ -1,951 +1,33 @@
1
- import { BaseSessionLayout } from '@luckystack/core';
2
- export { AuthProps, BaseSessionLayout, SessionLocation } from '@luckystack/core';
3
- import { IncomingMessage, ServerResponse } from 'node:http';
4
- import { Socket } from 'socket.io';
5
-
6
- /**
7
- * Module augmentation: extends `@luckystack/core`'s `HookPayloads` interface
8
- * with auth and session lifecycle hooks owned by this package.
9
- *
10
- * This file is picked up by the TypeScript compiler because it is in the
11
- * tsconfig `include` path. A side-effect `import './hookPayloads';` in
12
- * `index.ts` makes the dependency explicit for readers even though TS would
13
- * merge the declaration regardless.
14
- */
15
-
16
- interface PreLoginPayload {
17
- email: string;
18
- provider: string;
19
- }
20
- interface PostLoginPayload {
21
- userId: string;
22
- provider: string;
23
- isNewUser: boolean;
24
- token: string;
25
- }
26
- interface PreRegisterPayload {
27
- email: string;
28
- provider: string;
29
- name?: string;
30
- }
31
- interface PostRegisterPayload {
32
- userId: string;
33
- provider: string;
34
- }
35
- interface PreLogoutPayload {
36
- userId: string | null;
37
- token: string | null;
38
- }
39
- interface PostLogoutPayload {
40
- userId: string | null;
41
- token: string | null;
42
- }
43
- interface PreSessionCreatePayload {
44
- token: string;
45
- user: BaseSessionLayout;
46
- persistent: boolean;
47
- }
48
- interface PostSessionCreatePayload {
49
- token: string;
50
- user: BaseSessionLayout;
51
- persistent: boolean;
52
- }
53
- interface PreSessionDeletePayload {
54
- token: string;
55
- userId: string | null;
56
- }
57
- interface PostSessionDeletePayload {
58
- token: string;
59
- userId: string | null;
60
- }
61
- interface PasswordResetRequestedPayload {
62
- /** Email the user typed in the reset form. May or may not match a real user. */
63
- email: string;
64
- /** True when a matching user was found and an email is being sent. */
65
- matched: boolean;
66
- /** The userId, when matched. */
67
- userId?: string;
68
- /** Token TTL in seconds. */
69
- ttlSeconds?: number;
70
- }
71
- interface PasswordResetCompletedPayload {
72
- /** User whose password was reset via the forgot-password flow. */
73
- userId: string;
74
- /** Whether the framework auto-revoked all other active sessions. */
75
- revokedOtherSessions: boolean;
76
- }
77
- interface PasswordChangedPayload {
78
- /** User whose password was changed via the in-session change flow. */
79
- userId: string;
80
- /** True if the user verified their current password successfully. */
81
- verifiedCurrent: boolean;
82
- /** Whether sessions other than the current one were revoked. */
83
- revokedOtherSessions: boolean;
84
- }
85
- interface PrePasswordResetCompletedPayload {
86
- /** User whose password is about to be reset via the forgot-password flow. */
87
- userId: string;
88
- }
89
- interface PrePasswordChangedPayload {
90
- /** User whose password is about to be changed via the in-session flow. */
91
- userId: string;
92
- /** True if the user verified their current password successfully. */
93
- verifiedCurrent: boolean;
94
- }
95
- interface PreEmailChangePayload {
96
- userId: string;
97
- currentEmail: string;
98
- newEmail: string;
99
- }
100
- interface PostEmailChangeRequestedPayload {
101
- userId: string;
102
- currentEmail: string;
103
- newEmail: string;
104
- sent?: boolean;
105
- }
106
- interface PostEmailChangedPayload {
107
- userId: string;
108
- oldEmail: string;
109
- newEmail: string;
110
- }
111
- interface LoginFailedPayload {
112
- /** Email the attempt was for, when available. */
113
- email?: string;
114
- /** Resolved userId, when the attempt matched a real account. */
115
- userId?: string;
116
- /** Auth provider (e.g. `credentials`, `google`). */
117
- provider: string;
118
- /** i18n reason key describing why the attempt failed. */
119
- reason: string;
120
- /** Which flow the failure occurred in. */
121
- stage: 'login' | 'register' | 'oauth';
122
- requesterIp?: string;
123
- }
124
- interface PreAccountDeletePayload {
125
- userId: string;
126
- email?: string;
127
- }
128
- interface PostAccountDeletePayload {
129
- userId: string;
130
- email?: string;
131
- }
132
- declare module '@luckystack/core' {
133
- interface HookPayloads {
134
- preLogin: PreLoginPayload;
135
- postLogin: PostLoginPayload;
136
- preRegister: PreRegisterPayload;
137
- postRegister: PostRegisterPayload;
138
- preLogout: PreLogoutPayload;
139
- postLogout: PostLogoutPayload;
140
- preSessionCreate: PreSessionCreatePayload;
141
- postSessionCreate: PostSessionCreatePayload;
142
- preSessionDelete: PreSessionDeletePayload;
143
- postSessionDelete: PostSessionDeletePayload;
144
- passwordResetRequested: PasswordResetRequestedPayload;
145
- prePasswordResetCompleted: PrePasswordResetCompletedPayload;
146
- passwordResetCompleted: PasswordResetCompletedPayload;
147
- prePasswordChanged: PrePasswordChangedPayload;
148
- passwordChanged: PasswordChangedPayload;
149
- preEmailChange: PreEmailChangePayload;
150
- postEmailChangeRequested: PostEmailChangeRequestedPayload;
151
- postEmailChanged: PostEmailChangedPayload;
152
- loginFailed: LoginFailedPayload;
153
- preAccountDelete: PreAccountDeletePayload;
154
- postAccountDelete: PostAccountDeletePayload;
155
- }
156
- }
157
-
158
- /**
159
- * Save or update a user session through the active session adapter.
160
- *
161
- * @param token - The session token (unique identifier)
162
- * @param data - The session data to store
163
- * @param newUser - If true, this is a new login (triggers single-session enforcement)
164
- * @param options.supersedeToken - A token belonging to the SAME browser that is
165
- * being replaced by this login (e.g. re-login while already signed in). It is
166
- * excluded from single-session enforcement so the user's current device is not
167
- * kicked by its own new session. The caller is responsible for deleting it
168
- * afterwards (use `deleteSession(supersedeToken, { skipSocketLogout: true })`).
169
- */
170
- declare const saveSession: (token: string, data: BaseSessionLayout, newUser?: boolean, options?: {
171
- supersedeToken?: string;
172
- }) => Promise<{
173
- ok: true;
174
- } | {
175
- ok: false;
176
- errorCode: string;
177
- }>;
178
- /**
179
- * Retrieve a user session through the active session adapter.
180
- *
181
- * @param token - The session token
182
- * @param options.refresh - When false, skip the sliding-expiration TTL
183
- * extension. Defaults to true. Pass false for read-only probes (e.g. a
184
- * background audit, an admin lookup) where a stolen token should NOT earn
185
- * extra lifetime just because the server touched it (LOGIN-F9).
186
- * @returns The session data or null if not found
187
- */
188
- declare const getSession: (token: string | null, options?: {
189
- refresh?: boolean;
190
- }) => Promise<BaseSessionLayout | null>;
191
- /**
192
- * Delete a user session through the active adapter and notify connected
193
- * clients.
194
- *
195
- * @param token - The session token to delete
196
- * @param options.skipSocketLogout - When true, the session data + active-token
197
- * tracking are cleaned up and the pre/post delete hooks still fire, but NO
198
- * `logout` is emitted to sockets in the token's room. Use this when the SAME
199
- * browser is replacing its session (re-login): emitting a logout would bounce
200
- * that browser to the login page and null its brand-new session.
201
- * @returns true if successful
202
- */
203
- declare const deleteSession: (token: string, options?: {
204
- skipSocketLogout?: boolean;
205
- }) => Promise<boolean>;
206
- /**
207
- * Get all active sessions (admin utility). Returns empty when the active
208
- * adapter doesn't implement enumeration (signed-JWT-stateless, log-only).
209
- *
210
- * @returns Array of all session data
211
- */
212
- declare const getAllSessions: () => Promise<BaseSessionLayout[]>;
213
- /**
214
- * Revoke every active session for a user, optionally keeping one alive.
215
- *
216
- * @param userId - The user whose sessions to revoke
217
- * @param exceptToken - If provided, this session is left untouched. Pass the
218
- * caller's own token when revoking after a password change so the user
219
- * isn't logged out of the device they just changed it from. Pass null/omit
220
- * to revoke everything (account deletion, "sign out everywhere", etc.).
221
- *
222
- * Each revocation goes through `deleteSession`, so connected sockets are
223
- * told to log out via the same flow as a normal logout. Returns the number
224
- * of sessions actually revoked.
225
- */
226
- declare const revokeUserSessions: (userId: string, exceptToken?: string | null) => Promise<number>;
227
- declare const sessionKeyFor: (token: string) => string;
228
- declare const activeUsersKeyFor: (userId: string) => string;
229
-
230
- interface SessionAdapter {
231
- /** Human-readable identifier (used in logs/diagnostics). */
232
- name: string;
233
- /** Read the raw stored session string for `token`. Null when missing. */
234
- getRaw(token: string): Promise<string | null>;
235
- /**
236
- * Write the raw session string for `token` with a TTL. Implementations
237
- * MUST honour the TTL — sessions without one would never expire.
238
- */
239
- setRaw(token: string, value: string, ttlSeconds: number): Promise<void>;
240
- /** Delete the session record for `token`. Idempotent. */
241
- delete(token: string): Promise<void>;
242
- /**
243
- * Refresh the TTL on an existing session record (sliding-window
244
- * expiration). Returns true when the underlying record existed and the
245
- * TTL was applied, false otherwise. Implementations must not create a
246
- * record on miss.
247
- */
248
- expire(token: string, ttlSeconds: number): Promise<boolean>;
249
- /** Remaining TTL in seconds (or null when the record is missing). */
250
- ttl(token: string): Promise<number | null>;
251
- trackActive(userId: string, token: string, ttlSeconds: number): Promise<void>;
252
- untrackActive(userId: string, token: string): Promise<void>;
253
- listActive(userId: string): Promise<string[]>;
254
- /**
255
- * Refresh the TTL on the active-tokens-per-user record WITHOUT adding a
256
- * token (sliding-window expiration parity with the session key). Called on
257
- * an authenticated read so a session kept alive purely by reads does not
258
- * outlive its activeUsers entry — which would otherwise let a stolen token
259
- * survive a sign-out-everywhere / password reset. Optional: backends whose
260
- * active-set entries do not expire independently (or that have no TTL
261
- * concept) can omit it. Must not create a record on miss.
262
- */
263
- touchActive?(userId: string, ttlSeconds: number): Promise<void>;
264
- /**
265
- * Admin walk: yield every active session record. Optional because
266
- * non-scannable backends (signed-JWT-stateless, log-only) cannot
267
- * enumerate. Callers fall back to a per-user listActive when omitted.
268
- *
269
- * **Dev/admin-only (LOGIN-F14).** Implementations scan the full keyspace and
270
- * load every session into memory in one call — O(all sessions) in both Redis
271
- * round-trips and memory. Do NOT call on the hot request path or from any
272
- * user-facing API. Use `listActive(userId)` for per-user lookups.
273
- */
274
- listAll?(): Promise<{
275
- token: string;
276
- raw: string;
277
- }[]>;
278
- }
279
- declare const redisSessionAdapter: SessionAdapter;
280
- /**
281
- * Replace the active session storage backend. Call once at boot from
282
- * `luckystack/server/index.ts`, BEFORE the first login request. Common
283
- * use cases:
284
- *
285
- * - DynamoDB / Cosmos DB for serverless / edge deployments
286
- * - Postgres `sessions` table when you already operate Postgres
287
- * - signed-JWT-stateless (the adapter writes nothing, reads from the
288
- * JWT, and stores activeUsers per token in a small Redis hash)
289
- * - in-memory mock for integration tests
290
- *
291
- * Last-write-wins: subsequent calls overwrite the active adapter.
292
- */
293
- declare const registerSessionAdapter: (adapter: SessionAdapter) => void;
294
- /** Read the currently-active session adapter (defaults to Redis). */
295
- declare const getSessionAdapter: () => SessionAdapter;
296
-
297
- type SessionSanitizer = (session: BaseSessionLayout) => BaseSessionLayout;
298
- /**
299
- * Register a redactor applied to the session record before it is persisted to
300
- * the store and broadcast to connected clients. Call once at boot. Last write
301
- * wins. Pass a function that returns a shallow copy with sensitive fields
302
- * stripped, e.g. `({ totpSecret, stripeCustomerId, ...safe }) => safe`.
303
- */
304
- declare const registerSessionSanitizer: (sanitizer: SessionSanitizer) => void;
305
- /** Read the active sanitizer (or `null` when none is registered). */
306
- declare const getSessionSanitizer: () => SessionSanitizer | null;
307
-
308
- interface UserAdapterCreateInput {
309
- email: string;
310
- provider: string;
311
- name?: string | null;
312
- password?: string | null;
313
- avatar?: string | null;
314
- avatarFallback?: string | null;
315
- language?: string | null;
316
- }
317
- interface UserRecord extends BaseSessionLayout {
318
- password?: string | null;
319
- provider?: string | null;
320
- /** Most recent successful login. Mirrors the optional `lastLogin` column on User. */
321
- lastLogin?: Date | null;
322
- /**
323
- * 2FA (ADR 0024): whether this user enrolled a second factor. All three 2FA
324
- * fields are OPTIONAL so pre-0.6 schemas/adapters keep working unchanged —
325
- * an adapter that never returns them simply has no 2FA-capable users.
326
- */
327
- twoFactorEnabled?: boolean | null;
328
- /**
329
- * 2FA: the TOTP shared secret (base32; new encrypted rows use
330
- * `enc:v2:<key-id>:...`, while legacy `gcm:` rows remain readable through
331
- * TOTP_ENCRYPTION_LEGACY_KEYS). NEVER reaches the client — stripped by
332
- * `sanitizeUserForSession` before any session persist/broadcast.
333
- */
334
- totpSecret?: string | null;
335
- /** 2FA: sha256 hashes of the UNUSED one-time recovery codes (also stripped). */
336
- recoveryCodes?: string[] | null;
337
- }
338
- interface UserAdapter {
339
- findByEmail: (params: {
340
- email: string;
341
- provider: string;
342
- }) => Promise<UserRecord | null>;
343
- /**
344
- * Look up a user by email IRRESPECTIVE of provider. Required only when
345
- * `auth.providerAccountStrategy === 'unified'` — the framework resolves
346
- * accounts by email alone so the same address maps to one User across
347
- * providers. Optional so existing custom adapters keep compiling; when a
348
- * project sets `'unified'` but its adapter omits this method, the framework
349
- * logs a one-time warning and falls back to provider-scoped lookup.
350
- */
351
- findByEmailAnyProvider?: (params: {
352
- email: string;
353
- }) => Promise<UserRecord | null>;
354
- findById: (id: string) => Promise<UserRecord | null>;
355
- create: (input: UserAdapterCreateInput) => Promise<UserRecord>;
356
- update: (id: string, patch: Partial<UserRecord>) => Promise<UserRecord>;
357
- /**
358
- * Hard-delete a user. Account-deletion flows (`deleteAccount_v1`) call this
359
- * instead of reaching past the adapter to `prisma.user.delete`, so a custom
360
- * adapter / non-Prisma store / soft-delete strategy stays in control. To
361
- * implement a soft delete, ignore the hard delete here and instead expose it
362
- * through `update({ deletedAt: new Date() })` from your route.
363
- *
364
- * Optional so existing custom adapters keep compiling; the default Prisma
365
- * adapter implements it. Consumers calling account deletion against a custom
366
- * adapter that omits `delete` get a clear "adapter does not support delete"
367
- * failure rather than a silent Prisma bypass.
368
- */
369
- delete?: (id: string) => Promise<void>;
370
- }
371
- declare const registerUserAdapter: (adapter: UserAdapter) => UserAdapter;
372
- declare const isUserAdapterRegistered: () => boolean;
373
- declare const defaultPrismaUserAdapter: () => UserAdapter;
374
- declare const getUserAdapter: () => UserAdapter;
375
-
376
- interface LoginOrRegisterParams {
377
- email?: string;
378
- password?: string;
379
- name?: string;
380
- confirmPassword?: string;
381
- }
382
- type TwoFactorMethod = 'totp' | 'email-code' | 'recovery-code';
383
- interface CredentialsLoginSuccess {
384
- status: true;
385
- reason: string;
386
- newToken: string;
387
- session: BaseSessionLayout;
388
- requiresTwoFactor?: undefined;
389
- }
390
- interface CredentialsLoginChallenge {
391
- status: true;
392
- reason: string;
393
- requiresTwoFactor: true;
394
- challengeToken: string;
395
- twoFactorMethods: TwoFactorMethod[];
396
- newToken?: undefined;
397
- session?: undefined;
398
- }
399
- interface CredentialsLoginFailure {
400
- status: false;
401
- reason: string;
402
- newToken?: undefined;
403
- session?: undefined;
404
- requiresTwoFactor?: undefined;
405
- }
406
- type CredentialsLoginResult = CredentialsLoginSuccess | CredentialsLoginChallenge | CredentialsLoginFailure;
407
- declare const OAUTH_STATE_COOKIE_NAME = "ls-oauth-state";
408
- interface CreateOAuthStateResult {
409
- /** Opaque `state` param put on the authorize URL + echoed back on callback. */
410
- state: string;
411
- /**
412
- * Random nonce the server must set as the value of the {@link OAUTH_STATE_COOKIE_NAME}
413
- * cookie (HttpOnly, SameSite=Lax, short TTL) at the authorize redirect. The
414
- * callback requires this back to accept the flow (browser-binding, F1).
415
- */
416
- stateCookie: string;
417
- /**
418
- * PKCE `code_challenge` (S256) when the provider opted into PKCE — the server
419
- * appends `code_challenge=<value>&code_challenge_method=S256` to the authorize
420
- * URL. `undefined` for non-PKCE providers (default).
421
- */
422
- codeChallenge?: string;
423
- }
424
- declare const createOAuthState: (providerName: string, options?: {
425
- usePkce?: boolean;
426
- returnUrl?: string;
427
- }) => Promise<CreateOAuthStateResult | null>;
428
- declare const registerWithCredentials: ({ email, password, name, confirmPassword, }: {
429
- email: string;
430
- password: string;
431
- name: string;
432
- confirmPassword: string;
433
- }, options?: {
434
- supersedeToken?: string;
435
- }) => Promise<CredentialsLoginResult>;
436
- type TwoFactorGate = (user: UserRecord, context: {
437
- requesterIp?: string;
438
- }) => Promise<CredentialsLoginChallenge | null>;
439
- declare const registerTwoFactorGate: (gate: TwoFactorGate) => void;
440
- declare const finalizeLogin: (user: UserRecord, options: {
441
- provider: string;
442
- email?: string;
443
- supersedeToken?: string;
444
- requesterIp?: string;
445
- }) => Promise<CredentialsLoginResult>;
446
- declare const loginWithCredentialsCore: ({ email, password }: {
447
- email: string;
448
- password: string;
449
- }, options?: {
450
- supersedeToken?: string;
451
- requesterIp?: string;
452
- }) => Promise<CredentialsLoginResult>;
453
- declare const loginWithCredentials: (params: LoginOrRegisterParams, options?: {
454
- supersedeToken?: string;
455
- requesterIp?: string;
456
- }) => Promise<CredentialsLoginResult>;
457
- interface OAuthCallbackResult {
458
- token: string;
459
- redirectUrl: string;
460
- userId: string;
461
- provider: string;
462
- isNewUser: boolean;
463
- }
464
- declare const loginCallback: (pathname: string, req: IncomingMessage, _res: ServerResponse, options?: {
465
- defaultRedirectUrl?: string;
466
- supersedeToken?: string;
467
- }) => Promise<OAuthCallbackResult | false>;
468
-
469
- declare const logout: ({ token, socket, userId, skipSessionDelete }: {
470
- token: string | null;
471
- socket: Socket | undefined;
472
- userId: string | null;
473
- skipSessionDelete?: boolean;
474
- }) => Promise<void>;
475
-
476
- /**
477
- * Whether `accountKey` is currently locked out (failed-attempt counter already
478
- * at/over the cap). Non-incrementing — safe to call on every login attempt.
479
- * Always `false` when the feature is disabled.
480
- * Pass `requesterIp` to check the IP+account composite bucket (DD-LOGIN-F5).
481
- */
482
- declare const isAccountLocked: (accountKey: string, requesterIp?: string) => Promise<boolean>;
483
- /**
484
- * Increment the failed-attempt counter for `accountKey`. Call on every failed
485
- * credentials login for a known/looked-up account. No-op when disabled.
486
- * Pass `requesterIp` to increment the IP+account composite bucket (DD-LOGIN-F5).
487
- */
488
- declare const recordAuthFailure: (accountKey: string, requesterIp?: string) => Promise<void>;
489
- /**
490
- * Reset the failed-attempt counter for `accountKey` (call after a SUCCESSFUL
491
- * login so a user who eventually gets their password right isn't penalised by
492
- * earlier typos). No-op when disabled.
493
- * Pass `requesterIp` to clear the IP+account composite bucket (DD-LOGIN-F5).
494
- */
495
- declare const clearAuthFailures: (accountKey: string, requesterIp?: string) => Promise<void>;
496
-
497
- /**
498
- * Create a one-time password-reset token bound to a user id. Any previously
499
- * issued (but not yet consumed) token for the same user is invalidated first
500
- * (LOGIN-F16). Stored in Redis with the configured TTL — only `sha256(token)`
501
- * is persisted (hash-at-rest). Returns the raw token string — caller emails it
502
- * to the user (typically embedded in a URL).
503
- */
504
- declare const createPasswordResetToken: (userId: string) => Promise<string>;
505
- /**
506
- * Validate and consume a password-reset token. Returns the bound userId on
507
- * success and removes the token (one-time use, atomic GET+DEL). Returns null
508
- * when the token is missing, expired, malformed, or already consumed.
509
- */
510
- declare const consumePasswordResetToken: (token: string) => Promise<string | null>;
511
- /**
512
- * Bcrypt-hash a plaintext password and write it to the user record via the
513
- * registered user adapter. Used by the reset flow and by the settings
514
- * password-change flow. Validates against the active password policy
515
- * (`projectConfig.auth.passwordPolicy`) before hashing — throws a `PasswordPolicyError`
516
- * when the plaintext fails policy. Catch and surface the error code to the
517
- * client; framework-mode reset/change flows already do this.
518
- *
519
- * ADR — DD-LOGIN-F17: this primitive intentionally does NOT revoke sessions.
520
- * Session revocation is the responsibility of each calling route because the
521
- * right behaviour differs per use-case:
522
- * - `reset-password/confirmReset_v1` revokes ALL sessions (`revokeUserSessions(userId, null)`)
523
- * because a forgot-password reset means the old credential is compromised.
524
- * - `settings/changePassword_v1` revokes every OTHER session but keeps the
525
- * current one active (`revokeUserSessions(userId, user.token)`) so the
526
- * user is not kicked from the device they changed the password on.
527
- * - Custom consumers may want a third behaviour (e.g. re-use the existing
528
- * token after an admin password-force) — keeping the primitive pure lets
529
- * them compose it freely.
530
- * If you call this function directly, YOU must call `revokeUserSessions`
531
- * afterwards unless you have a deliberate reason not to.
532
- */
533
- declare const updatePasswordHash: (userId: string, plaintext: string) => Promise<void>;
534
- /**
535
- * Thrown by `updatePasswordHash` when the supplied plaintext violates the
536
- * active password policy. `errorCode` matches the i18n reason key the rest
537
- * of the login flow uses (e.g. `login.passwordRequiresUppercase`).
538
- */
539
- declare class PasswordPolicyError extends Error {
540
- readonly errorCode: string;
541
- constructor(errorCode: string);
542
- }
543
- /** Verify a plaintext password against a stored bcrypt hash. */
544
- declare const verifyPassword: (plaintext: string, hash: string) => Promise<boolean>;
545
-
546
- /**
547
- * Validate `password` against the active project's password policy.
548
- * Returns null on success, a string reason-key on the first failure.
549
- *
550
- * The policy lives in `projectConfig.auth.passwordPolicy` and is read at
551
- * call time, so consumers can override defaults via `registerProjectConfig`.
552
- */
553
- declare const validatePassword: (password: string) => string | null;
554
-
555
- interface SendResetEmailArgs {
556
- email: string;
557
- /**
558
- * App-facing display name used in the email greeting + subject. If omitted,
559
- * falls back to `projectConfig.auth.passwordResetBrand`, then to `'LuckyStack'`
560
- * as the absolute fallback. Real consumers should set this on either the
561
- * call site OR via `projectConfig.auth.passwordResetBrand`.
562
- */
563
- brand?: string;
564
- }
565
- /**
566
- * Look up the user by credentials-provider email, generate a reset token,
567
- * and email them a reset link. Always resolves successfully regardless of
568
- * whether the email matched a user (anti-enumeration). Returns the result
569
- * of the email send for diagnostics.
570
- */
571
- declare const sendPasswordResetEmail: ({ email, brand }: SendResetEmailArgs) => Promise<{
572
- ok: boolean;
573
- reason?: string;
574
- }>;
575
-
576
- interface EmailChangePayload {
577
- userId: string;
578
- newEmail: string;
579
- }
580
- /**
581
- * Create a one-time email-change token bound to a user id + the new email
582
- * address. Any previously issued (but not yet consumed) token for the same
583
- * user is invalidated first (LOGIN-F16) so at most one confirmation link per
584
- * user is redeemable. Stored in Redis with the configured TTL — only
585
- * `sha256(token)` is persisted (hash-at-rest). Returns the raw token string —
586
- * caller emails the token (embedded in a URL) to the NEW address so the
587
- * confirmation click proves ownership of the new mailbox.
588
- */
589
- declare const createEmailChangeToken: (userId: string, newEmail: string) => Promise<string>;
590
- /**
591
- * Validate and consume an email-change token. Returns the bound
592
- * `{ userId, newEmail }` on success and removes the token (one-time use, atomic
593
- * GET+DEL). Returns null when the token is missing, expired, malformed, or
594
- * already consumed.
595
- */
596
- declare const consumeEmailChangeToken: (token: string) => Promise<EmailChangePayload | null>;
597
-
598
- interface SendEmailChangeArgs {
599
- userId: string;
600
- newEmail: string;
601
- /** Optional display name used in the email greeting (falls back to "there"). */
602
- userName?: string | null;
603
- /**
604
- * App-facing display name used in the email greeting + subject. Falls back
605
- * to `projectConfig.auth.passwordResetBrand` (the shared brand label), then
606
- * to `'LuckyStack'` as the absolute fallback.
607
- */
608
- brand?: string;
609
- }
610
- /**
611
- * Mint a one-shot email-change token and send a confirmation email to the
612
- * NEW address with the tokenized URL. Returns the send result for diagnostics
613
- * + the token itself (callers can audit-log it through the
614
- * `postEmailChangeRequested` hook).
615
- */
616
- declare const sendEmailChangeConfirmation: ({ userId, newEmail, userName, brand }: SendEmailChangeArgs) => Promise<{
617
- ok: boolean;
618
- reason?: string;
619
- token: string;
620
- }>;
621
-
622
- type OAuthUserData = Record<string, unknown>;
623
- declare const asOAuthUserData: (value: unknown) => OAuthUserData;
624
- interface CredentialsProvider {
625
- name: 'credentials';
626
- }
627
- interface FullOAuthProvider {
628
- name: string;
629
- clientID: string;
630
- clientSecret: string;
631
- callbackURL: string;
632
- authorizationURL: string;
633
- tokenExchangeURL: string;
634
- tokenExchangeMethod: 'json' | 'form';
635
- userInfoURL: string;
636
- /**
637
- * OAuth scopes requested at the authorization step. Consumers can extend
638
- * a built-in provider's defaults to request additional permissions:
639
- *
640
- * ```ts
641
- * const customGoogle = googleProvider({...});
642
- * customGoogle.scope.push('https://www.googleapis.com/auth/calendar.readonly');
643
- * ```
644
- *
645
- * Or pass `extraScopes` to the built-in factory (see `OAuthHelperInput`).
646
- */
647
- scope: string[];
648
- nameKey: string;
649
- emailKey: string;
650
- /**
651
- * Optional key on the userInfo response holding a provider "email verified"
652
- * boolean. When set AND the field is present-and-falsy, the framework rejects
653
- * the profile rather than trusting an unverified address — defense-in-depth
654
- * against account-linking takeover under the `'unified'` strategy (SEC-21).
655
- * A missing field (provider doesn't report it) is treated as "trusted",
656
- * preserving behaviour for providers that don't expose the flag.
657
- */
658
- emailVerifiedKey?: string;
659
- /**
660
- * Set `true` for a provider whose userinfo ONLY returns an email once the
661
- * address is confirmed with the provider (e.g. Facebook Graph `/me` omits
662
- * unconfirmed emails). Such a provider exposes no separate verified flag, so
663
- * the mere PRESENCE of the email is the verified signal — this opts it in so
664
- * both first-login CREATION and cross-provider LINK treat it as verified.
665
- * Mutually exclusive in practice with `emailVerifiedKey`.
666
- */
667
- emailImpliesVerified?: boolean;
668
- avatarKey?: string;
669
- avatarCodeKey: string;
670
- getEmail?: (accessToken: string) => Promise<string | false | undefined>;
671
- getAvatar?: (params: {
672
- userData: OAuthUserData;
673
- avatarId?: string;
674
- accessToken: string;
675
- }) => string | undefined | Promise<string | undefined>;
676
- /**
677
- * Optional per-provider hook to attach extra runtime-only data to the
678
- * session (NOT to the User row). Return key/value pairs that get merged
679
- * into `BaseSessionLayout` before `saveSession`. To get strict typing
680
- * downstream, extend `BaseSessionLayout` via module augmentation:
681
- *
682
- * ```ts
683
- * declare module '@luckystack/core' {
684
- * interface BaseSessionLayout {
685
- * googleCalendarToken?: string;
686
- * }
687
- * }
688
- * ```
689
- *
690
- * Data lives on the session in Redis and disappears on logout — no Prisma
691
- * schema change required. Don't store secrets you wouldn't want surfaced
692
- * to a user inspecting their own session.
693
- */
694
- extraSessionFields?: (params: {
695
- userData: OAuthUserData;
696
- accessToken: string;
697
- }) => Promise<Record<string, unknown>> | Record<string, unknown>;
698
- /**
699
- * Extra query params appended to the authorization-redirect URL (CFG-21).
700
- * Lets a provider request e.g. `access_type=offline`, `login_hint=...`, or
701
- * override the framework's default `prompt=select_account`. The server's
702
- * authorize route merges these over the built-in params (a key present here
703
- * wins). Reserved OAuth params (`client_id`, `redirect_uri`, `scope`,
704
- * `response_type`, `state`, `code_challenge`, `code_challenge_method`) should
705
- * NOT be set here — they are framework-owned. Default unset.
706
- */
707
- extraAuthorizationParams?: Record<string, string>;
708
- /**
709
- * Opt this provider into PKCE (RFC 7636, S256) (F11). When `true`, the
710
- * framework generates a `code_verifier` at flow start, stores it server-side
711
- * with the OAuth state, sends `code_challenge`/`code_challenge_method=S256` on
712
- * the authorize redirect, and replays the verifier at token exchange. Required
713
- * by OAuth 2.1 / PKCE-mandating providers (X/Twitter, some Okta/Auth0
714
- * policies). Default `false` (no PKCE — existing flows are byte-identical).
715
- */
716
- usePkce?: boolean;
717
- }
718
- type OAuthProvider = CredentialsProvider | FullOAuthProvider;
719
- declare const isFullOAuthProvider: (provider: OAuthProvider) => provider is FullOAuthProvider;
720
- interface OAuthEndpointOverrides {
721
- /** Override the provider's authorization URL (e.g. GitHub Enterprise host, Microsoft custom tenant). */
722
- authorizationURL?: string;
723
- /** Override the token exchange URL. */
724
- tokenExchangeURL?: string;
725
- /** Override the userInfo URL. */
726
- userInfoURL?: string;
727
- }
728
- interface OAuthHelperInput {
729
- clientId: string | undefined;
730
- clientSecret: string | undefined;
731
- callbackUrl: string;
732
- /** Optional URL overrides for self-hosted / custom-tenant deployments. */
733
- endpoints?: OAuthEndpointOverrides;
734
- /**
735
- * Additional OAuth scopes to request alongside the built-in defaults. Use
736
- * for "give me access to the user's calendar / drive / repos" flows.
737
- * Merged with the helper's default scopes; duplicates are deduplicated.
738
- */
739
- extraScopes?: string[];
740
- /**
741
- * Per-provider hook for attaching runtime-only fields to the session.
742
- * See `FullOAuthProvider.extraSessionFields` for usage + module-augmentation
743
- * pattern.
744
- */
745
- extraSessionFields?: FullOAuthProvider['extraSessionFields'];
746
- /**
747
- * Extra query params for the authorize redirect (CFG-21). See
748
- * `FullOAuthProvider.extraAuthorizationParams`.
749
- */
750
- extraAuthorizationParams?: Record<string, string>;
751
- /** Opt this provider into PKCE (S256) (F11). See `FullOAuthProvider.usePkce`. */
752
- usePkce?: boolean;
753
- }
754
- declare const credentialsProvider: () => CredentialsProvider;
755
- declare const googleProvider: (input: OAuthHelperInput) => FullOAuthProvider;
756
- declare const githubProvider: (input: OAuthHelperInput) => FullOAuthProvider;
757
- declare const discordProvider: (input: OAuthHelperInput) => FullOAuthProvider;
758
- interface FacebookProviderInput extends OAuthHelperInput {
759
- /** Graph API version segment used in the URLs. Default `'v18.0'` (current as of 2026 — bump as Meta deprecates older versions). */
760
- apiVersion?: string;
761
- }
762
- declare const facebookProvider: (input: FacebookProviderInput) => FullOAuthProvider;
763
- interface MicrosoftProviderInput extends OAuthHelperInput {
764
- /** Tenant id segment in the authorize/token URLs. Default `'common'`. */
765
- tenant?: string;
766
- /** OAuth API version segment. Default `'v2.0'`. */
767
- apiVersion?: string;
768
- /** Graph API version segment for /me. Default `'v1.0'`. */
769
- graphApiVersion?: string;
770
- /**
771
- * Allow falling back to `userPrincipalName` as the account email when Graph
772
- * `/me.mail` is empty (SEC-11). A UPN is NOT guaranteed routable or verified;
773
- * under the `'unified'` account strategy a UPN equal to a victim's email would
774
- * link accounts, and it commonly mis-keys an account on a non-deliverable
775
- * address (reset emails never arrive). Default `false` — only enable when the
776
- * tenant guarantees UPN == primary SMTP. When `false` and `.mail` is empty the
777
- * provider reports "no email" and the login is rejected rather than linked.
778
- */
779
- allowUpnFallback?: boolean;
780
- }
781
- declare const microsoftProvider: (input: MicrosoftProviderInput) => FullOAuthProvider;
782
- declare const getOAuthProviders: () => OAuthProvider[];
783
- declare const registerOAuthProviders: (providers: OAuthProvider[]) => OAuthProvider[];
784
- /**
785
- * Register a factory function that builds the provider list on first use.
786
- * Prefer this over `registerOAuthProviders` from the package `register` side-
787
- * effect so config/env reads are deferred to request time and survive any
788
- * `registerProjectConfig` call that happens after package import.
789
- * A subsequent `registerOAuthProviders` call replaces the factory.
790
- */
791
- declare const registerOAuthProviderFactory: (factory: () => OAuthProvider[]) => void;
792
-
793
- declare const availableTwoFactorMethods: (user: UserRecord) => TwoFactorMethod[];
794
- declare const createTwoFactorChallengeIfRequired: (user: UserRecord, _context: {
795
- requesterIp?: string;
796
- }) => Promise<CredentialsLoginChallenge | null>;
797
- interface VerifyTwoFactorInput {
798
- challengeToken: string;
799
- code: string;
800
- /** Defaults to 'totp'; the client sends what the user picked. */
801
- method?: TwoFactorMethod;
802
- supersedeToken?: string;
803
- requesterIp?: string;
804
- }
805
- declare const verifyTwoFactorChallenge: (input: VerifyTwoFactorInput) => Promise<CredentialsLoginResult>;
806
- declare const requestTwoFactorEmailCode: (challengeToken: string) => Promise<{
807
- ok: boolean;
808
- reason?: string;
809
- }>;
810
- type TotpEnrollmentStart = {
811
- ok: true;
812
- secret: string;
813
- otpauthUri: string;
814
- } | {
815
- ok: false;
816
- reason: string;
817
- };
818
- /**
819
- * Step 1: mint a secret and park it as PENDING (Redis, 10 min) — the user
820
- * record is untouched until the user proves the app works via
821
- * `confirmTotpEnrollment`. Re-calling replaces the pending secret. Refused for
822
- * an already-enrolled user / a disabled feature (see enrollmentBlockedReason).
823
- */
824
- declare const beginTotpEnrollment: (user: UserRecord) => Promise<TotpEnrollmentStart>;
825
- type ConfirmTotpEnrollmentResult = {
826
- ok: true;
827
- recoveryCodes: string[];
828
- } | {
829
- ok: false;
830
- reason: string;
831
- };
832
- /**
833
- * Step 2: the user enters the first code from their app. On success the
834
- * secret + recovery-code hashes land on the user record and 2FA is ON.
835
- * The RAW recovery codes are returned exactly once — show + let them save.
836
- */
837
- declare const confirmTotpEnrollment: (user: UserRecord, code: string) => Promise<ConfirmTotpEnrollmentResult>;
838
- /**
839
- * Disable 2FA. Requires a currently-valid TOTP code OR an unused recovery
840
- * code, so a hijacked (but 2FA-less) session can't silently strip the factor.
841
- */
842
- declare const disableTwoFactor: (user: UserRecord, code: string) => Promise<{
843
- ok: boolean;
844
- reason?: string;
845
- }>;
846
- /** Replace the recovery-code set (requires a valid current TOTP code). */
847
- declare const regenerateRecoveryCodes: (user: UserRecord, code: string) => Promise<ConfirmTotpEnrollmentResult>;
848
-
849
- declare const base32Encode: (input: Buffer) => string;
850
- declare const base32Decode: (input: string) => Buffer | null;
851
- declare const hotp: (key: Buffer, counter: number, digits?: number) => string;
852
- interface TotpVerifyInput {
853
- /** base32-encoded shared secret (as provisioned to the authenticator app). */
854
- secret: string;
855
- /** The user-entered code (digits; surrounding whitespace tolerated). */
856
- code: string;
857
- /**
858
- * Accepted clock drift in 30s steps on EITHER side of "now" (default 1 —
859
- * i.e. the previous, current and next code are accepted; RFC 6238 §5.2
860
- * recommends at most one backward step).
861
- */
862
- window?: number;
863
- /** Injection point for tests; defaults to the real clock. */
864
- timestampMs?: number;
865
- }
866
- interface TotpVerifyResult {
867
- valid: boolean;
868
- /**
869
- * The timestep the code matched (only when valid). Persist the highest
870
- * accepted value per user and refuse `timestep <= lastAccepted` to make
871
- * every code single-use (replay protection across the drift window).
872
- */
873
- timestep: number | null;
874
- }
875
- declare const verifyTotp: ({ secret, code, window, timestampMs }: TotpVerifyInput) => TotpVerifyResult;
876
- /** 20 random bytes (RFC 4226 §4 minimum: 128 bits; 160 recommended), base32. */
877
- declare const generateTotpSecret: () => string;
878
- interface OtpauthUriInput {
879
- /** base32 secret from generateTotpSecret(). */
880
- secret: string;
881
- /** What the authenticator app shows as the account (usually the user's email). */
882
- accountName: string;
883
- /** App/brand name shown above the account (defaults to the project title). */
884
- issuer: string;
885
- }
886
- declare const buildOtpauthUri: ({ secret, accountName, issuer }: OtpauthUriInput) => string;
887
-
888
- interface RequestEmailLoginCodeInput {
889
- email: string;
890
- requesterIp?: string;
891
- }
892
- type RequestEmailLoginCodeResult = {
893
- ok: true;
894
- } | {
895
- ok: false;
896
- reason: string;
897
- };
898
- declare const requestEmailLoginCode: ({ email, requesterIp }: RequestEmailLoginCodeInput) => Promise<RequestEmailLoginCodeResult>;
899
- interface VerifyEmailLoginCodeInput {
900
- email: string;
901
- code: string;
902
- supersedeToken?: string;
903
- requesterIp?: string;
904
- }
905
- declare const verifyEmailLoginCode: (input: VerifyEmailLoginCodeInput) => Promise<CredentialsLoginResult>;
906
-
907
- type EmailOtpPurpose = 'login' | '2fa';
908
- declare const generateNumericCode: (digits: number) => string;
909
- interface IssueEmailCodeInput {
910
- purpose: EmailOtpPurpose;
911
- /** Email address (login) or userId (2fa). */
912
- identity: string;
913
- ttlSeconds: number;
914
- digits: number;
915
- }
916
- /**
917
- * Mint + store a fresh code for (purpose, identity). Any previously active
918
- * code for the same slot is replaced, and its attempt counter reset. Returns
919
- * the RAW code exactly once — the caller emails it; only sha256(code) is
920
- * stored (hash-at-rest, ADR 0010 spirit).
921
- */
922
- declare const issueEmailCode: ({ purpose, identity, ttlSeconds, digits }: IssueEmailCodeInput) => Promise<string>;
923
- type EmailCodeVerdict = 'valid' | 'invalid' | 'expired' | 'locked';
924
- interface VerifyEmailCodeInput {
925
- purpose: EmailOtpPurpose;
926
- identity: string;
927
- code: string;
928
- /** Wrong-code attempts before the code is burned (counter is server-side). */
929
- maxAttempts: number;
930
- }
931
- /**
932
- * Verify + consume. Verdicts: 'valid' (code matched, slot consumed),
933
- * 'invalid' (wrong code, attempt recorded), 'locked' (attempt budget spent —
934
- * the active code is burned so guessing can't continue), 'expired' (no active
935
- * code: TTL passed, never issued, or already consumed).
936
- */
937
- declare const verifyEmailCode: ({ purpose, identity, code, maxAttempts }: VerifyEmailCodeInput) => Promise<EmailCodeVerdict>;
938
- /** Drop any active code + counter for the slot (e.g. after a completed login). */
939
- declare const clearEmailCode: (purpose: EmailOtpPurpose, identity: string) => Promise<void>;
940
-
941
- interface PostLoginRedirectInput {
942
- userId: string;
943
- provider: string;
944
- isNewUser: boolean;
945
- defaultUrl: string;
946
- }
947
- type PostLoginRedirectResolver = (input: PostLoginRedirectInput) => string | Promise<string>;
948
- declare const registerPostLoginRedirect: (resolver: PostLoginRedirectResolver) => PostLoginRedirectResolver;
949
- declare const getPostLoginRedirect: () => PostLoginRedirectResolver | null;
950
-
951
- export { type ConfirmTotpEnrollmentResult, type CreateOAuthStateResult, type CredentialsLoginChallenge, type CredentialsLoginFailure, type CredentialsLoginResult, type CredentialsLoginSuccess, type CredentialsProvider, type EmailChangePayload, type EmailCodeVerdict, type EmailOtpPurpose, type FullOAuthProvider, type LoginFailedPayload, OAUTH_STATE_COOKIE_NAME, type OAuthCallbackResult, type OAuthProvider, type OAuthUserData, type PasswordChangedPayload, PasswordPolicyError, type PasswordResetCompletedPayload, type PasswordResetRequestedPayload, type PostAccountDeletePayload, type PostEmailChangeRequestedPayload, type PostEmailChangedPayload, type PostLoginPayload, type PostLoginRedirectInput, type PostLoginRedirectResolver, type PostLogoutPayload, type PostRegisterPayload, type PostSessionCreatePayload, type PostSessionDeletePayload, type PreAccountDeletePayload, type PreEmailChangePayload, type PreLoginPayload, type PreLogoutPayload, type PrePasswordChangedPayload, type PrePasswordResetCompletedPayload, type PreRegisterPayload, type PreSessionCreatePayload, type PreSessionDeletePayload, type RequestEmailLoginCodeInput, type RequestEmailLoginCodeResult, type SessionAdapter, type SessionSanitizer, type TotpEnrollmentStart, type TwoFactorGate, type TwoFactorMethod, type UserAdapter, type UserAdapterCreateInput, type UserRecord, type VerifyEmailLoginCodeInput, type VerifyTwoFactorInput, activeUsersKeyFor, asOAuthUserData, availableTwoFactorMethods, base32Decode, base32Encode, beginTotpEnrollment, buildOtpauthUri, clearAuthFailures, clearEmailCode, confirmTotpEnrollment, consumeEmailChangeToken, consumePasswordResetToken, createEmailChangeToken, createOAuthState, createPasswordResetToken, createTwoFactorChallengeIfRequired, credentialsProvider, defaultPrismaUserAdapter, deleteSession, disableTwoFactor, discordProvider, facebookProvider, finalizeLogin, generateNumericCode, generateTotpSecret, getAllSessions, getOAuthProviders, getPostLoginRedirect, getSession, getSessionAdapter, getSessionSanitizer, getUserAdapter, githubProvider, googleProvider, hotp, isAccountLocked, isFullOAuthProvider, isUserAdapterRegistered, issueEmailCode, loginCallback, loginWithCredentials, loginWithCredentialsCore, logout, microsoftProvider, recordAuthFailure, redisSessionAdapter, regenerateRecoveryCodes, registerOAuthProviderFactory, registerOAuthProviders, registerPostLoginRedirect, registerSessionAdapter, registerSessionSanitizer, registerTwoFactorGate, registerUserAdapter, registerWithCredentials, requestEmailLoginCode, requestTwoFactorEmailCode, revokeUserSessions, saveSession, sendEmailChangeConfirmation, sendPasswordResetEmail, sessionKeyFor, updatePasswordHash, validatePassword, verifyEmailCode, verifyEmailLoginCode, verifyPassword, verifyTotp, verifyTwoFactorChallenge };
1
+ import './hookPayloads.js';
2
+ export type { BaseSessionLayout, SessionLocation, AuthProps } from './sessionLayout.js';
3
+ export type { PreLoginPayload, PostLoginPayload, PreRegisterPayload, PostRegisterPayload, PreLogoutPayload, PostLogoutPayload, PreSessionCreatePayload, PostSessionCreatePayload, PreSessionDeletePayload, PostSessionDeletePayload, PasswordResetRequestedPayload, PrePasswordResetCompletedPayload, PasswordResetCompletedPayload, PrePasswordChangedPayload, PasswordChangedPayload, PreEmailChangePayload, PostEmailChangeRequestedPayload, PostEmailChangedPayload, LoginFailedPayload, PreAccountDeletePayload, PostAccountDeletePayload, } from './hookPayloads.js';
4
+ export { saveSession, getSession, deleteSession, getAllSessions, revokeUserSessions, sessionKeyFor, activeUsersKeyFor } from './session.js';
5
+ export { registerSessionAdapter, getSessionAdapter, redisSessionAdapter } from './sessionAdapter.js';
6
+ export type { SessionAdapter } from './sessionAdapter.js';
7
+ export { registerSessionSanitizer, getSessionSanitizer } from './sessionSanitizer.js';
8
+ export type { SessionSanitizer } from './sessionSanitizer.js';
9
+ export { loginWithCredentials, loginCallback, createOAuthState, registerWithCredentials, loginWithCredentialsCore, finalizeLogin, registerTwoFactorGate } from './login.js';
10
+ export type { CredentialsLoginResult, CredentialsLoginSuccess, CredentialsLoginChallenge, CredentialsLoginFailure, TwoFactorMethod, TwoFactorGate, OAuthCallbackResult, } from './login.js';
11
+ export type { CreateOAuthStateResult } from './login.js';
12
+ export { OAUTH_STATE_COOKIE_NAME } from './login.js';
13
+ export { logout } from './logout.js';
14
+ export { isAccountLocked, recordAuthFailure, clearAuthFailures } from './authLockout.js';
15
+ export { createPasswordResetToken, consumePasswordResetToken, updatePasswordHash, verifyPassword, PasswordPolicyError, } from './passwordReset.js';
16
+ export { validatePassword } from './passwordPolicy.js';
17
+ export { sendPasswordResetEmail } from './forgotPassword.js';
18
+ export { createEmailChangeToken, consumeEmailChangeToken, } from './emailChange.js';
19
+ export type { EmailChangePayload } from './emailChange.js';
20
+ export { sendEmailChangeConfirmation } from './emailChangeNotification.js';
21
+ export { registerOAuthProviders, registerOAuthProviderFactory, getOAuthProviders, isFullOAuthProvider, asOAuthUserData, credentialsProvider, googleProvider, githubProvider, discordProvider, facebookProvider, microsoftProvider, } from './oauthProviders.js';
22
+ export type { OAuthProvider, CredentialsProvider, FullOAuthProvider, OAuthUserData, } from './oauthProviders.js';
23
+ export { registerUserAdapter, getUserAdapter, isUserAdapterRegistered, defaultPrismaUserAdapter, } from './userAdapter.js';
24
+ export type { UserAdapter, UserAdapterCreateInput, UserRecord, } from './userAdapter.js';
25
+ export { beginTotpEnrollment, confirmTotpEnrollment, disableTwoFactor, regenerateRecoveryCodes, verifyTwoFactorChallenge, requestTwoFactorEmailCode, availableTwoFactorMethods, createTwoFactorChallengeIfRequired, } from './twoFactor.js';
26
+ export type { TotpEnrollmentStart, ConfirmTotpEnrollmentResult, VerifyTwoFactorInput } from './twoFactor.js';
27
+ export { verifyTotp, generateTotpSecret, buildOtpauthUri, hotp, base32Encode, base32Decode } from './totp.js';
28
+ export { requestEmailLoginCode, verifyEmailLoginCode } from './emailCodeLogin.js';
29
+ export type { RequestEmailLoginCodeInput, RequestEmailLoginCodeResult, VerifyEmailLoginCodeInput } from './emailCodeLogin.js';
30
+ export { issueEmailCode, verifyEmailCode, clearEmailCode, generateNumericCode } from './emailOtp.js';
31
+ export type { EmailOtpPurpose, EmailCodeVerdict } from './emailOtp.js';
32
+ export { registerPostLoginRedirect, getPostLoginRedirect, } from './redirectResolver.js';
33
+ export type { PostLoginRedirectResolver, PostLoginRedirectInput, } from './redirectResolver.js';