@luckystack/login 0.1.8 → 0.2.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
@@ -102,12 +102,34 @@ interface PreEmailChangePayload {
102
102
  interface PostEmailChangeRequestedPayload {
103
103
  userId: string;
104
104
  newEmail: string;
105
+ sent?: boolean;
105
106
  }
106
107
  interface PostEmailChangedPayload {
107
108
  userId: string;
108
109
  oldEmail: string;
109
110
  newEmail: string;
110
111
  }
112
+ interface LoginFailedPayload {
113
+ /** Email the attempt was for, when available. */
114
+ email?: string;
115
+ /** Resolved userId, when the attempt matched a real account. */
116
+ userId?: string;
117
+ /** Auth provider (e.g. `credentials`, `google`). */
118
+ provider: string;
119
+ /** i18n reason key describing why the attempt failed. */
120
+ reason: string;
121
+ /** Which flow the failure occurred in. */
122
+ stage: 'login' | 'register' | 'oauth';
123
+ requesterIp?: string;
124
+ }
125
+ interface PreAccountDeletePayload {
126
+ userId: string;
127
+ email?: string;
128
+ }
129
+ interface PostAccountDeletePayload {
130
+ userId: string;
131
+ email?: string;
132
+ }
111
133
  declare module '@luckystack/core' {
112
134
  interface HookPayloads {
113
135
  preLogin: PreLoginPayload;
@@ -128,6 +150,9 @@ declare module '@luckystack/core' {
128
150
  preEmailChange: PreEmailChangePayload;
129
151
  postEmailChangeRequested: PostEmailChangeRequestedPayload;
130
152
  postEmailChanged: PostEmailChangedPayload;
153
+ loginFailed: LoginFailedPayload;
154
+ preAccountDelete: PreAccountDeletePayload;
155
+ postAccountDelete: PostAccountDeletePayload;
131
156
  }
132
157
  }
133
158
 
@@ -137,23 +162,48 @@ declare module '@luckystack/core' {
137
162
  * @param token - The session token (unique identifier)
138
163
  * @param data - The session data to store
139
164
  * @param newUser - If true, this is a new login (triggers single-session enforcement)
165
+ * @param options.supersedeToken - A token belonging to the SAME browser that is
166
+ * being replaced by this login (e.g. re-login while already signed in). It is
167
+ * excluded from single-session enforcement so the user's current device is not
168
+ * kicked by its own new session. The caller is responsible for deleting it
169
+ * afterwards (use `deleteSession(supersedeToken, { skipSocketLogout: true })`).
140
170
  */
141
- declare const saveSession: (token: string, data: BaseSessionLayout, newUser?: boolean) => Promise<void>;
171
+ declare const saveSession: (token: string, data: BaseSessionLayout, newUser?: boolean, options?: {
172
+ supersedeToken?: string;
173
+ }) => Promise<{
174
+ ok: true;
175
+ } | {
176
+ ok: false;
177
+ errorCode: string;
178
+ }>;
142
179
  /**
143
180
  * Retrieve a user session through the active session adapter.
144
181
  *
145
182
  * @param token - The session token
183
+ * @param options.refresh - When false, skip the sliding-expiration TTL
184
+ * extension. Defaults to true. Pass false for read-only probes (e.g. a
185
+ * background audit, an admin lookup) where a stolen token should NOT earn
186
+ * extra lifetime just because the server touched it (LOGIN-F9).
146
187
  * @returns The session data or null if not found
147
188
  */
148
- declare const getSession: (token: string | null) => Promise<BaseSessionLayout | null>;
189
+ declare const getSession: (token: string | null, options?: {
190
+ refresh?: boolean;
191
+ }) => Promise<BaseSessionLayout | null>;
149
192
  /**
150
193
  * Delete a user session through the active adapter and notify connected
151
194
  * clients.
152
195
  *
153
196
  * @param token - The session token to delete
197
+ * @param options.skipSocketLogout - When true, the session data + active-token
198
+ * tracking are cleaned up and the pre/post delete hooks still fire, but NO
199
+ * `logout` is emitted to sockets in the token's room. Use this when the SAME
200
+ * browser is replacing its session (re-login): emitting a logout would bounce
201
+ * that browser to the login page and null its brand-new session.
154
202
  * @returns true if successful
155
203
  */
156
- declare const deleteSession: (token: string) => Promise<boolean>;
204
+ declare const deleteSession: (token: string, options?: {
205
+ skipSocketLogout?: boolean;
206
+ }) => Promise<boolean>;
157
207
  /**
158
208
  * Get all active sessions (admin utility). Returns empty when the active
159
209
  * adapter doesn't implement enumeration (signed-JWT-stateless, log-only).
@@ -202,10 +252,25 @@ interface SessionAdapter {
202
252
  trackActive(userId: string, token: string, ttlSeconds: number): Promise<void>;
203
253
  untrackActive(userId: string, token: string): Promise<void>;
204
254
  listActive(userId: string): Promise<string[]>;
255
+ /**
256
+ * Refresh the TTL on the active-tokens-per-user record WITHOUT adding a
257
+ * token (sliding-window expiration parity with the session key). Called on
258
+ * an authenticated read so a session kept alive purely by reads does not
259
+ * outlive its activeUsers entry — which would otherwise let a stolen token
260
+ * survive a sign-out-everywhere / password reset. Optional: backends whose
261
+ * active-set entries do not expire independently (or that have no TTL
262
+ * concept) can omit it. Must not create a record on miss.
263
+ */
264
+ touchActive?(userId: string, ttlSeconds: number): Promise<void>;
205
265
  /**
206
266
  * Admin walk: yield every active session record. Optional because
207
267
  * non-scannable backends (signed-JWT-stateless, log-only) cannot
208
268
  * enumerate. Callers fall back to a per-user listActive when omitted.
269
+ *
270
+ * **Dev/admin-only (LOGIN-F14).** Implementations scan the full keyspace and
271
+ * load every session into memory in one call — O(all sessions) in both Redis
272
+ * round-trips and memory. Do NOT call on the hot request path or from any
273
+ * user-facing API. Use `listActive(userId)` for per-user lookups.
209
274
  */
210
275
  listAll?(): Promise<{
211
276
  token: string;
@@ -230,97 +295,74 @@ declare const registerSessionAdapter: (adapter: SessionAdapter) => void;
230
295
  /** Read the currently-active session adapter (defaults to Redis). */
231
296
  declare const getSessionAdapter: () => SessionAdapter;
232
297
 
233
- interface UserAdapterCreateInput {
234
- email: string;
235
- provider: string;
236
- name?: string | null;
237
- password?: string | null;
238
- avatar?: string | null;
239
- avatarFallback?: string | null;
240
- language?: string | null;
241
- }
242
- interface UserRecord extends BaseSessionLayout {
243
- password?: string | null;
244
- provider?: string | null;
245
- /** Most recent successful login. Mirrors the optional `lastLogin` column on User. */
246
- lastLogin?: Date | null;
247
- }
248
- interface UserAdapter {
249
- findByEmail: (params: {
250
- email: string;
251
- provider: string;
252
- }) => Promise<UserRecord | null>;
253
- findById: (id: string) => Promise<UserRecord | null>;
254
- create: (input: UserAdapterCreateInput) => Promise<UserRecord>;
255
- update: (id: string, patch: Partial<UserRecord>) => Promise<UserRecord>;
256
- }
257
- declare const registerUserAdapter: (adapter: UserAdapter) => UserAdapter;
258
- declare const isUserAdapterRegistered: () => boolean;
259
- declare const defaultPrismaUserAdapter: () => UserAdapter;
260
- declare const getUserAdapter: () => UserAdapter;
298
+ type SessionSanitizer = (session: BaseSessionLayout) => BaseSessionLayout;
299
+ /**
300
+ * Register a redactor applied to the session record before it is persisted to
301
+ * the store and broadcast to connected clients. Call once at boot. Last write
302
+ * wins. Pass a function that returns a shallow copy with sensitive fields
303
+ * stripped, e.g. `({ totpSecret, stripeCustomerId, ...safe }) => safe`.
304
+ */
305
+ declare const registerSessionSanitizer: (sanitizer: SessionSanitizer) => void;
306
+ /** Read the active sanitizer (or `null` when none is registered). */
307
+ declare const getSessionSanitizer: () => SessionSanitizer | null;
261
308
 
262
- interface paramsType {
309
+ interface LoginOrRegisterParams {
263
310
  email?: string;
264
311
  password?: string;
265
312
  name?: string;
266
313
  confirmPassword?: string;
267
314
  }
268
- declare const createOAuthState: (providerName: string) => Promise<string | null>;
315
+ interface CredentialsLoginSuccess {
316
+ status: true;
317
+ reason: string;
318
+ newToken: string;
319
+ session: BaseSessionLayout;
320
+ }
321
+ interface CredentialsLoginFailure {
322
+ status: false;
323
+ reason: string;
324
+ }
325
+ type CredentialsLoginResult = CredentialsLoginSuccess | CredentialsLoginFailure;
326
+ declare const OAUTH_STATE_COOKIE_NAME = "ls-oauth-state";
327
+ interface CreateOAuthStateResult {
328
+ /** Opaque `state` param put on the authorize URL + echoed back on callback. */
329
+ state: string;
330
+ /**
331
+ * Random nonce the server must set as the value of the {@link OAUTH_STATE_COOKIE_NAME}
332
+ * cookie (HttpOnly, SameSite=Lax, short TTL) at the authorize redirect. The
333
+ * callback requires this back to accept the flow (browser-binding, F1).
334
+ */
335
+ stateCookie: string;
336
+ /**
337
+ * PKCE `code_challenge` (S256) when the provider opted into PKCE — the server
338
+ * appends `code_challenge=<value>&code_challenge_method=S256` to the authorize
339
+ * URL. `undefined` for non-PKCE providers (default).
340
+ */
341
+ codeChallenge?: string;
342
+ }
343
+ declare const createOAuthState: (providerName: string, options?: {
344
+ usePkce?: boolean;
345
+ returnUrl?: string;
346
+ }) => Promise<CreateOAuthStateResult | null>;
269
347
  declare const registerWithCredentials: ({ email, password, name, confirmPassword, }: {
270
348
  email: string;
271
349
  password: string;
272
350
  name: string;
273
351
  confirmPassword: string;
274
- }) => Promise<{
275
- status: boolean;
276
- reason: string;
277
- session?: undefined;
278
- } | {
279
- status: boolean;
280
- reason: string;
281
- session: Omit<UserRecord, "password">;
282
- }>;
352
+ }, options?: {
353
+ supersedeToken?: string;
354
+ }) => Promise<CredentialsLoginResult>;
283
355
  declare const loginWithCredentialsCore: ({ email, password }: {
284
356
  email: string;
285
357
  password: string;
286
- }) => Promise<{
287
- status: boolean;
288
- reason: string;
289
- newToken?: undefined;
290
- session?: undefined;
291
- } | {
292
- status: boolean;
293
- reason: Error;
294
- newToken?: undefined;
295
- session?: undefined;
296
- } | {
297
- status: boolean;
298
- reason: string;
299
- newToken: string;
300
- session: BaseSessionLayout;
301
- }>;
302
- declare const loginWithCredentials: (params: paramsType) => Promise<{
303
- status: false;
304
- reason: string;
305
- } | {
306
- status: boolean;
307
- reason: string;
308
- session?: undefined;
309
- } | {
310
- status: boolean;
311
- reason: string;
312
- session: Omit<UserRecord, "password">;
313
- } | {
314
- status: boolean;
315
- reason: Error;
316
- newToken?: undefined;
317
- session?: undefined;
318
- } | {
319
- status: boolean;
320
- reason: string;
321
- newToken: string;
322
- session: BaseSessionLayout;
323
- }>;
358
+ }, options?: {
359
+ supersedeToken?: string;
360
+ requesterIp?: string;
361
+ }) => Promise<CredentialsLoginResult>;
362
+ declare const loginWithCredentials: (params: LoginOrRegisterParams, options?: {
363
+ supersedeToken?: string;
364
+ requesterIp?: string;
365
+ }) => Promise<CredentialsLoginResult>;
324
366
  interface OAuthCallbackResult {
325
367
  token: string;
326
368
  redirectUrl: string;
@@ -330,6 +372,7 @@ interface OAuthCallbackResult {
330
372
  }
331
373
  declare const loginCallback: (pathname: string, req: IncomingMessage, _res: ServerResponse, options?: {
332
374
  defaultRedirectUrl?: string;
375
+ supersedeToken?: string;
333
376
  }) => Promise<OAuthCallbackResult | false>;
334
377
 
335
378
  declare const logout: ({ token, socket, userId, skipSessionDelete }: {
@@ -340,15 +383,38 @@ declare const logout: ({ token, socket, userId, skipSessionDelete }: {
340
383
  }) => Promise<void>;
341
384
 
342
385
  /**
343
- * Create a one-time password-reset token bound to a user id. Stored in Redis
344
- * with the configured TTL. Returns the token string — caller emails it to
345
- * the user (typically embedded in a URL).
386
+ * Whether `accountKey` is currently locked out (failed-attempt counter already
387
+ * at/over the cap). Non-incrementing — safe to call on every login attempt.
388
+ * Always `false` when the feature is disabled.
389
+ * Pass `requesterIp` to check the IP+account composite bucket (DD-LOGIN-F5).
390
+ */
391
+ declare const isAccountLocked: (accountKey: string, requesterIp?: string) => Promise<boolean>;
392
+ /**
393
+ * Increment the failed-attempt counter for `accountKey`. Call on every failed
394
+ * credentials login for a known/looked-up account. No-op when disabled.
395
+ * Pass `requesterIp` to increment the IP+account composite bucket (DD-LOGIN-F5).
396
+ */
397
+ declare const recordAuthFailure: (accountKey: string, requesterIp?: string) => Promise<void>;
398
+ /**
399
+ * Reset the failed-attempt counter for `accountKey` (call after a SUCCESSFUL
400
+ * login so a user who eventually gets their password right isn't penalised by
401
+ * earlier typos). No-op when disabled.
402
+ * Pass `requesterIp` to clear the IP+account composite bucket (DD-LOGIN-F5).
403
+ */
404
+ declare const clearAuthFailures: (accountKey: string, requesterIp?: string) => Promise<void>;
405
+
406
+ /**
407
+ * Create a one-time password-reset token bound to a user id. Any previously
408
+ * issued (but not yet consumed) token for the same user is invalidated first
409
+ * (LOGIN-F16). Stored in Redis with the configured TTL — only `sha256(token)`
410
+ * is persisted (hash-at-rest). Returns the raw token string — caller emails it
411
+ * to the user (typically embedded in a URL).
346
412
  */
347
413
  declare const createPasswordResetToken: (userId: string) => Promise<string>;
348
414
  /**
349
415
  * Validate and consume a password-reset token. Returns the bound userId on
350
- * success and removes the token (one-time use). Returns null when the token
351
- * is missing, expired, or malformed.
416
+ * success and removes the token (one-time use, atomic GET+DEL). Returns null
417
+ * when the token is missing, expired, malformed, or already consumed.
352
418
  */
353
419
  declare const consumePasswordResetToken: (token: string) => Promise<string | null>;
354
420
  /**
@@ -358,6 +424,20 @@ declare const consumePasswordResetToken: (token: string) => Promise<string | nul
358
424
  * (`projectConfig.auth.passwordPolicy`) before hashing — throws a `PasswordPolicyError`
359
425
  * when the plaintext fails policy. Catch and surface the error code to the
360
426
  * client; framework-mode reset/change flows already do this.
427
+ *
428
+ * ADR — DD-LOGIN-F17: this primitive intentionally does NOT revoke sessions.
429
+ * Session revocation is the responsibility of each calling route because the
430
+ * right behaviour differs per use-case:
431
+ * - `reset-password/confirmReset_v1` revokes ALL sessions (`revokeUserSessions(userId, null)`)
432
+ * because a forgot-password reset means the old credential is compromised.
433
+ * - `settings/changePassword_v1` revokes every OTHER session but keeps the
434
+ * current one active (`revokeUserSessions(userId, user.token)`) so the
435
+ * user is not kicked from the device they changed the password on.
436
+ * - Custom consumers may want a third behaviour (e.g. re-use the existing
437
+ * token after an admin password-force) — keeping the primitive pure lets
438
+ * them compose it freely.
439
+ * If you call this function directly, YOU must call `revokeUserSessions`
440
+ * afterwards unless you have a deliberate reason not to.
361
441
  */
362
442
  declare const updatePasswordHash: (userId: string, plaintext: string) => Promise<void>;
363
443
  /**
@@ -408,15 +488,19 @@ interface EmailChangePayload {
408
488
  }
409
489
  /**
410
490
  * Create a one-time email-change token bound to a user id + the new email
411
- * address. Stored in Redis with the configured TTL. Returns the token string
412
- * — caller emails the token (embedded in a URL) to the NEW address so the
491
+ * address. Any previously issued (but not yet consumed) token for the same
492
+ * user is invalidated first (LOGIN-F16) so at most one confirmation link per
493
+ * user is redeemable. Stored in Redis with the configured TTL — only
494
+ * `sha256(token)` is persisted (hash-at-rest). Returns the raw token string —
495
+ * caller emails the token (embedded in a URL) to the NEW address so the
413
496
  * confirmation click proves ownership of the new mailbox.
414
497
  */
415
498
  declare const createEmailChangeToken: (userId: string, newEmail: string) => Promise<string>;
416
499
  /**
417
500
  * Validate and consume an email-change token. Returns the bound
418
- * `{ userId, newEmail }` on success and removes the token (one-time use).
419
- * Returns null when the token is missing, expired, or malformed.
501
+ * `{ userId, newEmail }` on success and removes the token (one-time use, atomic
502
+ * GET+DEL). Returns null when the token is missing, expired, malformed, or
503
+ * already consumed.
420
504
  */
421
505
  declare const consumeEmailChangeToken: (token: string) => Promise<EmailChangePayload | null>;
422
506
 
@@ -445,6 +529,7 @@ declare const sendEmailChangeConfirmation: ({ userId, newEmail, userName, brand
445
529
  }>;
446
530
 
447
531
  type OAuthUserData = Record<string, unknown>;
532
+ declare const asOAuthUserData: (value: unknown) => OAuthUserData;
448
533
  interface CredentialsProvider {
449
534
  name: 'credentials';
450
535
  }
@@ -471,6 +556,15 @@ interface FullOAuthProvider {
471
556
  scope: string[];
472
557
  nameKey: string;
473
558
  emailKey: string;
559
+ /**
560
+ * Optional key on the userInfo response holding a provider "email verified"
561
+ * boolean. When set AND the field is present-and-falsy, the framework rejects
562
+ * the profile rather than trusting an unverified address — defense-in-depth
563
+ * against account-linking takeover under the `'unified'` strategy (SEC-21).
564
+ * A missing field (provider doesn't report it) is treated as "trusted",
565
+ * preserving behaviour for providers that don't expose the flag.
566
+ */
567
+ emailVerifiedKey?: string;
474
568
  avatarKey?: string;
475
569
  avatarCodeKey: string;
476
570
  getEmail?: (accessToken: string) => Promise<string | false | undefined>;
@@ -501,6 +595,25 @@ interface FullOAuthProvider {
501
595
  userData: OAuthUserData;
502
596
  accessToken: string;
503
597
  }) => Promise<Record<string, unknown>> | Record<string, unknown>;
598
+ /**
599
+ * Extra query params appended to the authorization-redirect URL (CFG-21).
600
+ * Lets a provider request e.g. `access_type=offline`, `login_hint=...`, or
601
+ * override the framework's default `prompt=select_account`. The server's
602
+ * authorize route merges these over the built-in params (a key present here
603
+ * wins). Reserved OAuth params (`client_id`, `redirect_uri`, `scope`,
604
+ * `response_type`, `state`, `code_challenge`, `code_challenge_method`) should
605
+ * NOT be set here — they are framework-owned. Default unset.
606
+ */
607
+ extraAuthorizationParams?: Record<string, string>;
608
+ /**
609
+ * Opt this provider into PKCE (RFC 7636, S256) (F11). When `true`, the
610
+ * framework generates a `code_verifier` at flow start, stores it server-side
611
+ * with the OAuth state, sends `code_challenge`/`code_challenge_method=S256` on
612
+ * the authorize redirect, and replays the verifier at token exchange. Required
613
+ * by OAuth 2.1 / PKCE-mandating providers (X/Twitter, some Okta/Auth0
614
+ * policies). Default `false` (no PKCE — existing flows are byte-identical).
615
+ */
616
+ usePkce?: boolean;
504
617
  }
505
618
  type OAuthProvider = CredentialsProvider | FullOAuthProvider;
506
619
  declare const isFullOAuthProvider: (provider: OAuthProvider) => provider is FullOAuthProvider;
@@ -530,6 +643,13 @@ interface OAuthHelperInput {
530
643
  * pattern.
531
644
  */
532
645
  extraSessionFields?: FullOAuthProvider['extraSessionFields'];
646
+ /**
647
+ * Extra query params for the authorize redirect (CFG-21). See
648
+ * `FullOAuthProvider.extraAuthorizationParams`.
649
+ */
650
+ extraAuthorizationParams?: Record<string, string>;
651
+ /** Opt this provider into PKCE (S256) (F11). See `FullOAuthProvider.usePkce`. */
652
+ usePkce?: boolean;
533
653
  }
534
654
  declare const credentialsProvider: () => CredentialsProvider;
535
655
  declare const googleProvider: (input: OAuthHelperInput) => FullOAuthProvider;
@@ -547,10 +667,81 @@ interface MicrosoftProviderInput extends OAuthHelperInput {
547
667
  apiVersion?: string;
548
668
  /** Graph API version segment for /me. Default `'v1.0'`. */
549
669
  graphApiVersion?: string;
670
+ /**
671
+ * Allow falling back to `userPrincipalName` as the account email when Graph
672
+ * `/me.mail` is empty (SEC-11). A UPN is NOT guaranteed routable or verified;
673
+ * under the `'unified'` account strategy a UPN equal to a victim's email would
674
+ * link accounts, and it commonly mis-keys an account on a non-deliverable
675
+ * address (reset emails never arrive). Default `false` — only enable when the
676
+ * tenant guarantees UPN == primary SMTP. When `false` and `.mail` is empty the
677
+ * provider reports "no email" and the login is rejected rather than linked.
678
+ */
679
+ allowUpnFallback?: boolean;
550
680
  }
551
681
  declare const microsoftProvider: (input: MicrosoftProviderInput) => FullOAuthProvider;
552
682
  declare const getOAuthProviders: () => OAuthProvider[];
553
683
  declare const registerOAuthProviders: (providers: OAuthProvider[]) => OAuthProvider[];
684
+ /**
685
+ * Register a factory function that builds the provider list on first use.
686
+ * Prefer this over `registerOAuthProviders` from the package `register` side-
687
+ * effect so config/env reads are deferred to request time and survive any
688
+ * `registerProjectConfig` call that happens after package import.
689
+ * A subsequent `registerOAuthProviders` call replaces the factory.
690
+ */
691
+ declare const registerOAuthProviderFactory: (factory: () => OAuthProvider[]) => void;
692
+
693
+ interface UserAdapterCreateInput {
694
+ email: string;
695
+ provider: string;
696
+ name?: string | null;
697
+ password?: string | null;
698
+ avatar?: string | null;
699
+ avatarFallback?: string | null;
700
+ language?: string | null;
701
+ }
702
+ interface UserRecord extends BaseSessionLayout {
703
+ password?: string | null;
704
+ provider?: string | null;
705
+ /** Most recent successful login. Mirrors the optional `lastLogin` column on User. */
706
+ lastLogin?: Date | null;
707
+ }
708
+ interface UserAdapter {
709
+ findByEmail: (params: {
710
+ email: string;
711
+ provider: string;
712
+ }) => Promise<UserRecord | null>;
713
+ /**
714
+ * Look up a user by email IRRESPECTIVE of provider. Required only when
715
+ * `auth.providerAccountStrategy === 'unified'` — the framework resolves
716
+ * accounts by email alone so the same address maps to one User across
717
+ * providers. Optional so existing custom adapters keep compiling; when a
718
+ * project sets `'unified'` but its adapter omits this method, the framework
719
+ * logs a one-time warning and falls back to provider-scoped lookup.
720
+ */
721
+ findByEmailAnyProvider?: (params: {
722
+ email: string;
723
+ }) => Promise<UserRecord | null>;
724
+ findById: (id: string) => Promise<UserRecord | null>;
725
+ create: (input: UserAdapterCreateInput) => Promise<UserRecord>;
726
+ update: (id: string, patch: Partial<UserRecord>) => Promise<UserRecord>;
727
+ /**
728
+ * Hard-delete a user. Account-deletion flows (`deleteAccount_v1`) call this
729
+ * instead of reaching past the adapter to `prisma.user.delete`, so a custom
730
+ * adapter / non-Prisma store / soft-delete strategy stays in control. To
731
+ * implement a soft delete, ignore the hard delete here and instead expose it
732
+ * through `update({ deletedAt: new Date() })` from your route.
733
+ *
734
+ * Optional so existing custom adapters keep compiling; the default Prisma
735
+ * adapter implements it. Consumers calling account deletion against a custom
736
+ * adapter that omits `delete` get a clear "adapter does not support delete"
737
+ * failure rather than a silent Prisma bypass.
738
+ */
739
+ delete?: (id: string) => Promise<void>;
740
+ }
741
+ declare const registerUserAdapter: (adapter: UserAdapter) => UserAdapter;
742
+ declare const isUserAdapterRegistered: () => boolean;
743
+ declare const defaultPrismaUserAdapter: () => UserAdapter;
744
+ declare const getUserAdapter: () => UserAdapter;
554
745
 
555
746
  interface PostLoginRedirectInput {
556
747
  userId: string;
@@ -562,4 +753,4 @@ type PostLoginRedirectResolver = (input: PostLoginRedirectInput) => string | Pro
562
753
  declare const registerPostLoginRedirect: (resolver: PostLoginRedirectResolver) => PostLoginRedirectResolver;
563
754
  declare const getPostLoginRedirect: () => PostLoginRedirectResolver | null;
564
755
 
565
- export { type CredentialsProvider, type EmailChangePayload, type FullOAuthProvider, type OAuthProvider, PasswordPolicyError, type PostEmailChangeRequestedPayload, type PostEmailChangedPayload, type PostLoginPayload, type PostLoginRedirectInput, type PostLoginRedirectResolver, type PostLogoutPayload, type PostRegisterPayload, type PostSessionCreatePayload, type PostSessionDeletePayload, type PreEmailChangePayload, type PreLoginPayload, type PreLogoutPayload, type PreRegisterPayload, type PreSessionCreatePayload, type PreSessionDeletePayload, type SessionAdapter, type UserAdapter, type UserAdapterCreateInput, type UserRecord, activeUsersKeyFor, consumeEmailChangeToken, consumePasswordResetToken, createEmailChangeToken, createOAuthState, createPasswordResetToken, credentialsProvider, defaultPrismaUserAdapter, deleteSession, discordProvider, facebookProvider, getAllSessions, getOAuthProviders, getPostLoginRedirect, getSession, getSessionAdapter, getUserAdapter, githubProvider, googleProvider, isFullOAuthProvider, isUserAdapterRegistered, loginCallback, loginWithCredentials, loginWithCredentialsCore, logout, microsoftProvider, redisSessionAdapter, registerOAuthProviders, registerPostLoginRedirect, registerSessionAdapter, registerUserAdapter, registerWithCredentials, revokeUserSessions, saveSession, sendEmailChangeConfirmation, sendPasswordResetEmail, sessionKeyFor, updatePasswordHash, validatePassword, verifyPassword };
756
+ export { type CreateOAuthStateResult, type CredentialsLoginFailure, type CredentialsLoginResult, type CredentialsLoginSuccess, type CredentialsProvider, type EmailChangePayload, type FullOAuthProvider, type LoginFailedPayload, OAUTH_STATE_COOKIE_NAME, type OAuthCallbackResult, type OAuthProvider, type OAuthUserData, PasswordPolicyError, 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 PreRegisterPayload, type PreSessionCreatePayload, type PreSessionDeletePayload, type SessionAdapter, type SessionSanitizer, type UserAdapter, type UserAdapterCreateInput, type UserRecord, activeUsersKeyFor, asOAuthUserData, clearAuthFailures, consumeEmailChangeToken, consumePasswordResetToken, createEmailChangeToken, createOAuthState, createPasswordResetToken, credentialsProvider, defaultPrismaUserAdapter, deleteSession, discordProvider, facebookProvider, getAllSessions, getOAuthProviders, getPostLoginRedirect, getSession, getSessionAdapter, getSessionSanitizer, getUserAdapter, githubProvider, googleProvider, isAccountLocked, isFullOAuthProvider, isUserAdapterRegistered, loginCallback, loginWithCredentials, loginWithCredentialsCore, logout, microsoftProvider, recordAuthFailure, redisSessionAdapter, registerOAuthProviderFactory, registerOAuthProviders, registerPostLoginRedirect, registerSessionAdapter, registerSessionSanitizer, registerUserAdapter, registerWithCredentials, revokeUserSessions, saveSession, sendEmailChangeConfirmation, sendPasswordResetEmail, sessionKeyFor, updatePasswordHash, validatePassword, verifyPassword };