@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/AGENTS.md +7 -0
- package/CHANGELOG.md +6 -1
- package/README.md +7 -235
- package/dist/accountStrategy.d.ts +12 -0
- package/dist/authLockout.d.ts +29 -0
- package/dist/data/commonPasswords.d.ts +2 -0
- package/dist/emailChange.d.ts +21 -0
- package/dist/emailChangeNotification.d.ts +24 -0
- package/dist/emailCodeLogin.d.ts +19 -0
- package/dist/emailModuleLoader.d.ts +13 -0
- package/dist/emailOtp.d.ts +33 -0
- package/dist/forgotPassword.d.ts +21 -0
- package/dist/hookPayloads.d.ts +151 -0
- package/dist/index.d.ts +33 -951
- package/dist/login.d.ts +96 -0
- package/dist/logout.d.ts +7 -0
- package/dist/oauthProviders.d.ts +171 -0
- package/dist/passwordPolicy.d.ts +8 -0
- package/dist/passwordReset.d.ts +48 -0
- package/dist/redirectResolver.d.ts +19 -0
- package/dist/register.d.ts +1 -2
- package/dist/session.d.ts +73 -0
- package/dist/sessionAdapter.d.ts +66 -0
- package/dist/sessionLayout.d.ts +1 -0
- package/dist/sessionSanitizer.d.ts +19 -0
- package/dist/totp.d.ts +38 -0
- package/dist/twoFactor.d.ts +60 -0
- package/dist/userAdapter.d.ts +68 -0
- package/docs/credentials-auth.md +3 -217
- package/docs/hooks.md +3 -442
- package/docs/logout-flow.md +3 -233
- package/docs/oauth-providers.md +2 -437
- package/docs/password-reset.md +2 -332
- package/docs/redirect-validation.md +4 -389
- package/docs/session-management.md +3 -282
- package/docs/user-adapter.md +2 -350
- package/package.json +4 -4
- package/CLAUDE.md +0 -197
package/dist/index.d.ts
CHANGED
|
@@ -1,951 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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';
|