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

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.js CHANGED
@@ -387,10 +387,65 @@ var WeakPasswordError = class extends LixaError {
387
387
  }
388
388
  };
389
389
  var CredentialsNotConfiguredError = class extends LixaError {
390
- constructor(message = "Credentials authentication is not configured in this Lixa instance. Pass credentials: {} in LixaConfig to enable it.", details) {
390
+ constructor(message = "Credentials authentication is not configured in this Lixa instance. Pass credentials: {} or identity: ... in LixaConfig to enable it.", details) {
391
391
  super(message, "CREDENTIALS_NOT_CONFIGURED", details);
392
392
  }
393
393
  };
394
+ var IdentityProviderError = class extends LixaError {
395
+ provider;
396
+ statusCode;
397
+ constructor(message, provider, statusCode, details) {
398
+ super(message, "IDENTITY_PROVIDER_ERROR", { provider, statusCode, ...details });
399
+ this.provider = provider;
400
+ this.statusCode = statusCode;
401
+ }
402
+ };
403
+ var IdentityNotConfiguredError = class extends CredentialsNotConfiguredError {
404
+ constructor(message = "Identity provider is not configured in this Lixa instance. Pass identity: ... or credentials: {} in LixaConfig to enable it.", details) {
405
+ super(message, details);
406
+ }
407
+ };
408
+ var AccountLinkingChallengeRequiredError = class extends LixaError {
409
+ challengeToken;
410
+ email;
411
+ incomingProvider;
412
+ incomingProviderUserId;
413
+ existingUserId;
414
+ existingProviders;
415
+ hasPassword;
416
+ constructor(message, details) {
417
+ super(message, "ACCOUNT_LINKING_CHALLENGE_REQUIRED", details);
418
+ this.challengeToken = details.challengeToken;
419
+ this.email = details.email;
420
+ this.incomingProvider = details.incomingProvider;
421
+ this.incomingProviderUserId = details.incomingProviderUserId;
422
+ this.existingUserId = details.existingUserId;
423
+ this.existingProviders = details.existingProviders;
424
+ this.hasPassword = details.hasPassword;
425
+ }
426
+ };
427
+ var SSOAccountAlreadyExistsError = class extends LixaError {
428
+ existingProviders;
429
+ email;
430
+ constructor(email, existingProviders, details) {
431
+ super(
432
+ `An account with email '${email}' already exists via ${existingProviders.join(", ")}. Please sign in with your original provider and set a password in Settings.`,
433
+ "SSO_ACCOUNT_EXISTS",
434
+ { email, existingProviders, ...details }
435
+ );
436
+ this.email = email;
437
+ this.existingProviders = existingProviders;
438
+ }
439
+ };
440
+ var AccountNotVerifiedError = class extends LixaError {
441
+ constructor(identifier, details) {
442
+ super(
443
+ `Account${identifier ? ` '${identifier}'` : ""} has not been verified. Please confirm your account with the verification code sent to your email.`,
444
+ "ACCOUNT_NOT_VERIFIED",
445
+ { identifier, ...details }
446
+ );
447
+ }
448
+ };
394
449
 
395
450
  // src/credentials/credentials-manager.ts
396
451
  var CredentialsManager = class {
@@ -554,6 +609,41 @@ var CredentialsManager = class {
554
609
  await this.storage.updatePassword(user.id, newHash);
555
610
  return true;
556
611
  }
612
+ /**
613
+ * Sets a password for a user who doesn't have one yet (e.g. SSO-first users),
614
+ * or updates an existing password without requiring the old one.
615
+ * This should only be called from an authenticated context (user already verified their identity via SSO).
616
+ *
617
+ * @param params - userId and newPassword
618
+ * @returns True if password was successfully set
619
+ */
620
+ async setPassword(params) {
621
+ const policyResult = await validatePasswordPolicy(params.newPassword, this.policy);
622
+ if (!policyResult.valid) {
623
+ throw new WeakPasswordError(policyResult.errors);
624
+ }
625
+ const newHash = await this.hasher.hash(params.newPassword);
626
+ const existingUser = await this.storage.findUserById(params.userId);
627
+ if (existingUser) {
628
+ await this.storage.updatePassword(params.userId, newHash);
629
+ return true;
630
+ }
631
+ const now = Date.now();
632
+ const identifier = params.identifier || params.email || params.userId;
633
+ const normalized = this.normalizeIdentifier(identifier);
634
+ const isEmail = normalized.includes("@");
635
+ const user = {
636
+ id: params.userId,
637
+ identifier: normalized,
638
+ email: isEmail ? normalized : params.email?.trim().toLowerCase(),
639
+ username: !isEmail ? normalized : void 0,
640
+ passwordHash: newHash,
641
+ createdAt: now,
642
+ updatedAt: now
643
+ };
644
+ await this.storage.saveUser(user);
645
+ return true;
646
+ }
557
647
  /**
558
648
  * Finds a user by ID and returns safe user data.
559
649
  */
@@ -586,10 +676,388 @@ var CredentialsManager = class {
586
676
  }
587
677
  };
588
678
 
679
+ // src/identity/self-managed.ts
680
+ import crypto3 from "crypto";
681
+ var SelfManagedIdentityProvider = class {
682
+ name = "self-managed";
683
+ manager;
684
+ pendingSignups = /* @__PURE__ */ new Map();
685
+ requireVerification;
686
+ config;
687
+ constructor(configOrManager) {
688
+ if (configOrManager instanceof CredentialsManager) {
689
+ this.manager = configOrManager;
690
+ this.config = {};
691
+ this.requireVerification = false;
692
+ } else {
693
+ this.config = configOrManager || {};
694
+ this.manager = new CredentialsManager(this.config);
695
+ this.requireVerification = this.config.requireVerification ?? false;
696
+ }
697
+ }
698
+ mapUser(user) {
699
+ return {
700
+ id: user.id,
701
+ identifier: user.identifier,
702
+ username: user.username,
703
+ email: user.email,
704
+ emailVerified: true,
705
+ createdAt: user.createdAt,
706
+ updatedAt: user.updatedAt,
707
+ metadata: user.metadata,
708
+ provider: this.name
709
+ };
710
+ }
711
+ generateConfirmationCode() {
712
+ return String(crypto3.randomInt(1e5, 999999));
713
+ }
714
+ /**
715
+ * Registers a new user with optional pending verification.
716
+ * When requireVerification is true, credentials are NOT persisted to the database until confirmSignUp() is called.
717
+ */
718
+ async signUp(params) {
719
+ if (!this.requireVerification) {
720
+ const user = await this.manager.signUp(params);
721
+ return this.mapUser(user);
722
+ }
723
+ const rawIdentifier = params.username || params.identifier || params.email || "";
724
+ const trimmed = (rawIdentifier || "").trim();
725
+ if (!trimmed) {
726
+ throw new InvalidCredentialsError("Identifier cannot be empty");
727
+ }
728
+ const identifierType = this.config.identifierType || "both";
729
+ if (identifierType === "email") {
730
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
731
+ if (!emailRegex.test(trimmed)) {
732
+ throw new InvalidCredentialsError("Identifier must be a valid email address");
733
+ }
734
+ }
735
+ const normalized = trimmed.toLowerCase();
736
+ const isEmail = normalized.includes("@");
737
+ const email = params.email ? params.email.trim().toLowerCase() : isEmail ? normalized : void 0;
738
+ const username = params.username ? params.username.trim() : !isEmail ? normalized : void 0;
739
+ if (this.config.requireUsername && (!username || username.trim() === "")) {
740
+ throw new InvalidCredentialsError("Username is required");
741
+ }
742
+ if (this.config.requireEmail && (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))) {
743
+ throw new InvalidCredentialsError("A valid email address is required");
744
+ }
745
+ const existing = await this.manager.getUserByIdentifier(normalized);
746
+ if (existing) {
747
+ throw new UserAlreadyExistsError(normalized);
748
+ }
749
+ const existingPending = this.pendingSignups.get(normalized);
750
+ if (existingPending && existingPending.expiresAt > Date.now()) {
751
+ throw new UserAlreadyExistsError(normalized);
752
+ }
753
+ const policy = { minLength: 8, maxLength: 128, ...this.config.policy };
754
+ const policyResult = await validatePasswordPolicy(params.password, policy);
755
+ if (!policyResult.valid) {
756
+ throw new WeakPasswordError(policyResult.errors);
757
+ }
758
+ const passwordHash = await this.manager.getHasher().hash(params.password);
759
+ const userId = typeof crypto3.randomUUID === "function" ? crypto3.randomUUID() : crypto3.randomBytes(16).toString("hex");
760
+ const confirmationCode = this.generateConfirmationCode();
761
+ const now = Date.now();
762
+ const pending = {
763
+ identifier: normalized,
764
+ email,
765
+ username,
766
+ passwordHash,
767
+ confirmationCode,
768
+ userId,
769
+ metadata: params.metadata,
770
+ createdAt: now,
771
+ expiresAt: now + 15 * 60 * 1e3
772
+ // 15 minutes TTL
773
+ };
774
+ this.pendingSignups.set(normalized, pending);
775
+ console.log(`[Lixa Self-Managed] Verification code for '${normalized}': ${confirmationCode}`);
776
+ return {
777
+ id: userId,
778
+ identifier: normalized,
779
+ username,
780
+ email,
781
+ emailVerified: false,
782
+ createdAt: now,
783
+ updatedAt: now,
784
+ metadata: {
785
+ ...params.metadata,
786
+ requiresVerification: true,
787
+ devConfirmationCode: confirmationCode
788
+ },
789
+ provider: this.name
790
+ };
791
+ }
792
+ /**
793
+ * Confirms a pending signup registration using the verification code.
794
+ * Only after successful confirmation is the user persisted to the database.
795
+ */
796
+ async confirmSignUp(params) {
797
+ const normalized = (params.identifier || "").trim().toLowerCase();
798
+ const pending = this.pendingSignups.get(normalized);
799
+ if (!pending) {
800
+ throw new InvalidCredentialsError("No pending registration found for this identifier. Please sign up first.");
801
+ }
802
+ if (pending.expiresAt < Date.now()) {
803
+ this.pendingSignups.delete(normalized);
804
+ throw new InvalidCredentialsError("Verification code has expired. Please sign up again.");
805
+ }
806
+ const code = (params.confirmationCode || "").trim();
807
+ if (code !== pending.confirmationCode) {
808
+ throw new InvalidCredentialsError("Invalid verification code.");
809
+ }
810
+ const storage = this.manager.getStorage();
811
+ const user = {
812
+ id: pending.userId,
813
+ identifier: pending.identifier,
814
+ email: pending.email,
815
+ username: pending.username,
816
+ passwordHash: pending.passwordHash,
817
+ createdAt: pending.createdAt,
818
+ updatedAt: Date.now(),
819
+ metadata: pending.metadata
820
+ };
821
+ await storage.saveUser(user);
822
+ this.pendingSignups.delete(normalized);
823
+ console.log(`[Lixa Self-Managed] User '${normalized}' verified and persisted to database.`);
824
+ return true;
825
+ }
826
+ /**
827
+ * Resends the verification code for a pending signup.
828
+ */
829
+ async resendConfirmationCode(params) {
830
+ const normalized = (params.identifier || "").trim().toLowerCase();
831
+ const pending = this.pendingSignups.get(normalized);
832
+ if (!pending) {
833
+ throw new InvalidCredentialsError("No pending registration found for this identifier.");
834
+ }
835
+ const newCode = this.generateConfirmationCode();
836
+ pending.confirmationCode = newCode;
837
+ pending.expiresAt = Date.now() + 15 * 60 * 1e3;
838
+ this.pendingSignups.set(normalized, pending);
839
+ console.log(`[Lixa Self-Managed] New verification code for '${normalized}': ${newCode}`);
840
+ return {
841
+ success: true,
842
+ deliveryMedium: "CONSOLE",
843
+ destination: pending.email || normalized
844
+ };
845
+ }
846
+ /**
847
+ * Authenticates a user with password.
848
+ */
849
+ async signIn(params) {
850
+ const user = await this.manager.verifyCredentials(params);
851
+ if (!user) {
852
+ throw new InvalidCredentialsError();
853
+ }
854
+ return {
855
+ user: this.mapUser(user)
856
+ };
857
+ }
858
+ /**
859
+ * Verifies credentials without throwing an error if invalid.
860
+ */
861
+ async verifyCredentials(params) {
862
+ const user = await this.manager.verifyCredentials(params);
863
+ return user ? this.mapUser(user) : null;
864
+ }
865
+ /**
866
+ * Changes a user's password.
867
+ */
868
+ async changePassword(params) {
869
+ return await this.manager.changePassword(params);
870
+ }
871
+ /**
872
+ * Retrieves a user by their unique ID.
873
+ */
874
+ async getUserById(id) {
875
+ const user = await this.manager.getUserById(id);
876
+ return user ? this.mapUser(user) : null;
877
+ }
878
+ /**
879
+ * Retrieves a user by their identifier (email or username).
880
+ */
881
+ async getUserByIdentifier(identifier) {
882
+ const user = await this.manager.getUserByIdentifier(identifier);
883
+ return user ? this.mapUser(user) : null;
884
+ }
885
+ /**
886
+ * Deletes a user from storage if supported by the storage adapter.
887
+ */
888
+ async deleteUser(id) {
889
+ const storage = this.manager.getStorage();
890
+ if (typeof storage.deleteUser === "function") {
891
+ await storage.deleteUser(id);
892
+ }
893
+ }
894
+ /**
895
+ * Returns the underlying CredentialsManager instance.
896
+ */
897
+ getCredentialsManager() {
898
+ return this.manager;
899
+ }
900
+ };
901
+
902
+ // src/identity/composite.ts
903
+ var CompositeIdentityProvider = class {
904
+ name = "composite";
905
+ providers;
906
+ defaultProvider;
907
+ oauthProviders;
908
+ constructor(options) {
909
+ if (!options.providers || Object.keys(options.providers).length === 0) {
910
+ throw new IdentityProviderError(
911
+ "CompositeIdentityProvider requires at least one child provider in 'providers'",
912
+ this.name
913
+ );
914
+ }
915
+ this.providers = options.providers;
916
+ this.defaultProvider = options.defaultProvider;
917
+ this.oauthProviders = options.oauthProviders;
918
+ if (this.defaultProvider && !this.providers[this.defaultProvider]) {
919
+ throw new IdentityProviderError(
920
+ `Configured defaultProvider '${String(this.defaultProvider)}' does not exist in 'providers'. Available: ${Object.keys(this.providers).join(", ")}`,
921
+ this.name
922
+ );
923
+ }
924
+ }
925
+ /**
926
+ * Resolves the target child provider based on explicit parameter or defaultProvider.
927
+ * Throws an error immediately if the provider cannot be resolved or does not exist.
928
+ */
929
+ selectProvider(providerName) {
930
+ const targetKey = providerName || this.defaultProvider;
931
+ if (!targetKey) {
932
+ throw new IdentityProviderError(
933
+ `No provider specified and no defaultProvider configured in CompositeIdentityProvider. Available providers: ${Object.keys(this.providers).join(", ")}`,
934
+ this.name
935
+ );
936
+ }
937
+ const provider = this.providers[targetKey];
938
+ if (!provider) {
939
+ throw new IdentityProviderError(
940
+ `Provider '${targetKey}' is not configured in CompositeIdentityProvider. Available providers: ${Object.keys(this.providers).join(", ")}`,
941
+ this.name
942
+ );
943
+ }
944
+ return provider;
945
+ }
946
+ /**
947
+ * Returns all OAuth / OIDC provider configurations aggregated across this composite provider
948
+ * and any child providers exposing `getOAuthProviders()` or `getProviders()`.
949
+ */
950
+ getProviders() {
951
+ const result = {};
952
+ if (this.oauthProviders) {
953
+ Object.assign(result, this.oauthProviders);
954
+ }
955
+ for (const child of Object.values(this.providers)) {
956
+ if (typeof child.getOAuthProviders === "function") {
957
+ Object.assign(result, child.getOAuthProviders());
958
+ } else if (typeof child.getProviders === "function") {
959
+ Object.assign(result, child.getProviders());
960
+ }
961
+ }
962
+ return result;
963
+ }
964
+ async signUp(params) {
965
+ const provider = this.selectProvider(params.provider);
966
+ return provider.signUp(params);
967
+ }
968
+ async signIn(params) {
969
+ const provider = this.selectProvider(params.provider);
970
+ return provider.signIn(params);
971
+ }
972
+ async verifyCredentials(params) {
973
+ const provider = this.selectProvider(params.provider);
974
+ if (typeof provider.verifyCredentials === "function") {
975
+ return provider.verifyCredentials(params);
976
+ }
977
+ const result = await provider.signIn(params);
978
+ return result.user;
979
+ }
980
+ async changePassword(params) {
981
+ const provider = this.selectProvider(params.provider);
982
+ if (typeof provider.changePassword !== "function") {
983
+ throw new IdentityProviderError(
984
+ `Password change is not supported by provider '${provider.name}'`,
985
+ this.name
986
+ );
987
+ }
988
+ return provider.changePassword(params);
989
+ }
990
+ async forgotPassword(params) {
991
+ const provider = this.selectProvider(params.provider);
992
+ if (typeof provider.forgotPassword !== "function") {
993
+ throw new IdentityProviderError(
994
+ `Password reset is not supported by provider '${provider.name}'`,
995
+ this.name
996
+ );
997
+ }
998
+ return provider.forgotPassword(params);
999
+ }
1000
+ async confirmPasswordReset(params) {
1001
+ const provider = this.selectProvider(params.provider);
1002
+ if (typeof provider.confirmPasswordReset !== "function") {
1003
+ throw new IdentityProviderError(
1004
+ `Confirm password reset is not supported by provider '${provider.name}'`,
1005
+ this.name
1006
+ );
1007
+ }
1008
+ return provider.confirmPasswordReset(params);
1009
+ }
1010
+ async confirmSignUp(params) {
1011
+ const provider = this.selectProvider(params.provider);
1012
+ if (typeof provider.confirmSignUp !== "function") {
1013
+ throw new IdentityProviderError(
1014
+ `Account confirmation is not supported by provider '${provider.name}'`,
1015
+ this.name
1016
+ );
1017
+ }
1018
+ return provider.confirmSignUp(params);
1019
+ }
1020
+ async resendConfirmationCode(params) {
1021
+ const provider = this.selectProvider(params.provider);
1022
+ if (typeof provider.resendConfirmationCode !== "function") {
1023
+ throw new IdentityProviderError(
1024
+ `Resending confirmation code is not supported by provider '${provider.name}'`,
1025
+ this.name
1026
+ );
1027
+ }
1028
+ return provider.resendConfirmationCode(params);
1029
+ }
1030
+ async getUserById(id, providerName) {
1031
+ const provider = this.selectProvider(providerName);
1032
+ if (typeof provider.getUserById !== "function") {
1033
+ return null;
1034
+ }
1035
+ return provider.getUserById(id);
1036
+ }
1037
+ async getUserByIdentifier(identifier, providerName) {
1038
+ const provider = this.selectProvider(providerName);
1039
+ if (typeof provider.getUserByIdentifier !== "function") {
1040
+ return null;
1041
+ }
1042
+ return provider.getUserByIdentifier(identifier);
1043
+ }
1044
+ async deleteUser(id, providerName) {
1045
+ const provider = this.selectProvider(providerName);
1046
+ if (typeof provider.deleteUser !== "function") {
1047
+ throw new IdentityProviderError(
1048
+ `User deletion is not supported by provider '${provider.name}'`,
1049
+ this.name
1050
+ );
1051
+ }
1052
+ return provider.deleteUser(id);
1053
+ }
1054
+ };
1055
+
589
1056
  // src/types.ts
590
1057
  var AccountLinkingStrategy = /* @__PURE__ */ ((AccountLinkingStrategy2) => {
591
1058
  AccountLinkingStrategy2["AUTO_LINK_BY_VERIFIED_EMAIL"] = "AUTO_LINK_BY_VERIFIED_EMAIL";
592
1059
  AccountLinkingStrategy2["ISOLATED"] = "ISOLATED";
1060
+ AccountLinkingStrategy2["STEP_UP_VERIFICATION"] = "STEP_UP_VERIFICATION";
593
1061
  return AccountLinkingStrategy2;
594
1062
  })(AccountLinkingStrategy || {});
595
1063
 
@@ -633,7 +1101,7 @@ var LocalStateHandler = class {
633
1101
  };
634
1102
 
635
1103
  // src/lixa.ts
636
- import crypto3 from "crypto";
1104
+ import crypto4 from "crypto";
637
1105
 
638
1106
  // src/dao/session-cache.ts
639
1107
  import NodeCache2 from "node-cache";
@@ -691,10 +1159,7 @@ var LocalSessionHandler = class {
691
1159
  if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
692
1160
  throw new Error("No valid access token found in OAuth response");
693
1161
  }
694
- const session = {
695
- token: tokenData.access_token,
696
- raw: tokenData
697
- };
1162
+ const session = {};
698
1163
  return session;
699
1164
  }
700
1165
  // PascalCase alias for backward compatibility
@@ -755,7 +1220,7 @@ async function fetchUserInfo(accessToken, userInfoEndpoint) {
755
1220
  family_name: "family_name" in data ? String(data.family_name) : void 0,
756
1221
  name: "name" in data ? String(data.name) : void 0,
757
1222
  picture: "picture" in data ? String(data.picture) : void 0,
758
- email_verified: "email_verified" in data ? Boolean(data.email_verified) : void 0,
1223
+ email_verified: "email_verified" in data ? data.email_verified === true || data.email_verified === "true" : void 0,
759
1224
  iss: "iss" in data ? String(data.iss) : void 0
760
1225
  };
761
1226
  }
@@ -763,15 +1228,151 @@ async function extractUserInfo(tokenData, providerMetadata) {
763
1228
  let userInfo;
764
1229
  const userInfoEndpoint = providerMetadata.endpoints.userInfo;
765
1230
  if (tokenData.id_token) {
766
- userInfo = decodeIdToken(tokenData.id_token);
767
- } else if (tokenData.access_token) {
1231
+ try {
1232
+ userInfo = decodeIdToken(tokenData.id_token);
1233
+ if (userInfo && userInfo.email_verified !== void 0) {
1234
+ userInfo.email_verified = userInfo.email_verified === true || userInfo.email_verified === "true";
1235
+ }
1236
+ } catch {
1237
+ }
1238
+ }
1239
+ if ((!userInfo || !userInfo.email) && tokenData.access_token && userInfoEndpoint) {
1240
+ try {
1241
+ const fetched = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);
1242
+ userInfo = { ...fetched, ...userInfo || {}, email: fetched.email || userInfo?.email || "" };
1243
+ if (fetched.email_verified !== void 0 && (userInfo.email_verified === void 0 || userInfo.email_verified === false)) {
1244
+ userInfo.email_verified = fetched.email_verified === true || fetched.email_verified === "true";
1245
+ }
1246
+ } catch {
1247
+ }
1248
+ } else if (!userInfo && tokenData.access_token && userInfoEndpoint) {
768
1249
  userInfo = await fetchUserInfo(tokenData.access_token, userInfoEndpoint);
769
- } else {
1250
+ }
1251
+ if (!userInfo) {
770
1252
  throw new Error("No ID token or access token available to fetch user info");
771
1253
  }
772
1254
  return { userInfo };
773
1255
  }
774
1256
 
1257
+ // src/utils/cookies.ts
1258
+ var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
1259
+ var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
1260
+ var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
1261
+ var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
1262
+ function isProductionEnvironment() {
1263
+ return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
1264
+ }
1265
+ function serializeCookie(name, value, options) {
1266
+ const isProd = isProductionEnvironment();
1267
+ const path = options?.path ?? "/";
1268
+ const httpOnly = options?.httpOnly ?? true;
1269
+ const secure = options?.secure ?? isProd;
1270
+ const sameSite = options?.sameSite ?? "lax";
1271
+ const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
1272
+ if (path) {
1273
+ parts.push(`Path=${path}`);
1274
+ }
1275
+ if (typeof options?.maxAge === "number") {
1276
+ parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
1277
+ const expires = new Date(Date.now() + options.maxAge * 1e3).toUTCString();
1278
+ parts.push(`Expires=${expires}`);
1279
+ }
1280
+ if (options?.domain) {
1281
+ parts.push(`Domain=${options.domain}`);
1282
+ }
1283
+ if (httpOnly) {
1284
+ parts.push("HttpOnly");
1285
+ }
1286
+ if (secure) {
1287
+ parts.push("Secure");
1288
+ }
1289
+ if (sameSite) {
1290
+ const capitalized = sameSite.charAt(0).toUpperCase() + sameSite.slice(1).toLowerCase();
1291
+ parts.push(`SameSite=${capitalized}`);
1292
+ }
1293
+ return parts.join("; ");
1294
+ }
1295
+ function createSessionCookie(sessionId, options) {
1296
+ const isProd = isProductionEnvironment();
1297
+ const resolvedOptions = {
1298
+ name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
1299
+ path: options?.path ?? "/",
1300
+ maxAge: options?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
1301
+ httpOnly: options?.httpOnly ?? true,
1302
+ secure: options?.secure ?? isProd,
1303
+ sameSite: options?.sameSite ?? "lax",
1304
+ domain: options?.domain
1305
+ };
1306
+ const name = resolvedOptions.name;
1307
+ const header = serializeCookie(name, sessionId, resolvedOptions);
1308
+ return {
1309
+ name,
1310
+ value: sessionId,
1311
+ options: resolvedOptions,
1312
+ header
1313
+ };
1314
+ }
1315
+ function clearSessionCookie(options) {
1316
+ const isProd = isProductionEnvironment();
1317
+ const resolvedOptions = {
1318
+ name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
1319
+ path: options?.path ?? "/",
1320
+ maxAge: 0,
1321
+ httpOnly: options?.httpOnly ?? true,
1322
+ secure: options?.secure ?? isProd,
1323
+ sameSite: options?.sameSite ?? "lax",
1324
+ domain: options?.domain
1325
+ };
1326
+ const name = resolvedOptions.name;
1327
+ const header = serializeCookie(name, "", resolvedOptions);
1328
+ return {
1329
+ name,
1330
+ value: "",
1331
+ options: resolvedOptions,
1332
+ header
1333
+ };
1334
+ }
1335
+ function createStateCookie(state, options) {
1336
+ const isProd = isProductionEnvironment();
1337
+ const resolvedOptions = {
1338
+ name: options?.name || DEFAULT_STATE_COOKIE_NAME,
1339
+ path: options?.path ?? "/",
1340
+ maxAge: options?.maxAge ?? DEFAULT_STATE_MAX_AGE_SECONDS,
1341
+ httpOnly: options?.httpOnly ?? true,
1342
+ secure: options?.secure ?? isProd,
1343
+ sameSite: options?.sameSite ?? "lax",
1344
+ domain: options?.domain
1345
+ };
1346
+ const name = resolvedOptions.name;
1347
+ const header = serializeCookie(name, state, resolvedOptions);
1348
+ return {
1349
+ name,
1350
+ value: state,
1351
+ options: resolvedOptions,
1352
+ header
1353
+ };
1354
+ }
1355
+ function clearStateCookie(options) {
1356
+ const isProd = isProductionEnvironment();
1357
+ const resolvedOptions = {
1358
+ name: options?.name || DEFAULT_STATE_COOKIE_NAME,
1359
+ path: options?.path ?? "/",
1360
+ maxAge: 0,
1361
+ httpOnly: options?.httpOnly ?? true,
1362
+ secure: options?.secure ?? isProd,
1363
+ sameSite: options?.sameSite ?? "lax",
1364
+ domain: options?.domain
1365
+ };
1366
+ const name = resolvedOptions.name;
1367
+ const header = serializeCookie(name, "", resolvedOptions);
1368
+ return {
1369
+ name,
1370
+ value: "",
1371
+ options: resolvedOptions,
1372
+ header
1373
+ };
1374
+ }
1375
+
775
1376
  // src/lixa.ts
776
1377
  var Lixa = class _Lixa {
777
1378
  static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
@@ -781,14 +1382,19 @@ var Lixa = class _Lixa {
781
1382
  localStateHandler;
782
1383
  localSessionHandler;
783
1384
  localResourceHandler;
1385
+ localAccountHandler;
784
1386
  userResourceStore = /* @__PURE__ */ new Map();
1387
+ userAccountStore = /* @__PURE__ */ new Map();
785
1388
  refreshMutexes = /* @__PURE__ */ new Map();
1389
+ identityProvider;
786
1390
  credentialsManager;
787
1391
  config;
788
1392
  stateHandler;
789
1393
  sessionHandler;
790
1394
  resourceHandler;
1395
+ accountHandler;
791
1396
  debug;
1397
+ sessionCookieResolver;
792
1398
  /**
793
1399
  * Creates a new Lixa instance with the provided configuration.
794
1400
  *
@@ -803,7 +1409,11 @@ var Lixa = class _Lixa {
803
1409
  * @throws Error when provider is not available and no inline implementation is provided
804
1410
  */
805
1411
  constructor(config) {
806
- this.config = config;
1412
+ this.sessionCookieResolver = config.sessionCookieName;
1413
+ this.config = {
1414
+ ...config,
1415
+ ...config.providers ? { providers: config.providers } : {}
1416
+ };
807
1417
  this.debug = config.debug || false;
808
1418
  this.localStateHandler = new LocalStateHandler();
809
1419
  this.localSessionHandler = new LocalSessionHandler();
@@ -839,19 +1449,84 @@ var Lixa = class _Lixa {
839
1449
  }
840
1450
  }
841
1451
  };
842
- this.stateHandler = config.stateHandler || this.localStateHandler;
843
- this.sessionHandler = config.sessionHandler || this.localSessionHandler;
844
- this.resourceHandler = config.resourceHandler || this.localResourceHandler;
845
- if (config.credentials !== void 0 && config.credentials.enabled !== false) {
846
- this.credentialsManager = new CredentialsManager(config.credentials);
847
- }
1452
+ this.localAccountHandler = {
1453
+ accountStorage: {
1454
+ saveAccount: async (userId, provider, account) => {
1455
+ let userMap = this.userAccountStore.get(userId);
1456
+ if (!userMap) {
1457
+ userMap = /* @__PURE__ */ new Map();
1458
+ this.userAccountStore.set(userId, userMap);
1459
+ }
1460
+ userMap.set(provider.toLowerCase(), account);
1461
+ },
1462
+ getAccount: async (userId, provider) => {
1463
+ const userMap = this.userAccountStore.get(userId);
1464
+ return userMap?.get(provider.toLowerCase()) || null;
1465
+ },
1466
+ getUserAccounts: async (userId) => {
1467
+ const userMap = this.userAccountStore.get(userId);
1468
+ const result = {};
1469
+ if (userMap) {
1470
+ for (const [p, a] of userMap.entries()) {
1471
+ result[p] = a;
1472
+ }
1473
+ }
1474
+ return result;
1475
+ },
1476
+ deleteAccount: async (userId, provider) => {
1477
+ const userMap = this.userAccountStore.get(userId);
1478
+ if (userMap) {
1479
+ userMap.delete(provider.toLowerCase());
1480
+ }
1481
+ }
1482
+ }
1483
+ };
1484
+ const storage = config.storage;
1485
+ this.stateHandler = config.stateHandler || (storage?.stateStorage ? { stateStorage: storage.stateStorage } : this.localStateHandler);
1486
+ this.sessionHandler = config.sessionHandler || (storage?.sessionStorage ? { sessionStorage: storage.sessionStorage } : this.localSessionHandler);
1487
+ this.resourceHandler = config.resourceHandler || (storage?.resourceStorage ? { resourceStorage: storage.resourceStorage } : this.localResourceHandler);
1488
+ this.accountHandler = storage?.accountStorage ? { accountStorage: storage.accountStorage } : this.localAccountHandler;
1489
+ const identityConfig = config.identityProvider || config.identity;
1490
+ if (identityConfig) {
1491
+ if (typeof identityConfig.signUp === "function") {
1492
+ this.identityProvider = identityConfig;
1493
+ if (this.identityProvider instanceof SelfManagedIdentityProvider) {
1494
+ this.credentialsManager = this.identityProvider.getCredentialsManager();
1495
+ } else if (this.identityProvider instanceof CompositeIdentityProvider) {
1496
+ for (const child of Object.values(this.identityProvider.providers)) {
1497
+ if (child instanceof SelfManagedIdentityProvider) {
1498
+ this.credentialsManager = child.getCredentialsManager();
1499
+ break;
1500
+ }
1501
+ }
1502
+ }
1503
+ } else {
1504
+ const selfManaged = new SelfManagedIdentityProvider(identityConfig);
1505
+ this.identityProvider = selfManaged;
1506
+ this.credentialsManager = selfManaged.getCredentialsManager();
1507
+ }
1508
+ } else if (config.credentials !== void 0 && config.credentials.enabled !== false) {
1509
+ const selfManaged = new SelfManagedIdentityProvider(config.credentials);
1510
+ this.identityProvider = selfManaged;
1511
+ this.credentialsManager = selfManaged.getCredentialsManager();
1512
+ }
1513
+ if (this.identityProvider && typeof this.identityProvider.getProviders === "function") {
1514
+ const idpOAuthProviders = this.identityProvider.getProviders();
1515
+ this.config.providers = {
1516
+ ...idpOAuthProviders,
1517
+ ...this.config.providers || {}
1518
+ };
1519
+ }
1520
+ const oauthProviders = this.config.providers;
848
1521
  this.log("INFO", "Init", "Initializing Lixa instance", {
849
- providers: config.providers ? Object.keys(config.providers) : [],
850
- credentialsEnabled: this.credentialsManager !== void 0,
1522
+ providers: oauthProviders ? Object.keys(oauthProviders) : [],
1523
+ identityProvider: this.identityProvider ? this.identityProvider.name : void 0,
1524
+ identityEnabled: this.identityProvider !== void 0,
1525
+ credentialsEnabled: this.identityProvider !== void 0,
851
1526
  debug: this.debug
852
1527
  });
853
- if (config.providers) {
854
- for (const [providerName, providerConfig] of Object.entries(config.providers)) {
1528
+ if (oauthProviders) {
1529
+ for (const [providerName, providerConfig] of Object.entries(oauthProviders)) {
855
1530
  const name = providerName.toLowerCase();
856
1531
  const typedConfig = providerConfig;
857
1532
  this.validateProviderConfig(providerName, typedConfig);
@@ -880,7 +1555,22 @@ var Lixa = class _Lixa {
880
1555
  * @throws InvalidProviderConfigError when required fields are missing or invalid
881
1556
  */
882
1557
  validateProviderConfig(name, config) {
883
- const requiredFields = ["clientId", "clientSecret", "redirectUri", "scopes"];
1558
+ const hasScopes = Array.isArray(config.scopes) && config.scopes.length > 0 || config.scopes && typeof config.scopes === "object" && !Array.isArray(config.scopes) && (config.scopes.auth && config.scopes.auth.length > 0 || config.scopes.resource && config.scopes.resource.length > 0) || config.authScopes && config.authScopes.length > 0 || config.resourceScopes && config.resourceScopes.length > 0;
1559
+ if (!hasScopes) {
1560
+ const p = config.provider;
1561
+ if (p?.defaultScopes && Array.isArray(p.defaultScopes) && p.defaultScopes.length > 0) {
1562
+ config.scopes = { auth: [...p.defaultScopes] };
1563
+ } else if (p?.authScopes && Array.isArray(p.authScopes) && p.authScopes.length > 0) {
1564
+ config.scopes = { auth: [...p.authScopes] };
1565
+ } else if (name === "google" || name.startsWith("google")) {
1566
+ config.scopes = { auth: ["openid", "profile", "email"] };
1567
+ } else if (name === "github" || name.startsWith("github")) {
1568
+ config.scopes = { auth: ["read:user", "user:email"] };
1569
+ } else if (name.startsWith("cognito")) {
1570
+ config.scopes = { auth: ["openid", "email", "profile"] };
1571
+ }
1572
+ }
1573
+ const requiredFields = ["clientId", "clientSecret", "redirectUri"];
884
1574
  const missingFields = requiredFields.filter((field) => {
885
1575
  const value = config[field];
886
1576
  return value === void 0 || value === null || typeof value === "string" && value.trim() === "";
@@ -891,16 +1581,33 @@ var Lixa = class _Lixa {
891
1581
  { provider: name, missingFields }
892
1582
  );
893
1583
  }
894
- if (!Array.isArray(config.scopes)) {
895
- throw new InvalidProviderConfigError(
896
- `Provider '${name}' configuration error: 'scopes' must be an array of strings`,
897
- { provider: name }
898
- );
899
- }
900
- if (config.scopes.length === 0) {
1584
+ if (config.scopes) {
1585
+ if (Array.isArray(config.scopes)) {
1586
+ if (config.scopes.length === 0) {
1587
+ throw new InvalidProviderConfigError(
1588
+ `Provider '${name}' configuration error: 'scopes' array cannot be empty`,
1589
+ { provider: name }
1590
+ );
1591
+ }
1592
+ } else if (typeof config.scopes === "object") {
1593
+ const hasAuth = Array.isArray(config.scopes.auth) && config.scopes.auth.length > 0;
1594
+ const hasResource = Array.isArray(config.scopes.resource) && config.scopes.resource.length > 0;
1595
+ if (!hasAuth && !hasResource) {
1596
+ throw new InvalidProviderConfigError(
1597
+ `Provider '${name}' configuration error: 'scopes' object must specify non-empty 'auth' or 'resource' array`,
1598
+ { provider: name }
1599
+ );
1600
+ }
1601
+ } else {
1602
+ throw new InvalidProviderConfigError(
1603
+ `Provider '${name}' configuration error: 'scopes' must be an array of strings or { auth?: string[]; resource?: string[] }`,
1604
+ { provider: name }
1605
+ );
1606
+ }
1607
+ } else if (!config.authScopes && !config.resourceScopes) {
901
1608
  throw new InvalidProviderConfigError(
902
- `Provider '${name}' configuration error: 'scopes' array cannot be empty`,
903
- { provider: name }
1609
+ `Provider '${name}' configuration is missing required fields: scopes`,
1610
+ { provider: name, missingFields: ["scopes"] }
904
1611
  );
905
1612
  }
906
1613
  }
@@ -1156,7 +1863,7 @@ var Lixa = class _Lixa {
1156
1863
  * @internal
1157
1864
  */
1158
1865
  static buildCodeChallenge(codeVerifier) {
1159
- const hash = crypto3.createHash("sha256").update(codeVerifier).digest("base64");
1866
+ const hash = crypto4.createHash("sha256").update(codeVerifier).digest("base64");
1160
1867
  return hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1161
1868
  }
1162
1869
  /**
@@ -1214,12 +1921,17 @@ var Lixa = class _Lixa {
1214
1921
  }
1215
1922
  const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
1216
1923
  this.log("INFO", "State", `Saved state for provider: ${providerType}`, { state: stateValue });
1924
+ const rawAuthScopes = providerConfig.authScopes || (Array.isArray(providerConfig.scopes) ? providerConfig.scopes : providerConfig.scopes?.auth) || [];
1217
1925
  const authNScopes = this.resolveAuthNScopes(
1218
1926
  providerType,
1219
- providerConfig.scopes,
1927
+ rawAuthScopes,
1220
1928
  providerImpl,
1221
1929
  providerConfig.allowNonAuthScopes
1222
1930
  );
1931
+ const authorizationParams = {
1932
+ ...providerConfig.extraConfig || {},
1933
+ ...providerConfig.authorizationParams || {}
1934
+ };
1223
1935
  const params = new URLSearchParams({
1224
1936
  client_id: providerConfig.clientId,
1225
1937
  redirect_uri: providerConfig.redirectUri,
@@ -1228,7 +1940,7 @@ var Lixa = class _Lixa {
1228
1940
  response_type: "code",
1229
1941
  code_challenge: codeChallenge,
1230
1942
  code_challenge_method: "S256",
1231
- ...providerConfig.extraConfig
1943
+ ...authorizationParams
1232
1944
  });
1233
1945
  const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;
1234
1946
  this.log("INFO", "Auth", `Authorization URL generated successfully`, {
@@ -1302,7 +2014,8 @@ var Lixa = class _Lixa {
1302
2014
  async handleCallback({
1303
2015
  provider,
1304
2016
  code,
1305
- state
2017
+ state,
2018
+ sessionId
1306
2019
  }) {
1307
2020
  const providerType = String(provider).toLowerCase();
1308
2021
  this.log("INFO", "Auth", `Handling OAuth callback for provider: ${providerType}`);
@@ -1366,53 +2079,156 @@ var Lixa = class _Lixa {
1366
2079
  }
1367
2080
  const session = await generateSession(tokens, providerMetadata);
1368
2081
  session.provider = session.provider || providerType;
2082
+ session.token = session.token || tokens.access_token;
2083
+ session.raw = session.raw || tokens;
2084
+ if (extractedUserInfo?.sub && !session.userId) {
2085
+ session.userId = extractedUserInfo.sub;
2086
+ } else if (extractedUserInfo?.id && !session.userId) {
2087
+ session.userId = String(extractedUserInfo.id);
2088
+ }
1369
2089
  if (extractedUserInfo?.email && !session.email) {
1370
2090
  session.email = extractedUserInfo.email;
1371
2091
  }
1372
2092
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1373
2093
  const linkingConfig = this.config.accountLinking;
1374
- const mode = String(linkingConfig?.mode || "");
1375
- const isLinkByEmail = mode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" || mode === "linkByEmail";
2094
+ const mode = typeof linkingConfig === "object" ? String(linkingConfig?.mode || "") : "";
2095
+ const isLinkByEmail = linkingConfig === true || linkingConfig === "linkByEmail" || linkingConfig === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" || mode === "linkByEmail";
2096
+ const isStepUpMode = linkingConfig === "STEP_UP_VERIFICATION" || linkingConfig === "STEP_UP_VERIFICATION" /* STEP_UP_VERIFICATION */ || mode === "STEP_UP_VERIFICATION" || mode === "STEP_UP_VERIFICATION" /* STEP_UP_VERIFICATION */ || mode === "stepUp";
1376
2097
  const email = extractedUserInfo?.email;
1377
- const isVerified = extractedUserInfo?.email_verified !== false;
1378
- const requireVerified = linkingConfig?.requireVerifiedEmail ?? true;
1379
- const canLink = isLinkByEmail && email && (!requireVerified || isVerified);
2098
+ const isVerified = extractedUserInfo?.email_verified === true;
2099
+ const requireVerified = typeof linkingConfig === "object" ? linkingConfig?.requireVerifiedEmail ?? true : true;
2100
+ const canLink = (isLinkByEmail || isStepUpMode) && email && (!requireVerified || isVerified);
2101
+ if ((isLinkByEmail || isStepUpMode) && email && !isVerified && requireVerified) {
2102
+ this.log("WARN", "AccountLinking", `Rejected account linking for provider '${providerType}': email '${email}' is not cryptographically verified by the provider.`);
2103
+ }
2104
+ if (sessionId) {
2105
+ const activeSession = await sessionStorage.getSession(sessionId);
2106
+ if (activeSession) {
2107
+ this.log("INFO", "AccountLinking", `Linking provider '${providerType}' directly to active session '${sessionId}'`);
2108
+ const linkedAcc2 = {
2109
+ provider: providerType,
2110
+ providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
2111
+ email: extractedUserInfo?.email || activeSession.email,
2112
+ linkedAt: Date.now()
2113
+ };
2114
+ if (this.accountHandler?.accountStorage) {
2115
+ const userKey = this.getUserKeyFromSession(activeSession);
2116
+ await this.accountHandler.accountStorage.saveAccount(userKey, providerType, this.sanitizeIdentityForStorage(linkedAcc2));
2117
+ }
2118
+ return sessionId;
2119
+ }
2120
+ }
2121
+ if (canLink) {
2122
+ const existingAccounts = this.accountHandler?.accountStorage?.findAccountsByEmail ? await this.accountHandler.accountStorage.findAccountsByEmail(email) : [];
2123
+ const existingSessionRecord = sessionStorage.getSessionByEmail ? await sessionStorage.getSessionByEmail(email) : null;
2124
+ const existingProviders = /* @__PURE__ */ new Set();
2125
+ let existingUserId;
2126
+ if (existingSessionRecord) {
2127
+ existingUserId = existingSessionRecord.session.userId || existingSessionRecord.session.email;
2128
+ if (existingSessionRecord.session.provider) {
2129
+ existingProviders.add(existingSessionRecord.session.provider.toLowerCase());
2130
+ }
2131
+ if (existingSessionRecord.session.accounts) {
2132
+ Object.keys(existingSessionRecord.session.accounts).forEach(
2133
+ (p) => existingProviders.add(p.toLowerCase())
2134
+ );
2135
+ }
2136
+ }
2137
+ for (const acc of existingAccounts) {
2138
+ if (!existingUserId) existingUserId = acc.userId;
2139
+ existingProviders.add(acc.provider.toLowerCase());
2140
+ }
2141
+ if (existingProviders.size > 0 && !existingProviders.has(providerType.toLowerCase())) {
2142
+ if (isStepUpMode) {
2143
+ let hasPassword = existingProviders.has("credentials") || existingProviders.has("self-managed") || existingProviders.has("cognito");
2144
+ if (!hasPassword && this.credentialsManager) {
2145
+ try {
2146
+ const credUser = await this.credentialsManager.getUserByIdentifier(email);
2147
+ if (credUser) {
2148
+ hasPassword = true;
2149
+ }
2150
+ } catch {
2151
+ }
2152
+ }
2153
+ const challengeToken = randomBytes2(24).toString("hex");
2154
+ const stateStorage2 = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
2155
+ await stateStorage2.saveState(
2156
+ `linking_challenge:${challengeToken}`,
2157
+ {
2158
+ provider: providerType,
2159
+ codeVerifier: "",
2160
+ createdAt: Date.now(),
2161
+ challengeToken,
2162
+ email,
2163
+ incomingProvider: providerType,
2164
+ incomingProviderUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
2165
+ existingUserId: existingUserId || email,
2166
+ existingProviders: Array.from(existingProviders),
2167
+ hasPassword,
2168
+ tokens
2169
+ },
2170
+ 600
2171
+ );
2172
+ this.log(
2173
+ "WARN",
2174
+ "AccountLinking",
2175
+ `Step-up challenge required for user '${email}'. Existing: [${Array.from(existingProviders).join(
2176
+ ", "
2177
+ )}], Incoming: '${providerType}'`
2178
+ );
2179
+ throw new AccountLinkingChallengeRequiredError(
2180
+ `An account with email '${email}' already exists. Verification required before linking.`,
2181
+ {
2182
+ challengeToken,
2183
+ email,
2184
+ incomingProvider: providerType,
2185
+ incomingProviderUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
2186
+ existingUserId: existingUserId || email,
2187
+ existingProviders: Array.from(existingProviders),
2188
+ hasPassword
2189
+ }
2190
+ );
2191
+ }
2192
+ }
2193
+ }
1380
2194
  if (canLink && sessionStorage.getSessionByEmail) {
1381
2195
  const existingRecord = await sessionStorage.getSessionByEmail(email);
1382
2196
  if (existingRecord) {
1383
2197
  this.log("INFO", "AccountLinking", `Linking provider '${providerType}' to existing session for email '${email}'`);
1384
2198
  const { sessionId: existingSessionId, session: existingSession } = existingRecord;
1385
- existingSession.accounts = existingSession.accounts || {};
1386
- existingSession.accounts[providerType] = {
2199
+ const linkedAcc2 = {
1387
2200
  provider: providerType,
1388
2201
  providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
1389
2202
  email,
1390
- accessToken: tokens.access_token,
1391
- raw: tokens,
1392
2203
  linkedAt: Date.now()
1393
2204
  };
1394
2205
  existingSession.provider = providerType;
1395
2206
  existingSession.token = tokens.access_token;
1396
2207
  existingSession.raw = tokens;
1397
2208
  await sessionStorage.saveSession(existingSessionId, existingSession, 86400);
2209
+ if (this.accountHandler?.accountStorage) {
2210
+ const userKey = this.getUserKeyFromSession(existingSession);
2211
+ await this.accountHandler.accountStorage.saveAccount(userKey, providerType, this.sanitizeIdentityForStorage(linkedAcc2));
2212
+ }
1398
2213
  return existingSessionId;
1399
2214
  }
1400
2215
  }
1401
- session.accounts = session.accounts || {};
1402
- session.accounts[providerType] = {
2216
+ const linkedAcc = {
1403
2217
  provider: providerType,
1404
2218
  providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
1405
2219
  email: extractedUserInfo?.email,
1406
- accessToken: tokens.access_token,
1407
- raw: tokens,
1408
2220
  linkedAt: Date.now()
1409
2221
  };
1410
- const sessionId = randomBytes2(32).toString("hex");
1411
- session.id = sessionId;
1412
- this.log("INFO", "Session", "Storing session", { sessionId });
1413
- await sessionStorage.saveSession(sessionId, session, 86400);
1414
- this.log("INFO", "Session", "Session created successfully", { sessionId });
1415
- return sessionId;
2222
+ if (this.accountHandler?.accountStorage) {
2223
+ const userKey = this.getUserKeyFromSession(session);
2224
+ await this.accountHandler.accountStorage.saveAccount(userKey, providerType, this.sanitizeIdentityForStorage(linkedAcc));
2225
+ }
2226
+ const newSessionId = randomBytes2(32).toString("hex");
2227
+ session.sessionId = newSessionId;
2228
+ this.log("INFO", "Session", "Storing session", { sessionId: newSessionId });
2229
+ await sessionStorage.saveSession(newSessionId, session, 86400);
2230
+ this.log("INFO", "Session", "Session created successfully", { sessionId: newSessionId });
2231
+ return newSessionId;
1416
2232
  }
1417
2233
  /**
1418
2234
  * Explicitly link a new OAuth provider account to an active session.
@@ -1461,19 +2277,28 @@ var Lixa = class _Lixa {
1461
2277
  extractedUserInfo = userInfo;
1462
2278
  } catch {
1463
2279
  }
1464
- existingSession.accounts = existingSession.accounts || {};
1465
- existingSession.accounts[providerType] = {
2280
+ const linkedAcc = {
1466
2281
  provider: providerType,
1467
2282
  providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
1468
2283
  email: extractedUserInfo?.email,
1469
- accessToken: tokens.access_token,
1470
- raw: tokens,
1471
2284
  linkedAt: Date.now()
1472
2285
  };
1473
2286
  if (extractedUserInfo?.email && !existingSession.email) {
1474
2287
  existingSession.email = extractedUserInfo.email;
2288
+ await sessionStorage.saveSession(sessionId, existingSession, 86400);
2289
+ }
2290
+ if (this.accountHandler?.accountStorage) {
2291
+ const userKey = this.getUserKeyFromSession(existingSession);
2292
+ await this.accountHandler.accountStorage.saveAccount(
2293
+ userKey,
2294
+ providerType,
2295
+ this.sanitizeIdentityForStorage(linkedAcc)
2296
+ );
1475
2297
  }
1476
- await sessionStorage.saveSession(sessionId, existingSession, 86400);
2298
+ this.log("INFO", "AccountLinking", `Linked provider '${providerType}' directly to session '${sessionId}'`, {
2299
+ provider: providerType,
2300
+ sessionId
2301
+ });
1477
2302
  return sessionId;
1478
2303
  }
1479
2304
  /**
@@ -1486,19 +2311,171 @@ var Lixa = class _Lixa {
1486
2311
  async unlinkAccount(sessionId, providerToUnlink) {
1487
2312
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1488
2313
  const session = await sessionStorage.getSession(sessionId);
1489
- if (!session || !session.accounts) {
1490
- throw new AccountUnlinkError("Session not found or has no linked accounts.", { sessionId });
2314
+ if (!session) {
2315
+ throw new AccountUnlinkError("Session not found.", { sessionId });
2316
+ }
2317
+ const providerKey = providerToUnlink.toLowerCase();
2318
+ const userKey = this.getUserKeyFromSession(session);
2319
+ let allAccounts = {};
2320
+ if (this.accountHandler?.accountStorage) {
2321
+ allAccounts = await this.accountHandler.accountStorage.getUserAccounts(userKey) || {};
2322
+ }
2323
+ if (!allAccounts[providerKey]) {
2324
+ throw new AccountUnlinkError(`Provider '${providerToUnlink}' is not linked to this account.`, {
2325
+ sessionId,
2326
+ provider: providerToUnlink
2327
+ });
2328
+ }
2329
+ const linkedProviders = Object.keys(allAccounts);
2330
+ if (linkedProviders.length <= 1) {
2331
+ throw new AccountUnlinkError("Cannot unlink the only authentication provider for this account.", {
2332
+ sessionId,
2333
+ provider: providerToUnlink
2334
+ });
2335
+ }
2336
+ if (this.accountHandler?.accountStorage) {
2337
+ await this.accountHandler.accountStorage.deleteAccount(userKey, providerKey);
2338
+ }
2339
+ if (session.provider?.toLowerCase() === providerKey) {
2340
+ const remaining = linkedProviders.filter((p) => p !== providerKey);
2341
+ session.provider = remaining.length > 0 ? remaining[0] : void 0;
2342
+ await sessionStorage.saveSession(sessionId, session, 86400);
2343
+ }
2344
+ this.log("INFO", "AccountLinking", `Unlinked provider '${providerKey}' for user '${session.userId || session.email}'`, {
2345
+ sessionId,
2346
+ provider: providerKey
2347
+ });
2348
+ return true;
2349
+ }
2350
+ /**
2351
+ * Confirms a pending account linking step-up challenge using the user's password.
2352
+ * On successful verification, links the pending provider to the existing account and creates a session.
2353
+ *
2354
+ * @param params - challengeToken and password
2355
+ * @returns Session ID, Session, and User identity
2356
+ * @throws InvalidStateError if challenge is invalid or expired
2357
+ * @throws InvalidCredentialsError if password verification fails
2358
+ */
2359
+ async confirmLinkingWithPassword(params) {
2360
+ const { challengeToken, password } = params;
2361
+ const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
2362
+ const stateKey = `linking_challenge:${challengeToken}`;
2363
+ const cached = await stateStorage.getState(stateKey);
2364
+ if (!cached || !cached.challengeToken) {
2365
+ throw new InvalidStateError("Invalid or expired account linking challenge.");
2366
+ }
2367
+ let verifiedUser = null;
2368
+ let passwordTokens = void 0;
2369
+ if (this.identityProvider) {
2370
+ try {
2371
+ const signInResult = await this.identityProvider.signIn({
2372
+ identifier: cached.email,
2373
+ password
2374
+ });
2375
+ verifiedUser = signInResult.user;
2376
+ passwordTokens = signInResult.tokens;
2377
+ } catch {
2378
+ verifiedUser = await this.verifyCredentials({
2379
+ identifier: cached.email,
2380
+ password
2381
+ });
2382
+ }
2383
+ } else {
2384
+ verifiedUser = await this.verifyCredentials({
2385
+ identifier: cached.email,
2386
+ password
2387
+ });
2388
+ }
2389
+ if (!verifiedUser) {
2390
+ throw new InvalidCredentialsError("Invalid password for account linking verification.");
2391
+ }
2392
+ await stateStorage.deleteState(stateKey);
2393
+ const providerType = String(cached.incomingProvider).toLowerCase();
2394
+ const incomingTokens = cached.tokens;
2395
+ const linkedAcc = {
2396
+ provider: providerType,
2397
+ providerUserId: cached.incomingProviderUserId,
2398
+ email: cached.email,
2399
+ linkedAt: Date.now()
2400
+ };
2401
+ if (this.accountHandler?.accountStorage) {
2402
+ await this.accountHandler.accountStorage.saveAccount(
2403
+ cached.existingUserId,
2404
+ providerType,
2405
+ this.sanitizeIdentityForStorage(linkedAcc)
2406
+ );
2407
+ }
2408
+ const passwordProviderName = verifiedUser && verifiedUser.provider || (this.identityProvider instanceof CompositeIdentityProvider ? this.identityProvider.defaultProvider : this.identityProvider?.name) || "credentials";
2409
+ const passwordAccountKey = passwordProviderName === "self-managed" ? "credentials" : passwordProviderName;
2410
+ if (this.accountHandler?.accountStorage) {
2411
+ await this.accountHandler.accountStorage.saveAccount(
2412
+ cached.existingUserId,
2413
+ passwordAccountKey,
2414
+ this.sanitizeIdentityForStorage({
2415
+ provider: passwordAccountKey,
2416
+ providerUserId: verifiedUser.id,
2417
+ email: verifiedUser.email,
2418
+ linkedAt: Date.now()
2419
+ })
2420
+ );
2421
+ }
2422
+ const sessionId = randomBytes2(32).toString("hex");
2423
+ const session = {
2424
+ sessionId,
2425
+ userId: cached.existingUserId,
2426
+ email: cached.email,
2427
+ provider: providerType,
2428
+ token: incomingTokens?.access_token,
2429
+ raw: incomingTokens
2430
+ };
2431
+ const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
2432
+ await sessionStorage.saveSession(sessionId, session, 86400);
2433
+ this.log(
2434
+ "INFO",
2435
+ "AccountLinking",
2436
+ `Successfully linked '${providerType}' to user '${cached.existingUserId}' via password step-up.`
2437
+ );
2438
+ return { sessionId, session, user: verifiedUser };
2439
+ }
2440
+ /**
2441
+ * Sets a password for an SSO-authenticated user who doesn't have one yet.
2442
+ * Must be called from an authenticated context (user has active session proving identity).
2443
+ *
2444
+ * @param params - userId, newPassword, optional identifier and email
2445
+ * @returns True if password was set successfully
2446
+ * @throws IdentityNotConfiguredError if no identity provider configured
2447
+ * @throws WeakPasswordError if password doesn't meet policy
2448
+ */
2449
+ async setPassword(params) {
2450
+ this.log("INFO", "Identity", "Processing setPassword request", { userId: params.userId });
2451
+ if (this.credentialsManager) {
2452
+ const result = await this.credentialsManager.setPassword(params);
2453
+ if (result && this.accountHandler?.accountStorage) {
2454
+ const providerName = this.identityProvider?.name === "self-managed" ? "credentials" : this.identityProvider?.name || "credentials";
2455
+ const credentialsAccount = {
2456
+ provider: providerName,
2457
+ providerUserId: params.userId,
2458
+ email: params.email,
2459
+ linkedAt: Date.now()
2460
+ };
2461
+ await this.accountHandler.accountStorage.saveAccount(
2462
+ params.userId,
2463
+ providerName,
2464
+ this.sanitizeIdentityForStorage(credentialsAccount)
2465
+ );
2466
+ }
2467
+ this.log("INFO", "Identity", "Password set successfully", { userId: params.userId });
2468
+ return result;
1491
2469
  }
1492
- const linkedProviders = Object.keys(session.accounts);
1493
- if (linkedProviders.length <= 1) {
1494
- throw new AccountUnlinkError("Cannot unlink the only authentication provider for this account.", {
1495
- sessionId,
1496
- provider: providerToUnlink
1497
- });
2470
+ if (this.identityProvider && typeof this.identityProvider.changePassword === "function") {
2471
+ throw new IdentityProviderError(
2472
+ "Setting a password for SSO users is only supported with self-managed credentials. Use the provider's admin console instead.",
2473
+ this.identityProvider.name
2474
+ );
1498
2475
  }
1499
- delete session.accounts[providerToUnlink.toLowerCase()];
1500
- await sessionStorage.saveSession(sessionId, session, 86400);
1501
- return true;
2476
+ throw new CredentialsNotConfiguredError(
2477
+ "Cannot set password: No credentials manager is configured on this Lixa instance."
2478
+ );
1502
2479
  }
1503
2480
  /**
1504
2481
  * Generates an authorization URL for connecting a resource provider (AuthZ) post-login.
@@ -1512,7 +2489,7 @@ var Lixa = class _Lixa {
1512
2489
  * @returns The authorization URL for resource consent
1513
2490
  */
1514
2491
  async getResourceAuthUrl(params) {
1515
- const { sessionId, provider, scopes, state, prompt, extraConfig } = params;
2492
+ const { sessionId, provider, state, prompt, extraConfig, authorizationParams } = params;
1516
2493
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1517
2494
  const activeSession = await sessionStorage.getSession(sessionId);
1518
2495
  if (!activeSession) {
@@ -1523,6 +2500,11 @@ var Lixa = class _Lixa {
1523
2500
  if (!providerConfig) {
1524
2501
  throw new ProviderNotConfiguredError(String(provider));
1525
2502
  }
2503
+ const defaultResourceScopes = providerConfig.resourceScopes || (Array.isArray(providerConfig.scopes) ? void 0 : providerConfig.scopes?.resource) || [];
2504
+ const requestedScopes = params.scopes && params.scopes.length > 0 ? params.scopes : defaultResourceScopes;
2505
+ if (requestedScopes.length === 0) {
2506
+ throw new Error(`No scopes specified for resource '${provider}'. Provide scopes in getResourceAuthUrl() or configure 'scopes.resource' / 'resourceScopes' on the provider.`);
2507
+ }
1526
2508
  const providerImpl = this.getProvider(providerType, providerConfig);
1527
2509
  const stateValue = state || randomBytes2(16).toString("hex");
1528
2510
  const codeVerifier = randomBytes2(32).toString("hex");
@@ -1537,22 +2519,35 @@ var Lixa = class _Lixa {
1537
2519
  300
1538
2520
  );
1539
2521
  const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
2522
+ const mergedAuthParams = {
2523
+ ...providerConfig.extraConfig || {},
2524
+ ...providerConfig.authorizationParams || {},
2525
+ ...extraConfig || {},
2526
+ ...authorizationParams || {}
2527
+ };
1540
2528
  const searchParams = new URLSearchParams({
1541
2529
  client_id: providerConfig.clientId,
1542
2530
  redirect_uri: providerConfig.redirectUri,
1543
- scope: scopes.join(" "),
2531
+ scope: requestedScopes.join(" "),
1544
2532
  state: stateValue,
1545
2533
  response_type: "code",
1546
2534
  code_challenge: codeChallenge,
1547
2535
  code_challenge_method: "S256",
1548
2536
  ...prompt ? { prompt } : {},
1549
- ...providerConfig.extraConfig,
1550
- ...extraConfig
2537
+ ...mergedAuthParams
1551
2538
  });
1552
2539
  return `${providerImpl.authorizationEndpoint}?${searchParams.toString()}`;
1553
2540
  }
1554
2541
  getUserKeyFromSession(session) {
1555
- return session.userId || session.email || session.id || "anonymous";
2542
+ return session.userId || session.email || session.sessionId || "anonymous";
2543
+ }
2544
+ sanitizeIdentityForStorage(account) {
2545
+ return {
2546
+ provider: account.provider,
2547
+ providerUserId: account.providerUserId,
2548
+ email: account.email,
2549
+ linkedAt: account.linkedAt || Date.now()
2550
+ };
1556
2551
  }
1557
2552
  /**
1558
2553
  * Handles the OAuth callback for a connected resource provider and stores resource tokens bound to user account.
@@ -1582,12 +2577,14 @@ var Lixa = class _Lixa {
1582
2577
  }
1583
2578
  const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier);
1584
2579
  const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
2580
+ const defaultResourceScopes = providerConfig.resourceScopes || (Array.isArray(providerConfig.scopes) ? void 0 : providerConfig.scopes?.resource) || [];
2581
+ const effectiveScopes = scopes || (tokens.scope ? tokens.scope.split(" ") : defaultResourceScopes);
1585
2582
  const connectedResource = {
1586
2583
  provider: providerType,
1587
2584
  accessToken: tokens.access_token,
1588
2585
  refreshToken: tokens.refresh_token,
1589
2586
  expiresAt,
1590
- scopes: scopes || (tokens.scope ? tokens.scope.split(" ") : []),
2587
+ scopes: effectiveScopes,
1591
2588
  raw: tokens,
1592
2589
  connectedAt: Date.now()
1593
2590
  };
@@ -1599,6 +2596,45 @@ var Lixa = class _Lixa {
1599
2596
  await sessionStorage.saveSession(sessionId, activeSession, 86400);
1600
2597
  return activeSession;
1601
2598
  }
2599
+ /**
2600
+ * Retrieves configured default resource scopes for a provider.
2601
+ *
2602
+ * @param provider - Provider name (e.g. 'github', 'google')
2603
+ * @returns Array of configured resource scopes, or undefined if not configured
2604
+ */
2605
+ getResourceScopes(provider) {
2606
+ const providerConfig = this.findProviderByType(String(provider).toLowerCase());
2607
+ if (!providerConfig) return void 0;
2608
+ if (providerConfig.resourceScopes) return providerConfig.resourceScopes;
2609
+ if (providerConfig.scopes && !Array.isArray(providerConfig.scopes)) {
2610
+ return providerConfig.scopes.resource;
2611
+ }
2612
+ return void 0;
2613
+ }
2614
+ /**
2615
+ * Returns the resolved session cookie name.
2616
+ * Dynamically reflects any changes to the active identity provider.
2617
+ *
2618
+ * @param req - Optional request object for multi-tenant request-scoped cookie resolution
2619
+ * @returns Resolved session cookie name
2620
+ */
2621
+ getSessionCookieName(req) {
2622
+ if (typeof this.sessionCookieResolver === "function") {
2623
+ return this.sessionCookieResolver(this, req);
2624
+ }
2625
+ if (typeof this.sessionCookieResolver === "string" && this.sessionCookieResolver.length > 0) {
2626
+ return this.sessionCookieResolver;
2627
+ }
2628
+ return DEFAULT_SESSION_COOKIE_NAME;
2629
+ }
2630
+ /**
2631
+ * Sets or updates the session cookie name or dynamic resolver on this instance.
2632
+ *
2633
+ * @param resolver - Static cookie name or dynamic resolver function
2634
+ */
2635
+ setSessionCookieName(resolver) {
2636
+ this.sessionCookieResolver = resolver;
2637
+ }
1602
2638
  /**
1603
2639
  * Retrieves a connected resource for a specific User ID / Email directly (independent of session IDs).
1604
2640
  * Automatically refreshes expired access tokens if a refresh token is present.
@@ -1779,7 +2815,32 @@ var Lixa = class _Lixa {
1779
2815
  */
1780
2816
  async fetchSessionInfo(sessionId) {
1781
2817
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1782
- return await sessionStorage.getSession(sessionId);
2818
+ const session = await sessionStorage.getSession(sessionId);
2819
+ if (!session) return null;
2820
+ if (session.accounts) {
2821
+ delete session.accounts;
2822
+ }
2823
+ return session;
2824
+ }
2825
+ /**
2826
+ * Retrieves all linked accounts for a user from account storage.
2827
+ *
2828
+ * @param userIdOrSessionId - User ID, email, or active session ID
2829
+ * @returns Record mapping provider name to linked account metadata
2830
+ */
2831
+ async getUserAccounts(userIdOrSessionId) {
2832
+ let userKey = userIdOrSessionId;
2833
+ if (this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage) {
2834
+ const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
2835
+ const session = await sessionStorage.getSession(userIdOrSessionId);
2836
+ if (session) {
2837
+ userKey = this.getUserKeyFromSession(session);
2838
+ }
2839
+ }
2840
+ if (this.accountHandler?.accountStorage) {
2841
+ return await this.accountHandler.accountStorage.getUserAccounts(userKey) || {};
2842
+ }
2843
+ return {};
1783
2844
  }
1784
2845
  /**
1785
2846
  * Deletes a session from session storage (e.g. on logout).
@@ -1830,36 +2891,91 @@ var Lixa = class _Lixa {
1830
2891
  return response.json();
1831
2892
  }
1832
2893
  /**
1833
- * Checks if credentials (username and password) authentication is configured and enabled.
2894
+ * Checks if an identity provider (Self-Managed Database, AWS Cognito, Auth0, Okta, etc.) is configured.
2895
+ *
2896
+ * @returns True if identity provider is configured and available
2897
+ */
2898
+ isIdentityEnabled() {
2899
+ return this.identityProvider !== void 0;
2900
+ }
2901
+ /**
2902
+ * Checks if credentials / identity authentication is configured and enabled.
1834
2903
  *
1835
- * @returns True if credentials authentication is available
2904
+ * @returns True if credentials / identity authentication is available
1836
2905
  */
1837
2906
  isCredentialsEnabled() {
1838
- return this.credentialsManager !== void 0;
2907
+ return this.identityProvider !== void 0;
1839
2908
  }
1840
2909
  /**
1841
- * Returns the underlying CredentialsManager instance if configured.
2910
+ * Returns the configured IdentityProvider instance.
2911
+ */
2912
+ getIdentityProvider() {
2913
+ return this.identityProvider;
2914
+ }
2915
+ /**
2916
+ * Dynamically sets or updates the active identity provider (e.g. SelfManaged, Cognito, Auth0, Okta).
2917
+ *
2918
+ * @param provider - Identity provider instance or undefined to disable
2919
+ */
2920
+ setIdentityProvider(provider) {
2921
+ this.identityProvider = provider;
2922
+ if (provider instanceof SelfManagedIdentityProvider) {
2923
+ this.credentialsManager = provider.getCredentialsManager();
2924
+ } else {
2925
+ this.credentialsManager = void 0;
2926
+ }
2927
+ }
2928
+ /**
2929
+ * Returns the underlying CredentialsManager instance if using Self-Managed identity.
1842
2930
  */
1843
2931
  getCredentialsManager() {
1844
2932
  return this.credentialsManager;
1845
2933
  }
1846
2934
  /**
1847
- * Registers a new user with username/email and password.
1848
- * Automatically enforces password policy, hashes password with Scrypt/configured hasher,
1849
- * stores credentials, and creates a session (unless autoCreateSessionOnSignUp is false).
2935
+ * Registers a new user with the configured identity provider (Self-Managed DB, Cognito, Auth0, Okta).
2936
+ * Automatically creates an active session unless autoCreateSessionOnSignUp is explicitly set to false.
1850
2937
  *
1851
2938
  * @param params - Registration parameters (identifier, password, email, username, metadata)
1852
- * @returns Created user (without password hash) and optional active session
2939
+ * @returns Created user identity and optional active session
1853
2940
  * @throws WeakPasswordError if password does not meet policy requirements
1854
2941
  * @throws UserAlreadyExistsError if identifier is already registered
1855
- * @throws CredentialsNotConfiguredError if credentials auth is not configured
2942
+ * @throws IdentityNotConfiguredError if identity authentication is not configured
1856
2943
  */
1857
2944
  async signUp(params) {
1858
- if (!this.credentialsManager) {
1859
- throw new CredentialsNotConfiguredError();
2945
+ if (!this.identityProvider) {
2946
+ throw new IdentityNotConfiguredError();
2947
+ }
2948
+ const rawProviderName = params.provider || this.identityProvider.name;
2949
+ const providerName = rawProviderName === "self-managed" ? "credentials" : rawProviderName;
2950
+ this.log("INFO", "Identity", `Processing signUp request via ${providerName}`, { identifier: params.identifier });
2951
+ const signUpEmail = params.email || (params.identifier?.includes("@") ? params.identifier : void 0);
2952
+ if (signUpEmail && this.accountHandler?.accountStorage?.findAccountsByEmail) {
2953
+ const normalizedEmail = signUpEmail.trim().toLowerCase();
2954
+ const existingAccounts = await this.accountHandler.accountStorage.findAccountsByEmail(normalizedEmail);
2955
+ if (existingAccounts.length > 0) {
2956
+ const existingProviderNames = existingAccounts.map((a) => a.provider);
2957
+ const hasCredentials = existingProviderNames.some(
2958
+ (p) => p === "credentials" || p === "self-managed" || p === "cognito"
2959
+ );
2960
+ if (!hasCredentials) {
2961
+ this.log("WARN", "Identity", `SignUp blocked: SSO account already exists for '${normalizedEmail}' via [${existingProviderNames.join(", ")}]`);
2962
+ throw new SSOAccountAlreadyExistsError(normalizedEmail, existingProviderNames);
2963
+ }
2964
+ }
2965
+ }
2966
+ const user = await this.identityProvider.signUp(params);
2967
+ const isVerificationRequired = user.metadata?.requiresVerification === true || user.emailVerified === false && (user.metadata?.devConfirmationCode !== void 0 || this.identityProvider.name === "self-managed" || this.identityProvider.name === "cognito");
2968
+ if (isVerificationRequired) {
2969
+ this.log("INFO", "Identity", "SignUp requires verification \u2014 no session created", {
2970
+ identifier: user.identifier,
2971
+ email: user.email
2972
+ });
2973
+ return {
2974
+ user,
2975
+ requiresVerification: true,
2976
+ devConfirmationCode: user.metadata?.devConfirmationCode
2977
+ };
1860
2978
  }
1861
- this.log("INFO", "Credentials", "Processing signUp request", { identifier: params.identifier });
1862
- const user = await this.credentialsManager.signUp(params);
1863
2979
  const autoSession = this.config.credentials?.autoCreateSessionOnSignUp ?? true;
1864
2980
  if (!autoSession) {
1865
2981
  return { user };
@@ -1867,80 +2983,86 @@ var Lixa = class _Lixa {
1867
2983
  const sessionId = randomBytes2(16).toString("hex");
1868
2984
  const token = randomBytes2(32).toString("hex");
1869
2985
  const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
2986
+ const activeProviderName = params.provider || user && user.provider || (this.identityProvider instanceof CompositeIdentityProvider ? this.identityProvider.defaultProvider : this.identityProvider.name);
2987
+ const accountKey = activeProviderName === "self-managed" ? "credentials" : activeProviderName || "credentials";
1870
2988
  const credentialsAccount = {
1871
- provider: "credentials",
2989
+ provider: accountKey,
1872
2990
  providerUserId: user.id,
1873
2991
  email: user.email,
1874
- accessToken: token,
1875
- raw: {
1876
- access_token: token,
1877
- token_type: "Bearer",
1878
- expires_in: ttl
1879
- },
1880
2992
  linkedAt: Date.now()
1881
2993
  };
1882
2994
  const session = {
1883
- id: sessionId,
2995
+ sessionId,
1884
2996
  userId: user.id,
1885
2997
  email: user.email,
1886
- token,
1887
- provider: "credentials",
1888
- accounts: {
1889
- credentials: credentialsAccount
1890
- }
2998
+ provider: accountKey
1891
2999
  };
3000
+ if (this.accountHandler?.accountStorage) {
3001
+ const userKey = this.getUserKeyFromSession(session);
3002
+ await this.accountHandler.accountStorage.saveAccount(userKey, accountKey, this.sanitizeIdentityForStorage(credentialsAccount));
3003
+ }
1892
3004
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1893
3005
  await sessionStorage.saveSession(sessionId, session, ttl);
1894
- this.log("INFO", "Credentials", "Created session on signUp", { userId: user.id, sessionId });
3006
+ this.log("INFO", "Identity", "Created session on signUp", { userId: user.id, sessionId, provider: accountKey });
1895
3007
  return { user, sessionId, session };
1896
3008
  }
1897
3009
  /**
1898
- * Authenticates a user with username/email and password.
1899
- * Performs constant-time verification with timing attack mitigation, and creates an active session.
1900
- * If account linking is configured with AUTO_LINK_BY_VERIFIED_EMAIL, merges with existing session.
3010
+ * Authenticates a user with credentials via the configured identity provider (Self-Managed DB, Cognito, Auth0, Okta, or Composite).
3011
+ * Creates an active session and automatically links with existing sessions when AUTO_LINK_BY_VERIFIED_EMAIL is configured.
1901
3012
  *
1902
- * @param params - Sign-in parameters (identifier, password)
3013
+ * @param params - Sign-in parameters (identifier, password, optional provider)
1903
3014
  * @returns Authenticated user, session ID, and session object
1904
3015
  * @throws InvalidCredentialsError if authentication fails
1905
- * @throws CredentialsNotConfiguredError if credentials auth is not configured
3016
+ * @throws IdentityNotConfiguredError if identity authentication is not configured
1906
3017
  */
1907
3018
  async signIn(params) {
1908
- if (!this.credentialsManager) {
1909
- throw new CredentialsNotConfiguredError();
3019
+ if (!this.identityProvider) {
3020
+ throw new IdentityNotConfiguredError();
1910
3021
  }
1911
3022
  const lookupKey = params.username || params.identifier;
1912
- this.log("INFO", "Credentials", "Processing signIn request", { identifier: lookupKey });
1913
- const user = await this.credentialsManager.verifyCredentials(params);
1914
- if (!user) {
1915
- this.log("WARN", "Credentials", "Invalid credentials provided", { identifier: lookupKey });
1916
- throw new InvalidCredentialsError();
1917
- }
3023
+ const rawProviderName = params.provider || this.identityProvider.name;
3024
+ const providerName = rawProviderName === "self-managed" ? "credentials" : rawProviderName;
3025
+ this.log("INFO", "Identity", `Processing signIn request via ${providerName}`, { identifier: lookupKey });
3026
+ const authResult = await this.identityProvider.signIn(params);
3027
+ const user = authResult.user;
3028
+ const tokens = authResult.tokens;
1918
3029
  const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
1919
- const token = randomBytes2(32).toString("hex");
3030
+ const token = tokens?.accessToken || randomBytes2(32).toString("hex");
3031
+ const rawTokens = {
3032
+ access_token: token,
3033
+ token_type: tokens?.tokenType || "Bearer",
3034
+ expires_in: tokens?.expiresIn || ttl,
3035
+ id_token: tokens?.idToken,
3036
+ refresh_token: tokens?.refreshToken,
3037
+ ...tokens?.raw
3038
+ };
3039
+ const activeProviderName = params.provider || user && user.provider || (this.identityProvider instanceof CompositeIdentityProvider ? this.identityProvider.defaultProvider : this.identityProvider.name);
3040
+ const accountKey = activeProviderName === "self-managed" ? "credentials" : activeProviderName || "credentials";
1920
3041
  const credentialsAccount = {
1921
- provider: "credentials",
3042
+ provider: accountKey,
1922
3043
  providerUserId: user.id,
1923
3044
  email: user.email,
1924
- accessToken: token,
1925
- raw: {
1926
- access_token: token,
1927
- token_type: "Bearer",
1928
- expires_in: ttl
1929
- },
1930
3045
  linkedAt: Date.now()
1931
3046
  };
1932
3047
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1933
- const linkingMode = this.config.accountLinking?.mode;
1934
- const isAutoLink = linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" || linkingMode === "linkByEmail";
3048
+ const linkingConfig = this.config.accountLinking;
3049
+ const linkingMode = typeof linkingConfig === "object" ? linkingConfig?.mode : void 0;
3050
+ const isAutoLink = linkingConfig === true || linkingConfig === "linkByEmail" || linkingConfig === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" || linkingMode === "linkByEmail";
1935
3051
  if (isAutoLink && user.email && sessionStorage.getSessionByEmail) {
1936
3052
  const existing = await sessionStorage.getSessionByEmail(user.email);
1937
3053
  if (existing) {
1938
3054
  const mergedSession = { ...existing.session };
1939
- mergedSession.accounts = { ...mergedSession.accounts || {}, credentials: credentialsAccount };
1940
3055
  if (!mergedSession.userId) mergedSession.userId = user.id;
1941
3056
  if (!mergedSession.email) mergedSession.email = user.email;
3057
+ mergedSession.provider = accountKey;
3058
+ mergedSession.token = token;
3059
+ mergedSession.raw = rawTokens;
3060
+ if (this.accountHandler?.accountStorage) {
3061
+ const userKey = this.getUserKeyFromSession(mergedSession);
3062
+ await this.accountHandler.accountStorage.saveAccount(userKey, accountKey, this.sanitizeIdentityForStorage(credentialsAccount));
3063
+ }
1942
3064
  await sessionStorage.saveSession(existing.sessionId, mergedSession, ttl);
1943
- this.log("INFO", "AccountLinking", "Linked credentials to existing session by email", {
3065
+ this.log("INFO", "AccountLinking", `Linked ${providerName} to existing session by email`, {
1944
3066
  sessionId: existing.sessionId,
1945
3067
  email: user.email
1946
3068
  });
@@ -1952,52 +3074,228 @@ var Lixa = class _Lixa {
1952
3074
  }
1953
3075
  }
1954
3076
  const sessionId = randomBytes2(16).toString("hex");
3077
+ let targetUserId = user.id;
3078
+ const allAccounts = {
3079
+ [accountKey]: credentialsAccount
3080
+ };
3081
+ if (this.accountHandler?.accountStorage) {
3082
+ if (user.email && this.accountHandler.accountStorage.findAccountsByEmail) {
3083
+ const matches = await this.accountHandler.accountStorage.findAccountsByEmail(user.email);
3084
+ if (matches.length > 0) {
3085
+ targetUserId = matches[0].userId;
3086
+ for (const match of matches) {
3087
+ const userAccs = await this.accountHandler.accountStorage.getUserAccounts(match.userId);
3088
+ if (userAccs) {
3089
+ for (const [k, v] of Object.entries(userAccs)) {
3090
+ if (k !== accountKey && !allAccounts[k]) {
3091
+ allAccounts[k] = v;
3092
+ }
3093
+ }
3094
+ }
3095
+ }
3096
+ }
3097
+ }
3098
+ const directAccounts = await this.accountHandler.accountStorage.getUserAccounts(user.id);
3099
+ if (directAccounts) {
3100
+ for (const [k, v] of Object.entries(directAccounts)) {
3101
+ if (k !== accountKey && !allAccounts[k]) {
3102
+ allAccounts[k] = v;
3103
+ }
3104
+ }
3105
+ }
3106
+ await this.accountHandler.accountStorage.saveAccount(
3107
+ targetUserId,
3108
+ accountKey,
3109
+ this.sanitizeIdentityForStorage(credentialsAccount)
3110
+ );
3111
+ for (const [pKey, pAcc] of Object.entries(allAccounts)) {
3112
+ if (pKey !== accountKey) {
3113
+ await this.accountHandler.accountStorage.saveAccount(
3114
+ targetUserId,
3115
+ pKey,
3116
+ this.sanitizeIdentityForStorage(pAcc)
3117
+ );
3118
+ }
3119
+ }
3120
+ }
1955
3121
  const session = {
1956
- id: sessionId,
1957
- userId: user.id,
3122
+ sessionId,
3123
+ userId: targetUserId,
1958
3124
  email: user.email,
3125
+ provider: accountKey,
1959
3126
  token,
1960
- provider: "credentials",
1961
- accounts: {
1962
- credentials: credentialsAccount
1963
- }
3127
+ raw: rawTokens
1964
3128
  };
1965
3129
  await sessionStorage.saveSession(sessionId, session, ttl);
1966
- this.log("INFO", "Credentials", "User signed in successfully", { userId: user.id, sessionId });
3130
+ this.log("INFO", "Identity", `User signed in successfully via ${accountKey}`, { userId: targetUserId, sessionId });
1967
3131
  return { user, sessionId, session };
1968
3132
  }
1969
3133
  /**
1970
3134
  * Verifies username/email and password credentials without generating a session.
1971
3135
  *
1972
- * @param params - Verification parameters (identifier, password)
1973
- * @returns User credentials (without password hash) or null if invalid
1974
- * @throws CredentialsNotConfiguredError if credentials auth is not configured
3136
+ * @param params - Verification parameters (identifier, password, optional provider)
3137
+ * @returns User credentials/identity or null if invalid
3138
+ * @throws IdentityNotConfiguredError if identity auth is not configured
1975
3139
  */
1976
3140
  async verifyCredentials(params) {
1977
- if (!this.credentialsManager) {
1978
- throw new CredentialsNotConfiguredError();
3141
+ if (!this.identityProvider) {
3142
+ throw new IdentityNotConfiguredError();
3143
+ }
3144
+ if (typeof this.identityProvider.verifyCredentials === "function") {
3145
+ return this.identityProvider.verifyCredentials(params);
3146
+ }
3147
+ try {
3148
+ const result = await this.identityProvider.signIn(params);
3149
+ return result.user;
3150
+ } catch {
3151
+ return null;
1979
3152
  }
1980
- return this.credentialsManager.verifyCredentials(params);
1981
3153
  }
1982
3154
  /**
1983
3155
  * Updates a user's password after verifying the current password and enforcing policy on the new password.
1984
3156
  *
1985
- * @param params - Change password parameters (userId/identifier, oldPassword, newPassword)
3157
+ * @param params - Change password parameters (userId/identifier, oldPassword, newPassword, optional provider)
1986
3158
  * @returns True if password was updated successfully
1987
3159
  * @throws UserNotFoundError if user is not found
1988
3160
  * @throws InvalidCredentialsError if current password is incorrect
1989
3161
  * @throws WeakPasswordError if new password does not meet policy requirements
1990
- * @throws CredentialsNotConfiguredError if credentials auth is not configured
3162
+ * @throws IdentityNotConfiguredError if identity auth is not configured
1991
3163
  */
1992
3164
  async changePassword(params) {
1993
- if (!this.credentialsManager) {
1994
- throw new CredentialsNotConfiguredError();
3165
+ if (!this.identityProvider) {
3166
+ throw new IdentityNotConfiguredError();
1995
3167
  }
1996
- this.log("INFO", "Credentials", "Processing changePassword request", {
3168
+ if (typeof this.identityProvider.changePassword !== "function") {
3169
+ throw new IdentityProviderError(
3170
+ `Password change is not supported by the '${this.identityProvider.name}' identity provider`,
3171
+ this.identityProvider.name
3172
+ );
3173
+ }
3174
+ this.log("INFO", "Identity", "Processing changePassword request", {
1997
3175
  userId: params.userId,
1998
3176
  identifier: params.identifier
1999
3177
  });
2000
- return this.credentialsManager.changePassword(params);
3178
+ return this.identityProvider.changePassword(params);
3179
+ }
3180
+ /**
3181
+ * Initiates a password reset (forgot password flow) through the configured identity provider.
3182
+ *
3183
+ * @param params - Forgot password parameters (identifier, optional metadata, optional provider)
3184
+ * @returns ForgotPasswordResult indicating status and delivery medium (email/SMS)
3185
+ * @throws IdentityNotConfiguredError if identity auth is not configured
3186
+ */
3187
+ async forgotPassword(params) {
3188
+ if (!this.identityProvider) {
3189
+ throw new IdentityNotConfiguredError();
3190
+ }
3191
+ if (typeof this.identityProvider.forgotPassword !== "function") {
3192
+ throw new IdentityProviderError(
3193
+ `Password reset request is not supported by the '${this.identityProvider.name}' identity provider`,
3194
+ this.identityProvider.name
3195
+ );
3196
+ }
3197
+ this.log("INFO", "Identity", "Processing forgotPassword request", { identifier: params.identifier });
3198
+ return this.identityProvider.forgotPassword(params);
3199
+ }
3200
+ /**
3201
+ * Confirms a password reset using a verification code / token and sets the new password.
3202
+ *
3203
+ * @param params - Verification code, identifier, new password, and optional provider
3204
+ * @returns True if password reset was successfully confirmed
3205
+ * @throws IdentityNotConfiguredError if identity auth is not configured
3206
+ */
3207
+ async confirmPasswordReset(params) {
3208
+ if (!this.identityProvider) {
3209
+ throw new IdentityNotConfiguredError();
3210
+ }
3211
+ if (typeof this.identityProvider.confirmPasswordReset !== "function") {
3212
+ throw new IdentityProviderError(
3213
+ `Confirm password reset is not supported by the '${this.identityProvider.name}' identity provider`,
3214
+ this.identityProvider.name
3215
+ );
3216
+ }
3217
+ this.log("INFO", "Identity", "Processing confirmPasswordReset request", { identifier: params.identifier });
3218
+ return this.identityProvider.confirmPasswordReset(params);
3219
+ }
3220
+ /**
3221
+ * Confirms user account registration using a verification code.
3222
+ *
3223
+ * @param params - Verification parameters including identifier, confirmationCode, and optional provider
3224
+ * @returns True if confirmation was successful
3225
+ * @throws IdentityNotConfiguredError if identity auth is not configured
3226
+ * @throws IdentityProviderError if identity provider does not support confirmation
3227
+ */
3228
+ async confirmSignUp(params) {
3229
+ if (!this.identityProvider) {
3230
+ throw new IdentityNotConfiguredError();
3231
+ }
3232
+ if (typeof this.identityProvider.confirmSignUp !== "function") {
3233
+ throw new IdentityProviderError(
3234
+ `Account confirmation is not supported by the '${this.identityProvider.name}' identity provider`,
3235
+ this.identityProvider.name
3236
+ );
3237
+ }
3238
+ this.log("INFO", "Identity", "Processing confirmSignUp request", { identifier: params.identifier });
3239
+ return this.identityProvider.confirmSignUp(params);
3240
+ }
3241
+ /**
3242
+ * Resends the sign up confirmation code to the user.
3243
+ *
3244
+ * @param params - Resend parameters including user identifier and optional provider
3245
+ * @returns Details about the code delivery (medium, destination)
3246
+ * @throws IdentityNotConfiguredError if identity auth is not configured
3247
+ * @throws IdentityProviderError if identity provider does not support resending codes
3248
+ */
3249
+ async resendConfirmationCode(params) {
3250
+ if (!this.identityProvider) {
3251
+ throw new IdentityNotConfiguredError();
3252
+ }
3253
+ if (typeof this.identityProvider.resendConfirmationCode !== "function") {
3254
+ throw new IdentityProviderError(
3255
+ `Resending confirmation code is not supported by the '${this.identityProvider.name}' identity provider`,
3256
+ this.identityProvider.name
3257
+ );
3258
+ }
3259
+ this.log("INFO", "Identity", "Processing resendConfirmationCode request", { identifier: params.identifier });
3260
+ return this.identityProvider.resendConfirmationCode(params);
3261
+ }
3262
+ /**
3263
+ * Retrieves a user by their unique ID from the configured identity provider.
3264
+ *
3265
+ * @param id - Unique user ID
3266
+ * @param provider - Optional provider key when using CompositeIdentityProvider
3267
+ * @returns UserIdentity or null if not found
3268
+ */
3269
+ async getUserById(id, provider) {
3270
+ if (!this.identityProvider || typeof this.identityProvider.getUserById !== "function") {
3271
+ return null;
3272
+ }
3273
+ return this.identityProvider.getUserById(id, provider);
3274
+ }
3275
+ /**
3276
+ * Retrieves a user by their identifier (username or email) from the configured identity provider.
3277
+ *
3278
+ * @param identifier - Username or email string
3279
+ * @param provider - Optional provider key when using CompositeIdentityProvider
3280
+ * @returns UserIdentity or null if not found
3281
+ */
3282
+ async getUserByIdentifier(identifier, provider) {
3283
+ if (!this.identityProvider || typeof this.identityProvider.getUserByIdentifier !== "function") {
3284
+ return null;
3285
+ }
3286
+ return this.identityProvider.getUserByIdentifier(identifier, provider);
3287
+ }
3288
+ /**
3289
+ * Deletes a user account from the configured identity provider.
3290
+ *
3291
+ * @param id - Unique user ID
3292
+ * @param provider - Optional provider key when using CompositeIdentityProvider
3293
+ */
3294
+ async deleteUser(id, provider) {
3295
+ if (!this.identityProvider || typeof this.identityProvider.deleteUser !== "function") {
3296
+ return;
3297
+ }
3298
+ await this.identityProvider.deleteUser(id, provider);
2001
3299
  }
2002
3300
  findProviderByType(providerType) {
2003
3301
  for (const [key, value] of Object.entries(this.config.providers || {})) {
@@ -2009,125 +3307,6 @@ var Lixa = class _Lixa {
2009
3307
  }
2010
3308
  };
2011
3309
 
2012
- // src/utils/cookies.ts
2013
- var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
2014
- var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
2015
- var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
2016
- var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
2017
- function isProductionEnvironment() {
2018
- return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
2019
- }
2020
- function serializeCookie(name, value, options) {
2021
- const isProd = isProductionEnvironment();
2022
- const path = options?.path ?? "/";
2023
- const httpOnly = options?.httpOnly ?? true;
2024
- const secure = options?.secure ?? isProd;
2025
- const sameSite = options?.sameSite ?? "lax";
2026
- const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
2027
- if (path) {
2028
- parts.push(`Path=${path}`);
2029
- }
2030
- if (typeof options?.maxAge === "number") {
2031
- parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
2032
- const expires = new Date(Date.now() + options.maxAge * 1e3).toUTCString();
2033
- parts.push(`Expires=${expires}`);
2034
- }
2035
- if (options?.domain) {
2036
- parts.push(`Domain=${options.domain}`);
2037
- }
2038
- if (httpOnly) {
2039
- parts.push("HttpOnly");
2040
- }
2041
- if (secure) {
2042
- parts.push("Secure");
2043
- }
2044
- if (sameSite) {
2045
- const capitalized = sameSite.charAt(0).toUpperCase() + sameSite.slice(1).toLowerCase();
2046
- parts.push(`SameSite=${capitalized}`);
2047
- }
2048
- return parts.join("; ");
2049
- }
2050
- function createSessionCookie(sessionId, options) {
2051
- const isProd = isProductionEnvironment();
2052
- const resolvedOptions = {
2053
- name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
2054
- path: options?.path ?? "/",
2055
- maxAge: options?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
2056
- httpOnly: options?.httpOnly ?? true,
2057
- secure: options?.secure ?? isProd,
2058
- sameSite: options?.sameSite ?? "lax",
2059
- domain: options?.domain
2060
- };
2061
- const name = resolvedOptions.name;
2062
- const header = serializeCookie(name, sessionId, resolvedOptions);
2063
- return {
2064
- name,
2065
- value: sessionId,
2066
- options: resolvedOptions,
2067
- header
2068
- };
2069
- }
2070
- function clearSessionCookie(options) {
2071
- const isProd = isProductionEnvironment();
2072
- const resolvedOptions = {
2073
- name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
2074
- path: options?.path ?? "/",
2075
- maxAge: 0,
2076
- httpOnly: options?.httpOnly ?? true,
2077
- secure: options?.secure ?? isProd,
2078
- sameSite: options?.sameSite ?? "lax",
2079
- domain: options?.domain
2080
- };
2081
- const name = resolvedOptions.name;
2082
- const header = serializeCookie(name, "", resolvedOptions);
2083
- return {
2084
- name,
2085
- value: "",
2086
- options: resolvedOptions,
2087
- header
2088
- };
2089
- }
2090
- function createStateCookie(state, options) {
2091
- const isProd = isProductionEnvironment();
2092
- const resolvedOptions = {
2093
- name: options?.name || DEFAULT_STATE_COOKIE_NAME,
2094
- path: options?.path ?? "/",
2095
- maxAge: options?.maxAge ?? DEFAULT_STATE_MAX_AGE_SECONDS,
2096
- httpOnly: options?.httpOnly ?? true,
2097
- secure: options?.secure ?? isProd,
2098
- sameSite: options?.sameSite ?? "lax",
2099
- domain: options?.domain
2100
- };
2101
- const name = resolvedOptions.name;
2102
- const header = serializeCookie(name, state, resolvedOptions);
2103
- return {
2104
- name,
2105
- value: state,
2106
- options: resolvedOptions,
2107
- header
2108
- };
2109
- }
2110
- function clearStateCookie(options) {
2111
- const isProd = isProductionEnvironment();
2112
- const resolvedOptions = {
2113
- name: options?.name || DEFAULT_STATE_COOKIE_NAME,
2114
- path: options?.path ?? "/",
2115
- maxAge: 0,
2116
- httpOnly: options?.httpOnly ?? true,
2117
- secure: options?.secure ?? isProd,
2118
- sameSite: options?.sameSite ?? "lax",
2119
- domain: options?.domain
2120
- };
2121
- const name = resolvedOptions.name;
2122
- const header = serializeCookie(name, "", resolvedOptions);
2123
- return {
2124
- name,
2125
- value: "",
2126
- options: resolvedOptions,
2127
- header
2128
- };
2129
- }
2130
-
2131
3310
  // src/credentials/index.ts
2132
3311
  var credentials_exports = {};
2133
3312
  __export(credentials_exports, {
@@ -2137,9 +3316,19 @@ __export(credentials_exports, {
2137
3316
  ScryptPasswordHasher: () => ScryptPasswordHasher,
2138
3317
  validatePasswordPolicy: () => validatePasswordPolicy
2139
3318
  });
3319
+
3320
+ // src/identity/index.ts
3321
+ var identity_exports = {};
3322
+ __export(identity_exports, {
3323
+ CompositeIdentityProvider: () => CompositeIdentityProvider,
3324
+ SelfManagedIdentityProvider: () => SelfManagedIdentityProvider
3325
+ });
2140
3326
  export {
3327
+ AccountLinkingChallengeRequiredError,
2141
3328
  AccountLinkingStrategy,
3329
+ AccountNotVerifiedError,
2142
3330
  AccountUnlinkError,
3331
+ CompositeIdentityProvider,
2143
3332
  CredentialsManager,
2144
3333
  CredentialsNotConfiguredError,
2145
3334
  DEFAULT_SESSION_COOKIE_NAME,
@@ -2147,6 +3336,8 @@ export {
2147
3336
  DEFAULT_STATE_COOKIE_NAME,
2148
3337
  DEFAULT_STATE_MAX_AGE_SECONDS,
2149
3338
  EmailNotVerifiedError,
3339
+ IdentityNotConfiguredError,
3340
+ IdentityProviderError,
2150
3341
  InvalidCredentialsError,
2151
3342
  InvalidOAuthCallbackError,
2152
3343
  InvalidProviderConfigError,
@@ -2157,7 +3348,9 @@ export {
2157
3348
  Pbkdf2PasswordHasher,
2158
3349
  ProviderNotConfiguredError,
2159
3350
  RefreshTokenError,
3351
+ SSOAccountAlreadyExistsError,
2160
3352
  ScryptPasswordHasher,
3353
+ SelfManagedIdentityProvider,
2161
3354
  SessionNotFoundError,
2162
3355
  TokenExchangeError,
2163
3356
  UserAlreadyExistsError,
@@ -2172,6 +3365,7 @@ export {
2172
3365
  determineProviderFromIssuer,
2173
3366
  extractUserInfo,
2174
3367
  fetchUserInfo,
3368
+ identity_exports as identity,
2175
3369
  isProductionEnvironment,
2176
3370
  serializeCookie,
2177
3371
  validatePasswordPolicy