@vunexa/lixa 0.1.6-alpha.12 → 0.1.6-alpha.13

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.cjs CHANGED
@@ -32,35 +32,625 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AccountLinkingStrategy: () => AccountLinkingStrategy,
34
34
  AccountUnlinkError: () => AccountUnlinkError,
35
+ CredentialsManager: () => CredentialsManager,
36
+ CredentialsNotConfiguredError: () => CredentialsNotConfiguredError,
35
37
  DEFAULT_SESSION_COOKIE_NAME: () => DEFAULT_SESSION_COOKIE_NAME,
36
38
  DEFAULT_SESSION_MAX_AGE_SECONDS: () => DEFAULT_SESSION_MAX_AGE_SECONDS,
37
39
  DEFAULT_STATE_COOKIE_NAME: () => DEFAULT_STATE_COOKIE_NAME,
38
40
  DEFAULT_STATE_MAX_AGE_SECONDS: () => DEFAULT_STATE_MAX_AGE_SECONDS,
39
41
  EmailNotVerifiedError: () => EmailNotVerifiedError,
42
+ InvalidCredentialsError: () => InvalidCredentialsError,
40
43
  InvalidOAuthCallbackError: () => InvalidOAuthCallbackError,
41
44
  InvalidProviderConfigError: () => InvalidProviderConfigError,
42
45
  InvalidStateError: () => InvalidStateError,
43
46
  Lixa: () => Lixa,
44
47
  LixaError: () => LixaError,
48
+ LocalCredentialsStorage: () => LocalCredentialsStorage,
49
+ Pbkdf2PasswordHasher: () => Pbkdf2PasswordHasher,
45
50
  ProviderNotConfiguredError: () => ProviderNotConfiguredError,
46
51
  RefreshTokenError: () => RefreshTokenError,
52
+ ScryptPasswordHasher: () => ScryptPasswordHasher,
47
53
  SessionNotFoundError: () => SessionNotFoundError,
48
54
  TokenExchangeError: () => TokenExchangeError,
55
+ UserAlreadyExistsError: () => UserAlreadyExistsError,
56
+ UserNotFoundError: () => UserNotFoundError,
57
+ WeakPasswordError: () => WeakPasswordError,
49
58
  clearSessionCookie: () => clearSessionCookie,
50
59
  clearStateCookie: () => clearStateCookie,
51
60
  createSessionCookie: () => createSessionCookie,
52
61
  createStateCookie: () => createStateCookie,
62
+ credentials: () => credentials_exports,
53
63
  decodeIdToken: () => decodeIdToken,
54
64
  determineProviderFromIssuer: () => determineProviderFromIssuer,
55
65
  extractUserInfo: () => extractUserInfo,
56
66
  fetchUserInfo: () => fetchUserInfo,
57
67
  isProductionEnvironment: () => isProductionEnvironment,
58
- serializeCookie: () => serializeCookie
68
+ serializeCookie: () => serializeCookie,
69
+ validatePasswordPolicy: () => validatePasswordPolicy
59
70
  });
60
71
  module.exports = __toCommonJS(src_exports);
61
72
 
62
73
  // src/lixa.ts
63
- var import_crypto2 = require("crypto");
74
+ var import_crypto4 = require("crypto");
75
+
76
+ // src/credentials/hasher.ts
77
+ var import_crypto = __toESM(require("crypto"), 1);
78
+ var ScryptPasswordHasher = class {
79
+ cost;
80
+ blockSize;
81
+ parallelization;
82
+ saltLength;
83
+ keyLength;
84
+ maxmem;
85
+ constructor(options = {}) {
86
+ this.cost = options.cost ?? 16384;
87
+ this.blockSize = options.blockSize ?? 8;
88
+ this.parallelization = options.parallelization ?? 1;
89
+ this.saltLength = options.saltLength ?? 16;
90
+ this.keyLength = options.keyLength ?? 64;
91
+ this.maxmem = options.maxmem ?? 32 * 1024 * 1024;
92
+ }
93
+ /**
94
+ * Hashes a plaintext password using Scrypt.
95
+ *
96
+ * @param password - Plaintext password
97
+ * @returns Formatted Scrypt hash string
98
+ */
99
+ async hash(password) {
100
+ const salt = import_crypto.default.randomBytes(this.saltLength).toString("hex");
101
+ const derivedKey = await this.deriveKey(
102
+ password,
103
+ salt,
104
+ this.keyLength,
105
+ this.cost,
106
+ this.blockSize,
107
+ this.parallelization
108
+ );
109
+ return `$scrypt$N=${this.cost},r=${this.blockSize},p=${this.parallelization}$${salt}$${derivedKey.toString("hex")}`;
110
+ }
111
+ /**
112
+ * Verifies a password against a stored Scrypt hash.
113
+ *
114
+ * @param password - Plaintext password
115
+ * @param hash - Formatted Scrypt hash string
116
+ * @returns True if password matches hash
117
+ */
118
+ async verify(password, hash) {
119
+ if (!hash || typeof hash !== "string" || !hash.startsWith("$scrypt$")) {
120
+ return false;
121
+ }
122
+ const parts = hash.split("$");
123
+ if (parts.length !== 5) {
124
+ return false;
125
+ }
126
+ const paramsStr = parts[2];
127
+ const salt = parts[3];
128
+ const storedKeyHex = parts[4];
129
+ if (!paramsStr || !salt || !storedKeyHex) {
130
+ return false;
131
+ }
132
+ const params = /* @__PURE__ */ new Map();
133
+ for (const param of paramsStr.split(",")) {
134
+ const [k, v] = param.split("=");
135
+ if (k && v) {
136
+ params.set(k.trim(), parseInt(v.trim(), 10));
137
+ }
138
+ }
139
+ const cost = params.get("N") ?? this.cost;
140
+ const blockSize = params.get("r") ?? this.blockSize;
141
+ const parallelization = params.get("p") ?? this.parallelization;
142
+ const storedKeyBuffer = Buffer.from(storedKeyHex, "hex");
143
+ const derivedKeyBuffer = await this.deriveKey(
144
+ password,
145
+ salt,
146
+ storedKeyBuffer.length,
147
+ cost,
148
+ blockSize,
149
+ parallelization
150
+ );
151
+ if (storedKeyBuffer.length !== derivedKeyBuffer.length) {
152
+ return false;
153
+ }
154
+ return import_crypto.default.timingSafeEqual(storedKeyBuffer, derivedKeyBuffer);
155
+ }
156
+ deriveKey(password, salt, keyLength, cost, blockSize, parallelization) {
157
+ return new Promise((resolve, reject) => {
158
+ import_crypto.default.scrypt(
159
+ password,
160
+ salt,
161
+ keyLength,
162
+ {
163
+ N: cost,
164
+ r: blockSize,
165
+ p: parallelization,
166
+ maxmem: this.maxmem
167
+ },
168
+ (err, derivedKey) => {
169
+ if (err) {
170
+ reject(err);
171
+ } else {
172
+ resolve(derivedKey);
173
+ }
174
+ }
175
+ );
176
+ });
177
+ }
178
+ };
179
+ var Pbkdf2PasswordHasher = class {
180
+ iterations;
181
+ digest;
182
+ saltLength;
183
+ keyLength;
184
+ constructor(options = {}) {
185
+ this.iterations = options.iterations ?? 1e5;
186
+ this.digest = options.digest ?? "sha512";
187
+ this.saltLength = options.saltLength ?? 16;
188
+ this.keyLength = options.keyLength ?? 64;
189
+ }
190
+ async hash(password) {
191
+ const salt = import_crypto.default.randomBytes(this.saltLength).toString("hex");
192
+ const derivedKey = await this.deriveKey(
193
+ password,
194
+ salt,
195
+ this.iterations,
196
+ this.keyLength,
197
+ this.digest
198
+ );
199
+ return `$pbkdf2$i=${this.iterations},d=${this.digest}$${salt}$${derivedKey.toString("hex")}`;
200
+ }
201
+ async verify(password, hash) {
202
+ if (!hash || typeof hash !== "string" || !hash.startsWith("$pbkdf2$")) {
203
+ return false;
204
+ }
205
+ const parts = hash.split("$");
206
+ if (parts.length !== 5) {
207
+ return false;
208
+ }
209
+ const paramsStr = parts[2];
210
+ const salt = parts[3];
211
+ const storedKeyHex = parts[4];
212
+ if (!paramsStr || !salt || !storedKeyHex) {
213
+ return false;
214
+ }
215
+ let iterations = this.iterations;
216
+ let digest = this.digest;
217
+ for (const param of paramsStr.split(",")) {
218
+ const [k, v] = param.split("=");
219
+ if (k === "i" && v) {
220
+ iterations = parseInt(v, 10);
221
+ } else if (k === "d" && v) {
222
+ digest = v;
223
+ }
224
+ }
225
+ const storedKeyBuffer = Buffer.from(storedKeyHex, "hex");
226
+ const derivedKeyBuffer = await this.deriveKey(
227
+ password,
228
+ salt,
229
+ iterations,
230
+ storedKeyBuffer.length,
231
+ digest
232
+ );
233
+ if (storedKeyBuffer.length !== derivedKeyBuffer.length) {
234
+ return false;
235
+ }
236
+ return import_crypto.default.timingSafeEqual(storedKeyBuffer, derivedKeyBuffer);
237
+ }
238
+ deriveKey(password, salt, iterations, keyLength, digest) {
239
+ return new Promise((resolve, reject) => {
240
+ import_crypto.default.pbkdf2(password, salt, iterations, keyLength, digest, (err, derivedKey) => {
241
+ if (err) {
242
+ reject(err);
243
+ } else {
244
+ resolve(derivedKey);
245
+ }
246
+ });
247
+ });
248
+ }
249
+ };
250
+
251
+ // src/credentials/policy.ts
252
+ async function validatePasswordPolicy(password, policy = {}) {
253
+ const errors = [];
254
+ if (typeof password !== "string") {
255
+ return {
256
+ valid: false,
257
+ errors: ["Password must be a string"]
258
+ };
259
+ }
260
+ const minLength = policy.minLength ?? 8;
261
+ const maxLength = policy.maxLength ?? 128;
262
+ if (password.length < minLength) {
263
+ errors.push(`Password must be at least ${minLength} characters long`);
264
+ }
265
+ if (password.length > maxLength) {
266
+ errors.push(`Password must not exceed ${maxLength} characters`);
267
+ }
268
+ if (policy.requireUppercase && !/[A-Z]/.test(password)) {
269
+ errors.push("Password must contain at least one uppercase letter (A-Z)");
270
+ }
271
+ if (policy.requireLowercase && !/[a-z]/.test(password)) {
272
+ errors.push("Password must contain at least one lowercase letter (a-z)");
273
+ }
274
+ if (policy.requireNumbers && !/[0-9]/.test(password)) {
275
+ errors.push("Password must contain at least one number (0-9)");
276
+ }
277
+ if (policy.requireSpecialChars && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~`]/.test(password)) {
278
+ errors.push("Password must contain at least one special character");
279
+ }
280
+ if (policy.customValidator) {
281
+ try {
282
+ const customResult = await policy.customValidator(password);
283
+ if (customResult === false) {
284
+ errors.push("Password failed custom validation rule");
285
+ } else if (typeof customResult === "string" && customResult.trim().length > 0) {
286
+ errors.push(customResult);
287
+ }
288
+ } catch (err) {
289
+ errors.push(`Custom password validator failed: ${err?.message || "Unknown error"}`);
290
+ }
291
+ }
292
+ return {
293
+ valid: errors.length === 0,
294
+ errors
295
+ };
296
+ }
297
+
298
+ // src/credentials/local-storage.ts
299
+ var LocalCredentialsStorage = class {
300
+ usersById = /* @__PURE__ */ new Map();
301
+ identifierToId = /* @__PURE__ */ new Map();
302
+ async saveUser(user) {
303
+ const normalizedIdentifier = user.identifier.trim().toLowerCase();
304
+ this.usersById.set(user.id, { ...user, identifier: normalizedIdentifier });
305
+ this.identifierToId.set(normalizedIdentifier, user.id);
306
+ if (user.email) {
307
+ this.identifierToId.set(user.email.trim().toLowerCase(), user.id);
308
+ }
309
+ if (user.username) {
310
+ this.identifierToId.set(user.username.trim().toLowerCase(), user.id);
311
+ }
312
+ }
313
+ async findUserByIdentifier(identifier) {
314
+ const normalized = identifier.trim().toLowerCase();
315
+ const id = this.identifierToId.get(normalized);
316
+ if (!id) {
317
+ return null;
318
+ }
319
+ const user = this.usersById.get(id);
320
+ return user ? { ...user } : null;
321
+ }
322
+ async findUserById(id) {
323
+ const user = this.usersById.get(id);
324
+ return user ? { ...user } : null;
325
+ }
326
+ async updatePassword(id, newPasswordHash) {
327
+ const user = this.usersById.get(id);
328
+ if (user) {
329
+ user.passwordHash = newPasswordHash;
330
+ user.updatedAt = Date.now();
331
+ this.usersById.set(id, user);
332
+ }
333
+ }
334
+ async deleteUser(id) {
335
+ const user = this.usersById.get(id);
336
+ if (user) {
337
+ this.identifierToId.delete(user.identifier.toLowerCase());
338
+ if (user.email) this.identifierToId.delete(user.email.toLowerCase());
339
+ if (user.username) this.identifierToId.delete(user.username.toLowerCase());
340
+ this.usersById.delete(id);
341
+ }
342
+ }
343
+ clear() {
344
+ this.usersById.clear();
345
+ this.identifierToId.clear();
346
+ }
347
+ };
348
+
349
+ // src/credentials/credentials-manager.ts
350
+ var import_crypto2 = __toESM(require("crypto"), 1);
351
+
352
+ // src/errors.ts
353
+ var LixaError = class extends Error {
354
+ /**
355
+ * Standard error code string.
356
+ */
357
+ code;
358
+ /**
359
+ * Additional error context data.
360
+ */
361
+ details;
362
+ constructor(message, code = "LIXA_ERROR", details) {
363
+ super(message);
364
+ this.name = this.constructor.name;
365
+ this.code = code;
366
+ this.details = details;
367
+ Object.setPrototypeOf(this, new.target.prototype);
368
+ }
369
+ };
370
+ var InvalidStateError = class extends LixaError {
371
+ constructor(message = "Invalid or expired state", details) {
372
+ super(message, "INVALID_STATE", details);
373
+ }
374
+ };
375
+ var ProviderNotConfiguredError = class extends LixaError {
376
+ constructor(provider, details) {
377
+ const message = details?.message && typeof details.message === "string" ? details.message : `Provider '${provider}' is not available. Either import it from '@vunexa/lixa-providers' and include it in the configuration, or provide a custom implementation using the 'provider' field: { provider: new CustomProvider(), clientId: '...', ... }`;
378
+ super(message, "PROVIDER_NOT_CONFIGURED", {
379
+ provider,
380
+ ...details
381
+ });
382
+ }
383
+ };
384
+ var InvalidProviderConfigError = class extends LixaError {
385
+ constructor(message, details) {
386
+ super(message, "INVALID_PROVIDER_CONFIG", details);
387
+ }
388
+ };
389
+ var InvalidOAuthCallbackError = class extends LixaError {
390
+ constructor(message, details) {
391
+ super(message, "INVALID_OAUTH_CALLBACK", details);
392
+ }
393
+ };
394
+ var TokenExchangeError = class extends LixaError {
395
+ status;
396
+ constructor(message, status, details) {
397
+ super(message, "TOKEN_EXCHANGE_FAILED", { status, ...details });
398
+ this.status = status;
399
+ }
400
+ };
401
+ var SessionNotFoundError = class extends LixaError {
402
+ constructor(message = "Active session not found or has expired", details) {
403
+ super(message, "SESSION_NOT_FOUND", details);
404
+ }
405
+ };
406
+ var EmailNotVerifiedError = class extends LixaError {
407
+ constructor(email, details) {
408
+ super(
409
+ `Cannot link account: email '${email || "unknown"}' is not verified by the identity provider`,
410
+ "EMAIL_NOT_VERIFIED",
411
+ { email, ...details }
412
+ );
413
+ }
414
+ };
415
+ var AccountUnlinkError = class extends LixaError {
416
+ constructor(message, details) {
417
+ super(message, "ACCOUNT_UNLINK_ERROR", details);
418
+ }
419
+ };
420
+ var RefreshTokenError = class extends LixaError {
421
+ constructor(message, details) {
422
+ super(message, "REFRESH_TOKEN_ERROR", details);
423
+ }
424
+ };
425
+ var InvalidCredentialsError = class extends LixaError {
426
+ constructor(message = "Invalid identifier or password", details) {
427
+ super(message, "INVALID_CREDENTIALS", details);
428
+ }
429
+ };
430
+ var UserAlreadyExistsError = class extends LixaError {
431
+ constructor(identifier, details) {
432
+ super(`User with identifier '${identifier}' already exists`, "USER_ALREADY_EXISTS", {
433
+ identifier,
434
+ ...details
435
+ });
436
+ }
437
+ };
438
+ var UserNotFoundError = class extends LixaError {
439
+ constructor(identifierOrId, details) {
440
+ super(
441
+ `User not found${identifierOrId ? `: '${identifierOrId}'` : ""}`,
442
+ "USER_NOT_FOUND",
443
+ { identifierOrId, ...details }
444
+ );
445
+ }
446
+ };
447
+ var WeakPasswordError = class extends LixaError {
448
+ validationErrors;
449
+ constructor(validationErrors = [], details) {
450
+ const message = validationErrors.length > 0 ? `Password does not meet security requirements: ${validationErrors.join("; ")}` : "Password does not meet security requirements";
451
+ super(message, "WEAK_PASSWORD", { validationErrors, ...details });
452
+ this.validationErrors = validationErrors;
453
+ }
454
+ };
455
+ var CredentialsNotConfiguredError = class extends LixaError {
456
+ constructor(message = "Credentials authentication is not configured in this Lixa instance. Pass credentials: {} in LixaConfig to enable it.", details) {
457
+ super(message, "CREDENTIALS_NOT_CONFIGURED", details);
458
+ }
459
+ };
460
+
461
+ // src/credentials/credentials-manager.ts
462
+ var CredentialsManager = class {
463
+ storage;
464
+ hasher;
465
+ policy;
466
+ identifierType;
467
+ requireUsername;
468
+ requireEmail;
469
+ timingAttackProtection;
470
+ dummyHash = "";
471
+ constructor(config = {}) {
472
+ this.storage = config.storage || new LocalCredentialsStorage();
473
+ this.hasher = config.hasher || new ScryptPasswordHasher();
474
+ this.policy = {
475
+ minLength: 8,
476
+ maxLength: 128,
477
+ ...config.policy
478
+ };
479
+ this.identifierType = config.identifierType || "both";
480
+ this.requireUsername = config.requireUsername ?? false;
481
+ this.requireEmail = config.requireEmail ?? false;
482
+ this.timingAttackProtection = config.timingAttackProtection ?? true;
483
+ this.initDummyHash().catch(() => {
484
+ this.dummyHash = "$scrypt$N=16384,r=8,p=1$0123456789abcdef0123456789abcdef$0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
485
+ });
486
+ }
487
+ async initDummyHash() {
488
+ this.dummyHash = await this.hasher.hash("dummy_constant_password_12345");
489
+ }
490
+ /**
491
+ * Normalizes an identifier string.
492
+ */
493
+ normalizeIdentifier(identifier) {
494
+ return (identifier || "").trim().toLowerCase();
495
+ }
496
+ /**
497
+ * Validates identifier format based on configured identifierType.
498
+ */
499
+ validateIdentifierFormat(identifier) {
500
+ const trimmed = (identifier || "").trim();
501
+ if (!trimmed) {
502
+ throw new InvalidCredentialsError("Identifier cannot be empty");
503
+ }
504
+ if (this.identifierType === "email") {
505
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
506
+ if (!emailRegex.test(trimmed)) {
507
+ throw new InvalidCredentialsError("Identifier must be a valid email address");
508
+ }
509
+ }
510
+ }
511
+ /**
512
+ * Registers a new user with password hashing and policy enforcement.
513
+ *
514
+ * @param params - Registration parameters
515
+ * @returns Created user credentials without password hash
516
+ */
517
+ async signUp(params) {
518
+ const rawIdentifier = params.username || params.identifier || params.email || "";
519
+ this.validateIdentifierFormat(rawIdentifier);
520
+ const normalized = this.normalizeIdentifier(rawIdentifier);
521
+ const isEmail = normalized.includes("@");
522
+ const email = params.email ? params.email.trim().toLowerCase() : isEmail ? normalized : void 0;
523
+ const username = params.username ? params.username.trim() : !isEmail ? normalized : void 0;
524
+ if (this.requireUsername && (!username || username.trim() === "")) {
525
+ throw new InvalidCredentialsError("Username is required");
526
+ }
527
+ if (this.requireEmail && (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))) {
528
+ throw new InvalidCredentialsError("A valid email address is required");
529
+ }
530
+ const existing = await this.storage.findUserByIdentifier(normalized);
531
+ if (existing) {
532
+ throw new UserAlreadyExistsError(normalized);
533
+ }
534
+ const policyResult = await validatePasswordPolicy(params.password, this.policy);
535
+ if (!policyResult.valid) {
536
+ throw new WeakPasswordError(policyResult.errors);
537
+ }
538
+ const passwordHash = await this.hasher.hash(params.password);
539
+ const now = Date.now();
540
+ const id = typeof import_crypto2.default.randomUUID === "function" ? import_crypto2.default.randomUUID() : import_crypto2.default.randomBytes(16).toString("hex");
541
+ const user = {
542
+ id,
543
+ identifier: normalized,
544
+ email,
545
+ username,
546
+ passwordHash,
547
+ createdAt: now,
548
+ updatedAt: now,
549
+ metadata: params.metadata
550
+ };
551
+ await this.storage.saveUser(user);
552
+ const { passwordHash: _, ...safeUser } = user;
553
+ return safeUser;
554
+ }
555
+ /**
556
+ * Verifies credentials against storage with timing attack protection.
557
+ *
558
+ * @param params - Verification parameters
559
+ * @returns User credentials without password hash, or null if verification fails
560
+ */
561
+ async verifyCredentials(params) {
562
+ const rawIdentifier = params.username || params.identifier;
563
+ if (!rawIdentifier || typeof params.password !== "string") {
564
+ return null;
565
+ }
566
+ const normalized = this.normalizeIdentifier(rawIdentifier);
567
+ const user = await this.storage.findUserByIdentifier(normalized);
568
+ if (!user) {
569
+ if (this.timingAttackProtection) {
570
+ try {
571
+ if (!this.dummyHash) {
572
+ await this.initDummyHash();
573
+ }
574
+ await this.hasher.verify(params.password, this.dummyHash);
575
+ } catch {
576
+ }
577
+ }
578
+ return null;
579
+ }
580
+ const isMatch = await this.hasher.verify(params.password, user.passwordHash);
581
+ if (!isMatch) {
582
+ return null;
583
+ }
584
+ const { passwordHash: _, ...safeUser } = user;
585
+ return safeUser;
586
+ }
587
+ /**
588
+ * Changes a user's password with old password verification and new password policy enforcement.
589
+ *
590
+ * @param params - Change password parameters
591
+ * @returns True if password was successfully updated
592
+ */
593
+ async changePassword(params) {
594
+ let user = null;
595
+ const lookupKey = params.username || params.identifier;
596
+ if (params.userId) {
597
+ user = await this.storage.findUserById(params.userId);
598
+ } else if (lookupKey) {
599
+ user = await this.storage.findUserByIdentifier(this.normalizeIdentifier(lookupKey));
600
+ }
601
+ if (!user) {
602
+ if (this.timingAttackProtection) {
603
+ try {
604
+ if (!this.dummyHash) await this.initDummyHash();
605
+ await this.hasher.verify(params.oldPassword, this.dummyHash);
606
+ } catch {
607
+ }
608
+ }
609
+ throw new UserNotFoundError(params.userId || lookupKey || "unknown");
610
+ }
611
+ const isOldValid = await this.hasher.verify(params.oldPassword, user.passwordHash);
612
+ if (!isOldValid) {
613
+ throw new InvalidCredentialsError("Current password is incorrect");
614
+ }
615
+ const policyResult = await validatePasswordPolicy(params.newPassword, this.policy);
616
+ if (!policyResult.valid) {
617
+ throw new WeakPasswordError(policyResult.errors);
618
+ }
619
+ const newHash = await this.hasher.hash(params.newPassword);
620
+ await this.storage.updatePassword(user.id, newHash);
621
+ return true;
622
+ }
623
+ /**
624
+ * Finds a user by ID and returns safe user data.
625
+ */
626
+ async getUserById(id) {
627
+ const user = await this.storage.findUserById(id);
628
+ if (!user) return null;
629
+ const { passwordHash: _, ...safeUser } = user;
630
+ return safeUser;
631
+ }
632
+ /**
633
+ * Finds a user by identifier and returns safe user data.
634
+ */
635
+ async getUserByIdentifier(identifier) {
636
+ const user = await this.storage.findUserByIdentifier(this.normalizeIdentifier(identifier));
637
+ if (!user) return null;
638
+ const { passwordHash: _, ...safeUser } = user;
639
+ return safeUser;
640
+ }
641
+ /**
642
+ * Gets the underlying storage instance.
643
+ */
644
+ getStorage() {
645
+ return this.storage;
646
+ }
647
+ /**
648
+ * Gets the underlying hasher instance.
649
+ */
650
+ getHasher() {
651
+ return this.hasher;
652
+ }
653
+ };
64
654
 
65
655
  // src/types.ts
66
656
  var AccountLinkingStrategy = /* @__PURE__ */ ((AccountLinkingStrategy2) => {
@@ -71,7 +661,7 @@ var AccountLinkingStrategy = /* @__PURE__ */ ((AccountLinkingStrategy2) => {
71
661
 
72
662
  // src/dao/state-cache.ts
73
663
  var import_node_cache = __toESM(require("node-cache"), 1);
74
- var import_crypto = require("crypto");
664
+ var import_crypto3 = require("crypto");
75
665
  var LocalStateHandler = class {
76
666
  cache;
77
667
  stateStorage;
@@ -91,8 +681,8 @@ var LocalStateHandler = class {
91
681
  }
92
682
  // Default generateState implementation
93
683
  async generateState(provider) {
94
- const state = (0, import_crypto.randomBytes)(16).toString("hex");
95
- const codeVerifier = (0, import_crypto.randomBytes)(32).toString("hex");
684
+ const state = (0, import_crypto3.randomBytes)(16).toString("hex");
685
+ const codeVerifier = (0, import_crypto3.randomBytes)(32).toString("hex");
96
686
  return {
97
687
  state,
98
688
  data: {
@@ -109,7 +699,7 @@ var LocalStateHandler = class {
109
699
  };
110
700
 
111
701
  // src/lixa.ts
112
- var import_crypto3 = __toESM(require("crypto"), 1);
702
+ var import_crypto5 = __toESM(require("crypto"), 1);
113
703
 
114
704
  // src/dao/session-cache.ts
115
705
  var import_node_cache2 = __toESM(require("node-cache"), 1);
@@ -248,80 +838,6 @@ async function extractUserInfo(tokenData, providerMetadata) {
248
838
  return { userInfo };
249
839
  }
250
840
 
251
- // src/errors.ts
252
- var LixaError = class extends Error {
253
- /**
254
- * Standard error code string.
255
- */
256
- code;
257
- /**
258
- * Additional error context data.
259
- */
260
- details;
261
- constructor(message, code = "LIXA_ERROR", details) {
262
- super(message);
263
- this.name = this.constructor.name;
264
- this.code = code;
265
- this.details = details;
266
- Object.setPrototypeOf(this, new.target.prototype);
267
- }
268
- };
269
- var InvalidStateError = class extends LixaError {
270
- constructor(message = "Invalid or expired state", details) {
271
- super(message, "INVALID_STATE", details);
272
- }
273
- };
274
- var ProviderNotConfiguredError = class extends LixaError {
275
- constructor(provider, details) {
276
- const message = details?.message && typeof details.message === "string" ? details.message : `Provider '${provider}' is not available. Either import it from '@vunexa/lixa-providers' and include it in the configuration, or provide a custom implementation using the 'provider' field: { provider: new CustomProvider(), clientId: '...', ... }`;
277
- super(message, "PROVIDER_NOT_CONFIGURED", {
278
- provider,
279
- ...details
280
- });
281
- }
282
- };
283
- var InvalidProviderConfigError = class extends LixaError {
284
- constructor(message, details) {
285
- super(message, "INVALID_PROVIDER_CONFIG", details);
286
- }
287
- };
288
- var InvalidOAuthCallbackError = class extends LixaError {
289
- constructor(message, details) {
290
- super(message, "INVALID_OAUTH_CALLBACK", details);
291
- }
292
- };
293
- var TokenExchangeError = class extends LixaError {
294
- status;
295
- constructor(message, status, details) {
296
- super(message, "TOKEN_EXCHANGE_FAILED", { status, ...details });
297
- this.status = status;
298
- }
299
- };
300
- var SessionNotFoundError = class extends LixaError {
301
- constructor(message = "Active session not found or has expired", details) {
302
- super(message, "SESSION_NOT_FOUND", details);
303
- }
304
- };
305
- var EmailNotVerifiedError = class extends LixaError {
306
- constructor(email, details) {
307
- super(
308
- `Cannot link account: email '${email || "unknown"}' is not verified by the identity provider`,
309
- "EMAIL_NOT_VERIFIED",
310
- { email, ...details }
311
- );
312
- }
313
- };
314
- var AccountUnlinkError = class extends LixaError {
315
- constructor(message, details) {
316
- super(message, "ACCOUNT_UNLINK_ERROR", details);
317
- }
318
- };
319
- var RefreshTokenError = class extends LixaError {
320
- constructor(message, details) {
321
- super(message, "REFRESH_TOKEN_ERROR", details);
322
- }
323
- };
324
-
325
841
  // src/lixa.ts
326
842
  var Lixa = class _Lixa {
327
843
  static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
@@ -333,6 +849,7 @@ var Lixa = class _Lixa {
333
849
  localResourceHandler;
334
850
  userResourceStore = /* @__PURE__ */ new Map();
335
851
  refreshMutexes = /* @__PURE__ */ new Map();
852
+ credentialsManager;
336
853
  config;
337
854
  stateHandler;
338
855
  sessionHandler;
@@ -391,26 +908,32 @@ var Lixa = class _Lixa {
391
908
  this.stateHandler = config.stateHandler || this.localStateHandler;
392
909
  this.sessionHandler = config.sessionHandler || this.localSessionHandler;
393
910
  this.resourceHandler = config.resourceHandler || this.localResourceHandler;
911
+ if (config.credentials !== void 0 && config.credentials.enabled !== false) {
912
+ this.credentialsManager = new CredentialsManager(config.credentials);
913
+ }
394
914
  this.log("INFO", "Init", "Initializing Lixa instance", {
395
- providers: Object.keys(config.providers),
915
+ providers: config.providers ? Object.keys(config.providers) : [],
916
+ credentialsEnabled: this.credentialsManager !== void 0,
396
917
  debug: this.debug
397
918
  });
398
- for (const [providerName, providerConfig] of Object.entries(config.providers)) {
399
- const name = providerName.toLowerCase();
400
- const typedConfig = providerConfig;
401
- this.validateProviderConfig(providerName, typedConfig);
402
- if (typedConfig.provider) {
403
- this.validateProviderImplementation(providerName, typedConfig.provider);
404
- this.log("INFO", "Init", `Registered inline provider: ${providerName}`);
405
- } else {
406
- if (!_Lixa.DEFAULT_PROVIDERS.has(name) && !_Lixa.CONFIGURED_PROVIDERS.has(name)) {
407
- this.log("ERROR", "Init", `Provider '${providerName}' not available`);
408
- throw new ProviderNotConfiguredError(
409
- providerName,
410
- { hint: `Ensure the provider is registered via '@vunexa/lixa-providers' or provided inline.` }
411
- );
919
+ if (config.providers) {
920
+ for (const [providerName, providerConfig] of Object.entries(config.providers)) {
921
+ const name = providerName.toLowerCase();
922
+ const typedConfig = providerConfig;
923
+ this.validateProviderConfig(providerName, typedConfig);
924
+ if (typedConfig.provider) {
925
+ this.validateProviderImplementation(providerName, typedConfig.provider);
926
+ this.log("INFO", "Init", `Registered inline provider: ${providerName}`);
927
+ } else {
928
+ if (!_Lixa.DEFAULT_PROVIDERS.has(name) && !_Lixa.CONFIGURED_PROVIDERS.has(name)) {
929
+ this.log("ERROR", "Init", `Provider '${providerName}' not available`);
930
+ throw new ProviderNotConfiguredError(
931
+ providerName,
932
+ { hint: `Ensure the provider is registered via '@vunexa/lixa-providers' or provided inline.` }
933
+ );
934
+ }
935
+ this.log("INFO", "Init", `Using registered provider: ${providerName}`);
412
936
  }
413
- this.log("INFO", "Init", `Using registered provider: ${providerName}`);
414
937
  }
415
938
  }
416
939
  this.log("INFO", "Init", "Lixa instance initialized successfully");
@@ -471,7 +994,7 @@ var Lixa = class _Lixa {
471
994
  * Structured logging with standardized format and custom logger support.
472
995
  *
473
996
  * @param level - Log level (INFO, WARN, ERROR, DEBUG)
474
- * @param context - Context of the log (Init, Auth, Token, Session, State, AccountLinking, Resource)
997
+ * @param context - Context of the log (Init, Auth, Token, Session, State, AccountLinking, Resource, Credentials)
475
998
  * @param message - Log message
476
999
  * @param data - Optional data to log
477
1000
  */
@@ -508,7 +1031,7 @@ var Lixa = class _Lixa {
508
1031
  */
509
1032
  isProviderConfigured(provider) {
510
1033
  const providerType = provider.toLowerCase();
511
- return this.config.providers.hasOwnProperty(providerType);
1034
+ return Boolean(this.config.providers && this.config.providers.hasOwnProperty(providerType));
512
1035
  }
513
1036
  /**
514
1037
  * Gets a provider implementation by name.
@@ -616,7 +1139,7 @@ var Lixa = class _Lixa {
616
1139
  * The state parameter is used to prevent CSRF attacks in OAuth flows.
617
1140
  */
618
1141
  static generateRandomState() {
619
- return (0, import_crypto2.randomBytes)(16).toString("hex");
1142
+ return (0, import_crypto4.randomBytes)(16).toString("hex");
620
1143
  }
621
1144
  /**
622
1145
  * Generates a cryptographically secure code verifier for PKCE flows.
@@ -653,7 +1176,7 @@ var Lixa = class _Lixa {
653
1176
  * @internal
654
1177
  */
655
1178
  static generateCodeVerifier() {
656
- return (0, import_crypto2.randomBytes)(32).toString("hex");
1179
+ return (0, import_crypto4.randomBytes)(32).toString("hex");
657
1180
  }
658
1181
  /**
659
1182
  * Generates a code challenge from a code verifier for PKCE flows.
@@ -699,7 +1222,7 @@ var Lixa = class _Lixa {
699
1222
  * @internal
700
1223
  */
701
1224
  static buildCodeChallenge(codeVerifier) {
702
- const hash = import_crypto3.default.createHash("sha256").update(codeVerifier).digest("base64");
1225
+ const hash = import_crypto5.default.createHash("sha256").update(codeVerifier).digest("base64");
703
1226
  return hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
704
1227
  }
705
1228
  /**
@@ -741,8 +1264,8 @@ var Lixa = class _Lixa {
741
1264
  await storage.saveState(stateValue, generated.data, 300);
742
1265
  } else {
743
1266
  this.log("INFO", "State", "Using default state generation");
744
- stateValue = state || (0, import_crypto2.randomBytes)(16).toString("hex");
745
- codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
1267
+ stateValue = state || (0, import_crypto4.randomBytes)(16).toString("hex");
1268
+ codeVerifier = (0, import_crypto4.randomBytes)(32).toString("hex");
746
1269
  const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
747
1270
  await storage.saveState(
748
1271
  stateValue,
@@ -950,7 +1473,7 @@ var Lixa = class _Lixa {
950
1473
  raw: tokens,
951
1474
  linkedAt: Date.now()
952
1475
  };
953
- const sessionId = (0, import_crypto2.randomBytes)(32).toString("hex");
1476
+ const sessionId = (0, import_crypto4.randomBytes)(32).toString("hex");
954
1477
  session.id = sessionId;
955
1478
  this.log("INFO", "Session", "Storing session", { sessionId });
956
1479
  await sessionStorage.saveSession(sessionId, session, 86400);
@@ -1067,8 +1590,8 @@ var Lixa = class _Lixa {
1067
1590
  throw new ProviderNotConfiguredError(String(provider));
1068
1591
  }
1069
1592
  const providerImpl = this.getProvider(providerType, providerConfig);
1070
- const stateValue = state || (0, import_crypto2.randomBytes)(16).toString("hex");
1071
- const codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
1593
+ const stateValue = state || (0, import_crypto4.randomBytes)(16).toString("hex");
1594
+ const codeVerifier = (0, import_crypto4.randomBytes)(32).toString("hex");
1072
1595
  const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
1073
1596
  await storage.saveState(
1074
1597
  stateValue,
@@ -1113,7 +1636,7 @@ var Lixa = class _Lixa {
1113
1636
  throw new ProviderNotConfiguredError(String(provider));
1114
1637
  }
1115
1638
  const providerImpl = this.getProvider(providerType, providerConfig);
1116
- let codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
1639
+ let codeVerifier = (0, import_crypto4.randomBytes)(32).toString("hex");
1117
1640
  if (state) {
1118
1641
  const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
1119
1642
  const cachedState = await stateStorage.getState(state);
@@ -1372,8 +1895,178 @@ var Lixa = class _Lixa {
1372
1895
  this.log("INFO", "Token", "Token exchange response received successfully");
1373
1896
  return response.json();
1374
1897
  }
1898
+ /**
1899
+ * Checks if credentials (username and password) authentication is configured and enabled.
1900
+ *
1901
+ * @returns True if credentials authentication is available
1902
+ */
1903
+ isCredentialsEnabled() {
1904
+ return this.credentialsManager !== void 0;
1905
+ }
1906
+ /**
1907
+ * Returns the underlying CredentialsManager instance if configured.
1908
+ */
1909
+ getCredentialsManager() {
1910
+ return this.credentialsManager;
1911
+ }
1912
+ /**
1913
+ * Registers a new user with username/email and password.
1914
+ * Automatically enforces password policy, hashes password with Scrypt/configured hasher,
1915
+ * stores credentials, and creates a session (unless autoCreateSessionOnSignUp is false).
1916
+ *
1917
+ * @param params - Registration parameters (identifier, password, email, username, metadata)
1918
+ * @returns Created user (without password hash) and optional active session
1919
+ * @throws WeakPasswordError if password does not meet policy requirements
1920
+ * @throws UserAlreadyExistsError if identifier is already registered
1921
+ * @throws CredentialsNotConfiguredError if credentials auth is not configured
1922
+ */
1923
+ async signUp(params) {
1924
+ if (!this.credentialsManager) {
1925
+ throw new CredentialsNotConfiguredError();
1926
+ }
1927
+ this.log("INFO", "Credentials", "Processing signUp request", { identifier: params.identifier });
1928
+ const user = await this.credentialsManager.signUp(params);
1929
+ const autoSession = this.config.credentials?.autoCreateSessionOnSignUp ?? true;
1930
+ if (!autoSession) {
1931
+ return { user };
1932
+ }
1933
+ const sessionId = (0, import_crypto4.randomBytes)(16).toString("hex");
1934
+ const token = (0, import_crypto4.randomBytes)(32).toString("hex");
1935
+ const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
1936
+ const credentialsAccount = {
1937
+ provider: "credentials",
1938
+ providerUserId: user.id,
1939
+ email: user.email,
1940
+ accessToken: token,
1941
+ raw: {
1942
+ access_token: token,
1943
+ token_type: "Bearer",
1944
+ expires_in: ttl
1945
+ },
1946
+ linkedAt: Date.now()
1947
+ };
1948
+ const session = {
1949
+ id: sessionId,
1950
+ userId: user.id,
1951
+ email: user.email,
1952
+ token,
1953
+ provider: "credentials",
1954
+ accounts: {
1955
+ credentials: credentialsAccount
1956
+ }
1957
+ };
1958
+ const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1959
+ await sessionStorage.saveSession(sessionId, session, ttl);
1960
+ this.log("INFO", "Credentials", "Created session on signUp", { userId: user.id, sessionId });
1961
+ return { user, sessionId, session };
1962
+ }
1963
+ /**
1964
+ * Authenticates a user with username/email and password.
1965
+ * Performs constant-time verification with timing attack mitigation, and creates an active session.
1966
+ * If account linking is configured with AUTO_LINK_BY_VERIFIED_EMAIL, merges with existing session.
1967
+ *
1968
+ * @param params - Sign-in parameters (identifier, password)
1969
+ * @returns Authenticated user, session ID, and session object
1970
+ * @throws InvalidCredentialsError if authentication fails
1971
+ * @throws CredentialsNotConfiguredError if credentials auth is not configured
1972
+ */
1973
+ async signIn(params) {
1974
+ if (!this.credentialsManager) {
1975
+ throw new CredentialsNotConfiguredError();
1976
+ }
1977
+ const lookupKey = params.username || params.identifier;
1978
+ this.log("INFO", "Credentials", "Processing signIn request", { identifier: lookupKey });
1979
+ const user = await this.credentialsManager.verifyCredentials(params);
1980
+ if (!user) {
1981
+ this.log("WARN", "Credentials", "Invalid credentials provided", { identifier: lookupKey });
1982
+ throw new InvalidCredentialsError();
1983
+ }
1984
+ const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
1985
+ const token = (0, import_crypto4.randomBytes)(32).toString("hex");
1986
+ const credentialsAccount = {
1987
+ provider: "credentials",
1988
+ providerUserId: user.id,
1989
+ email: user.email,
1990
+ accessToken: token,
1991
+ raw: {
1992
+ access_token: token,
1993
+ token_type: "Bearer",
1994
+ expires_in: ttl
1995
+ },
1996
+ linkedAt: Date.now()
1997
+ };
1998
+ const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1999
+ const linkingMode = this.config.accountLinking?.mode;
2000
+ const isAutoLink = linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" || linkingMode === "linkByEmail";
2001
+ if (isAutoLink && user.email && sessionStorage.getSessionByEmail) {
2002
+ const existing = await sessionStorage.getSessionByEmail(user.email);
2003
+ if (existing) {
2004
+ const mergedSession = { ...existing.session };
2005
+ mergedSession.accounts = { ...mergedSession.accounts || {}, credentials: credentialsAccount };
2006
+ if (!mergedSession.userId) mergedSession.userId = user.id;
2007
+ if (!mergedSession.email) mergedSession.email = user.email;
2008
+ await sessionStorage.saveSession(existing.sessionId, mergedSession, ttl);
2009
+ this.log("INFO", "AccountLinking", "Linked credentials to existing session by email", {
2010
+ sessionId: existing.sessionId,
2011
+ email: user.email
2012
+ });
2013
+ return {
2014
+ user,
2015
+ sessionId: existing.sessionId,
2016
+ session: mergedSession
2017
+ };
2018
+ }
2019
+ }
2020
+ const sessionId = (0, import_crypto4.randomBytes)(16).toString("hex");
2021
+ const session = {
2022
+ id: sessionId,
2023
+ userId: user.id,
2024
+ email: user.email,
2025
+ token,
2026
+ provider: "credentials",
2027
+ accounts: {
2028
+ credentials: credentialsAccount
2029
+ }
2030
+ };
2031
+ await sessionStorage.saveSession(sessionId, session, ttl);
2032
+ this.log("INFO", "Credentials", "User signed in successfully", { userId: user.id, sessionId });
2033
+ return { user, sessionId, session };
2034
+ }
2035
+ /**
2036
+ * Verifies username/email and password credentials without generating a session.
2037
+ *
2038
+ * @param params - Verification parameters (identifier, password)
2039
+ * @returns User credentials (without password hash) or null if invalid
2040
+ * @throws CredentialsNotConfiguredError if credentials auth is not configured
2041
+ */
2042
+ async verifyCredentials(params) {
2043
+ if (!this.credentialsManager) {
2044
+ throw new CredentialsNotConfiguredError();
2045
+ }
2046
+ return this.credentialsManager.verifyCredentials(params);
2047
+ }
2048
+ /**
2049
+ * Updates a user's password after verifying the current password and enforcing policy on the new password.
2050
+ *
2051
+ * @param params - Change password parameters (userId/identifier, oldPassword, newPassword)
2052
+ * @returns True if password was updated successfully
2053
+ * @throws UserNotFoundError if user is not found
2054
+ * @throws InvalidCredentialsError if current password is incorrect
2055
+ * @throws WeakPasswordError if new password does not meet policy requirements
2056
+ * @throws CredentialsNotConfiguredError if credentials auth is not configured
2057
+ */
2058
+ async changePassword(params) {
2059
+ if (!this.credentialsManager) {
2060
+ throw new CredentialsNotConfiguredError();
2061
+ }
2062
+ this.log("INFO", "Credentials", "Processing changePassword request", {
2063
+ userId: params.userId,
2064
+ identifier: params.identifier
2065
+ });
2066
+ return this.credentialsManager.changePassword(params);
2067
+ }
1375
2068
  findProviderByType(providerType) {
1376
- for (const [key, value] of Object.entries(this.config.providers)) {
2069
+ for (const [key, value] of Object.entries(this.config.providers || {})) {
1377
2070
  if (key.toLowerCase() === providerType.toLowerCase()) {
1378
2071
  return value;
1379
2072
  }
@@ -1500,33 +2193,54 @@ function clearStateCookie(options) {
1500
2193
  header
1501
2194
  };
1502
2195
  }
2196
+
2197
+ // src/credentials/index.ts
2198
+ var credentials_exports = {};
2199
+ __export(credentials_exports, {
2200
+ CredentialsManager: () => CredentialsManager,
2201
+ LocalCredentialsStorage: () => LocalCredentialsStorage,
2202
+ Pbkdf2PasswordHasher: () => Pbkdf2PasswordHasher,
2203
+ ScryptPasswordHasher: () => ScryptPasswordHasher,
2204
+ validatePasswordPolicy: () => validatePasswordPolicy
2205
+ });
1503
2206
  // Annotate the CommonJS export names for ESM import in node:
1504
2207
  0 && (module.exports = {
1505
2208
  AccountLinkingStrategy,
1506
2209
  AccountUnlinkError,
2210
+ CredentialsManager,
2211
+ CredentialsNotConfiguredError,
1507
2212
  DEFAULT_SESSION_COOKIE_NAME,
1508
2213
  DEFAULT_SESSION_MAX_AGE_SECONDS,
1509
2214
  DEFAULT_STATE_COOKIE_NAME,
1510
2215
  DEFAULT_STATE_MAX_AGE_SECONDS,
1511
2216
  EmailNotVerifiedError,
2217
+ InvalidCredentialsError,
1512
2218
  InvalidOAuthCallbackError,
1513
2219
  InvalidProviderConfigError,
1514
2220
  InvalidStateError,
1515
2221
  Lixa,
1516
2222
  LixaError,
2223
+ LocalCredentialsStorage,
2224
+ Pbkdf2PasswordHasher,
1517
2225
  ProviderNotConfiguredError,
1518
2226
  RefreshTokenError,
2227
+ ScryptPasswordHasher,
1519
2228
  SessionNotFoundError,
1520
2229
  TokenExchangeError,
2230
+ UserAlreadyExistsError,
2231
+ UserNotFoundError,
2232
+ WeakPasswordError,
1521
2233
  clearSessionCookie,
1522
2234
  clearStateCookie,
1523
2235
  createSessionCookie,
1524
2236
  createStateCookie,
2237
+ credentials,
1525
2238
  decodeIdToken,
1526
2239
  determineProviderFromIssuer,
1527
2240
  extractUserInfo,
1528
2241
  fetchUserInfo,
1529
2242
  isProductionEnvironment,
1530
- serializeCookie
2243
+ serializeCookie,
2244
+ validatePasswordPolicy
1531
2245
  });
1532
2246
  //# sourceMappingURL=index.cjs.map