@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/README.md +101 -687
- package/dist/credentials/types.d.ts +6 -6
- package/dist/credentials/types.d.ts.map +1 -1
- package/dist/dao/session-cache.d.ts.map +1 -1
- package/dist/dao/types.d.ts +19 -0
- package/dist/dao/types.d.ts.map +1 -1
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/export-types/index.d.ts +556 -41
- package/dist/identity/index.d.ts +3 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/self-managed.d.ts +52 -0
- package/dist/identity/self-managed.d.ts.map +1 -0
- package/dist/identity/types.d.ts +224 -0
- package/dist/identity/types.d.ts.map +1 -0
- package/dist/index.cjs +658 -213
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +563 -65
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +654 -213
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +123 -28
- package/dist/lixa.d.ts.map +1 -1
- package/dist/models/session.d.ts +2 -2
- package/dist/models/session.d.ts.map +1 -1
- package/dist/providers/IProvider.d.ts +4 -0
- package/dist/providers/IProvider.d.ts.map +1 -1
- package/dist/types.d.ts +82 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -11
package/dist/index.js
CHANGED
|
@@ -387,10 +387,24 @@ 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
|
+
};
|
|
394
408
|
|
|
395
409
|
// src/credentials/credentials-manager.ts
|
|
396
410
|
var CredentialsManager = class {
|
|
@@ -586,6 +600,94 @@ var CredentialsManager = class {
|
|
|
586
600
|
}
|
|
587
601
|
};
|
|
588
602
|
|
|
603
|
+
// src/identity/self-managed.ts
|
|
604
|
+
var SelfManagedIdentityProvider = class {
|
|
605
|
+
name = "self-managed";
|
|
606
|
+
manager;
|
|
607
|
+
constructor(configOrManager) {
|
|
608
|
+
if (configOrManager instanceof CredentialsManager) {
|
|
609
|
+
this.manager = configOrManager;
|
|
610
|
+
} else {
|
|
611
|
+
this.manager = new CredentialsManager(configOrManager || {});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
mapUser(user) {
|
|
615
|
+
return {
|
|
616
|
+
id: user.id,
|
|
617
|
+
identifier: user.identifier,
|
|
618
|
+
username: user.username,
|
|
619
|
+
email: user.email,
|
|
620
|
+
emailVerified: true,
|
|
621
|
+
// Self-managed accounts verify locally unless configured otherwise
|
|
622
|
+
createdAt: user.createdAt,
|
|
623
|
+
updatedAt: user.updatedAt,
|
|
624
|
+
metadata: user.metadata,
|
|
625
|
+
provider: this.name
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Registers a new user.
|
|
630
|
+
*/
|
|
631
|
+
async signUp(params) {
|
|
632
|
+
const user = await this.manager.signUp(params);
|
|
633
|
+
return this.mapUser(user);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Authenticates a user with password.
|
|
637
|
+
*/
|
|
638
|
+
async signIn(params) {
|
|
639
|
+
const user = await this.manager.verifyCredentials(params);
|
|
640
|
+
if (!user) {
|
|
641
|
+
throw new InvalidCredentialsError();
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
user: this.mapUser(user)
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Verifies credentials without throwing an error if invalid.
|
|
649
|
+
*/
|
|
650
|
+
async verifyCredentials(params) {
|
|
651
|
+
const user = await this.manager.verifyCredentials(params);
|
|
652
|
+
return user ? this.mapUser(user) : null;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Changes a user's password.
|
|
656
|
+
*/
|
|
657
|
+
async changePassword(params) {
|
|
658
|
+
return await this.manager.changePassword(params);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Retrieves a user by their unique ID.
|
|
662
|
+
*/
|
|
663
|
+
async getUserById(id) {
|
|
664
|
+
const user = await this.manager.getUserById(id);
|
|
665
|
+
return user ? this.mapUser(user) : null;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Retrieves a user by their identifier (email or username).
|
|
669
|
+
*/
|
|
670
|
+
async getUserByIdentifier(identifier) {
|
|
671
|
+
const user = await this.manager.getUserByIdentifier(identifier);
|
|
672
|
+
return user ? this.mapUser(user) : null;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Deletes a user from storage if supported by the storage adapter.
|
|
676
|
+
*/
|
|
677
|
+
async deleteUser(id) {
|
|
678
|
+
const storage = this.manager.getStorage();
|
|
679
|
+
if (typeof storage.deleteUser === "function") {
|
|
680
|
+
await storage.deleteUser(id);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Returns the underlying CredentialsManager instance.
|
|
685
|
+
*/
|
|
686
|
+
getCredentialsManager() {
|
|
687
|
+
return this.manager;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
|
|
589
691
|
// src/types.ts
|
|
590
692
|
var AccountLinkingStrategy = /* @__PURE__ */ ((AccountLinkingStrategy2) => {
|
|
591
693
|
AccountLinkingStrategy2["AUTO_LINK_BY_VERIFIED_EMAIL"] = "AUTO_LINK_BY_VERIFIED_EMAIL";
|
|
@@ -691,10 +793,7 @@ var LocalSessionHandler = class {
|
|
|
691
793
|
if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
|
|
692
794
|
throw new Error("No valid access token found in OAuth response");
|
|
693
795
|
}
|
|
694
|
-
const session = {
|
|
695
|
-
token: tokenData.access_token,
|
|
696
|
-
raw: tokenData
|
|
697
|
-
};
|
|
796
|
+
const session = {};
|
|
698
797
|
return session;
|
|
699
798
|
}
|
|
700
799
|
// PascalCase alias for backward compatibility
|
|
@@ -772,6 +871,125 @@ async function extractUserInfo(tokenData, providerMetadata) {
|
|
|
772
871
|
return { userInfo };
|
|
773
872
|
}
|
|
774
873
|
|
|
874
|
+
// src/utils/cookies.ts
|
|
875
|
+
var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
|
|
876
|
+
var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
|
|
877
|
+
var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
|
|
878
|
+
var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
|
|
879
|
+
function isProductionEnvironment() {
|
|
880
|
+
return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
|
|
881
|
+
}
|
|
882
|
+
function serializeCookie(name, value, options) {
|
|
883
|
+
const isProd = isProductionEnvironment();
|
|
884
|
+
const path = options?.path ?? "/";
|
|
885
|
+
const httpOnly = options?.httpOnly ?? true;
|
|
886
|
+
const secure = options?.secure ?? isProd;
|
|
887
|
+
const sameSite = options?.sameSite ?? "lax";
|
|
888
|
+
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
889
|
+
if (path) {
|
|
890
|
+
parts.push(`Path=${path}`);
|
|
891
|
+
}
|
|
892
|
+
if (typeof options?.maxAge === "number") {
|
|
893
|
+
parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
|
|
894
|
+
const expires = new Date(Date.now() + options.maxAge * 1e3).toUTCString();
|
|
895
|
+
parts.push(`Expires=${expires}`);
|
|
896
|
+
}
|
|
897
|
+
if (options?.domain) {
|
|
898
|
+
parts.push(`Domain=${options.domain}`);
|
|
899
|
+
}
|
|
900
|
+
if (httpOnly) {
|
|
901
|
+
parts.push("HttpOnly");
|
|
902
|
+
}
|
|
903
|
+
if (secure) {
|
|
904
|
+
parts.push("Secure");
|
|
905
|
+
}
|
|
906
|
+
if (sameSite) {
|
|
907
|
+
const capitalized = sameSite.charAt(0).toUpperCase() + sameSite.slice(1).toLowerCase();
|
|
908
|
+
parts.push(`SameSite=${capitalized}`);
|
|
909
|
+
}
|
|
910
|
+
return parts.join("; ");
|
|
911
|
+
}
|
|
912
|
+
function createSessionCookie(sessionId, options) {
|
|
913
|
+
const isProd = isProductionEnvironment();
|
|
914
|
+
const resolvedOptions = {
|
|
915
|
+
name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
|
|
916
|
+
path: options?.path ?? "/",
|
|
917
|
+
maxAge: options?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
918
|
+
httpOnly: options?.httpOnly ?? true,
|
|
919
|
+
secure: options?.secure ?? isProd,
|
|
920
|
+
sameSite: options?.sameSite ?? "lax",
|
|
921
|
+
domain: options?.domain
|
|
922
|
+
};
|
|
923
|
+
const name = resolvedOptions.name;
|
|
924
|
+
const header = serializeCookie(name, sessionId, resolvedOptions);
|
|
925
|
+
return {
|
|
926
|
+
name,
|
|
927
|
+
value: sessionId,
|
|
928
|
+
options: resolvedOptions,
|
|
929
|
+
header
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
function clearSessionCookie(options) {
|
|
933
|
+
const isProd = isProductionEnvironment();
|
|
934
|
+
const resolvedOptions = {
|
|
935
|
+
name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
|
|
936
|
+
path: options?.path ?? "/",
|
|
937
|
+
maxAge: 0,
|
|
938
|
+
httpOnly: options?.httpOnly ?? true,
|
|
939
|
+
secure: options?.secure ?? isProd,
|
|
940
|
+
sameSite: options?.sameSite ?? "lax",
|
|
941
|
+
domain: options?.domain
|
|
942
|
+
};
|
|
943
|
+
const name = resolvedOptions.name;
|
|
944
|
+
const header = serializeCookie(name, "", resolvedOptions);
|
|
945
|
+
return {
|
|
946
|
+
name,
|
|
947
|
+
value: "",
|
|
948
|
+
options: resolvedOptions,
|
|
949
|
+
header
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
function createStateCookie(state, options) {
|
|
953
|
+
const isProd = isProductionEnvironment();
|
|
954
|
+
const resolvedOptions = {
|
|
955
|
+
name: options?.name || DEFAULT_STATE_COOKIE_NAME,
|
|
956
|
+
path: options?.path ?? "/",
|
|
957
|
+
maxAge: options?.maxAge ?? DEFAULT_STATE_MAX_AGE_SECONDS,
|
|
958
|
+
httpOnly: options?.httpOnly ?? true,
|
|
959
|
+
secure: options?.secure ?? isProd,
|
|
960
|
+
sameSite: options?.sameSite ?? "lax",
|
|
961
|
+
domain: options?.domain
|
|
962
|
+
};
|
|
963
|
+
const name = resolvedOptions.name;
|
|
964
|
+
const header = serializeCookie(name, state, resolvedOptions);
|
|
965
|
+
return {
|
|
966
|
+
name,
|
|
967
|
+
value: state,
|
|
968
|
+
options: resolvedOptions,
|
|
969
|
+
header
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
function clearStateCookie(options) {
|
|
973
|
+
const isProd = isProductionEnvironment();
|
|
974
|
+
const resolvedOptions = {
|
|
975
|
+
name: options?.name || DEFAULT_STATE_COOKIE_NAME,
|
|
976
|
+
path: options?.path ?? "/",
|
|
977
|
+
maxAge: 0,
|
|
978
|
+
httpOnly: options?.httpOnly ?? true,
|
|
979
|
+
secure: options?.secure ?? isProd,
|
|
980
|
+
sameSite: options?.sameSite ?? "lax",
|
|
981
|
+
domain: options?.domain
|
|
982
|
+
};
|
|
983
|
+
const name = resolvedOptions.name;
|
|
984
|
+
const header = serializeCookie(name, "", resolvedOptions);
|
|
985
|
+
return {
|
|
986
|
+
name,
|
|
987
|
+
value: "",
|
|
988
|
+
options: resolvedOptions,
|
|
989
|
+
header
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
|
|
775
993
|
// src/lixa.ts
|
|
776
994
|
var Lixa = class _Lixa {
|
|
777
995
|
static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
|
|
@@ -781,14 +999,19 @@ var Lixa = class _Lixa {
|
|
|
781
999
|
localStateHandler;
|
|
782
1000
|
localSessionHandler;
|
|
783
1001
|
localResourceHandler;
|
|
1002
|
+
localAccountHandler;
|
|
784
1003
|
userResourceStore = /* @__PURE__ */ new Map();
|
|
1004
|
+
userAccountStore = /* @__PURE__ */ new Map();
|
|
785
1005
|
refreshMutexes = /* @__PURE__ */ new Map();
|
|
1006
|
+
identityProvider;
|
|
786
1007
|
credentialsManager;
|
|
787
1008
|
config;
|
|
788
1009
|
stateHandler;
|
|
789
1010
|
sessionHandler;
|
|
790
1011
|
resourceHandler;
|
|
1012
|
+
accountHandler;
|
|
791
1013
|
debug;
|
|
1014
|
+
sessionCookieResolver;
|
|
792
1015
|
/**
|
|
793
1016
|
* Creates a new Lixa instance with the provided configuration.
|
|
794
1017
|
*
|
|
@@ -803,7 +1026,15 @@ var Lixa = class _Lixa {
|
|
|
803
1026
|
* @throws Error when provider is not available and no inline implementation is provided
|
|
804
1027
|
*/
|
|
805
1028
|
constructor(config) {
|
|
806
|
-
this.
|
|
1029
|
+
this.sessionCookieResolver = config.sessionCookieName;
|
|
1030
|
+
const federatedOAuthProviders = config.federatedOAuthProviders || config.providers;
|
|
1031
|
+
this.config = {
|
|
1032
|
+
...config,
|
|
1033
|
+
...federatedOAuthProviders ? {
|
|
1034
|
+
providers: federatedOAuthProviders,
|
|
1035
|
+
federatedOAuthProviders
|
|
1036
|
+
} : {}
|
|
1037
|
+
};
|
|
807
1038
|
this.debug = config.debug || false;
|
|
808
1039
|
this.localStateHandler = new LocalStateHandler();
|
|
809
1040
|
this.localSessionHandler = new LocalSessionHandler();
|
|
@@ -839,19 +1070,70 @@ var Lixa = class _Lixa {
|
|
|
839
1070
|
}
|
|
840
1071
|
}
|
|
841
1072
|
};
|
|
842
|
-
this.
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1073
|
+
this.localAccountHandler = {
|
|
1074
|
+
accountStorage: {
|
|
1075
|
+
saveAccount: async (userId, provider, account) => {
|
|
1076
|
+
let userMap = this.userAccountStore.get(userId);
|
|
1077
|
+
if (!userMap) {
|
|
1078
|
+
userMap = /* @__PURE__ */ new Map();
|
|
1079
|
+
this.userAccountStore.set(userId, userMap);
|
|
1080
|
+
}
|
|
1081
|
+
userMap.set(provider.toLowerCase(), account);
|
|
1082
|
+
},
|
|
1083
|
+
getAccount: async (userId, provider) => {
|
|
1084
|
+
const userMap = this.userAccountStore.get(userId);
|
|
1085
|
+
return userMap?.get(provider.toLowerCase()) || null;
|
|
1086
|
+
},
|
|
1087
|
+
getUserAccounts: async (userId) => {
|
|
1088
|
+
const userMap = this.userAccountStore.get(userId);
|
|
1089
|
+
const result = {};
|
|
1090
|
+
if (userMap) {
|
|
1091
|
+
for (const [p, a] of userMap.entries()) {
|
|
1092
|
+
result[p] = a;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return result;
|
|
1096
|
+
},
|
|
1097
|
+
deleteAccount: async (userId, provider) => {
|
|
1098
|
+
const userMap = this.userAccountStore.get(userId);
|
|
1099
|
+
if (userMap) {
|
|
1100
|
+
userMap.delete(provider.toLowerCase());
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
const storage = config.storage;
|
|
1106
|
+
this.stateHandler = config.stateHandler || (storage?.stateStorage ? { storage: storage.stateStorage } : this.localStateHandler);
|
|
1107
|
+
this.sessionHandler = config.sessionHandler || (storage?.sessionStorage ? { storage: storage.sessionStorage } : this.localSessionHandler);
|
|
1108
|
+
this.resourceHandler = config.resourceHandler || (storage?.resourceStorage ? { resourceStorage: storage.resourceStorage } : this.localResourceHandler);
|
|
1109
|
+
this.accountHandler = storage?.accountStorage ? { accountStorage: storage.accountStorage } : this.localAccountHandler;
|
|
1110
|
+
const identityConfig = config.identityProvider || config.identity;
|
|
1111
|
+
if (identityConfig) {
|
|
1112
|
+
if (typeof identityConfig.signUp === "function") {
|
|
1113
|
+
this.identityProvider = identityConfig;
|
|
1114
|
+
if (this.identityProvider instanceof SelfManagedIdentityProvider) {
|
|
1115
|
+
this.credentialsManager = this.identityProvider.getCredentialsManager();
|
|
1116
|
+
}
|
|
1117
|
+
} else {
|
|
1118
|
+
const selfManaged = new SelfManagedIdentityProvider(identityConfig);
|
|
1119
|
+
this.identityProvider = selfManaged;
|
|
1120
|
+
this.credentialsManager = selfManaged.getCredentialsManager();
|
|
1121
|
+
}
|
|
1122
|
+
} else if (config.credentials !== void 0 && config.credentials.enabled !== false) {
|
|
1123
|
+
const selfManaged = new SelfManagedIdentityProvider(config.credentials);
|
|
1124
|
+
this.identityProvider = selfManaged;
|
|
1125
|
+
this.credentialsManager = selfManaged.getCredentialsManager();
|
|
847
1126
|
}
|
|
1127
|
+
const oauthProviders = this.config.providers;
|
|
848
1128
|
this.log("INFO", "Init", "Initializing Lixa instance", {
|
|
849
|
-
providers:
|
|
850
|
-
|
|
1129
|
+
providers: oauthProviders ? Object.keys(oauthProviders) : [],
|
|
1130
|
+
identityProvider: this.identityProvider ? this.identityProvider.name : void 0,
|
|
1131
|
+
identityEnabled: this.identityProvider !== void 0,
|
|
1132
|
+
credentialsEnabled: this.identityProvider !== void 0,
|
|
851
1133
|
debug: this.debug
|
|
852
1134
|
});
|
|
853
|
-
if (
|
|
854
|
-
for (const [providerName, providerConfig] of Object.entries(
|
|
1135
|
+
if (oauthProviders) {
|
|
1136
|
+
for (const [providerName, providerConfig] of Object.entries(oauthProviders)) {
|
|
855
1137
|
const name = providerName.toLowerCase();
|
|
856
1138
|
const typedConfig = providerConfig;
|
|
857
1139
|
this.validateProviderConfig(providerName, typedConfig);
|
|
@@ -880,6 +1162,20 @@ var Lixa = class _Lixa {
|
|
|
880
1162
|
* @throws InvalidProviderConfigError when required fields are missing or invalid
|
|
881
1163
|
*/
|
|
882
1164
|
validateProviderConfig(name, config) {
|
|
1165
|
+
if (!config.scopes || config.scopes.length === 0) {
|
|
1166
|
+
const p = config.provider;
|
|
1167
|
+
if (p?.defaultScopes && Array.isArray(p.defaultScopes) && p.defaultScopes.length > 0) {
|
|
1168
|
+
config.scopes = [...p.defaultScopes];
|
|
1169
|
+
} else if (p?.authScopes && Array.isArray(p.authScopes) && p.authScopes.length > 0) {
|
|
1170
|
+
config.scopes = [...p.authScopes];
|
|
1171
|
+
} else if (name === "google" || name.startsWith("google")) {
|
|
1172
|
+
config.scopes = ["openid", "profile", "email"];
|
|
1173
|
+
} else if (name === "github" || name.startsWith("github")) {
|
|
1174
|
+
config.scopes = ["read:user", "user:email"];
|
|
1175
|
+
} else if (name.startsWith("cognito")) {
|
|
1176
|
+
config.scopes = ["openid", "email", "profile"];
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
883
1179
|
const requiredFields = ["clientId", "clientSecret", "redirectUri", "scopes"];
|
|
884
1180
|
const missingFields = requiredFields.filter((field) => {
|
|
885
1181
|
const value = config[field];
|
|
@@ -1216,7 +1512,7 @@ var Lixa = class _Lixa {
|
|
|
1216
1512
|
this.log("INFO", "State", `Saved state for provider: ${providerType}`, { state: stateValue });
|
|
1217
1513
|
const authNScopes = this.resolveAuthNScopes(
|
|
1218
1514
|
providerType,
|
|
1219
|
-
providerConfig.scopes,
|
|
1515
|
+
providerConfig.scopes || [],
|
|
1220
1516
|
providerImpl,
|
|
1221
1517
|
providerConfig.allowNonAuthScopes
|
|
1222
1518
|
);
|
|
@@ -1302,7 +1598,8 @@ var Lixa = class _Lixa {
|
|
|
1302
1598
|
async handleCallback({
|
|
1303
1599
|
provider,
|
|
1304
1600
|
code,
|
|
1305
|
-
state
|
|
1601
|
+
state,
|
|
1602
|
+
sessionId
|
|
1306
1603
|
}) {
|
|
1307
1604
|
const providerType = String(provider).toLowerCase();
|
|
1308
1605
|
this.log("INFO", "Auth", `Handling OAuth callback for provider: ${providerType}`);
|
|
@@ -1371,19 +1668,40 @@ var Lixa = class _Lixa {
|
|
|
1371
1668
|
}
|
|
1372
1669
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1373
1670
|
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";
|
|
1671
|
+
const mode = typeof linkingConfig === "object" ? String(linkingConfig?.mode || "") : "";
|
|
1672
|
+
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";
|
|
1376
1673
|
const email = extractedUserInfo?.email;
|
|
1377
1674
|
const isVerified = extractedUserInfo?.email_verified !== false;
|
|
1378
|
-
const requireVerified = linkingConfig?.requireVerifiedEmail ?? true;
|
|
1675
|
+
const requireVerified = typeof linkingConfig === "object" ? linkingConfig?.requireVerifiedEmail ?? true : true;
|
|
1379
1676
|
const canLink = isLinkByEmail && email && (!requireVerified || isVerified);
|
|
1677
|
+
if (sessionId) {
|
|
1678
|
+
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1679
|
+
if (activeSession) {
|
|
1680
|
+
this.log("INFO", "AccountLinking", `Linking provider '${providerType}' directly to active session '${sessionId}'`);
|
|
1681
|
+
const linkedAcc2 = {
|
|
1682
|
+
provider: providerType,
|
|
1683
|
+
providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
|
|
1684
|
+
email: extractedUserInfo?.email || activeSession.email,
|
|
1685
|
+
accessToken: tokens.access_token,
|
|
1686
|
+
raw: tokens,
|
|
1687
|
+
linkedAt: Date.now()
|
|
1688
|
+
};
|
|
1689
|
+
activeSession.accounts = activeSession.accounts || {};
|
|
1690
|
+
activeSession.accounts[providerType] = linkedAcc2;
|
|
1691
|
+
await sessionStorage.saveSession(sessionId, activeSession, 86400);
|
|
1692
|
+
if (this.accountHandler?.accountStorage) {
|
|
1693
|
+
const userKey = this.getUserKeyFromSession(activeSession);
|
|
1694
|
+
await this.accountHandler.accountStorage.saveAccount(userKey, providerType, linkedAcc2);
|
|
1695
|
+
}
|
|
1696
|
+
return sessionId;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1380
1699
|
if (canLink && sessionStorage.getSessionByEmail) {
|
|
1381
1700
|
const existingRecord = await sessionStorage.getSessionByEmail(email);
|
|
1382
1701
|
if (existingRecord) {
|
|
1383
1702
|
this.log("INFO", "AccountLinking", `Linking provider '${providerType}' to existing session for email '${email}'`);
|
|
1384
1703
|
const { sessionId: existingSessionId, session: existingSession } = existingRecord;
|
|
1385
|
-
|
|
1386
|
-
existingSession.accounts[providerType] = {
|
|
1704
|
+
const linkedAcc2 = {
|
|
1387
1705
|
provider: providerType,
|
|
1388
1706
|
providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
|
|
1389
1707
|
email,
|
|
@@ -1391,15 +1709,18 @@ var Lixa = class _Lixa {
|
|
|
1391
1709
|
raw: tokens,
|
|
1392
1710
|
linkedAt: Date.now()
|
|
1393
1711
|
};
|
|
1712
|
+
existingSession.accounts = existingSession.accounts || {};
|
|
1713
|
+
existingSession.accounts[providerType] = linkedAcc2;
|
|
1394
1714
|
existingSession.provider = providerType;
|
|
1395
|
-
existingSession.token = tokens.access_token;
|
|
1396
|
-
existingSession.raw = tokens;
|
|
1397
1715
|
await sessionStorage.saveSession(existingSessionId, existingSession, 86400);
|
|
1716
|
+
if (this.accountHandler?.accountStorage) {
|
|
1717
|
+
const userKey = this.getUserKeyFromSession(existingSession);
|
|
1718
|
+
await this.accountHandler.accountStorage.saveAccount(userKey, providerType, linkedAcc2);
|
|
1719
|
+
}
|
|
1398
1720
|
return existingSessionId;
|
|
1399
1721
|
}
|
|
1400
1722
|
}
|
|
1401
|
-
|
|
1402
|
-
session.accounts[providerType] = {
|
|
1723
|
+
const linkedAcc = {
|
|
1403
1724
|
provider: providerType,
|
|
1404
1725
|
providerUserId: extractedUserInfo?.sub || extractedUserInfo?.id,
|
|
1405
1726
|
email: extractedUserInfo?.email,
|
|
@@ -1407,12 +1728,18 @@ var Lixa = class _Lixa {
|
|
|
1407
1728
|
raw: tokens,
|
|
1408
1729
|
linkedAt: Date.now()
|
|
1409
1730
|
};
|
|
1410
|
-
|
|
1411
|
-
session.
|
|
1412
|
-
this.
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1731
|
+
session.accounts = session.accounts || {};
|
|
1732
|
+
session.accounts[providerType] = linkedAcc;
|
|
1733
|
+
if (this.accountHandler?.accountStorage) {
|
|
1734
|
+
const userKey = this.getUserKeyFromSession(session);
|
|
1735
|
+
await this.accountHandler.accountStorage.saveAccount(userKey, providerType, linkedAcc);
|
|
1736
|
+
}
|
|
1737
|
+
const newSessionId = randomBytes2(32).toString("hex");
|
|
1738
|
+
session.sessionId = newSessionId;
|
|
1739
|
+
this.log("INFO", "Session", "Storing session", { sessionId: newSessionId });
|
|
1740
|
+
await sessionStorage.saveSession(newSessionId, session, 86400);
|
|
1741
|
+
this.log("INFO", "Session", "Session created successfully", { sessionId: newSessionId });
|
|
1742
|
+
return newSessionId;
|
|
1416
1743
|
}
|
|
1417
1744
|
/**
|
|
1418
1745
|
* Explicitly link a new OAuth provider account to an active session.
|
|
@@ -1498,6 +1825,10 @@ var Lixa = class _Lixa {
|
|
|
1498
1825
|
}
|
|
1499
1826
|
delete session.accounts[providerToUnlink.toLowerCase()];
|
|
1500
1827
|
await sessionStorage.saveSession(sessionId, session, 86400);
|
|
1828
|
+
if (this.accountHandler?.accountStorage) {
|
|
1829
|
+
const userKey = this.getUserKeyFromSession(session);
|
|
1830
|
+
await this.accountHandler.accountStorage.deleteAccount(userKey, providerToUnlink.toLowerCase());
|
|
1831
|
+
}
|
|
1501
1832
|
return true;
|
|
1502
1833
|
}
|
|
1503
1834
|
/**
|
|
@@ -1512,7 +1843,7 @@ var Lixa = class _Lixa {
|
|
|
1512
1843
|
* @returns The authorization URL for resource consent
|
|
1513
1844
|
*/
|
|
1514
1845
|
async getResourceAuthUrl(params) {
|
|
1515
|
-
const { sessionId, provider,
|
|
1846
|
+
const { sessionId, provider, state, prompt, extraConfig } = params;
|
|
1516
1847
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1517
1848
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1518
1849
|
if (!activeSession) {
|
|
@@ -1523,6 +1854,10 @@ var Lixa = class _Lixa {
|
|
|
1523
1854
|
if (!providerConfig) {
|
|
1524
1855
|
throw new ProviderNotConfiguredError(String(provider));
|
|
1525
1856
|
}
|
|
1857
|
+
const requestedScopes = params.scopes && params.scopes.length > 0 ? params.scopes : providerConfig.resourceScopes || [];
|
|
1858
|
+
if (requestedScopes.length === 0) {
|
|
1859
|
+
throw new Error(`No scopes specified for resource '${provider}'. Provide scopes in getResourceAuthUrl() or configure 'resourceScopes' on the provider.`);
|
|
1860
|
+
}
|
|
1526
1861
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
1527
1862
|
const stateValue = state || randomBytes2(16).toString("hex");
|
|
1528
1863
|
const codeVerifier = randomBytes2(32).toString("hex");
|
|
@@ -1540,7 +1875,7 @@ var Lixa = class _Lixa {
|
|
|
1540
1875
|
const searchParams = new URLSearchParams({
|
|
1541
1876
|
client_id: providerConfig.clientId,
|
|
1542
1877
|
redirect_uri: providerConfig.redirectUri,
|
|
1543
|
-
scope:
|
|
1878
|
+
scope: requestedScopes.join(" "),
|
|
1544
1879
|
state: stateValue,
|
|
1545
1880
|
response_type: "code",
|
|
1546
1881
|
code_challenge: codeChallenge,
|
|
@@ -1552,7 +1887,7 @@ var Lixa = class _Lixa {
|
|
|
1552
1887
|
return `${providerImpl.authorizationEndpoint}?${searchParams.toString()}`;
|
|
1553
1888
|
}
|
|
1554
1889
|
getUserKeyFromSession(session) {
|
|
1555
|
-
return session.userId || session.email || session.
|
|
1890
|
+
return session.userId || session.email || session.sessionId || "anonymous";
|
|
1556
1891
|
}
|
|
1557
1892
|
/**
|
|
1558
1893
|
* Handles the OAuth callback for a connected resource provider and stores resource tokens bound to user account.
|
|
@@ -1582,12 +1917,13 @@ var Lixa = class _Lixa {
|
|
|
1582
1917
|
}
|
|
1583
1918
|
const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier);
|
|
1584
1919
|
const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
|
|
1920
|
+
const effectiveScopes = scopes || (tokens.scope ? tokens.scope.split(" ") : providerConfig.resourceScopes || []);
|
|
1585
1921
|
const connectedResource = {
|
|
1586
1922
|
provider: providerType,
|
|
1587
1923
|
accessToken: tokens.access_token,
|
|
1588
1924
|
refreshToken: tokens.refresh_token,
|
|
1589
1925
|
expiresAt,
|
|
1590
|
-
scopes:
|
|
1926
|
+
scopes: effectiveScopes,
|
|
1591
1927
|
raw: tokens,
|
|
1592
1928
|
connectedAt: Date.now()
|
|
1593
1929
|
};
|
|
@@ -1599,6 +1935,40 @@ var Lixa = class _Lixa {
|
|
|
1599
1935
|
await sessionStorage.saveSession(sessionId, activeSession, 86400);
|
|
1600
1936
|
return activeSession;
|
|
1601
1937
|
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Retrieves configured default resource scopes for a provider.
|
|
1940
|
+
*
|
|
1941
|
+
* @param provider - Provider name (e.g. 'github', 'google')
|
|
1942
|
+
* @returns Array of configured resource scopes, or undefined if not configured
|
|
1943
|
+
*/
|
|
1944
|
+
getResourceScopes(provider) {
|
|
1945
|
+
const providerConfig = this.findProviderByType(String(provider).toLowerCase());
|
|
1946
|
+
return providerConfig?.resourceScopes;
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Returns the resolved session cookie name.
|
|
1950
|
+
* Dynamically reflects any changes to the active identity provider.
|
|
1951
|
+
*
|
|
1952
|
+
* @param req - Optional request object for multi-tenant request-scoped cookie resolution
|
|
1953
|
+
* @returns Resolved session cookie name
|
|
1954
|
+
*/
|
|
1955
|
+
getSessionCookieName(req) {
|
|
1956
|
+
if (typeof this.sessionCookieResolver === "function") {
|
|
1957
|
+
return this.sessionCookieResolver(this, req);
|
|
1958
|
+
}
|
|
1959
|
+
if (typeof this.sessionCookieResolver === "string" && this.sessionCookieResolver.length > 0) {
|
|
1960
|
+
return this.sessionCookieResolver;
|
|
1961
|
+
}
|
|
1962
|
+
return DEFAULT_SESSION_COOKIE_NAME;
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Sets or updates the session cookie name or dynamic resolver on this instance.
|
|
1966
|
+
*
|
|
1967
|
+
* @param resolver - Static cookie name or dynamic resolver function
|
|
1968
|
+
*/
|
|
1969
|
+
setSessionCookieName(resolver) {
|
|
1970
|
+
this.sessionCookieResolver = resolver;
|
|
1971
|
+
}
|
|
1602
1972
|
/**
|
|
1603
1973
|
* Retrieves a connected resource for a specific User ID / Email directly (independent of session IDs).
|
|
1604
1974
|
* Automatically refreshes expired access tokens if a refresh token is present.
|
|
@@ -1779,7 +2149,16 @@ var Lixa = class _Lixa {
|
|
|
1779
2149
|
*/
|
|
1780
2150
|
async fetchSessionInfo(sessionId) {
|
|
1781
2151
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1782
|
-
|
|
2152
|
+
const session = await sessionStorage.getSession(sessionId);
|
|
2153
|
+
if (!session) return null;
|
|
2154
|
+
if (this.accountHandler?.accountStorage) {
|
|
2155
|
+
const userKey = this.getUserKeyFromSession(session);
|
|
2156
|
+
const persistentAccounts = await this.accountHandler.accountStorage.getUserAccounts(userKey);
|
|
2157
|
+
if (persistentAccounts && Object.keys(persistentAccounts).length > 0) {
|
|
2158
|
+
session.accounts = { ...persistentAccounts, ...session.accounts || {} };
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return session;
|
|
1783
2162
|
}
|
|
1784
2163
|
/**
|
|
1785
2164
|
* Deletes a session from session storage (e.g. on logout).
|
|
@@ -1830,36 +2209,64 @@ var Lixa = class _Lixa {
|
|
|
1830
2209
|
return response.json();
|
|
1831
2210
|
}
|
|
1832
2211
|
/**
|
|
1833
|
-
* Checks if
|
|
2212
|
+
* Checks if an identity provider (Self-Managed Database, AWS Cognito, Auth0, Okta, etc.) is configured.
|
|
2213
|
+
*
|
|
2214
|
+
* @returns True if identity provider is configured and available
|
|
2215
|
+
*/
|
|
2216
|
+
isIdentityEnabled() {
|
|
2217
|
+
return this.identityProvider !== void 0;
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Checks if credentials / identity authentication is configured and enabled.
|
|
1834
2221
|
*
|
|
1835
|
-
* @returns True if credentials authentication is available
|
|
2222
|
+
* @returns True if credentials / identity authentication is available
|
|
1836
2223
|
*/
|
|
1837
2224
|
isCredentialsEnabled() {
|
|
1838
|
-
return this.
|
|
2225
|
+
return this.identityProvider !== void 0;
|
|
2226
|
+
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Returns the configured IdentityProvider instance.
|
|
2229
|
+
*/
|
|
2230
|
+
getIdentityProvider() {
|
|
2231
|
+
return this.identityProvider;
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Dynamically sets or updates the active identity provider (e.g. SelfManaged, Cognito, Auth0, Okta).
|
|
2235
|
+
*
|
|
2236
|
+
* @param provider - Identity provider instance or undefined to disable
|
|
2237
|
+
*/
|
|
2238
|
+
setIdentityProvider(provider) {
|
|
2239
|
+
this.identityProvider = provider;
|
|
2240
|
+
if (provider instanceof SelfManagedIdentityProvider) {
|
|
2241
|
+
this.credentialsManager = provider.getCredentialsManager();
|
|
2242
|
+
} else {
|
|
2243
|
+
this.credentialsManager = void 0;
|
|
2244
|
+
}
|
|
1839
2245
|
}
|
|
1840
2246
|
/**
|
|
1841
|
-
* Returns the underlying CredentialsManager instance if
|
|
2247
|
+
* Returns the underlying CredentialsManager instance if using Self-Managed identity.
|
|
1842
2248
|
*/
|
|
1843
2249
|
getCredentialsManager() {
|
|
1844
2250
|
return this.credentialsManager;
|
|
1845
2251
|
}
|
|
1846
2252
|
/**
|
|
1847
|
-
* Registers a new user with
|
|
1848
|
-
* Automatically
|
|
1849
|
-
* stores credentials, and creates a session (unless autoCreateSessionOnSignUp is false).
|
|
2253
|
+
* Registers a new user with the configured identity provider (Self-Managed DB, Cognito, Auth0, Okta).
|
|
2254
|
+
* Automatically creates an active session unless autoCreateSessionOnSignUp is explicitly set to false.
|
|
1850
2255
|
*
|
|
1851
2256
|
* @param params - Registration parameters (identifier, password, email, username, metadata)
|
|
1852
|
-
* @returns Created user
|
|
2257
|
+
* @returns Created user identity and optional active session
|
|
1853
2258
|
* @throws WeakPasswordError if password does not meet policy requirements
|
|
1854
2259
|
* @throws UserAlreadyExistsError if identifier is already registered
|
|
1855
|
-
* @throws
|
|
2260
|
+
* @throws IdentityNotConfiguredError if identity authentication is not configured
|
|
1856
2261
|
*/
|
|
1857
2262
|
async signUp(params) {
|
|
1858
|
-
if (!this.
|
|
1859
|
-
throw new
|
|
2263
|
+
if (!this.identityProvider) {
|
|
2264
|
+
throw new IdentityNotConfiguredError();
|
|
1860
2265
|
}
|
|
1861
|
-
|
|
1862
|
-
const
|
|
2266
|
+
const rawProviderName = this.identityProvider.name;
|
|
2267
|
+
const providerName = rawProviderName === "self-managed" ? "credentials" : rawProviderName;
|
|
2268
|
+
this.log("INFO", "Identity", `Processing signUp request via ${providerName}`, { identifier: params.identifier });
|
|
2269
|
+
const user = await this.identityProvider.signUp(params);
|
|
1863
2270
|
const autoSession = this.config.credentials?.autoCreateSessionOnSignUp ?? true;
|
|
1864
2271
|
if (!autoSession) {
|
|
1865
2272
|
return { user };
|
|
@@ -1867,8 +2274,9 @@ var Lixa = class _Lixa {
|
|
|
1867
2274
|
const sessionId = randomBytes2(16).toString("hex");
|
|
1868
2275
|
const token = randomBytes2(32).toString("hex");
|
|
1869
2276
|
const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
|
|
2277
|
+
const accountKey = providerName;
|
|
1870
2278
|
const credentialsAccount = {
|
|
1871
|
-
provider:
|
|
2279
|
+
provider: providerName,
|
|
1872
2280
|
providerUserId: user.id,
|
|
1873
2281
|
email: user.email,
|
|
1874
2282
|
accessToken: token,
|
|
@@ -1880,67 +2288,71 @@ var Lixa = class _Lixa {
|
|
|
1880
2288
|
linkedAt: Date.now()
|
|
1881
2289
|
};
|
|
1882
2290
|
const session = {
|
|
1883
|
-
|
|
2291
|
+
sessionId,
|
|
1884
2292
|
userId: user.id,
|
|
1885
2293
|
email: user.email,
|
|
1886
|
-
|
|
1887
|
-
provider: "credentials",
|
|
2294
|
+
provider: providerName,
|
|
1888
2295
|
accounts: {
|
|
1889
|
-
|
|
2296
|
+
[accountKey]: credentialsAccount
|
|
1890
2297
|
}
|
|
1891
2298
|
};
|
|
1892
2299
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1893
2300
|
await sessionStorage.saveSession(sessionId, session, ttl);
|
|
1894
|
-
this.log("INFO", "
|
|
2301
|
+
this.log("INFO", "Identity", "Created session on signUp", { userId: user.id, sessionId, provider: providerName });
|
|
1895
2302
|
return { user, sessionId, session };
|
|
1896
2303
|
}
|
|
1897
2304
|
/**
|
|
1898
|
-
* Authenticates a user with
|
|
1899
|
-
*
|
|
1900
|
-
* If account linking is configured with AUTO_LINK_BY_VERIFIED_EMAIL, merges with existing session.
|
|
2305
|
+
* Authenticates a user with credentials via the configured identity provider (Self-Managed DB, Cognito, Auth0, Okta).
|
|
2306
|
+
* Creates an active session and automatically links with existing sessions when AUTO_LINK_BY_VERIFIED_EMAIL is configured.
|
|
1901
2307
|
*
|
|
1902
2308
|
* @param params - Sign-in parameters (identifier, password)
|
|
1903
2309
|
* @returns Authenticated user, session ID, and session object
|
|
1904
2310
|
* @throws InvalidCredentialsError if authentication fails
|
|
1905
|
-
* @throws
|
|
2311
|
+
* @throws IdentityNotConfiguredError if identity authentication is not configured
|
|
1906
2312
|
*/
|
|
1907
2313
|
async signIn(params) {
|
|
1908
|
-
if (!this.
|
|
1909
|
-
throw new
|
|
2314
|
+
if (!this.identityProvider) {
|
|
2315
|
+
throw new IdentityNotConfiguredError();
|
|
1910
2316
|
}
|
|
1911
2317
|
const lookupKey = params.username || params.identifier;
|
|
1912
|
-
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
2318
|
+
const rawProviderName = this.identityProvider.name;
|
|
2319
|
+
const providerName = rawProviderName === "self-managed" ? "credentials" : rawProviderName;
|
|
2320
|
+
this.log("INFO", "Identity", `Processing signIn request via ${providerName}`, { identifier: lookupKey });
|
|
2321
|
+
const authResult = await this.identityProvider.signIn(params);
|
|
2322
|
+
const user = authResult.user;
|
|
2323
|
+
const tokens = authResult.tokens;
|
|
1918
2324
|
const ttl = this.config.credentials?.sessionTtlSeconds ?? 86400;
|
|
1919
|
-
const token = randomBytes2(32).toString("hex");
|
|
2325
|
+
const token = tokens?.accessToken || randomBytes2(32).toString("hex");
|
|
2326
|
+
const rawTokens = {
|
|
2327
|
+
access_token: token,
|
|
2328
|
+
token_type: tokens?.tokenType || "Bearer",
|
|
2329
|
+
expires_in: tokens?.expiresIn || ttl,
|
|
2330
|
+
id_token: tokens?.idToken,
|
|
2331
|
+
refresh_token: tokens?.refreshToken,
|
|
2332
|
+
...tokens?.raw
|
|
2333
|
+
};
|
|
2334
|
+
const accountKey = providerName === "self-managed" ? "credentials" : providerName;
|
|
1920
2335
|
const credentialsAccount = {
|
|
1921
|
-
provider:
|
|
2336
|
+
provider: providerName,
|
|
1922
2337
|
providerUserId: user.id,
|
|
1923
2338
|
email: user.email,
|
|
1924
2339
|
accessToken: token,
|
|
1925
|
-
raw:
|
|
1926
|
-
access_token: token,
|
|
1927
|
-
token_type: "Bearer",
|
|
1928
|
-
expires_in: ttl
|
|
1929
|
-
},
|
|
2340
|
+
raw: rawTokens,
|
|
1930
2341
|
linkedAt: Date.now()
|
|
1931
2342
|
};
|
|
1932
2343
|
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1933
|
-
const
|
|
1934
|
-
const
|
|
2344
|
+
const linkingConfig = this.config.accountLinking;
|
|
2345
|
+
const linkingMode = typeof linkingConfig === "object" ? linkingConfig?.mode : void 0;
|
|
2346
|
+
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
2347
|
if (isAutoLink && user.email && sessionStorage.getSessionByEmail) {
|
|
1936
2348
|
const existing = await sessionStorage.getSessionByEmail(user.email);
|
|
1937
2349
|
if (existing) {
|
|
1938
2350
|
const mergedSession = { ...existing.session };
|
|
1939
|
-
mergedSession.accounts = { ...mergedSession.accounts || {},
|
|
2351
|
+
mergedSession.accounts = { ...mergedSession.accounts || {}, [accountKey]: credentialsAccount };
|
|
1940
2352
|
if (!mergedSession.userId) mergedSession.userId = user.id;
|
|
1941
2353
|
if (!mergedSession.email) mergedSession.email = user.email;
|
|
1942
2354
|
await sessionStorage.saveSession(existing.sessionId, mergedSession, ttl);
|
|
1943
|
-
this.log("INFO", "AccountLinking",
|
|
2355
|
+
this.log("INFO", "AccountLinking", `Linked ${providerName} to existing session by email`, {
|
|
1944
2356
|
sessionId: existing.sessionId,
|
|
1945
2357
|
email: user.email
|
|
1946
2358
|
});
|
|
@@ -1953,31 +2365,46 @@ var Lixa = class _Lixa {
|
|
|
1953
2365
|
}
|
|
1954
2366
|
const sessionId = randomBytes2(16).toString("hex");
|
|
1955
2367
|
const session = {
|
|
1956
|
-
|
|
2368
|
+
sessionId,
|
|
1957
2369
|
userId: user.id,
|
|
1958
2370
|
email: user.email,
|
|
1959
|
-
|
|
1960
|
-
provider: "credentials",
|
|
2371
|
+
provider: providerName,
|
|
1961
2372
|
accounts: {
|
|
1962
|
-
|
|
2373
|
+
[accountKey]: credentialsAccount
|
|
1963
2374
|
}
|
|
1964
2375
|
};
|
|
2376
|
+
const userKey = this.getUserKeyFromSession(session);
|
|
2377
|
+
if (this.accountHandler?.accountStorage) {
|
|
2378
|
+
const persistentAccounts = await this.accountHandler.accountStorage.getUserAccounts(userKey);
|
|
2379
|
+
if (persistentAccounts && Object.keys(persistentAccounts).length > 0) {
|
|
2380
|
+
session.accounts = { ...persistentAccounts, ...session.accounts };
|
|
2381
|
+
}
|
|
2382
|
+
await this.accountHandler.accountStorage.saveAccount(userKey, accountKey, credentialsAccount);
|
|
2383
|
+
}
|
|
1965
2384
|
await sessionStorage.saveSession(sessionId, session, ttl);
|
|
1966
|
-
this.log("INFO", "
|
|
2385
|
+
this.log("INFO", "Identity", `User signed in successfully via ${providerName}`, { userId: user.id, sessionId });
|
|
1967
2386
|
return { user, sessionId, session };
|
|
1968
2387
|
}
|
|
1969
2388
|
/**
|
|
1970
2389
|
* Verifies username/email and password credentials without generating a session.
|
|
1971
2390
|
*
|
|
1972
2391
|
* @param params - Verification parameters (identifier, password)
|
|
1973
|
-
* @returns User credentials
|
|
1974
|
-
* @throws
|
|
2392
|
+
* @returns User credentials/identity or null if invalid
|
|
2393
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
1975
2394
|
*/
|
|
1976
2395
|
async verifyCredentials(params) {
|
|
1977
|
-
if (!this.
|
|
1978
|
-
throw new
|
|
2396
|
+
if (!this.identityProvider) {
|
|
2397
|
+
throw new IdentityNotConfiguredError();
|
|
2398
|
+
}
|
|
2399
|
+
if (typeof this.identityProvider.verifyCredentials === "function") {
|
|
2400
|
+
return this.identityProvider.verifyCredentials(params);
|
|
2401
|
+
}
|
|
2402
|
+
try {
|
|
2403
|
+
const result = await this.identityProvider.signIn(params);
|
|
2404
|
+
return result.user;
|
|
2405
|
+
} catch {
|
|
2406
|
+
return null;
|
|
1979
2407
|
}
|
|
1980
|
-
return this.credentialsManager.verifyCredentials(params);
|
|
1981
2408
|
}
|
|
1982
2409
|
/**
|
|
1983
2410
|
* Updates a user's password after verifying the current password and enforcing policy on the new password.
|
|
@@ -1987,17 +2414,140 @@ var Lixa = class _Lixa {
|
|
|
1987
2414
|
* @throws UserNotFoundError if user is not found
|
|
1988
2415
|
* @throws InvalidCredentialsError if current password is incorrect
|
|
1989
2416
|
* @throws WeakPasswordError if new password does not meet policy requirements
|
|
1990
|
-
* @throws
|
|
2417
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
1991
2418
|
*/
|
|
1992
2419
|
async changePassword(params) {
|
|
1993
|
-
if (!this.
|
|
1994
|
-
throw new
|
|
2420
|
+
if (!this.identityProvider) {
|
|
2421
|
+
throw new IdentityNotConfiguredError();
|
|
1995
2422
|
}
|
|
1996
|
-
this.
|
|
2423
|
+
if (typeof this.identityProvider.changePassword !== "function") {
|
|
2424
|
+
throw new IdentityProviderError(
|
|
2425
|
+
`Password change is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
2426
|
+
this.identityProvider.name
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
this.log("INFO", "Identity", "Processing changePassword request", {
|
|
1997
2430
|
userId: params.userId,
|
|
1998
2431
|
identifier: params.identifier
|
|
1999
2432
|
});
|
|
2000
|
-
return this.
|
|
2433
|
+
return this.identityProvider.changePassword(params);
|
|
2434
|
+
}
|
|
2435
|
+
/**
|
|
2436
|
+
* Initiates a password reset (forgot password flow) through the configured identity provider.
|
|
2437
|
+
*
|
|
2438
|
+
* @param params - Forgot password parameters (identifier, optional metadata)
|
|
2439
|
+
* @returns ForgotPasswordResult indicating status and delivery medium (email/SMS)
|
|
2440
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
2441
|
+
*/
|
|
2442
|
+
async forgotPassword(params) {
|
|
2443
|
+
if (!this.identityProvider) {
|
|
2444
|
+
throw new IdentityNotConfiguredError();
|
|
2445
|
+
}
|
|
2446
|
+
if (typeof this.identityProvider.forgotPassword !== "function") {
|
|
2447
|
+
throw new IdentityProviderError(
|
|
2448
|
+
`Password reset request is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
2449
|
+
this.identityProvider.name
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
this.log("INFO", "Identity", "Processing forgotPassword request", { identifier: params.identifier });
|
|
2453
|
+
return this.identityProvider.forgotPassword(params);
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Confirms a password reset using a verification code / token and sets the new password.
|
|
2457
|
+
*
|
|
2458
|
+
* @param params - Verification code, identifier, and new password
|
|
2459
|
+
* @returns True if password reset was successfully confirmed
|
|
2460
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
2461
|
+
*/
|
|
2462
|
+
async confirmPasswordReset(params) {
|
|
2463
|
+
if (!this.identityProvider) {
|
|
2464
|
+
throw new IdentityNotConfiguredError();
|
|
2465
|
+
}
|
|
2466
|
+
if (typeof this.identityProvider.confirmPasswordReset !== "function") {
|
|
2467
|
+
throw new IdentityProviderError(
|
|
2468
|
+
`Confirm password reset is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
2469
|
+
this.identityProvider.name
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
this.log("INFO", "Identity", "Processing confirmPasswordReset request", { identifier: params.identifier });
|
|
2473
|
+
return this.identityProvider.confirmPasswordReset(params);
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* Confirms user account registration using a verification code.
|
|
2477
|
+
*
|
|
2478
|
+
* @param params - Verification parameters including identifier and confirmationCode
|
|
2479
|
+
* @returns True if confirmation was successful
|
|
2480
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
2481
|
+
* @throws IdentityProviderError if identity provider does not support confirmation
|
|
2482
|
+
*/
|
|
2483
|
+
async confirmSignUp(params) {
|
|
2484
|
+
if (!this.identityProvider) {
|
|
2485
|
+
throw new IdentityNotConfiguredError();
|
|
2486
|
+
}
|
|
2487
|
+
if (typeof this.identityProvider.confirmSignUp !== "function") {
|
|
2488
|
+
throw new IdentityProviderError(
|
|
2489
|
+
`Account confirmation is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
2490
|
+
this.identityProvider.name
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
this.log("INFO", "Identity", "Processing confirmSignUp request", { identifier: params.identifier });
|
|
2494
|
+
return this.identityProvider.confirmSignUp(params);
|
|
2495
|
+
}
|
|
2496
|
+
/**
|
|
2497
|
+
* Resends the sign up confirmation code to the user.
|
|
2498
|
+
*
|
|
2499
|
+
* @param params - Resend parameters including user identifier
|
|
2500
|
+
* @returns Details about the code delivery (medium, destination)
|
|
2501
|
+
* @throws IdentityNotConfiguredError if identity auth is not configured
|
|
2502
|
+
* @throws IdentityProviderError if identity provider does not support resending codes
|
|
2503
|
+
*/
|
|
2504
|
+
async resendConfirmationCode(params) {
|
|
2505
|
+
if (!this.identityProvider) {
|
|
2506
|
+
throw new IdentityNotConfiguredError();
|
|
2507
|
+
}
|
|
2508
|
+
if (typeof this.identityProvider.resendConfirmationCode !== "function") {
|
|
2509
|
+
throw new IdentityProviderError(
|
|
2510
|
+
`Resending confirmation code is not supported by the '${this.identityProvider.name}' identity provider`,
|
|
2511
|
+
this.identityProvider.name
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
this.log("INFO", "Identity", "Processing resendConfirmationCode request", { identifier: params.identifier });
|
|
2515
|
+
return this.identityProvider.resendConfirmationCode(params);
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Retrieves a user by their unique ID from the configured identity provider.
|
|
2519
|
+
*
|
|
2520
|
+
* @param id - Unique user ID
|
|
2521
|
+
* @returns UserIdentity or null if not found
|
|
2522
|
+
*/
|
|
2523
|
+
async getUserById(id) {
|
|
2524
|
+
if (!this.identityProvider || typeof this.identityProvider.getUserById !== "function") {
|
|
2525
|
+
return null;
|
|
2526
|
+
}
|
|
2527
|
+
return this.identityProvider.getUserById(id);
|
|
2528
|
+
}
|
|
2529
|
+
/**
|
|
2530
|
+
* Retrieves a user by their identifier (username or email) from the configured identity provider.
|
|
2531
|
+
*
|
|
2532
|
+
* @param identifier - Username or email string
|
|
2533
|
+
* @returns UserIdentity or null if not found
|
|
2534
|
+
*/
|
|
2535
|
+
async getUserByIdentifier(identifier) {
|
|
2536
|
+
if (!this.identityProvider || typeof this.identityProvider.getUserByIdentifier !== "function") {
|
|
2537
|
+
return null;
|
|
2538
|
+
}
|
|
2539
|
+
return this.identityProvider.getUserByIdentifier(identifier);
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Deletes a user account from the configured identity provider.
|
|
2543
|
+
*
|
|
2544
|
+
* @param id - Unique user ID
|
|
2545
|
+
*/
|
|
2546
|
+
async deleteUser(id) {
|
|
2547
|
+
if (!this.identityProvider || typeof this.identityProvider.deleteUser !== "function") {
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
await this.identityProvider.deleteUser(id);
|
|
2001
2551
|
}
|
|
2002
2552
|
findProviderByType(providerType) {
|
|
2003
2553
|
for (const [key, value] of Object.entries(this.config.providers || {})) {
|
|
@@ -2009,125 +2559,6 @@ var Lixa = class _Lixa {
|
|
|
2009
2559
|
}
|
|
2010
2560
|
};
|
|
2011
2561
|
|
|
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
2562
|
// src/credentials/index.ts
|
|
2132
2563
|
var credentials_exports = {};
|
|
2133
2564
|
__export(credentials_exports, {
|
|
@@ -2137,6 +2568,12 @@ __export(credentials_exports, {
|
|
|
2137
2568
|
ScryptPasswordHasher: () => ScryptPasswordHasher,
|
|
2138
2569
|
validatePasswordPolicy: () => validatePasswordPolicy
|
|
2139
2570
|
});
|
|
2571
|
+
|
|
2572
|
+
// src/identity/index.ts
|
|
2573
|
+
var identity_exports = {};
|
|
2574
|
+
__export(identity_exports, {
|
|
2575
|
+
SelfManagedIdentityProvider: () => SelfManagedIdentityProvider
|
|
2576
|
+
});
|
|
2140
2577
|
export {
|
|
2141
2578
|
AccountLinkingStrategy,
|
|
2142
2579
|
AccountUnlinkError,
|
|
@@ -2147,6 +2584,8 @@ export {
|
|
|
2147
2584
|
DEFAULT_STATE_COOKIE_NAME,
|
|
2148
2585
|
DEFAULT_STATE_MAX_AGE_SECONDS,
|
|
2149
2586
|
EmailNotVerifiedError,
|
|
2587
|
+
IdentityNotConfiguredError,
|
|
2588
|
+
IdentityProviderError,
|
|
2150
2589
|
InvalidCredentialsError,
|
|
2151
2590
|
InvalidOAuthCallbackError,
|
|
2152
2591
|
InvalidProviderConfigError,
|
|
@@ -2158,6 +2597,7 @@ export {
|
|
|
2158
2597
|
ProviderNotConfiguredError,
|
|
2159
2598
|
RefreshTokenError,
|
|
2160
2599
|
ScryptPasswordHasher,
|
|
2600
|
+
SelfManagedIdentityProvider,
|
|
2161
2601
|
SessionNotFoundError,
|
|
2162
2602
|
TokenExchangeError,
|
|
2163
2603
|
UserAlreadyExistsError,
|
|
@@ -2172,6 +2612,7 @@ export {
|
|
|
2172
2612
|
determineProviderFromIssuer,
|
|
2173
2613
|
extractUserInfo,
|
|
2174
2614
|
fetchUserInfo,
|
|
2615
|
+
identity_exports as identity,
|
|
2175
2616
|
isProductionEnvironment,
|
|
2176
2617
|
serializeCookie,
|
|
2177
2618
|
validatePasswordPolicy
|