@vunexa/lixa 0.1.6-alpha.8 → 0.1.6-alpha.9
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/dao/session-cache.d.ts +2 -1
- package/dist/dao/session-cache.d.ts.map +1 -1
- package/dist/dao/state-cache.d.ts +4 -0
- package/dist/dao/state-cache.d.ts.map +1 -1
- package/dist/dao/types.d.ts +21 -75
- package/dist/dao/types.d.ts.map +1 -1
- package/dist/export-types/index.d.ts +177 -94
- package/dist/express.d.ts +103 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/index.cjs +212 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +166 -95
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +206 -97
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +14 -19
- package/dist/lixa.d.ts.map +1 -1
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/cookies.d.ts +1 -1
- package/dist/utils/cookies.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -47,7 +47,10 @@ __export(src_exports, {
|
|
|
47
47
|
SessionNotFoundError: () => SessionNotFoundError,
|
|
48
48
|
TokenExchangeError: () => TokenExchangeError,
|
|
49
49
|
clearSessionCookie: () => clearSessionCookie,
|
|
50
|
+
clearSessionCookieOnResponse: () => clearSessionCookieOnResponse,
|
|
50
51
|
clearStateCookie: () => clearStateCookie,
|
|
52
|
+
clearStateCookieOnResponse: () => clearStateCookieOnResponse,
|
|
53
|
+
createRequireAuthMiddleware: () => createRequireAuthMiddleware,
|
|
51
54
|
createSessionCookie: () => createSessionCookie,
|
|
52
55
|
createStateCookie: () => createStateCookie,
|
|
53
56
|
decodeIdToken: () => decodeIdToken,
|
|
@@ -55,7 +58,9 @@ __export(src_exports, {
|
|
|
55
58
|
extractUserInfo: () => extractUserInfo,
|
|
56
59
|
fetchUserInfo: () => fetchUserInfo,
|
|
57
60
|
isProductionEnvironment: () => isProductionEnvironment,
|
|
58
|
-
serializeCookie: () => serializeCookie
|
|
61
|
+
serializeCookie: () => serializeCookie,
|
|
62
|
+
setSessionCookieOnResponse: () => setSessionCookieOnResponse,
|
|
63
|
+
setStateCookieOnResponse: () => setStateCookieOnResponse
|
|
59
64
|
});
|
|
60
65
|
module.exports = __toCommonJS(src_exports);
|
|
61
66
|
|
|
@@ -89,7 +94,7 @@ var LocalStateHandler = class {
|
|
|
89
94
|
}
|
|
90
95
|
};
|
|
91
96
|
}
|
|
92
|
-
// Default
|
|
97
|
+
// Default generateState implementation
|
|
93
98
|
async generateState(provider) {
|
|
94
99
|
const state = (0, import_crypto.randomBytes)(16).toString("hex");
|
|
95
100
|
const codeVerifier = (0, import_crypto.randomBytes)(32).toString("hex");
|
|
@@ -102,6 +107,10 @@ var LocalStateHandler = class {
|
|
|
102
107
|
}
|
|
103
108
|
};
|
|
104
109
|
}
|
|
110
|
+
// PascalCase alias for backward compatibility
|
|
111
|
+
async GenerateState(provider) {
|
|
112
|
+
return this.generateState(provider);
|
|
113
|
+
}
|
|
105
114
|
};
|
|
106
115
|
|
|
107
116
|
// src/lixa.ts
|
|
@@ -114,7 +123,20 @@ var LocalSessionHandler = class {
|
|
|
114
123
|
emailToSessionMap = /* @__PURE__ */ new Map();
|
|
115
124
|
sessionStorage;
|
|
116
125
|
constructor(defaultTtlSeconds = 600) {
|
|
117
|
-
this.cache = new import_node_cache2.default({ stdTTL: defaultTtlSeconds });
|
|
126
|
+
this.cache = new import_node_cache2.default({ stdTTL: defaultTtlSeconds, checkperiod: 60 });
|
|
127
|
+
this.cache.on("expired", (_key, value) => {
|
|
128
|
+
if (value && typeof value === "object" && value.email) {
|
|
129
|
+
this.emailToSessionMap.delete(String(value.email).toLowerCase());
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
this.cache.on("del", (_key, value) => {
|
|
133
|
+
if (value && typeof value === "object" && value.email) {
|
|
134
|
+
this.emailToSessionMap.delete(String(value.email).toLowerCase());
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.cache.on("flush", () => {
|
|
138
|
+
this.emailToSessionMap.clear();
|
|
139
|
+
});
|
|
118
140
|
this.sessionStorage = {
|
|
119
141
|
saveSession: async (sessionId, session, expiresInSeconds) => {
|
|
120
142
|
this.cache.set(sessionId, session, expiresInSeconds);
|
|
@@ -145,8 +167,8 @@ var LocalSessionHandler = class {
|
|
|
145
167
|
}
|
|
146
168
|
};
|
|
147
169
|
}
|
|
148
|
-
// Default
|
|
149
|
-
async
|
|
170
|
+
// Default generateSession implementation
|
|
171
|
+
async generateSession(tokenData, _providerMetadata) {
|
|
150
172
|
if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
|
|
151
173
|
throw new Error("No valid access token found in OAuth response");
|
|
152
174
|
}
|
|
@@ -156,6 +178,10 @@ var LocalSessionHandler = class {
|
|
|
156
178
|
};
|
|
157
179
|
return session;
|
|
158
180
|
}
|
|
181
|
+
// PascalCase alias for backward compatibility
|
|
182
|
+
async GenerateSession(tokenData, providerMetadata) {
|
|
183
|
+
return this.generateSession(tokenData, providerMetadata);
|
|
184
|
+
}
|
|
159
185
|
};
|
|
160
186
|
|
|
161
187
|
// src/utils/user-info.ts
|
|
@@ -306,8 +332,12 @@ var Lixa = class _Lixa {
|
|
|
306
332
|
static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
|
|
307
333
|
static CONFIGURED_PROVIDERS = /* @__PURE__ */ new Map();
|
|
308
334
|
// Legacy registry for backward compatibility
|
|
309
|
-
|
|
310
|
-
|
|
335
|
+
// Instance-scoped fallback handlers to ensure no cross-instance state pollution
|
|
336
|
+
localStateHandler;
|
|
337
|
+
localSessionHandler;
|
|
338
|
+
localResourceHandler;
|
|
339
|
+
userResourceStore = /* @__PURE__ */ new Map();
|
|
340
|
+
refreshMutexes = /* @__PURE__ */ new Map();
|
|
311
341
|
config;
|
|
312
342
|
stateHandler;
|
|
313
343
|
sessionHandler;
|
|
@@ -328,10 +358,44 @@ var Lixa = class _Lixa {
|
|
|
328
358
|
*/
|
|
329
359
|
constructor(config) {
|
|
330
360
|
this.config = config;
|
|
331
|
-
this.stateHandler = config.stateHandler || _Lixa.LOCAL_STATE_HANDLER;
|
|
332
|
-
this.sessionHandler = config.sessionHandler || _Lixa.LOCAL_SESSION_HANDLER;
|
|
333
|
-
this.resourceHandler = config.resourceHandler || _Lixa.LOCAL_RESOURCE_HANDLER;
|
|
334
361
|
this.debug = config.debug || false;
|
|
362
|
+
this.localStateHandler = new LocalStateHandler();
|
|
363
|
+
this.localSessionHandler = new LocalSessionHandler();
|
|
364
|
+
this.localResourceHandler = {
|
|
365
|
+
resourceStorage: {
|
|
366
|
+
saveResource: async (userId, provider, resource) => {
|
|
367
|
+
let userMap = this.userResourceStore.get(userId);
|
|
368
|
+
if (!userMap) {
|
|
369
|
+
userMap = /* @__PURE__ */ new Map();
|
|
370
|
+
this.userResourceStore.set(userId, userMap);
|
|
371
|
+
}
|
|
372
|
+
userMap.set(provider.toLowerCase(), resource);
|
|
373
|
+
},
|
|
374
|
+
getResource: async (userId, provider) => {
|
|
375
|
+
const userMap = this.userResourceStore.get(userId);
|
|
376
|
+
return userMap?.get(provider.toLowerCase()) || null;
|
|
377
|
+
},
|
|
378
|
+
getUserResources: async (userId) => {
|
|
379
|
+
const userMap = this.userResourceStore.get(userId);
|
|
380
|
+
const result = {};
|
|
381
|
+
if (userMap) {
|
|
382
|
+
for (const [p, r] of userMap.entries()) {
|
|
383
|
+
result[p] = r;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return result;
|
|
387
|
+
},
|
|
388
|
+
deleteResource: async (userId, provider) => {
|
|
389
|
+
const userMap = this.userResourceStore.get(userId);
|
|
390
|
+
if (userMap) {
|
|
391
|
+
userMap.delete(provider.toLowerCase());
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
this.stateHandler = config.stateHandler || this.localStateHandler;
|
|
397
|
+
this.sessionHandler = config.sessionHandler || this.localSessionHandler;
|
|
398
|
+
this.resourceHandler = config.resourceHandler || this.localResourceHandler;
|
|
335
399
|
this.log("INFO", "Init", "Initializing Lixa instance", {
|
|
336
400
|
providers: Object.keys(config.providers),
|
|
337
401
|
debug: this.debug
|
|
@@ -409,18 +473,20 @@ var Lixa = class _Lixa {
|
|
|
409
473
|
}
|
|
410
474
|
}
|
|
411
475
|
/**
|
|
412
|
-
* Structured
|
|
476
|
+
* Structured logging with standardized format and custom logger support.
|
|
413
477
|
*
|
|
414
|
-
* @param level - Log level (INFO, WARN, ERROR)
|
|
415
|
-
* @param context - Context of the log (Init, Auth, Token, Session, State)
|
|
478
|
+
* @param level - Log level (INFO, WARN, ERROR, DEBUG)
|
|
479
|
+
* @param context - Context of the log (Init, Auth, Token, Session, State, AccountLinking, Resource)
|
|
416
480
|
* @param message - Log message
|
|
417
481
|
* @param data - Optional data to log
|
|
418
|
-
*
|
|
419
|
-
* @remarks
|
|
420
|
-
* Format: [Lixa] [timestamp] [level] [context] message
|
|
421
|
-
* Only logs when debug mode is enabled.
|
|
422
482
|
*/
|
|
423
483
|
log(level, context, message, data) {
|
|
484
|
+
if (this.config.logger) {
|
|
485
|
+
try {
|
|
486
|
+
this.config.logger.log(level, context, message, data);
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
424
490
|
if (!this.debug) return;
|
|
425
491
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
426
492
|
const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;
|
|
@@ -670,18 +736,19 @@ var Lixa = class _Lixa {
|
|
|
670
736
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
671
737
|
let stateValue;
|
|
672
738
|
let codeVerifier;
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
739
|
+
const generateStateFn = this.stateHandler.generateState || this.stateHandler.GenerateState;
|
|
740
|
+
if (generateStateFn) {
|
|
741
|
+
this.log("INFO", "State", "Calling custom generateState");
|
|
742
|
+
const generated = await generateStateFn(providerType);
|
|
676
743
|
stateValue = state || generated.state;
|
|
677
744
|
codeVerifier = generated.data.codeVerifier;
|
|
678
|
-
const storage = this.stateHandler.stateStorage ||
|
|
745
|
+
const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
679
746
|
await storage.saveState(stateValue, generated.data, 300);
|
|
680
747
|
} else {
|
|
681
748
|
this.log("INFO", "State", "Using default state generation");
|
|
682
749
|
stateValue = state || (0, import_crypto2.randomBytes)(16).toString("hex");
|
|
683
750
|
codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
|
|
684
|
-
const storage = this.stateHandler.stateStorage ||
|
|
751
|
+
const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
685
752
|
await storage.saveState(
|
|
686
753
|
stateValue,
|
|
687
754
|
{
|
|
@@ -690,12 +757,17 @@ var Lixa = class _Lixa {
|
|
|
690
757
|
codeVerifier
|
|
691
758
|
},
|
|
692
759
|
300
|
|
693
|
-
// 5 minutes in seconds
|
|
760
|
+
// 5 minutes in seconds (synchronized with cookie TTL)
|
|
694
761
|
);
|
|
695
762
|
}
|
|
696
763
|
const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
|
|
697
764
|
this.log("INFO", "State", `Saved state for provider: ${providerType}`, { state: stateValue });
|
|
698
|
-
const authNScopes = this.resolveAuthNScopes(
|
|
765
|
+
const authNScopes = this.resolveAuthNScopes(
|
|
766
|
+
providerType,
|
|
767
|
+
providerConfig.scopes,
|
|
768
|
+
providerImpl,
|
|
769
|
+
providerConfig.allowNonAuthScopes
|
|
770
|
+
);
|
|
699
771
|
const params = new URLSearchParams({
|
|
700
772
|
client_id: providerConfig.clientId,
|
|
701
773
|
redirect_uri: providerConfig.redirectUri,
|
|
@@ -714,9 +786,12 @@ var Lixa = class _Lixa {
|
|
|
714
786
|
return authUrl;
|
|
715
787
|
}
|
|
716
788
|
/**
|
|
717
|
-
* Restricts primary authentication scopes strictly to AuthN identity scopes.
|
|
789
|
+
* Restricts primary authentication scopes strictly to AuthN identity scopes unless allowNonAuthScopes is true.
|
|
718
790
|
*/
|
|
719
|
-
resolveAuthNScopes(providerType, configuredScopes, providerImpl) {
|
|
791
|
+
resolveAuthNScopes(providerType, configuredScopes, providerImpl, allowNonAuthScopes) {
|
|
792
|
+
if (allowNonAuthScopes) {
|
|
793
|
+
return configuredScopes && configuredScopes.length > 0 ? configuredScopes : providerImpl.authScopes || ["openid", "email", "profile"];
|
|
794
|
+
}
|
|
720
795
|
const defaultAuthScopes = {
|
|
721
796
|
google: ["openid", "email", "profile"],
|
|
722
797
|
github: ["read:user", "user:email"],
|
|
@@ -745,7 +820,7 @@ var Lixa = class _Lixa {
|
|
|
745
820
|
"Auth",
|
|
746
821
|
`Primary authentication is strictly limited to AuthN scopes. Excluded non-AuthN resource scopes: [${nonAuthNScopes.join(
|
|
747
822
|
", "
|
|
748
|
-
)}].
|
|
823
|
+
)}]. Set 'allowNonAuthScopes: true' on provider config to include them, or use lixa.getResourceAuthUrl() post-login to connect resource providers.`
|
|
749
824
|
);
|
|
750
825
|
}
|
|
751
826
|
if (validAuthNScopes.length > 0) {
|
|
@@ -788,7 +863,7 @@ var Lixa = class _Lixa {
|
|
|
788
863
|
throw new InvalidOAuthCallbackError("Invalid or missing state in callback");
|
|
789
864
|
}
|
|
790
865
|
this.log("INFO", "State", "Validating state parameter", { state });
|
|
791
|
-
const stateStorage = this.stateHandler.stateStorage ||
|
|
866
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
792
867
|
const cachedState = await stateStorage.getState(state);
|
|
793
868
|
if (!cachedState) {
|
|
794
869
|
this.log("ERROR", "State", "State validation failed: state not found or expired", { state });
|
|
@@ -828,9 +903,12 @@ var Lixa = class _Lixa {
|
|
|
828
903
|
extractedUserInfo = userInfo;
|
|
829
904
|
} catch {
|
|
830
905
|
}
|
|
831
|
-
const generateSession = this.sessionHandler.generateSession ||
|
|
832
|
-
if (
|
|
833
|
-
|
|
906
|
+
const generateSession = this.sessionHandler.generateSession || this.sessionHandler.GenerateSession || (this.localSessionHandler.generateSession ? this.localSessionHandler.generateSession.bind(this.localSessionHandler) : void 0);
|
|
907
|
+
if (!generateSession) {
|
|
908
|
+
throw new Error("No session generation handler available");
|
|
909
|
+
}
|
|
910
|
+
if (this.sessionHandler.generateSession || this.sessionHandler.GenerateSession) {
|
|
911
|
+
this.log("INFO", "Session", "Calling custom generateSession");
|
|
834
912
|
} else {
|
|
835
913
|
this.log("INFO", "Session", "Using default session generation");
|
|
836
914
|
}
|
|
@@ -839,7 +917,7 @@ var Lixa = class _Lixa {
|
|
|
839
917
|
if (extractedUserInfo?.email && !session.email) {
|
|
840
918
|
session.email = extractedUserInfo.email;
|
|
841
919
|
}
|
|
842
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
920
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
843
921
|
const linkingConfig = this.config.accountLinking;
|
|
844
922
|
const mode = String(linkingConfig?.mode || "");
|
|
845
923
|
const isLinkByEmail = mode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" || mode === "linkByEmail";
|
|
@@ -892,27 +970,31 @@ var Lixa = class _Lixa {
|
|
|
892
970
|
*/
|
|
893
971
|
async linkAccount(params) {
|
|
894
972
|
const { sessionId, provider, code, state } = params;
|
|
895
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
973
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
896
974
|
const existingSession = await sessionStorage.getSession(sessionId);
|
|
897
975
|
if (!existingSession) {
|
|
898
976
|
throw new SessionNotFoundError("Invalid session ID. User must be authenticated to link an account.", { sessionId });
|
|
899
977
|
}
|
|
978
|
+
if (!code || code.trim() === "") {
|
|
979
|
+
throw new InvalidOAuthCallbackError("Invalid or missing code in linkAccount");
|
|
980
|
+
}
|
|
900
981
|
const providerType = String(provider).toLowerCase();
|
|
901
982
|
const providerConfig = this.findProviderByType(providerType);
|
|
902
983
|
if (!providerConfig) {
|
|
903
984
|
throw new ProviderNotConfiguredError(String(provider));
|
|
904
985
|
}
|
|
905
986
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
906
|
-
let codeVerifier
|
|
987
|
+
let codeVerifier;
|
|
907
988
|
if (state) {
|
|
908
|
-
const stateStorage = this.stateHandler.stateStorage ||
|
|
989
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
909
990
|
const cachedState = await stateStorage.getState(state);
|
|
910
|
-
if (cachedState) {
|
|
911
|
-
|
|
912
|
-
await stateStorage.deleteState(state);
|
|
991
|
+
if (!cachedState) {
|
|
992
|
+
throw new InvalidStateError("Invalid or expired state during account linking", { state });
|
|
913
993
|
}
|
|
994
|
+
codeVerifier = cachedState.codeVerifier;
|
|
995
|
+
await stateStorage.deleteState(state);
|
|
914
996
|
}
|
|
915
|
-
const tokens = await this.exchangeCodeForToken(
|
|
997
|
+
const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier || "");
|
|
916
998
|
const providerMetadata = {
|
|
917
999
|
name: providerType,
|
|
918
1000
|
endpoints: {
|
|
@@ -950,7 +1032,7 @@ var Lixa = class _Lixa {
|
|
|
950
1032
|
* @returns Promise resolving to true on successful unlink
|
|
951
1033
|
*/
|
|
952
1034
|
async unlinkAccount(sessionId, providerToUnlink) {
|
|
953
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1035
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
954
1036
|
const session = await sessionStorage.getSession(sessionId);
|
|
955
1037
|
if (!session || !session.accounts) {
|
|
956
1038
|
throw new AccountUnlinkError("Session not found or has no linked accounts.", { sessionId });
|
|
@@ -979,7 +1061,7 @@ var Lixa = class _Lixa {
|
|
|
979
1061
|
*/
|
|
980
1062
|
async getResourceAuthUrl(params) {
|
|
981
1063
|
const { sessionId, provider, scopes, state, prompt, extraConfig } = params;
|
|
982
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1064
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
983
1065
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
984
1066
|
if (!activeSession) {
|
|
985
1067
|
throw new SessionNotFoundError("Authentication required. Active session must exist to connect resource providers.", { sessionId });
|
|
@@ -992,7 +1074,7 @@ var Lixa = class _Lixa {
|
|
|
992
1074
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
993
1075
|
const stateValue = state || (0, import_crypto2.randomBytes)(16).toString("hex");
|
|
994
1076
|
const codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
|
|
995
|
-
const storage = this.stateHandler.stateStorage ||
|
|
1077
|
+
const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
996
1078
|
await storage.saveState(
|
|
997
1079
|
stateValue,
|
|
998
1080
|
{
|
|
@@ -1017,45 +1099,6 @@ var Lixa = class _Lixa {
|
|
|
1017
1099
|
});
|
|
1018
1100
|
return `${providerImpl.authorizationEndpoint}?${searchParams.toString()}`;
|
|
1019
1101
|
}
|
|
1020
|
-
/**
|
|
1021
|
-
* Handles the OAuth callback for a connected resource provider and stores resource tokens on the session.
|
|
1022
|
-
*
|
|
1023
|
-
* @param params - Object containing sessionId, provider, code, state, and requested scopes
|
|
1024
|
-
* @returns Updated Session containing stored resource tokens under session.resources[provider]
|
|
1025
|
-
*/
|
|
1026
|
-
static userResourceStore = /* @__PURE__ */ new Map();
|
|
1027
|
-
static LOCAL_RESOURCE_HANDLER = {
|
|
1028
|
-
resourceStorage: {
|
|
1029
|
-
saveResource: async (userId, provider, resource) => {
|
|
1030
|
-
let userMap = _Lixa.userResourceStore.get(userId);
|
|
1031
|
-
if (!userMap) {
|
|
1032
|
-
userMap = /* @__PURE__ */ new Map();
|
|
1033
|
-
_Lixa.userResourceStore.set(userId, userMap);
|
|
1034
|
-
}
|
|
1035
|
-
userMap.set(provider.toLowerCase(), resource);
|
|
1036
|
-
},
|
|
1037
|
-
getResource: async (userId, provider) => {
|
|
1038
|
-
const userMap = _Lixa.userResourceStore.get(userId);
|
|
1039
|
-
return userMap?.get(provider.toLowerCase()) || null;
|
|
1040
|
-
},
|
|
1041
|
-
getUserResources: async (userId) => {
|
|
1042
|
-
const userMap = _Lixa.userResourceStore.get(userId);
|
|
1043
|
-
const result = {};
|
|
1044
|
-
if (userMap) {
|
|
1045
|
-
for (const [p, r] of userMap.entries()) {
|
|
1046
|
-
result[p] = r;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
return result;
|
|
1050
|
-
},
|
|
1051
|
-
deleteResource: async (userId, provider) => {
|
|
1052
|
-
const userMap = _Lixa.userResourceStore.get(userId);
|
|
1053
|
-
if (userMap) {
|
|
1054
|
-
userMap.delete(provider.toLowerCase());
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
};
|
|
1059
1102
|
getUserKeyFromSession(session) {
|
|
1060
1103
|
return session.userId || session.email || session.id || "anonymous";
|
|
1061
1104
|
}
|
|
@@ -1064,7 +1107,7 @@ var Lixa = class _Lixa {
|
|
|
1064
1107
|
*/
|
|
1065
1108
|
async handleResourceCallback(params) {
|
|
1066
1109
|
const { sessionId, provider, code, state, scopes } = params;
|
|
1067
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1110
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1068
1111
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1069
1112
|
if (!activeSession) {
|
|
1070
1113
|
throw new SessionNotFoundError("Authentication required. Active session not found for resource connection.", { sessionId });
|
|
@@ -1077,12 +1120,13 @@ var Lixa = class _Lixa {
|
|
|
1077
1120
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
1078
1121
|
let codeVerifier = (0, import_crypto2.randomBytes)(32).toString("hex");
|
|
1079
1122
|
if (state) {
|
|
1080
|
-
const stateStorage = this.stateHandler.stateStorage ||
|
|
1123
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
1081
1124
|
const cachedState = await stateStorage.getState(state);
|
|
1082
|
-
if (cachedState) {
|
|
1083
|
-
|
|
1084
|
-
await stateStorage.deleteState(state);
|
|
1125
|
+
if (!cachedState) {
|
|
1126
|
+
throw new InvalidStateError("Invalid or expired state during resource connection callback", { state });
|
|
1085
1127
|
}
|
|
1128
|
+
codeVerifier = cachedState.codeVerifier;
|
|
1129
|
+
await stateStorage.deleteState(state);
|
|
1086
1130
|
}
|
|
1087
1131
|
const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier);
|
|
1088
1132
|
const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
|
|
@@ -1096,7 +1140,7 @@ var Lixa = class _Lixa {
|
|
|
1096
1140
|
connectedAt: Date.now()
|
|
1097
1141
|
};
|
|
1098
1142
|
const userKey = this.getUserKeyFromSession(activeSession);
|
|
1099
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1143
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1100
1144
|
await resourceStorage.saveResource(userKey, providerType, connectedResource);
|
|
1101
1145
|
activeSession.resources = activeSession.resources || {};
|
|
1102
1146
|
activeSession.resources[providerType] = connectedResource;
|
|
@@ -1111,7 +1155,7 @@ var Lixa = class _Lixa {
|
|
|
1111
1155
|
* @param provider - Resource provider identifier (e.g. 'github', 'google')
|
|
1112
1156
|
*/
|
|
1113
1157
|
async getUserResource(userIdOrEmail, provider) {
|
|
1114
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1158
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1115
1159
|
const providerType = provider.toLowerCase();
|
|
1116
1160
|
const resource = await resourceStorage.getResource(userIdOrEmail, providerType);
|
|
1117
1161
|
if (!resource) return null;
|
|
@@ -1129,7 +1173,7 @@ var Lixa = class _Lixa {
|
|
|
1129
1173
|
* Retrieves all connected resources for a specific User ID / Email.
|
|
1130
1174
|
*/
|
|
1131
1175
|
async getUserResources(userIdOrEmail) {
|
|
1132
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1176
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1133
1177
|
return await resourceStorage.getUserResources(userIdOrEmail);
|
|
1134
1178
|
}
|
|
1135
1179
|
/**
|
|
@@ -1139,7 +1183,7 @@ var Lixa = class _Lixa {
|
|
|
1139
1183
|
* @param provider - Provider identifier (e.g. 'github', 'google')
|
|
1140
1184
|
*/
|
|
1141
1185
|
async getConnectedResource(sessionId, provider) {
|
|
1142
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1186
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1143
1187
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1144
1188
|
if (!activeSession) return null;
|
|
1145
1189
|
const providerType = provider.toLowerCase();
|
|
@@ -1164,12 +1208,29 @@ var Lixa = class _Lixa {
|
|
|
1164
1208
|
}
|
|
1165
1209
|
/**
|
|
1166
1210
|
* Refreshes a user's resource access token using its refresh token.
|
|
1211
|
+
* Deduplicates concurrent refresh requests via an in-flight promise mutex.
|
|
1167
1212
|
*
|
|
1168
1213
|
* @param userIdOrEmail - User identifier or email
|
|
1169
1214
|
* @param provider - Provider identifier (e.g. 'google', 'github')
|
|
1170
1215
|
*/
|
|
1171
1216
|
async refreshUserResourceToken(userIdOrEmail, provider, existingResource) {
|
|
1172
|
-
const
|
|
1217
|
+
const mutexKey = `${userIdOrEmail.toLowerCase()}:${provider.toLowerCase()}`;
|
|
1218
|
+
const existingPromise = this.refreshMutexes.get(mutexKey);
|
|
1219
|
+
if (existingPromise) {
|
|
1220
|
+
this.log("INFO", "Token", `Concurrent refresh request detected for '${mutexKey}'. Reusing in-flight promise.`);
|
|
1221
|
+
return existingPromise;
|
|
1222
|
+
}
|
|
1223
|
+
const refreshPromise = this.executeRefreshUserResourceToken(userIdOrEmail, provider, existingResource).finally(() => {
|
|
1224
|
+
this.refreshMutexes.delete(mutexKey);
|
|
1225
|
+
});
|
|
1226
|
+
this.refreshMutexes.set(mutexKey, refreshPromise);
|
|
1227
|
+
return refreshPromise;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Internal execution of refresh token exchange.
|
|
1231
|
+
*/
|
|
1232
|
+
async executeRefreshUserResourceToken(userIdOrEmail, provider, existingResource) {
|
|
1233
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1173
1234
|
const providerType = provider.toLowerCase();
|
|
1174
1235
|
let resource = existingResource || await resourceStorage.getResource(userIdOrEmail, providerType);
|
|
1175
1236
|
if (!resource || !resource.refreshToken) {
|
|
@@ -1222,7 +1283,7 @@ var Lixa = class _Lixa {
|
|
|
1222
1283
|
* Refreshes a connected resource access token for an active session.
|
|
1223
1284
|
*/
|
|
1224
1285
|
async refreshResourceToken(sessionId, provider) {
|
|
1225
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1286
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1226
1287
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1227
1288
|
if (!activeSession) {
|
|
1228
1289
|
throw new SessionNotFoundError("Active session not found.", { sessionId });
|
|
@@ -1240,7 +1301,7 @@ var Lixa = class _Lixa {
|
|
|
1240
1301
|
* Disconnects a resource provider for a specific User ID / Email.
|
|
1241
1302
|
*/
|
|
1242
1303
|
async disconnectUserResource(userIdOrEmail, provider) {
|
|
1243
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1304
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1244
1305
|
await resourceStorage.deleteResource(userIdOrEmail, provider.toLowerCase());
|
|
1245
1306
|
return true;
|
|
1246
1307
|
}
|
|
@@ -1248,7 +1309,7 @@ var Lixa = class _Lixa {
|
|
|
1248
1309
|
* Disconnects a resource provider from an active session and user account.
|
|
1249
1310
|
*/
|
|
1250
1311
|
async disconnectResource(sessionId, provider) {
|
|
1251
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1312
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1252
1313
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1253
1314
|
if (activeSession) {
|
|
1254
1315
|
const userKey = this.getUserKeyFromSession(activeSession);
|
|
@@ -1265,7 +1326,7 @@ var Lixa = class _Lixa {
|
|
|
1265
1326
|
* Retrieves active session details from session storage.
|
|
1266
1327
|
*/
|
|
1267
1328
|
async fetchSessionInfo(sessionId) {
|
|
1268
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1329
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1269
1330
|
return await sessionStorage.getSession(sessionId);
|
|
1270
1331
|
}
|
|
1271
1332
|
/**
|
|
@@ -1274,7 +1335,7 @@ var Lixa = class _Lixa {
|
|
|
1274
1335
|
* @param sessionId - Active session identifier
|
|
1275
1336
|
*/
|
|
1276
1337
|
async deleteSession(sessionId) {
|
|
1277
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1338
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1278
1339
|
await sessionStorage.deleteSession(sessionId);
|
|
1279
1340
|
}
|
|
1280
1341
|
async exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier) {
|
|
@@ -1330,7 +1391,7 @@ var Lixa = class _Lixa {
|
|
|
1330
1391
|
var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
|
|
1331
1392
|
var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
|
|
1332
1393
|
var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
|
|
1333
|
-
var DEFAULT_STATE_MAX_AGE_SECONDS =
|
|
1394
|
+
var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
|
|
1334
1395
|
function isProductionEnvironment() {
|
|
1335
1396
|
return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
|
|
1336
1397
|
}
|
|
@@ -1444,6 +1505,54 @@ function clearStateCookie(options) {
|
|
|
1444
1505
|
header
|
|
1445
1506
|
};
|
|
1446
1507
|
}
|
|
1508
|
+
|
|
1509
|
+
// src/express.ts
|
|
1510
|
+
function setSessionCookieOnResponse(res, sessionId, options) {
|
|
1511
|
+
const cookie = createSessionCookie(sessionId, options);
|
|
1512
|
+
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
1513
|
+
}
|
|
1514
|
+
function clearSessionCookieOnResponse(res, options) {
|
|
1515
|
+
const cookie = clearSessionCookie(options);
|
|
1516
|
+
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
1517
|
+
}
|
|
1518
|
+
function setStateCookieOnResponse(res, state, options) {
|
|
1519
|
+
const cookie = createStateCookie(state, options);
|
|
1520
|
+
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
1521
|
+
}
|
|
1522
|
+
function clearStateCookieOnResponse(res, options) {
|
|
1523
|
+
const cookie = clearStateCookie(options);
|
|
1524
|
+
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
1525
|
+
}
|
|
1526
|
+
function createRequireAuthMiddleware(lixa, options) {
|
|
1527
|
+
const cookieName = options?.sessionCookieName || DEFAULT_SESSION_COOKIE_NAME;
|
|
1528
|
+
return async (req, res, next) => {
|
|
1529
|
+
try {
|
|
1530
|
+
const sessionId = req.cookies?.[cookieName];
|
|
1531
|
+
if (!sessionId) {
|
|
1532
|
+
if (options?.unauthorizedHandler) {
|
|
1533
|
+
options.unauthorizedHandler(req, res);
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
res.status(401).json({ error: "Unauthorized: Missing session cookie" });
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const session = await lixa.fetchSessionInfo(sessionId);
|
|
1540
|
+
if (!session) {
|
|
1541
|
+
if (options?.unauthorizedHandler) {
|
|
1542
|
+
options.unauthorizedHandler(req, res);
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
res.status(401).json({ error: "Unauthorized: Invalid or expired session" });
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
req.sessionInfo = session;
|
|
1549
|
+
req.sessionId = sessionId;
|
|
1550
|
+
next();
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
next(error);
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1447
1556
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1448
1557
|
0 && (module.exports = {
|
|
1449
1558
|
AccountLinkingStrategy,
|
|
@@ -1463,7 +1572,10 @@ function clearStateCookie(options) {
|
|
|
1463
1572
|
SessionNotFoundError,
|
|
1464
1573
|
TokenExchangeError,
|
|
1465
1574
|
clearSessionCookie,
|
|
1575
|
+
clearSessionCookieOnResponse,
|
|
1466
1576
|
clearStateCookie,
|
|
1577
|
+
clearStateCookieOnResponse,
|
|
1578
|
+
createRequireAuthMiddleware,
|
|
1467
1579
|
createSessionCookie,
|
|
1468
1580
|
createStateCookie,
|
|
1469
1581
|
decodeIdToken,
|
|
@@ -1471,6 +1583,8 @@ function clearStateCookie(options) {
|
|
|
1471
1583
|
extractUserInfo,
|
|
1472
1584
|
fetchUserInfo,
|
|
1473
1585
|
isProductionEnvironment,
|
|
1474
|
-
serializeCookie
|
|
1586
|
+
serializeCookie,
|
|
1587
|
+
setSessionCookieOnResponse,
|
|
1588
|
+
setStateCookieOnResponse
|
|
1475
1589
|
});
|
|
1476
1590
|
//# sourceMappingURL=index.cjs.map
|