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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -39,6 +39,8 @@ __export(src_exports, {
39
39
  DEFAULT_STATE_COOKIE_NAME: () => DEFAULT_STATE_COOKIE_NAME,
40
40
  DEFAULT_STATE_MAX_AGE_SECONDS: () => DEFAULT_STATE_MAX_AGE_SECONDS,
41
41
  EmailNotVerifiedError: () => EmailNotVerifiedError,
42
+ IdentityNotConfiguredError: () => IdentityNotConfiguredError,
43
+ IdentityProviderError: () => IdentityProviderError,
42
44
  InvalidCredentialsError: () => InvalidCredentialsError,
43
45
  InvalidOAuthCallbackError: () => InvalidOAuthCallbackError,
44
46
  InvalidProviderConfigError: () => InvalidProviderConfigError,
@@ -50,6 +52,7 @@ __export(src_exports, {
50
52
  ProviderNotConfiguredError: () => ProviderNotConfiguredError,
51
53
  RefreshTokenError: () => RefreshTokenError,
52
54
  ScryptPasswordHasher: () => ScryptPasswordHasher,
55
+ SelfManagedIdentityProvider: () => SelfManagedIdentityProvider,
53
56
  SessionNotFoundError: () => SessionNotFoundError,
54
57
  TokenExchangeError: () => TokenExchangeError,
55
58
  UserAlreadyExistsError: () => UserAlreadyExistsError,
@@ -64,6 +67,7 @@ __export(src_exports, {
64
67
  determineProviderFromIssuer: () => determineProviderFromIssuer,
65
68
  extractUserInfo: () => extractUserInfo,
66
69
  fetchUserInfo: () => fetchUserInfo,
70
+ identity: () => identity_exports,
67
71
  isProductionEnvironment: () => isProductionEnvironment,
68
72
  serializeCookie: () => serializeCookie,
69
73
  validatePasswordPolicy: () => validatePasswordPolicy
@@ -453,10 +457,24 @@ var WeakPasswordError = class extends LixaError {
453
457
  }
454
458
  };
455
459
  var CredentialsNotConfiguredError = class extends LixaError {
456
- constructor(message = "Credentials authentication is not configured in this Lixa instance. Pass credentials: {} in LixaConfig to enable it.", details) {
460
+ constructor(message = "Credentials authentication is not configured in this Lixa instance. Pass credentials: {} or identity: ... in LixaConfig to enable it.", details) {
457
461
  super(message, "CREDENTIALS_NOT_CONFIGURED", details);
458
462
  }
459
463
  };
464
+ var IdentityProviderError = class extends LixaError {
465
+ provider;
466
+ statusCode;
467
+ constructor(message, provider, statusCode, details) {
468
+ super(message, "IDENTITY_PROVIDER_ERROR", { provider, statusCode, ...details });
469
+ this.provider = provider;
470
+ this.statusCode = statusCode;
471
+ }
472
+ };
473
+ var IdentityNotConfiguredError = class extends CredentialsNotConfiguredError {
474
+ constructor(message = "Identity provider is not configured in this Lixa instance. Pass identity: ... or credentials: {} in LixaConfig to enable it.", details) {
475
+ super(message, details);
476
+ }
477
+ };
460
478
 
461
479
  // src/credentials/credentials-manager.ts
462
480
  var CredentialsManager = class {
@@ -652,6 +670,94 @@ var CredentialsManager = class {
652
670
  }
653
671
  };
654
672
 
673
+ // src/identity/self-managed.ts
674
+ var SelfManagedIdentityProvider = class {
675
+ name = "self-managed";
676
+ manager;
677
+ constructor(configOrManager) {
678
+ if (configOrManager instanceof CredentialsManager) {
679
+ this.manager = configOrManager;
680
+ } else {
681
+ this.manager = new CredentialsManager(configOrManager || {});
682
+ }
683
+ }
684
+ mapUser(user) {
685
+ return {
686
+ id: user.id,
687
+ identifier: user.identifier,
688
+ username: user.username,
689
+ email: user.email,
690
+ emailVerified: true,
691
+ // Self-managed accounts verify locally unless configured otherwise
692
+ createdAt: user.createdAt,
693
+ updatedAt: user.updatedAt,
694
+ metadata: user.metadata,
695
+ provider: this.name
696
+ };
697
+ }
698
+ /**
699
+ * Registers a new user.
700
+ */
701
+ async signUp(params) {
702
+ const user = await this.manager.signUp(params);
703
+ return this.mapUser(user);
704
+ }
705
+ /**
706
+ * Authenticates a user with password.
707
+ */
708
+ async signIn(params) {
709
+ const user = await this.manager.verifyCredentials(params);
710
+ if (!user) {
711
+ throw new InvalidCredentialsError();
712
+ }
713
+ return {
714
+ user: this.mapUser(user)
715
+ };
716
+ }
717
+ /**
718
+ * Verifies credentials without throwing an error if invalid.
719
+ */
720
+ async verifyCredentials(params) {
721
+ const user = await this.manager.verifyCredentials(params);
722
+ return user ? this.mapUser(user) : null;
723
+ }
724
+ /**
725
+ * Changes a user's password.
726
+ */
727
+ async changePassword(params) {
728
+ return await this.manager.changePassword(params);
729
+ }
730
+ /**
731
+ * Retrieves a user by their unique ID.
732
+ */
733
+ async getUserById(id) {
734
+ const user = await this.manager.getUserById(id);
735
+ return user ? this.mapUser(user) : null;
736
+ }
737
+ /**
738
+ * Retrieves a user by their identifier (email or username).
739
+ */
740
+ async getUserByIdentifier(identifier) {
741
+ const user = await this.manager.getUserByIdentifier(identifier);
742
+ return user ? this.mapUser(user) : null;
743
+ }
744
+ /**
745
+ * Deletes a user from storage if supported by the storage adapter.
746
+ */
747
+ async deleteUser(id) {
748
+ const storage = this.manager.getStorage();
749
+ if (typeof storage.deleteUser === "function") {
750
+ await storage.deleteUser(id);
751
+ }
752
+ }
753
+ /**
754
+ * Returns the underlying CredentialsManager instance.
755
+ */
756
+ getCredentialsManager() {
757
+ return this.manager;
758
+ }
759
+ };
760
+
655
761
  // src/types.ts
656
762
  var AccountLinkingStrategy = /* @__PURE__ */ ((AccountLinkingStrategy2) => {
657
763
  AccountLinkingStrategy2["AUTO_LINK_BY_VERIFIED_EMAIL"] = "AUTO_LINK_BY_VERIFIED_EMAIL";
@@ -757,10 +863,7 @@ var LocalSessionHandler = class {
757
863
  if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
758
864
  throw new Error("No valid access token found in OAuth response");
759
865
  }
760
- const session = {
761
- token: tokenData.access_token,
762
- raw: tokenData
763
- };
866
+ const session = {};
764
867
  return session;
765
868
  }
766
869
  // PascalCase alias for backward compatibility
@@ -838,6 +941,125 @@ async function extractUserInfo(tokenData, providerMetadata) {
838
941
  return { userInfo };
839
942
  }
840
943
 
944
+ // src/utils/cookies.ts
945
+ var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
946
+ var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
947
+ var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
948
+ var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
949
+ function isProductionEnvironment() {
950
+ return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
951
+ }
952
+ function serializeCookie(name, value, options) {
953
+ const isProd = isProductionEnvironment();
954
+ const path = options?.path ?? "/";
955
+ const httpOnly = options?.httpOnly ?? true;
956
+ const secure = options?.secure ?? isProd;
957
+ const sameSite = options?.sameSite ?? "lax";
958
+ const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
959
+ if (path) {
960
+ parts.push(`Path=${path}`);
961
+ }
962
+ if (typeof options?.maxAge === "number") {
963
+ parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
964
+ const expires = new Date(Date.now() + options.maxAge * 1e3).toUTCString();
965
+ parts.push(`Expires=${expires}`);
966
+ }
967
+ if (options?.domain) {
968
+ parts.push(`Domain=${options.domain}`);
969
+ }
970
+ if (httpOnly) {
971
+ parts.push("HttpOnly");
972
+ }
973
+ if (secure) {
974
+ parts.push("Secure");
975
+ }
976
+ if (sameSite) {
977
+ const capitalized = sameSite.charAt(0).toUpperCase() + sameSite.slice(1).toLowerCase();
978
+ parts.push(`SameSite=${capitalized}`);
979
+ }
980
+ return parts.join("; ");
981
+ }
982
+ function createSessionCookie(sessionId, options) {
983
+ const isProd = isProductionEnvironment();
984
+ const resolvedOptions = {
985
+ name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
986
+ path: options?.path ?? "/",
987
+ maxAge: options?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
988
+ httpOnly: options?.httpOnly ?? true,
989
+ secure: options?.secure ?? isProd,
990
+ sameSite: options?.sameSite ?? "lax",
991
+ domain: options?.domain
992
+ };
993
+ const name = resolvedOptions.name;
994
+ const header = serializeCookie(name, sessionId, resolvedOptions);
995
+ return {
996
+ name,
997
+ value: sessionId,
998
+ options: resolvedOptions,
999
+ header
1000
+ };
1001
+ }
1002
+ function clearSessionCookie(options) {
1003
+ const isProd = isProductionEnvironment();
1004
+ const resolvedOptions = {
1005
+ name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
1006
+ path: options?.path ?? "/",
1007
+ maxAge: 0,
1008
+ httpOnly: options?.httpOnly ?? true,
1009
+ secure: options?.secure ?? isProd,
1010
+ sameSite: options?.sameSite ?? "lax",
1011
+ domain: options?.domain
1012
+ };
1013
+ const name = resolvedOptions.name;
1014
+ const header = serializeCookie(name, "", resolvedOptions);
1015
+ return {
1016
+ name,
1017
+ value: "",
1018
+ options: resolvedOptions,
1019
+ header
1020
+ };
1021
+ }
1022
+ function createStateCookie(state, options) {
1023
+ const isProd = isProductionEnvironment();
1024
+ const resolvedOptions = {
1025
+ name: options?.name || DEFAULT_STATE_COOKIE_NAME,
1026
+ path: options?.path ?? "/",
1027
+ maxAge: options?.maxAge ?? DEFAULT_STATE_MAX_AGE_SECONDS,
1028
+ httpOnly: options?.httpOnly ?? true,
1029
+ secure: options?.secure ?? isProd,
1030
+ sameSite: options?.sameSite ?? "lax",
1031
+ domain: options?.domain
1032
+ };
1033
+ const name = resolvedOptions.name;
1034
+ const header = serializeCookie(name, state, resolvedOptions);
1035
+ return {
1036
+ name,
1037
+ value: state,
1038
+ options: resolvedOptions,
1039
+ header
1040
+ };
1041
+ }
1042
+ function clearStateCookie(options) {
1043
+ const isProd = isProductionEnvironment();
1044
+ const resolvedOptions = {
1045
+ name: options?.name || DEFAULT_STATE_COOKIE_NAME,
1046
+ path: options?.path ?? "/",
1047
+ maxAge: 0,
1048
+ httpOnly: options?.httpOnly ?? true,
1049
+ secure: options?.secure ?? isProd,
1050
+ sameSite: options?.sameSite ?? "lax",
1051
+ domain: options?.domain
1052
+ };
1053
+ const name = resolvedOptions.name;
1054
+ const header = serializeCookie(name, "", resolvedOptions);
1055
+ return {
1056
+ name,
1057
+ value: "",
1058
+ options: resolvedOptions,
1059
+ header
1060
+ };
1061
+ }
1062
+
841
1063
  // src/lixa.ts
842
1064
  var Lixa = class _Lixa {
843
1065
  static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
@@ -847,14 +1069,19 @@ var Lixa = class _Lixa {
847
1069
  localStateHandler;
848
1070
  localSessionHandler;
849
1071
  localResourceHandler;
1072
+ localAccountHandler;
850
1073
  userResourceStore = /* @__PURE__ */ new Map();
1074
+ userAccountStore = /* @__PURE__ */ new Map();
851
1075
  refreshMutexes = /* @__PURE__ */ new Map();
1076
+ identityProvider;
852
1077
  credentialsManager;
853
1078
  config;
854
1079
  stateHandler;
855
1080
  sessionHandler;
856
1081
  resourceHandler;
1082
+ accountHandler;
857
1083
  debug;
1084
+ sessionCookieResolver;
858
1085
  /**
859
1086
  * Creates a new Lixa instance with the provided configuration.
860
1087
  *
@@ -869,7 +1096,15 @@ var Lixa = class _Lixa {
869
1096
  * @throws Error when provider is not available and no inline implementation is provided
870
1097
  */
871
1098
  constructor(config) {
872
- this.config = config;
1099
+ this.sessionCookieResolver = config.sessionCookieName;
1100
+ const federatedOAuthProviders = config.federatedOAuthProviders || config.providers;
1101
+ this.config = {
1102
+ ...config,
1103
+ ...federatedOAuthProviders ? {
1104
+ providers: federatedOAuthProviders,
1105
+ federatedOAuthProviders
1106
+ } : {}
1107
+ };
873
1108
  this.debug = config.debug || false;
874
1109
  this.localStateHandler = new LocalStateHandler();
875
1110
  this.localSessionHandler = new LocalSessionHandler();
@@ -905,19 +1140,70 @@ var Lixa = class _Lixa {
905
1140
  }
906
1141
  }
907
1142
  };
908
- this.stateHandler = config.stateHandler || this.localStateHandler;
909
- this.sessionHandler = config.sessionHandler || this.localSessionHandler;
910
- this.resourceHandler = config.resourceHandler || this.localResourceHandler;
911
- if (config.credentials !== void 0 && config.credentials.enabled !== false) {
912
- this.credentialsManager = new CredentialsManager(config.credentials);
1143
+ this.localAccountHandler = {
1144
+ accountStorage: {
1145
+ saveAccount: async (userId, provider, account) => {
1146
+ let userMap = this.userAccountStore.get(userId);
1147
+ if (!userMap) {
1148
+ userMap = /* @__PURE__ */ new Map();
1149
+ this.userAccountStore.set(userId, userMap);
1150
+ }
1151
+ userMap.set(provider.toLowerCase(), account);
1152
+ },
1153
+ getAccount: async (userId, provider) => {
1154
+ const userMap = this.userAccountStore.get(userId);
1155
+ return userMap?.get(provider.toLowerCase()) || null;
1156
+ },
1157
+ getUserAccounts: async (userId) => {
1158
+ const userMap = this.userAccountStore.get(userId);
1159
+ const result = {};
1160
+ if (userMap) {
1161
+ for (const [p, a] of userMap.entries()) {
1162
+ result[p] = a;
1163
+ }
1164
+ }
1165
+ return result;
1166
+ },
1167
+ deleteAccount: async (userId, provider) => {
1168
+ const userMap = this.userAccountStore.get(userId);
1169
+ if (userMap) {
1170
+ userMap.delete(provider.toLowerCase());
1171
+ }
1172
+ }
1173
+ }
1174
+ };
1175
+ const storage = config.storage;
1176
+ this.stateHandler = config.stateHandler || (storage?.stateStorage ? { storage: storage.stateStorage } : this.localStateHandler);
1177
+ this.sessionHandler = config.sessionHandler || (storage?.sessionStorage ? { storage: storage.sessionStorage } : this.localSessionHandler);
1178
+ this.resourceHandler = config.resourceHandler || (storage?.resourceStorage ? { resourceStorage: storage.resourceStorage } : this.localResourceHandler);
1179
+ this.accountHandler = storage?.accountStorage ? { accountStorage: storage.accountStorage } : this.localAccountHandler;
1180
+ const identityConfig = config.identityProvider || config.identity;
1181
+ if (identityConfig) {
1182
+ if (typeof identityConfig.signUp === "function") {
1183
+ this.identityProvider = identityConfig;
1184
+ if (this.identityProvider instanceof SelfManagedIdentityProvider) {
1185
+ this.credentialsManager = this.identityProvider.getCredentialsManager();
1186
+ }
1187
+ } else {
1188
+ const selfManaged = new SelfManagedIdentityProvider(identityConfig);
1189
+ this.identityProvider = selfManaged;
1190
+ this.credentialsManager = selfManaged.getCredentialsManager();
1191
+ }
1192
+ } else if (config.credentials !== void 0 && config.credentials.enabled !== false) {
1193
+ const selfManaged = new SelfManagedIdentityProvider(config.credentials);
1194
+ this.identityProvider = selfManaged;
1195
+ this.credentialsManager = selfManaged.getCredentialsManager();
913
1196
  }
1197
+ const oauthProviders = this.config.providers;
914
1198
  this.log("INFO", "Init", "Initializing Lixa instance", {
915
- providers: config.providers ? Object.keys(config.providers) : [],
916
- credentialsEnabled: this.credentialsManager !== void 0,
1199
+ providers: oauthProviders ? Object.keys(oauthProviders) : [],
1200
+ identityProvider: this.identityProvider ? this.identityProvider.name : void 0,
1201
+ identityEnabled: this.identityProvider !== void 0,
1202
+ credentialsEnabled: this.identityProvider !== void 0,
917
1203
  debug: this.debug
918
1204
  });
919
- if (config.providers) {
920
- for (const [providerName, providerConfig] of Object.entries(config.providers)) {
1205
+ if (oauthProviders) {
1206
+ for (const [providerName, providerConfig] of Object.entries(oauthProviders)) {
921
1207
  const name = providerName.toLowerCase();
922
1208
  const typedConfig = providerConfig;
923
1209
  this.validateProviderConfig(providerName, typedConfig);
@@ -946,6 +1232,20 @@ var Lixa = class _Lixa {
946
1232
  * @throws InvalidProviderConfigError when required fields are missing or invalid
947
1233
  */
948
1234
  validateProviderConfig(name, config) {
1235
+ if (!config.scopes || config.scopes.length === 0) {
1236
+ const p = config.provider;
1237
+ if (p?.defaultScopes && Array.isArray(p.defaultScopes) && p.defaultScopes.length > 0) {
1238
+ config.scopes = [...p.defaultScopes];
1239
+ } else if (p?.authScopes && Array.isArray(p.authScopes) && p.authScopes.length > 0) {
1240
+ config.scopes = [...p.authScopes];
1241
+ } else if (name === "google" || name.startsWith("google")) {
1242
+ config.scopes = ["openid", "profile", "email"];
1243
+ } else if (name === "github" || name.startsWith("github")) {
1244
+ config.scopes = ["read:user", "user:email"];
1245
+ } else if (name.startsWith("cognito")) {
1246
+ config.scopes = ["openid", "email", "profile"];
1247
+ }
1248
+ }
949
1249
  const requiredFields = ["clientId", "clientSecret", "redirectUri", "scopes"];
950
1250
  const missingFields = requiredFields.filter((field) => {
951
1251
  const value = config[field];
@@ -1282,7 +1582,7 @@ var Lixa = class _Lixa {
1282
1582
  this.log("INFO", "State", `Saved state for provider: ${providerType}`, { state: stateValue });
1283
1583
  const authNScopes = this.resolveAuthNScopes(
1284
1584
  providerType,
1285
- providerConfig.scopes,
1585
+ providerConfig.scopes || [],
1286
1586
  providerImpl,
1287
1587
  providerConfig.allowNonAuthScopes
1288
1588
  );
@@ -1368,7 +1668,8 @@ var Lixa = class _Lixa {
1368
1668
  async handleCallback({
1369
1669
  provider,
1370
1670
  code,
1371
- state
1671
+ state,
1672
+ sessionId
1372
1673
  }) {
1373
1674
  const providerType = String(provider).toLowerCase();
1374
1675
  this.log("INFO", "Auth", `Handling OAuth callback for provider: ${providerType}`);
@@ -1437,19 +1738,40 @@ var Lixa = class _Lixa {
1437
1738
  }
1438
1739
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1439
1740
  const linkingConfig = this.config.accountLinking;
1440
- const mode = String(linkingConfig?.mode || "");
1441
- const isLinkByEmail = mode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" || mode === "linkByEmail";
1741
+ const mode = typeof linkingConfig === "object" ? String(linkingConfig?.mode || "") : "";
1742
+ 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";
1442
1743
  const email = extractedUserInfo?.email;
1443
1744
  const isVerified = extractedUserInfo?.email_verified !== false;
1444
- const requireVerified = linkingConfig?.requireVerifiedEmail ?? true;
1745
+ const requireVerified = typeof linkingConfig === "object" ? linkingConfig?.requireVerifiedEmail ?? true : true;
1445
1746
  const canLink = isLinkByEmail && email && (!requireVerified || isVerified);
1747
+ if (sessionId) {
1748
+ const activeSession = await sessionStorage.getSession(sessionId);
1749
+ if (activeSession) {
1750
+ this.log("INFO", "AccountLinking", `Linking provider '${providerType}' directly to active session '${sessionId}'`);
1751
+ const linkedAcc2 = {
1752
+ provider: providerType,
1753
+ providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
1754
+ email: extractedUserInfo?.email || activeSession.email,
1755
+ accessToken: tokens.access_token,
1756
+ raw: tokens,
1757
+ linkedAt: Date.now()
1758
+ };
1759
+ activeSession.accounts = activeSession.accounts || {};
1760
+ activeSession.accounts[providerType] = linkedAcc2;
1761
+ await sessionStorage.saveSession(sessionId, activeSession, 86400);
1762
+ if (this.accountHandler?.accountStorage) {
1763
+ const userKey = this.getUserKeyFromSession(activeSession);
1764
+ await this.accountHandler.accountStorage.saveAccount(userKey, providerType, linkedAcc2);
1765
+ }
1766
+ return sessionId;
1767
+ }
1768
+ }
1446
1769
  if (canLink && sessionStorage.getSessionByEmail) {
1447
1770
  const existingRecord = await sessionStorage.getSessionByEmail(email);
1448
1771
  if (existingRecord) {
1449
1772
  this.log("INFO", "AccountLinking", `Linking provider '${providerType}' to existing session for email '${email}'`);
1450
1773
  const { sessionId: existingSessionId, session: existingSession } = existingRecord;
1451
- existingSession.accounts = existingSession.accounts || {};
1452
- existingSession.accounts[providerType] = {
1774
+ const linkedAcc2 = {
1453
1775
  provider: providerType,
1454
1776
  providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
1455
1777
  email,
@@ -1457,15 +1779,18 @@ var Lixa = class _Lixa {
1457
1779
  raw: tokens,
1458
1780
  linkedAt: Date.now()
1459
1781
  };
1782
+ existingSession.accounts = existingSession.accounts || {};
1783
+ existingSession.accounts[providerType] = linkedAcc2;
1460
1784
  existingSession.provider = providerType;
1461
- existingSession.token = tokens.access_token;
1462
- existingSession.raw = tokens;
1463
1785
  await sessionStorage.saveSession(existingSessionId, existingSession, 86400);
1786
+ if (this.accountHandler?.accountStorage) {
1787
+ const userKey = this.getUserKeyFromSession(existingSession);
1788
+ await this.accountHandler.accountStorage.saveAccount(userKey, providerType, linkedAcc2);
1789
+ }
1464
1790
  return existingSessionId;
1465
1791
  }
1466
1792
  }
1467
- session.accounts = session.accounts || {};
1468
- session.accounts[providerType] = {
1793
+ const linkedAcc = {
1469
1794
  provider: providerType,
1470
1795
  providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
1471
1796
  email: extractedUserInfo?.email,
@@ -1473,12 +1798,18 @@ var Lixa = class _Lixa {
1473
1798
  raw: tokens,
1474
1799
  linkedAt: Date.now()
1475
1800
  };
1476
- const sessionId = (0, import_crypto4.randomBytes)(32).toString("hex");
1477
- session.id = sessionId;
1478
- this.log("INFO", "Session", "Storing session", { sessionId });
1479
- await sessionStorage.saveSession(sessionId, session, 86400);
1480
- this.log("INFO", "Session", "Session created successfully", { sessionId });
1481
- return sessionId;
1801
+ session.accounts = session.accounts || {};
1802
+ session.accounts[providerType] = linkedAcc;
1803
+ if (this.accountHandler?.accountStorage) {
1804
+ const userKey = this.getUserKeyFromSession(session);
1805
+ await this.accountHandler.accountStorage.saveAccount(userKey, providerType, linkedAcc);
1806
+ }
1807
+ const newSessionId = (0, import_crypto4.randomBytes)(32).toString("hex");
1808
+ session.sessionId = newSessionId;
1809
+ this.log("INFO", "Session", "Storing session", { sessionId: newSessionId });
1810
+ await sessionStorage.saveSession(newSessionId, session, 86400);
1811
+ this.log("INFO", "Session", "Session created successfully", { sessionId: newSessionId });
1812
+ return newSessionId;
1482
1813
  }
1483
1814
  /**
1484
1815
  * Explicitly link a new OAuth provider account to an active session.
@@ -1564,6 +1895,10 @@ var Lixa = class _Lixa {
1564
1895
  }
1565
1896
  delete session.accounts[providerToUnlink.toLowerCase()];
1566
1897
  await sessionStorage.saveSession(sessionId, session, 86400);
1898
+ if (this.accountHandler?.accountStorage) {
1899
+ const userKey = this.getUserKeyFromSession(session);
1900
+ await this.accountHandler.accountStorage.deleteAccount(userKey, providerToUnlink.toLowerCase());
1901
+ }
1567
1902
  return true;
1568
1903
  }
1569
1904
  /**
@@ -1578,7 +1913,7 @@ var Lixa = class _Lixa {
1578
1913
  * @returns The authorization URL for resource consent
1579
1914
  */
1580
1915
  async getResourceAuthUrl(params) {
1581
- const { sessionId, provider, scopes, state, prompt, extraConfig } = params;
1916
+ const { sessionId, provider, state, prompt, extraConfig } = params;
1582
1917
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1583
1918
  const activeSession = await sessionStorage.getSession(sessionId);
1584
1919
  if (!activeSession) {
@@ -1589,6 +1924,10 @@ var Lixa = class _Lixa {
1589
1924
  if (!providerConfig) {
1590
1925
  throw new ProviderNotConfiguredError(String(provider));
1591
1926
  }
1927
+ const requestedScopes = params.scopes && params.scopes.length > 0 ? params.scopes : providerConfig.resourceScopes || [];
1928
+ if (requestedScopes.length === 0) {
1929
+ throw new Error(`No scopes specified for resource '${provider}'. Provide scopes in getResourceAuthUrl() or configure 'resourceScopes' on the provider.`);
1930
+ }
1592
1931
  const providerImpl = this.getProvider(providerType, providerConfig);
1593
1932
  const stateValue = state || (0, import_crypto4.randomBytes)(16).toString("hex");
1594
1933
  const codeVerifier = (0, import_crypto4.randomBytes)(32).toString("hex");
@@ -1606,7 +1945,7 @@ var Lixa = class _Lixa {
1606
1945
  const searchParams = new URLSearchParams({
1607
1946
  client_id: providerConfig.clientId,
1608
1947
  redirect_uri: providerConfig.redirectUri,
1609
- scope: scopes.join(" "),
1948
+ scope: requestedScopes.join(" "),
1610
1949
  state: stateValue,
1611
1950
  response_type: "code",
1612
1951
  code_challenge: codeChallenge,
@@ -1618,7 +1957,7 @@ var Lixa = class _Lixa {
1618
1957
  return `${providerImpl.authorizationEndpoint}?${searchParams.toString()}`;
1619
1958
  }
1620
1959
  getUserKeyFromSession(session) {
1621
- return session.userId || session.email || session.id || "anonymous";
1960
+ return session.userId || session.email || session.sessionId || "anonymous";
1622
1961
  }
1623
1962
  /**
1624
1963
  * Handles the OAuth callback for a connected resource provider and stores resource tokens bound to user account.
@@ -1648,12 +1987,13 @@ var Lixa = class _Lixa {
1648
1987
  }
1649
1988
  const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier);
1650
1989
  const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
1990
+ const effectiveScopes = scopes || (tokens.scope ? tokens.scope.split(" ") : providerConfig.resourceScopes || []);
1651
1991
  const connectedResource = {
1652
1992
  provider: providerType,
1653
1993
  accessToken: tokens.access_token,
1654
1994
  refreshToken: tokens.refresh_token,
1655
1995
  expiresAt,
1656
- scopes: scopes || (tokens.scope ? tokens.scope.split(" ") : []),
1996
+ scopes: effectiveScopes,
1657
1997
  raw: tokens,
1658
1998
  connectedAt: Date.now()
1659
1999
  };
@@ -1665,6 +2005,40 @@ var Lixa = class _Lixa {
1665
2005
  await sessionStorage.saveSession(sessionId, activeSession, 86400);
1666
2006
  return activeSession;
1667
2007
  }
2008
+ /**
2009
+ * Retrieves configured default resource scopes for a provider.
2010
+ *
2011
+ * @param provider - Provider name (e.g. 'github', 'google')
2012
+ * @returns Array of configured resource scopes, or undefined if not configured
2013
+ */
2014
+ getResourceScopes(provider) {
2015
+ const providerConfig = this.findProviderByType(String(provider).toLowerCase());
2016
+ return providerConfig?.resourceScopes;
2017
+ }
2018
+ /**
2019
+ * Returns the resolved session cookie name.
2020
+ * Dynamically reflects any changes to the active identity provider.
2021
+ *
2022
+ * @param req - Optional request object for multi-tenant request-scoped cookie resolution
2023
+ * @returns Resolved session cookie name
2024
+ */
2025
+ getSessionCookieName(req) {
2026
+ if (typeof this.sessionCookieResolver === "function") {
2027
+ return this.sessionCookieResolver(this, req);
2028
+ }
2029
+ if (typeof this.sessionCookieResolver === "string" && this.sessionCookieResolver.length > 0) {
2030
+ return this.sessionCookieResolver;
2031
+ }
2032
+ return DEFAULT_SESSION_COOKIE_NAME;
2033
+ }
2034
+ /**
2035
+ * Sets or updates the session cookie name or dynamic resolver on this instance.
2036
+ *
2037
+ * @param resolver - Static cookie name or dynamic resolver function
2038
+ */
2039
+ setSessionCookieName(resolver) {
2040
+ this.sessionCookieResolver = resolver;
2041
+ }
1668
2042
  /**
1669
2043
  * Retrieves a connected resource for a specific User ID / Email directly (independent of session IDs).
1670
2044
  * Automatically refreshes expired access tokens if a refresh token is present.
@@ -1845,7 +2219,16 @@ var Lixa = class _Lixa {
1845
2219
  */
1846
2220
  async fetchSessionInfo(sessionId) {
1847
2221
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1848
- return await sessionStorage.getSession(sessionId);
2222
+ const session = await sessionStorage.getSession(sessionId);
2223
+ if (!session) return null;
2224
+ if (this.accountHandler?.accountStorage) {
2225
+ const userKey = this.getUserKeyFromSession(session);
2226
+ const persistentAccounts = await this.accountHandler.accountStorage.getUserAccounts(userKey);
2227
+ if (persistentAccounts && Object.keys(persistentAccounts).length > 0) {
2228
+ session.accounts = { ...persistentAccounts, ...session.accounts || {} };
2229
+ }
2230
+ }
2231
+ return session;
1849
2232
  }
1850
2233
  /**
1851
2234
  * Deletes a session from session storage (e.g. on logout).
@@ -1896,36 +2279,64 @@ var Lixa = class _Lixa {
1896
2279
  return response.json();
1897
2280
  }
1898
2281
  /**
1899
- * Checks if credentials (username and password) authentication is configured and enabled.
2282
+ * Checks if an identity provider (Self-Managed Database, AWS Cognito, Auth0, Okta, etc.) is configured.
2283
+ *
2284
+ * @returns True if identity provider is configured and available
2285
+ */
2286
+ isIdentityEnabled() {
2287
+ return this.identityProvider !== void 0;
2288
+ }
2289
+ /**
2290
+ * Checks if credentials / identity authentication is configured and enabled.
1900
2291
  *
1901
- * @returns True if credentials authentication is available
2292
+ * @returns True if credentials / identity authentication is available
1902
2293
  */
1903
2294
  isCredentialsEnabled() {
1904
- return this.credentialsManager !== void 0;
2295
+ return this.identityProvider !== void 0;
2296
+ }
2297
+ /**
2298
+ * Returns the configured IdentityProvider instance.
2299
+ */
2300
+ getIdentityProvider() {
2301
+ return this.identityProvider;
2302
+ }
2303
+ /**
2304
+ * Dynamically sets or updates the active identity provider (e.g. SelfManaged, Cognito, Auth0, Okta).
2305
+ *
2306
+ * @param provider - Identity provider instance or undefined to disable
2307
+ */
2308
+ setIdentityProvider(provider) {
2309
+ this.identityProvider = provider;
2310
+ if (provider instanceof SelfManagedIdentityProvider) {
2311
+ this.credentialsManager = provider.getCredentialsManager();
2312
+ } else {
2313
+ this.credentialsManager = void 0;
2314
+ }
1905
2315
  }
1906
2316
  /**
1907
- * Returns the underlying CredentialsManager instance if configured.
2317
+ * Returns the underlying CredentialsManager instance if using Self-Managed identity.
1908
2318
  */
1909
2319
  getCredentialsManager() {
1910
2320
  return this.credentialsManager;
1911
2321
  }
1912
2322
  /**
1913
- * Registers a new user with username/email and password.
1914
- * Automatically enforces password policy, hashes password with Scrypt/configured hasher,
1915
- * stores credentials, and creates a session (unless autoCreateSessionOnSignUp is false).
2323
+ * Registers a new user with the configured identity provider (Self-Managed DB, Cognito, Auth0, Okta).
2324
+ * Automatically creates an active session unless autoCreateSessionOnSignUp is explicitly set to false.
1916
2325
  *
1917
2326
  * @param params - Registration parameters (identifier, password, email, username, metadata)
1918
- * @returns Created user (without password hash) and optional active session
2327
+ * @returns Created user identity and optional active session
1919
2328
  * @throws WeakPasswordError if password does not meet policy requirements
1920
2329
  * @throws UserAlreadyExistsError if identifier is already registered
1921
- * @throws CredentialsNotConfiguredError if credentials auth is not configured
2330
+ * @throws IdentityNotConfiguredError if identity authentication is not configured
1922
2331
  */
1923
2332
  async signUp(params) {
1924
- if (!this.credentialsManager) {
1925
- throw new CredentialsNotConfiguredError();
2333
+ if (!this.identityProvider) {
2334
+ throw new IdentityNotConfiguredError();
1926
2335
  }
1927
- this.log("INFO", "Credentials", "Processing signUp request", { identifier: params.identifier });
1928
- const user = await this.credentialsManager.signUp(params);
2336
+ const rawProviderName = this.identityProvider.name;
2337
+ const providerName = rawProviderName === "self-managed" ? "credentials" : rawProviderName;
2338
+ this.log("INFO", "Identity", `Processing signUp request via ${providerName}`, { identifier: params.identifier });
2339
+ const user = await this.identityProvider.signUp(params);
1929
2340
  const autoSession = this.config.credentials?.autoCreateSessionOnSignUp ?? true;
1930
2341
  if (!autoSession) {
1931
2342
  return { user };
@@ -1933,8 +2344,9 @@ var Lixa = class _Lixa {
1933
2344
  const sessionId = (0, import_crypto4.randomBytes)(16).toString("hex");
1934
2345
  const token = (0, import_crypto4.randomBytes)(32).toString("hex");
1935
2346
  const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
2347
+ const accountKey = providerName;
1936
2348
  const credentialsAccount = {
1937
- provider: "credentials",
2349
+ provider: providerName,
1938
2350
  providerUserId: user.id,
1939
2351
  email: user.email,
1940
2352
  accessToken: token,
@@ -1946,67 +2358,71 @@ var Lixa = class _Lixa {
1946
2358
  linkedAt: Date.now()
1947
2359
  };
1948
2360
  const session = {
1949
- id: sessionId,
2361
+ sessionId,
1950
2362
  userId: user.id,
1951
2363
  email: user.email,
1952
- token,
1953
- provider: "credentials",
2364
+ provider: providerName,
1954
2365
  accounts: {
1955
- credentials: credentialsAccount
2366
+ [accountKey]: credentialsAccount
1956
2367
  }
1957
2368
  };
1958
2369
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1959
2370
  await sessionStorage.saveSession(sessionId, session, ttl);
1960
- this.log("INFO", "Credentials", "Created session on signUp", { userId: user.id, sessionId });
2371
+ this.log("INFO", "Identity", "Created session on signUp", { userId: user.id, sessionId, provider: providerName });
1961
2372
  return { user, sessionId, session };
1962
2373
  }
1963
2374
  /**
1964
- * Authenticates a user with username/email and password.
1965
- * Performs constant-time verification with timing attack mitigation, and creates an active session.
1966
- * If account linking is configured with AUTO_LINK_BY_VERIFIED_EMAIL, merges with existing session.
2375
+ * Authenticates a user with credentials via the configured identity provider (Self-Managed DB, Cognito, Auth0, Okta).
2376
+ * Creates an active session and automatically links with existing sessions when AUTO_LINK_BY_VERIFIED_EMAIL is configured.
1967
2377
  *
1968
2378
  * @param params - Sign-in parameters (identifier, password)
1969
2379
  * @returns Authenticated user, session ID, and session object
1970
2380
  * @throws InvalidCredentialsError if authentication fails
1971
- * @throws CredentialsNotConfiguredError if credentials auth is not configured
2381
+ * @throws IdentityNotConfiguredError if identity authentication is not configured
1972
2382
  */
1973
2383
  async signIn(params) {
1974
- if (!this.credentialsManager) {
1975
- throw new CredentialsNotConfiguredError();
2384
+ if (!this.identityProvider) {
2385
+ throw new IdentityNotConfiguredError();
1976
2386
  }
1977
2387
  const lookupKey = params.username || params.identifier;
1978
- this.log("INFO", "Credentials", "Processing signIn request", { identifier: lookupKey });
1979
- const user = await this.credentialsManager.verifyCredentials(params);
1980
- if (!user) {
1981
- this.log("WARN", "Credentials", "Invalid credentials provided", { identifier: lookupKey });
1982
- throw new InvalidCredentialsError();
1983
- }
2388
+ const rawProviderName = this.identityProvider.name;
2389
+ const providerName = rawProviderName === "self-managed" ? "credentials" : rawProviderName;
2390
+ this.log("INFO", "Identity", `Processing signIn request via ${providerName}`, { identifier: lookupKey });
2391
+ const authResult = await this.identityProvider.signIn(params);
2392
+ const user = authResult.user;
2393
+ const tokens = authResult.tokens;
1984
2394
  const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
1985
- const token = (0, import_crypto4.randomBytes)(32).toString("hex");
2395
+ const token = tokens?.accessToken || (0, import_crypto4.randomBytes)(32).toString("hex");
2396
+ const rawTokens = {
2397
+ access_token: token,
2398
+ token_type: tokens?.tokenType || "Bearer",
2399
+ expires_in: tokens?.expiresIn || ttl,
2400
+ id_token: tokens?.idToken,
2401
+ refresh_token: tokens?.refreshToken,
2402
+ ...tokens?.raw
2403
+ };
2404
+ const accountKey = providerName === "self-managed" ? "credentials" : providerName;
1986
2405
  const credentialsAccount = {
1987
- provider: "credentials",
2406
+ provider: providerName,
1988
2407
  providerUserId: user.id,
1989
2408
  email: user.email,
1990
2409
  accessToken: token,
1991
- raw: {
1992
- access_token: token,
1993
- token_type: "Bearer",
1994
- expires_in: ttl
1995
- },
2410
+ raw: rawTokens,
1996
2411
  linkedAt: Date.now()
1997
2412
  };
1998
2413
  const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
1999
- const linkingMode = this.config.accountLinking?.mode;
2000
- const isAutoLink = linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || linkingMode === "AUTO_LINK_BY_VERIFIED_EMAIL" || linkingMode === "linkByEmail";
2414
+ const linkingConfig = this.config.accountLinking;
2415
+ const linkingMode = typeof linkingConfig === "object" ? linkingConfig?.mode : void 0;
2416
+ 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";
2001
2417
  if (isAutoLink && user.email && sessionStorage.getSessionByEmail) {
2002
2418
  const existing = await sessionStorage.getSessionByEmail(user.email);
2003
2419
  if (existing) {
2004
2420
  const mergedSession = { ...existing.session };
2005
- mergedSession.accounts = { ...mergedSession.accounts || {}, credentials: credentialsAccount };
2421
+ mergedSession.accounts = { ...mergedSession.accounts || {}, [accountKey]: credentialsAccount };
2006
2422
  if (!mergedSession.userId) mergedSession.userId = user.id;
2007
2423
  if (!mergedSession.email) mergedSession.email = user.email;
2008
2424
  await sessionStorage.saveSession(existing.sessionId, mergedSession, ttl);
2009
- this.log("INFO", "AccountLinking", "Linked credentials to existing session by email", {
2425
+ this.log("INFO", "AccountLinking", `Linked ${providerName} to existing session by email`, {
2010
2426
  sessionId: existing.sessionId,
2011
2427
  email: user.email
2012
2428
  });
@@ -2019,31 +2435,46 @@ var Lixa = class _Lixa {
2019
2435
  }
2020
2436
  const sessionId = (0, import_crypto4.randomBytes)(16).toString("hex");
2021
2437
  const session = {
2022
- id: sessionId,
2438
+ sessionId,
2023
2439
  userId: user.id,
2024
2440
  email: user.email,
2025
- token,
2026
- provider: "credentials",
2441
+ provider: providerName,
2027
2442
  accounts: {
2028
- credentials: credentialsAccount
2443
+ [accountKey]: credentialsAccount
2029
2444
  }
2030
2445
  };
2446
+ const userKey = this.getUserKeyFromSession(session);
2447
+ if (this.accountHandler?.accountStorage) {
2448
+ const persistentAccounts = await this.accountHandler.accountStorage.getUserAccounts(userKey);
2449
+ if (persistentAccounts && Object.keys(persistentAccounts).length > 0) {
2450
+ session.accounts = { ...persistentAccounts, ...session.accounts };
2451
+ }
2452
+ await this.accountHandler.accountStorage.saveAccount(userKey, accountKey, credentialsAccount);
2453
+ }
2031
2454
  await sessionStorage.saveSession(sessionId, session, ttl);
2032
- this.log("INFO", "Credentials", "User signed in successfully", { userId: user.id, sessionId });
2455
+ this.log("INFO", "Identity", `User signed in successfully via ${providerName}`, { userId: user.id, sessionId });
2033
2456
  return { user, sessionId, session };
2034
2457
  }
2035
2458
  /**
2036
2459
  * Verifies username/email and password credentials without generating a session.
2037
2460
  *
2038
2461
  * @param params - Verification parameters (identifier, password)
2039
- * @returns User credentials (without password hash) or null if invalid
2040
- * @throws CredentialsNotConfiguredError if credentials auth is not configured
2462
+ * @returns User credentials/identity or null if invalid
2463
+ * @throws IdentityNotConfiguredError if identity auth is not configured
2041
2464
  */
2042
2465
  async verifyCredentials(params) {
2043
- if (!this.credentialsManager) {
2044
- throw new CredentialsNotConfiguredError();
2466
+ if (!this.identityProvider) {
2467
+ throw new IdentityNotConfiguredError();
2468
+ }
2469
+ if (typeof this.identityProvider.verifyCredentials === "function") {
2470
+ return this.identityProvider.verifyCredentials(params);
2471
+ }
2472
+ try {
2473
+ const result = await this.identityProvider.signIn(params);
2474
+ return result.user;
2475
+ } catch {
2476
+ return null;
2045
2477
  }
2046
- return this.credentialsManager.verifyCredentials(params);
2047
2478
  }
2048
2479
  /**
2049
2480
  * Updates a user's password after verifying the current password and enforcing policy on the new password.
@@ -2053,17 +2484,140 @@ var Lixa = class _Lixa {
2053
2484
  * @throws UserNotFoundError if user is not found
2054
2485
  * @throws InvalidCredentialsError if current password is incorrect
2055
2486
  * @throws WeakPasswordError if new password does not meet policy requirements
2056
- * @throws CredentialsNotConfiguredError if credentials auth is not configured
2487
+ * @throws IdentityNotConfiguredError if identity auth is not configured
2057
2488
  */
2058
2489
  async changePassword(params) {
2059
- if (!this.credentialsManager) {
2060
- throw new CredentialsNotConfiguredError();
2490
+ if (!this.identityProvider) {
2491
+ throw new IdentityNotConfiguredError();
2061
2492
  }
2062
- this.log("INFO", "Credentials", "Processing changePassword request", {
2493
+ if (typeof this.identityProvider.changePassword !== "function") {
2494
+ throw new IdentityProviderError(
2495
+ `Password change is not supported by the '${this.identityProvider.name}' identity provider`,
2496
+ this.identityProvider.name
2497
+ );
2498
+ }
2499
+ this.log("INFO", "Identity", "Processing changePassword request", {
2063
2500
  userId: params.userId,
2064
2501
  identifier: params.identifier
2065
2502
  });
2066
- return this.credentialsManager.changePassword(params);
2503
+ return this.identityProvider.changePassword(params);
2504
+ }
2505
+ /**
2506
+ * Initiates a password reset (forgot password flow) through the configured identity provider.
2507
+ *
2508
+ * @param params - Forgot password parameters (identifier, optional metadata)
2509
+ * @returns ForgotPasswordResult indicating status and delivery medium (email/SMS)
2510
+ * @throws IdentityNotConfiguredError if identity auth is not configured
2511
+ */
2512
+ async forgotPassword(params) {
2513
+ if (!this.identityProvider) {
2514
+ throw new IdentityNotConfiguredError();
2515
+ }
2516
+ if (typeof this.identityProvider.forgotPassword !== "function") {
2517
+ throw new IdentityProviderError(
2518
+ `Password reset request is not supported by the '${this.identityProvider.name}' identity provider`,
2519
+ this.identityProvider.name
2520
+ );
2521
+ }
2522
+ this.log("INFO", "Identity", "Processing forgotPassword request", { identifier: params.identifier });
2523
+ return this.identityProvider.forgotPassword(params);
2524
+ }
2525
+ /**
2526
+ * Confirms a password reset using a verification code / token and sets the new password.
2527
+ *
2528
+ * @param params - Verification code, identifier, and new password
2529
+ * @returns True if password reset was successfully confirmed
2530
+ * @throws IdentityNotConfiguredError if identity auth is not configured
2531
+ */
2532
+ async confirmPasswordReset(params) {
2533
+ if (!this.identityProvider) {
2534
+ throw new IdentityNotConfiguredError();
2535
+ }
2536
+ if (typeof this.identityProvider.confirmPasswordReset !== "function") {
2537
+ throw new IdentityProviderError(
2538
+ `Confirm password reset is not supported by the '${this.identityProvider.name}' identity provider`,
2539
+ this.identityProvider.name
2540
+ );
2541
+ }
2542
+ this.log("INFO", "Identity", "Processing confirmPasswordReset request", { identifier: params.identifier });
2543
+ return this.identityProvider.confirmPasswordReset(params);
2544
+ }
2545
+ /**
2546
+ * Confirms user account registration using a verification code.
2547
+ *
2548
+ * @param params - Verification parameters including identifier and confirmationCode
2549
+ * @returns True if confirmation was successful
2550
+ * @throws IdentityNotConfiguredError if identity auth is not configured
2551
+ * @throws IdentityProviderError if identity provider does not support confirmation
2552
+ */
2553
+ async confirmSignUp(params) {
2554
+ if (!this.identityProvider) {
2555
+ throw new IdentityNotConfiguredError();
2556
+ }
2557
+ if (typeof this.identityProvider.confirmSignUp !== "function") {
2558
+ throw new IdentityProviderError(
2559
+ `Account confirmation is not supported by the '${this.identityProvider.name}' identity provider`,
2560
+ this.identityProvider.name
2561
+ );
2562
+ }
2563
+ this.log("INFO", "Identity", "Processing confirmSignUp request", { identifier: params.identifier });
2564
+ return this.identityProvider.confirmSignUp(params);
2565
+ }
2566
+ /**
2567
+ * Resends the sign up confirmation code to the user.
2568
+ *
2569
+ * @param params - Resend parameters including user identifier
2570
+ * @returns Details about the code delivery (medium, destination)
2571
+ * @throws IdentityNotConfiguredError if identity auth is not configured
2572
+ * @throws IdentityProviderError if identity provider does not support resending codes
2573
+ */
2574
+ async resendConfirmationCode(params) {
2575
+ if (!this.identityProvider) {
2576
+ throw new IdentityNotConfiguredError();
2577
+ }
2578
+ if (typeof this.identityProvider.resendConfirmationCode !== "function") {
2579
+ throw new IdentityProviderError(
2580
+ `Resending confirmation code is not supported by the '${this.identityProvider.name}' identity provider`,
2581
+ this.identityProvider.name
2582
+ );
2583
+ }
2584
+ this.log("INFO", "Identity", "Processing resendConfirmationCode request", { identifier: params.identifier });
2585
+ return this.identityProvider.resendConfirmationCode(params);
2586
+ }
2587
+ /**
2588
+ * Retrieves a user by their unique ID from the configured identity provider.
2589
+ *
2590
+ * @param id - Unique user ID
2591
+ * @returns UserIdentity or null if not found
2592
+ */
2593
+ async getUserById(id) {
2594
+ if (!this.identityProvider || typeof this.identityProvider.getUserById !== "function") {
2595
+ return null;
2596
+ }
2597
+ return this.identityProvider.getUserById(id);
2598
+ }
2599
+ /**
2600
+ * Retrieves a user by their identifier (username or email) from the configured identity provider.
2601
+ *
2602
+ * @param identifier - Username or email string
2603
+ * @returns UserIdentity or null if not found
2604
+ */
2605
+ async getUserByIdentifier(identifier) {
2606
+ if (!this.identityProvider || typeof this.identityProvider.getUserByIdentifier !== "function") {
2607
+ return null;
2608
+ }
2609
+ return this.identityProvider.getUserByIdentifier(identifier);
2610
+ }
2611
+ /**
2612
+ * Deletes a user account from the configured identity provider.
2613
+ *
2614
+ * @param id - Unique user ID
2615
+ */
2616
+ async deleteUser(id) {
2617
+ if (!this.identityProvider || typeof this.identityProvider.deleteUser !== "function") {
2618
+ return;
2619
+ }
2620
+ await this.identityProvider.deleteUser(id);
2067
2621
  }
2068
2622
  findProviderByType(providerType) {
2069
2623
  for (const [key, value] of Object.entries(this.config.providers || {})) {
@@ -2075,125 +2629,6 @@ var Lixa = class _Lixa {
2075
2629
  }
2076
2630
  };
2077
2631
 
2078
- // src/utils/cookies.ts
2079
- var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
2080
- var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
2081
- var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
2082
- var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
2083
- function isProductionEnvironment() {
2084
- return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
2085
- }
2086
- function serializeCookie(name, value, options) {
2087
- const isProd = isProductionEnvironment();
2088
- const path = options?.path ?? "/";
2089
- const httpOnly = options?.httpOnly ?? true;
2090
- const secure = options?.secure ?? isProd;
2091
- const sameSite = options?.sameSite ?? "lax";
2092
- const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
2093
- if (path) {
2094
- parts.push(`Path=${path}`);
2095
- }
2096
- if (typeof options?.maxAge === "number") {
2097
- parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
2098
- const expires = new Date(Date.now() + options.maxAge * 1e3).toUTCString();
2099
- parts.push(`Expires=${expires}`);
2100
- }
2101
- if (options?.domain) {
2102
- parts.push(`Domain=${options.domain}`);
2103
- }
2104
- if (httpOnly) {
2105
- parts.push("HttpOnly");
2106
- }
2107
- if (secure) {
2108
- parts.push("Secure");
2109
- }
2110
- if (sameSite) {
2111
- const capitalized = sameSite.charAt(0).toUpperCase() + sameSite.slice(1).toLowerCase();
2112
- parts.push(`SameSite=${capitalized}`);
2113
- }
2114
- return parts.join("; ");
2115
- }
2116
- function createSessionCookie(sessionId, options) {
2117
- const isProd = isProductionEnvironment();
2118
- const resolvedOptions = {
2119
- name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
2120
- path: options?.path ?? "/",
2121
- maxAge: options?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
2122
- httpOnly: options?.httpOnly ?? true,
2123
- secure: options?.secure ?? isProd,
2124
- sameSite: options?.sameSite ?? "lax",
2125
- domain: options?.domain
2126
- };
2127
- const name = resolvedOptions.name;
2128
- const header = serializeCookie(name, sessionId, resolvedOptions);
2129
- return {
2130
- name,
2131
- value: sessionId,
2132
- options: resolvedOptions,
2133
- header
2134
- };
2135
- }
2136
- function clearSessionCookie(options) {
2137
- const isProd = isProductionEnvironment();
2138
- const resolvedOptions = {
2139
- name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
2140
- path: options?.path ?? "/",
2141
- maxAge: 0,
2142
- httpOnly: options?.httpOnly ?? true,
2143
- secure: options?.secure ?? isProd,
2144
- sameSite: options?.sameSite ?? "lax",
2145
- domain: options?.domain
2146
- };
2147
- const name = resolvedOptions.name;
2148
- const header = serializeCookie(name, "", resolvedOptions);
2149
- return {
2150
- name,
2151
- value: "",
2152
- options: resolvedOptions,
2153
- header
2154
- };
2155
- }
2156
- function createStateCookie(state, options) {
2157
- const isProd = isProductionEnvironment();
2158
- const resolvedOptions = {
2159
- name: options?.name || DEFAULT_STATE_COOKIE_NAME,
2160
- path: options?.path ?? "/",
2161
- maxAge: options?.maxAge ?? DEFAULT_STATE_MAX_AGE_SECONDS,
2162
- httpOnly: options?.httpOnly ?? true,
2163
- secure: options?.secure ?? isProd,
2164
- sameSite: options?.sameSite ?? "lax",
2165
- domain: options?.domain
2166
- };
2167
- const name = resolvedOptions.name;
2168
- const header = serializeCookie(name, state, resolvedOptions);
2169
- return {
2170
- name,
2171
- value: state,
2172
- options: resolvedOptions,
2173
- header
2174
- };
2175
- }
2176
- function clearStateCookie(options) {
2177
- const isProd = isProductionEnvironment();
2178
- const resolvedOptions = {
2179
- name: options?.name || DEFAULT_STATE_COOKIE_NAME,
2180
- path: options?.path ?? "/",
2181
- maxAge: 0,
2182
- httpOnly: options?.httpOnly ?? true,
2183
- secure: options?.secure ?? isProd,
2184
- sameSite: options?.sameSite ?? "lax",
2185
- domain: options?.domain
2186
- };
2187
- const name = resolvedOptions.name;
2188
- const header = serializeCookie(name, "", resolvedOptions);
2189
- return {
2190
- name,
2191
- value: "",
2192
- options: resolvedOptions,
2193
- header
2194
- };
2195
- }
2196
-
2197
2632
  // src/credentials/index.ts
2198
2633
  var credentials_exports = {};
2199
2634
  __export(credentials_exports, {
@@ -2203,6 +2638,12 @@ __export(credentials_exports, {
2203
2638
  ScryptPasswordHasher: () => ScryptPasswordHasher,
2204
2639
  validatePasswordPolicy: () => validatePasswordPolicy
2205
2640
  });
2641
+
2642
+ // src/identity/index.ts
2643
+ var identity_exports = {};
2644
+ __export(identity_exports, {
2645
+ SelfManagedIdentityProvider: () => SelfManagedIdentityProvider
2646
+ });
2206
2647
  // Annotate the CommonJS export names for ESM import in node:
2207
2648
  0 && (module.exports = {
2208
2649
  AccountLinkingStrategy,
@@ -2214,6 +2655,8 @@ __export(credentials_exports, {
2214
2655
  DEFAULT_STATE_COOKIE_NAME,
2215
2656
  DEFAULT_STATE_MAX_AGE_SECONDS,
2216
2657
  EmailNotVerifiedError,
2658
+ IdentityNotConfiguredError,
2659
+ IdentityProviderError,
2217
2660
  InvalidCredentialsError,
2218
2661
  InvalidOAuthCallbackError,
2219
2662
  InvalidProviderConfigError,
@@ -2225,6 +2668,7 @@ __export(credentials_exports, {
2225
2668
  ProviderNotConfiguredError,
2226
2669
  RefreshTokenError,
2227
2670
  ScryptPasswordHasher,
2671
+ SelfManagedIdentityProvider,
2228
2672
  SessionNotFoundError,
2229
2673
  TokenExchangeError,
2230
2674
  UserAlreadyExistsError,
@@ -2239,6 +2683,7 @@ __export(credentials_exports, {
2239
2683
  determineProviderFromIssuer,
2240
2684
  extractUserInfo,
2241
2685
  fetchUserInfo,
2686
+ identity,
2242
2687
  isProductionEnvironment,
2243
2688
  serializeCookie,
2244
2689
  validatePasswordPolicy