@luckystack/login 0.1.9 → 0.2.1
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/CHANGELOG.md +14 -14
- package/CLAUDE.md +194 -194
- package/LICENSE +21 -21
- package/README.md +239 -212
- package/dist/chunk-22NFCMM7.js +299 -0
- package/dist/chunk-22NFCMM7.js.map +1 -0
- package/dist/chunk-NXXBZT3I.js +401 -0
- package/dist/chunk-NXXBZT3I.js.map +1 -0
- package/dist/index.d.ts +256 -89
- package/dist/index.js +518 -793
- package/dist/index.js.map +1 -1
- package/dist/logout-QQRCD2AX.js +7 -0
- package/dist/logout-QQRCD2AX.js.map +1 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.js +69 -0
- package/dist/register.js.map +1 -0
- package/docs/oauth-providers.md +431 -431
- package/package.json +9 -4
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
|
|
|
@@ -155,9 +180,15 @@ declare const saveSession: (token: string, data: BaseSessionLayout, newUser?: bo
|
|
|
155
180
|
* Retrieve a user session through the active session adapter.
|
|
156
181
|
*
|
|
157
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).
|
|
158
187
|
* @returns The session data or null if not found
|
|
159
188
|
*/
|
|
160
|
-
declare const getSession: (token: string | null
|
|
189
|
+
declare const getSession: (token: string | null, options?: {
|
|
190
|
+
refresh?: boolean;
|
|
191
|
+
}) => Promise<BaseSessionLayout | null>;
|
|
161
192
|
/**
|
|
162
193
|
* Delete a user session through the active adapter and notify connected
|
|
163
194
|
* clients.
|
|
@@ -221,10 +252,25 @@ interface SessionAdapter {
|
|
|
221
252
|
trackActive(userId: string, token: string, ttlSeconds: number): Promise<void>;
|
|
222
253
|
untrackActive(userId: string, token: string): Promise<void>;
|
|
223
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>;
|
|
224
265
|
/**
|
|
225
266
|
* Admin walk: yield every active session record. Optional because
|
|
226
267
|
* non-scannable backends (signed-JWT-stateless, log-only) cannot
|
|
227
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.
|
|
228
274
|
*/
|
|
229
275
|
listAll?(): Promise<{
|
|
230
276
|
token: string;
|
|
@@ -249,101 +295,74 @@ declare const registerSessionAdapter: (adapter: SessionAdapter) => void;
|
|
|
249
295
|
/** Read the currently-active session adapter (defaults to Redis). */
|
|
250
296
|
declare const getSessionAdapter: () => SessionAdapter;
|
|
251
297
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
password?: string | null;
|
|
263
|
-
provider?: string | null;
|
|
264
|
-
/** Most recent successful login. Mirrors the optional `lastLogin` column on User. */
|
|
265
|
-
lastLogin?: Date | null;
|
|
266
|
-
}
|
|
267
|
-
interface UserAdapter {
|
|
268
|
-
findByEmail: (params: {
|
|
269
|
-
email: string;
|
|
270
|
-
provider: string;
|
|
271
|
-
}) => Promise<UserRecord | null>;
|
|
272
|
-
findById: (id: string) => Promise<UserRecord | null>;
|
|
273
|
-
create: (input: UserAdapterCreateInput) => Promise<UserRecord>;
|
|
274
|
-
update: (id: string, patch: Partial<UserRecord>) => Promise<UserRecord>;
|
|
275
|
-
}
|
|
276
|
-
declare const registerUserAdapter: (adapter: UserAdapter) => UserAdapter;
|
|
277
|
-
declare const isUserAdapterRegistered: () => boolean;
|
|
278
|
-
declare const defaultPrismaUserAdapter: () => UserAdapter;
|
|
279
|
-
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;
|
|
280
308
|
|
|
281
|
-
interface
|
|
309
|
+
interface LoginOrRegisterParams {
|
|
282
310
|
email?: string;
|
|
283
311
|
password?: string;
|
|
284
312
|
name?: string;
|
|
285
313
|
confirmPassword?: string;
|
|
286
314
|
}
|
|
287
|
-
|
|
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>;
|
|
288
347
|
declare const registerWithCredentials: ({ email, password, name, confirmPassword, }: {
|
|
289
348
|
email: string;
|
|
290
349
|
password: string;
|
|
291
350
|
name: string;
|
|
292
351
|
confirmPassword: string;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
session?: undefined;
|
|
297
|
-
} | {
|
|
298
|
-
status: boolean;
|
|
299
|
-
reason: string;
|
|
300
|
-
session: Omit<UserRecord, "password">;
|
|
301
|
-
}>;
|
|
352
|
+
}, options?: {
|
|
353
|
+
supersedeToken?: string;
|
|
354
|
+
}) => Promise<CredentialsLoginResult>;
|
|
302
355
|
declare const loginWithCredentialsCore: ({ email, password }: {
|
|
303
356
|
email: string;
|
|
304
357
|
password: string;
|
|
305
358
|
}, options?: {
|
|
306
359
|
supersedeToken?: string;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
newToken?: undefined;
|
|
311
|
-
session?: undefined;
|
|
312
|
-
} | {
|
|
313
|
-
status: boolean;
|
|
314
|
-
reason: Error;
|
|
315
|
-
newToken?: undefined;
|
|
316
|
-
session?: undefined;
|
|
317
|
-
} | {
|
|
318
|
-
status: boolean;
|
|
319
|
-
reason: string;
|
|
320
|
-
newToken: string;
|
|
321
|
-
session: BaseSessionLayout;
|
|
322
|
-
}>;
|
|
323
|
-
declare const loginWithCredentials: (params: paramsType, options?: {
|
|
360
|
+
requesterIp?: string;
|
|
361
|
+
}) => Promise<CredentialsLoginResult>;
|
|
362
|
+
declare const loginWithCredentials: (params: LoginOrRegisterParams, options?: {
|
|
324
363
|
supersedeToken?: string;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
reason: string;
|
|
328
|
-
} | {
|
|
329
|
-
status: boolean;
|
|
330
|
-
reason: string;
|
|
331
|
-
session?: undefined;
|
|
332
|
-
} | {
|
|
333
|
-
status: boolean;
|
|
334
|
-
reason: string;
|
|
335
|
-
session: Omit<UserRecord, "password">;
|
|
336
|
-
} | {
|
|
337
|
-
status: boolean;
|
|
338
|
-
reason: Error;
|
|
339
|
-
newToken?: undefined;
|
|
340
|
-
session?: undefined;
|
|
341
|
-
} | {
|
|
342
|
-
status: boolean;
|
|
343
|
-
reason: string;
|
|
344
|
-
newToken: string;
|
|
345
|
-
session: BaseSessionLayout;
|
|
346
|
-
}>;
|
|
364
|
+
requesterIp?: string;
|
|
365
|
+
}) => Promise<CredentialsLoginResult>;
|
|
347
366
|
interface OAuthCallbackResult {
|
|
348
367
|
token: string;
|
|
349
368
|
redirectUrl: string;
|
|
@@ -364,15 +383,38 @@ declare const logout: ({ token, socket, userId, skipSessionDelete }: {
|
|
|
364
383
|
}) => Promise<void>;
|
|
365
384
|
|
|
366
385
|
/**
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
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).
|
|
370
412
|
*/
|
|
371
413
|
declare const createPasswordResetToken: (userId: string) => Promise<string>;
|
|
372
414
|
/**
|
|
373
415
|
* Validate and consume a password-reset token. Returns the bound userId on
|
|
374
|
-
* success and removes the token (one-time use). Returns null
|
|
375
|
-
* is missing, expired, or
|
|
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.
|
|
376
418
|
*/
|
|
377
419
|
declare const consumePasswordResetToken: (token: string) => Promise<string | null>;
|
|
378
420
|
/**
|
|
@@ -382,6 +424,20 @@ declare const consumePasswordResetToken: (token: string) => Promise<string | nul
|
|
|
382
424
|
* (`projectConfig.auth.passwordPolicy`) before hashing — throws a `PasswordPolicyError`
|
|
383
425
|
* when the plaintext fails policy. Catch and surface the error code to the
|
|
384
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.
|
|
385
441
|
*/
|
|
386
442
|
declare const updatePasswordHash: (userId: string, plaintext: string) => Promise<void>;
|
|
387
443
|
/**
|
|
@@ -432,15 +488,19 @@ interface EmailChangePayload {
|
|
|
432
488
|
}
|
|
433
489
|
/**
|
|
434
490
|
* Create a one-time email-change token bound to a user id + the new email
|
|
435
|
-
* address.
|
|
436
|
-
*
|
|
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
|
|
437
496
|
* confirmation click proves ownership of the new mailbox.
|
|
438
497
|
*/
|
|
439
498
|
declare const createEmailChangeToken: (userId: string, newEmail: string) => Promise<string>;
|
|
440
499
|
/**
|
|
441
500
|
* Validate and consume an email-change token. Returns the bound
|
|
442
|
-
* `{ userId, newEmail }` on success and removes the token (one-time use
|
|
443
|
-
* Returns null when the token is missing, expired, or
|
|
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.
|
|
444
504
|
*/
|
|
445
505
|
declare const consumeEmailChangeToken: (token: string) => Promise<EmailChangePayload | null>;
|
|
446
506
|
|
|
@@ -469,6 +529,7 @@ declare const sendEmailChangeConfirmation: ({ userId, newEmail, userName, brand
|
|
|
469
529
|
}>;
|
|
470
530
|
|
|
471
531
|
type OAuthUserData = Record<string, unknown>;
|
|
532
|
+
declare const asOAuthUserData: (value: unknown) => OAuthUserData;
|
|
472
533
|
interface CredentialsProvider {
|
|
473
534
|
name: 'credentials';
|
|
474
535
|
}
|
|
@@ -495,6 +556,15 @@ interface FullOAuthProvider {
|
|
|
495
556
|
scope: string[];
|
|
496
557
|
nameKey: string;
|
|
497
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;
|
|
498
568
|
avatarKey?: string;
|
|
499
569
|
avatarCodeKey: string;
|
|
500
570
|
getEmail?: (accessToken: string) => Promise<string | false | undefined>;
|
|
@@ -525,6 +595,25 @@ interface FullOAuthProvider {
|
|
|
525
595
|
userData: OAuthUserData;
|
|
526
596
|
accessToken: string;
|
|
527
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;
|
|
528
617
|
}
|
|
529
618
|
type OAuthProvider = CredentialsProvider | FullOAuthProvider;
|
|
530
619
|
declare const isFullOAuthProvider: (provider: OAuthProvider) => provider is FullOAuthProvider;
|
|
@@ -554,6 +643,13 @@ interface OAuthHelperInput {
|
|
|
554
643
|
* pattern.
|
|
555
644
|
*/
|
|
556
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;
|
|
557
653
|
}
|
|
558
654
|
declare const credentialsProvider: () => CredentialsProvider;
|
|
559
655
|
declare const googleProvider: (input: OAuthHelperInput) => FullOAuthProvider;
|
|
@@ -571,10 +667,81 @@ interface MicrosoftProviderInput extends OAuthHelperInput {
|
|
|
571
667
|
apiVersion?: string;
|
|
572
668
|
/** Graph API version segment for /me. Default `'v1.0'`. */
|
|
573
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;
|
|
574
680
|
}
|
|
575
681
|
declare const microsoftProvider: (input: MicrosoftProviderInput) => FullOAuthProvider;
|
|
576
682
|
declare const getOAuthProviders: () => OAuthProvider[];
|
|
577
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;
|
|
578
745
|
|
|
579
746
|
interface PostLoginRedirectInput {
|
|
580
747
|
userId: string;
|
|
@@ -586,4 +753,4 @@ type PostLoginRedirectResolver = (input: PostLoginRedirectInput) => string | Pro
|
|
|
586
753
|
declare const registerPostLoginRedirect: (resolver: PostLoginRedirectResolver) => PostLoginRedirectResolver;
|
|
587
754
|
declare const getPostLoginRedirect: () => PostLoginRedirectResolver | null;
|
|
588
755
|
|
|
589
|
-
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 };
|