@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.js
CHANGED
|
@@ -28,7 +28,7 @@ var LocalStateHandler = class {
|
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
// Default
|
|
31
|
+
// Default generateState implementation
|
|
32
32
|
async generateState(provider) {
|
|
33
33
|
const state = randomBytes(16).toString("hex");
|
|
34
34
|
const codeVerifier = randomBytes(32).toString("hex");
|
|
@@ -41,6 +41,10 @@ var LocalStateHandler = class {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
// PascalCase alias for backward compatibility
|
|
45
|
+
async GenerateState(provider) {
|
|
46
|
+
return this.generateState(provider);
|
|
47
|
+
}
|
|
44
48
|
};
|
|
45
49
|
|
|
46
50
|
// src/lixa.ts
|
|
@@ -53,7 +57,20 @@ var LocalSessionHandler = class {
|
|
|
53
57
|
emailToSessionMap = /* @__PURE__ */ new Map();
|
|
54
58
|
sessionStorage;
|
|
55
59
|
constructor(defaultTtlSeconds = 600) {
|
|
56
|
-
this.cache = new NodeCache2({ stdTTL: defaultTtlSeconds });
|
|
60
|
+
this.cache = new NodeCache2({ stdTTL: defaultTtlSeconds, checkperiod: 60 });
|
|
61
|
+
this.cache.on("expired", (_key, value) => {
|
|
62
|
+
if (value && typeof value === "object" && value.email) {
|
|
63
|
+
this.emailToSessionMap.delete(String(value.email).toLowerCase());
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
this.cache.on("del", (_key, value) => {
|
|
67
|
+
if (value && typeof value === "object" && value.email) {
|
|
68
|
+
this.emailToSessionMap.delete(String(value.email).toLowerCase());
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
this.cache.on("flush", () => {
|
|
72
|
+
this.emailToSessionMap.clear();
|
|
73
|
+
});
|
|
57
74
|
this.sessionStorage = {
|
|
58
75
|
saveSession: async (sessionId, session, expiresInSeconds) => {
|
|
59
76
|
this.cache.set(sessionId, session, expiresInSeconds);
|
|
@@ -84,8 +101,8 @@ var LocalSessionHandler = class {
|
|
|
84
101
|
}
|
|
85
102
|
};
|
|
86
103
|
}
|
|
87
|
-
// Default
|
|
88
|
-
async
|
|
104
|
+
// Default generateSession implementation
|
|
105
|
+
async generateSession(tokenData, _providerMetadata) {
|
|
89
106
|
if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
|
|
90
107
|
throw new Error("No valid access token found in OAuth response");
|
|
91
108
|
}
|
|
@@ -95,6 +112,10 @@ var LocalSessionHandler = class {
|
|
|
95
112
|
};
|
|
96
113
|
return session;
|
|
97
114
|
}
|
|
115
|
+
// PascalCase alias for backward compatibility
|
|
116
|
+
async GenerateSession(tokenData, providerMetadata) {
|
|
117
|
+
return this.generateSession(tokenData, providerMetadata);
|
|
118
|
+
}
|
|
98
119
|
};
|
|
99
120
|
|
|
100
121
|
// src/utils/user-info.ts
|
|
@@ -245,8 +266,12 @@ var Lixa = class _Lixa {
|
|
|
245
266
|
static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
|
|
246
267
|
static CONFIGURED_PROVIDERS = /* @__PURE__ */ new Map();
|
|
247
268
|
// Legacy registry for backward compatibility
|
|
248
|
-
|
|
249
|
-
|
|
269
|
+
// Instance-scoped fallback handlers to ensure no cross-instance state pollution
|
|
270
|
+
localStateHandler;
|
|
271
|
+
localSessionHandler;
|
|
272
|
+
localResourceHandler;
|
|
273
|
+
userResourceStore = /* @__PURE__ */ new Map();
|
|
274
|
+
refreshMutexes = /* @__PURE__ */ new Map();
|
|
250
275
|
config;
|
|
251
276
|
stateHandler;
|
|
252
277
|
sessionHandler;
|
|
@@ -267,10 +292,44 @@ var Lixa = class _Lixa {
|
|
|
267
292
|
*/
|
|
268
293
|
constructor(config) {
|
|
269
294
|
this.config = config;
|
|
270
|
-
this.stateHandler = config.stateHandler || _Lixa.LOCAL_STATE_HANDLER;
|
|
271
|
-
this.sessionHandler = config.sessionHandler || _Lixa.LOCAL_SESSION_HANDLER;
|
|
272
|
-
this.resourceHandler = config.resourceHandler || _Lixa.LOCAL_RESOURCE_HANDLER;
|
|
273
295
|
this.debug = config.debug || false;
|
|
296
|
+
this.localStateHandler = new LocalStateHandler();
|
|
297
|
+
this.localSessionHandler = new LocalSessionHandler();
|
|
298
|
+
this.localResourceHandler = {
|
|
299
|
+
resourceStorage: {
|
|
300
|
+
saveResource: async (userId, provider, resource) => {
|
|
301
|
+
let userMap = this.userResourceStore.get(userId);
|
|
302
|
+
if (!userMap) {
|
|
303
|
+
userMap = /* @__PURE__ */ new Map();
|
|
304
|
+
this.userResourceStore.set(userId, userMap);
|
|
305
|
+
}
|
|
306
|
+
userMap.set(provider.toLowerCase(), resource);
|
|
307
|
+
},
|
|
308
|
+
getResource: async (userId, provider) => {
|
|
309
|
+
const userMap = this.userResourceStore.get(userId);
|
|
310
|
+
return userMap?.get(provider.toLowerCase()) || null;
|
|
311
|
+
},
|
|
312
|
+
getUserResources: async (userId) => {
|
|
313
|
+
const userMap = this.userResourceStore.get(userId);
|
|
314
|
+
const result = {};
|
|
315
|
+
if (userMap) {
|
|
316
|
+
for (const [p, r] of userMap.entries()) {
|
|
317
|
+
result[p] = r;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
},
|
|
322
|
+
deleteResource: async (userId, provider) => {
|
|
323
|
+
const userMap = this.userResourceStore.get(userId);
|
|
324
|
+
if (userMap) {
|
|
325
|
+
userMap.delete(provider.toLowerCase());
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
this.stateHandler = config.stateHandler || this.localStateHandler;
|
|
331
|
+
this.sessionHandler = config.sessionHandler || this.localSessionHandler;
|
|
332
|
+
this.resourceHandler = config.resourceHandler || this.localResourceHandler;
|
|
274
333
|
this.log("INFO", "Init", "Initializing Lixa instance", {
|
|
275
334
|
providers: Object.keys(config.providers),
|
|
276
335
|
debug: this.debug
|
|
@@ -348,18 +407,20 @@ var Lixa = class _Lixa {
|
|
|
348
407
|
}
|
|
349
408
|
}
|
|
350
409
|
/**
|
|
351
|
-
* Structured
|
|
410
|
+
* Structured logging with standardized format and custom logger support.
|
|
352
411
|
*
|
|
353
|
-
* @param level - Log level (INFO, WARN, ERROR)
|
|
354
|
-
* @param context - Context of the log (Init, Auth, Token, Session, State)
|
|
412
|
+
* @param level - Log level (INFO, WARN, ERROR, DEBUG)
|
|
413
|
+
* @param context - Context of the log (Init, Auth, Token, Session, State, AccountLinking, Resource)
|
|
355
414
|
* @param message - Log message
|
|
356
415
|
* @param data - Optional data to log
|
|
357
|
-
*
|
|
358
|
-
* @remarks
|
|
359
|
-
* Format: [Lixa] [timestamp] [level] [context] message
|
|
360
|
-
* Only logs when debug mode is enabled.
|
|
361
416
|
*/
|
|
362
417
|
log(level, context, message, data) {
|
|
418
|
+
if (this.config.logger) {
|
|
419
|
+
try {
|
|
420
|
+
this.config.logger.log(level, context, message, data);
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
}
|
|
363
424
|
if (!this.debug) return;
|
|
364
425
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
365
426
|
const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;
|
|
@@ -609,18 +670,19 @@ var Lixa = class _Lixa {
|
|
|
609
670
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
610
671
|
let stateValue;
|
|
611
672
|
let codeVerifier;
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
673
|
+
const generateStateFn = this.stateHandler.generateState || this.stateHandler.GenerateState;
|
|
674
|
+
if (generateStateFn) {
|
|
675
|
+
this.log("INFO", "State", "Calling custom generateState");
|
|
676
|
+
const generated = await generateStateFn(providerType);
|
|
615
677
|
stateValue = state || generated.state;
|
|
616
678
|
codeVerifier = generated.data.codeVerifier;
|
|
617
|
-
const storage = this.stateHandler.stateStorage ||
|
|
679
|
+
const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
618
680
|
await storage.saveState(stateValue, generated.data, 300);
|
|
619
681
|
} else {
|
|
620
682
|
this.log("INFO", "State", "Using default state generation");
|
|
621
683
|
stateValue = state || randomBytes2(16).toString("hex");
|
|
622
684
|
codeVerifier = randomBytes2(32).toString("hex");
|
|
623
|
-
const storage = this.stateHandler.stateStorage ||
|
|
685
|
+
const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
624
686
|
await storage.saveState(
|
|
625
687
|
stateValue,
|
|
626
688
|
{
|
|
@@ -629,12 +691,17 @@ var Lixa = class _Lixa {
|
|
|
629
691
|
codeVerifier
|
|
630
692
|
},
|
|
631
693
|
300
|
|
632
|
-
// 5 minutes in seconds
|
|
694
|
+
// 5 minutes in seconds (synchronized with cookie TTL)
|
|
633
695
|
);
|
|
634
696
|
}
|
|
635
697
|
const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
|
|
636
698
|
this.log("INFO", "State", `Saved state for provider: ${providerType}`, { state: stateValue });
|
|
637
|
-
const authNScopes = this.resolveAuthNScopes(
|
|
699
|
+
const authNScopes = this.resolveAuthNScopes(
|
|
700
|
+
providerType,
|
|
701
|
+
providerConfig.scopes,
|
|
702
|
+
providerImpl,
|
|
703
|
+
providerConfig.allowNonAuthScopes
|
|
704
|
+
);
|
|
638
705
|
const params = new URLSearchParams({
|
|
639
706
|
client_id: providerConfig.clientId,
|
|
640
707
|
redirect_uri: providerConfig.redirectUri,
|
|
@@ -653,9 +720,12 @@ var Lixa = class _Lixa {
|
|
|
653
720
|
return authUrl;
|
|
654
721
|
}
|
|
655
722
|
/**
|
|
656
|
-
* Restricts primary authentication scopes strictly to AuthN identity scopes.
|
|
723
|
+
* Restricts primary authentication scopes strictly to AuthN identity scopes unless allowNonAuthScopes is true.
|
|
657
724
|
*/
|
|
658
|
-
resolveAuthNScopes(providerType, configuredScopes, providerImpl) {
|
|
725
|
+
resolveAuthNScopes(providerType, configuredScopes, providerImpl, allowNonAuthScopes) {
|
|
726
|
+
if (allowNonAuthScopes) {
|
|
727
|
+
return configuredScopes && configuredScopes.length > 0 ? configuredScopes : providerImpl.authScopes || ["openid", "email", "profile"];
|
|
728
|
+
}
|
|
659
729
|
const defaultAuthScopes = {
|
|
660
730
|
google: ["openid", "email", "profile"],
|
|
661
731
|
github: ["read:user", "user:email"],
|
|
@@ -684,7 +754,7 @@ var Lixa = class _Lixa {
|
|
|
684
754
|
"Auth",
|
|
685
755
|
`Primary authentication is strictly limited to AuthN scopes. Excluded non-AuthN resource scopes: [${nonAuthNScopes.join(
|
|
686
756
|
", "
|
|
687
|
-
)}].
|
|
757
|
+
)}]. Set 'allowNonAuthScopes: true' on provider config to include them, or use lixa.getResourceAuthUrl() post-login to connect resource providers.`
|
|
688
758
|
);
|
|
689
759
|
}
|
|
690
760
|
if (validAuthNScopes.length > 0) {
|
|
@@ -727,7 +797,7 @@ var Lixa = class _Lixa {
|
|
|
727
797
|
throw new InvalidOAuthCallbackError("Invalid or missing state in callback");
|
|
728
798
|
}
|
|
729
799
|
this.log("INFO", "State", "Validating state parameter", { state });
|
|
730
|
-
const stateStorage = this.stateHandler.stateStorage ||
|
|
800
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
731
801
|
const cachedState = await stateStorage.getState(state);
|
|
732
802
|
if (!cachedState) {
|
|
733
803
|
this.log("ERROR", "State", "State validation failed: state not found or expired", { state });
|
|
@@ -767,9 +837,12 @@ var Lixa = class _Lixa {
|
|
|
767
837
|
extractedUserInfo = userInfo;
|
|
768
838
|
} catch {
|
|
769
839
|
}
|
|
770
|
-
const generateSession = this.sessionHandler.generateSession ||
|
|
771
|
-
if (
|
|
772
|
-
|
|
840
|
+
const generateSession = this.sessionHandler.generateSession || this.sessionHandler.GenerateSession || (this.localSessionHandler.generateSession ? this.localSessionHandler.generateSession.bind(this.localSessionHandler) : void 0);
|
|
841
|
+
if (!generateSession) {
|
|
842
|
+
throw new Error("No session generation handler available");
|
|
843
|
+
}
|
|
844
|
+
if (this.sessionHandler.generateSession || this.sessionHandler.GenerateSession) {
|
|
845
|
+
this.log("INFO", "Session", "Calling custom generateSession");
|
|
773
846
|
} else {
|
|
774
847
|
this.log("INFO", "Session", "Using default session generation");
|
|
775
848
|
}
|
|
@@ -778,7 +851,7 @@ var Lixa = class _Lixa {
|
|
|
778
851
|
if (extractedUserInfo?.email && !session.email) {
|
|
779
852
|
session.email = extractedUserInfo.email;
|
|
780
853
|
}
|
|
781
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
854
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
782
855
|
const linkingConfig = this.config.accountLinking;
|
|
783
856
|
const mode = String(linkingConfig?.mode || "");
|
|
784
857
|
const isLinkByEmail = mode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" || mode === "linkByEmail";
|
|
@@ -831,27 +904,31 @@ var Lixa = class _Lixa {
|
|
|
831
904
|
*/
|
|
832
905
|
async linkAccount(params) {
|
|
833
906
|
const { sessionId, provider, code, state } = params;
|
|
834
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
907
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
835
908
|
const existingSession = await sessionStorage.getSession(sessionId);
|
|
836
909
|
if (!existingSession) {
|
|
837
910
|
throw new SessionNotFoundError("Invalid session ID. User must be authenticated to link an account.", { sessionId });
|
|
838
911
|
}
|
|
912
|
+
if (!code || code.trim() === "") {
|
|
913
|
+
throw new InvalidOAuthCallbackError("Invalid or missing code in linkAccount");
|
|
914
|
+
}
|
|
839
915
|
const providerType = String(provider).toLowerCase();
|
|
840
916
|
const providerConfig = this.findProviderByType(providerType);
|
|
841
917
|
if (!providerConfig) {
|
|
842
918
|
throw new ProviderNotConfiguredError(String(provider));
|
|
843
919
|
}
|
|
844
920
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
845
|
-
let codeVerifier
|
|
921
|
+
let codeVerifier;
|
|
846
922
|
if (state) {
|
|
847
|
-
const stateStorage = this.stateHandler.stateStorage ||
|
|
923
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
848
924
|
const cachedState = await stateStorage.getState(state);
|
|
849
|
-
if (cachedState) {
|
|
850
|
-
|
|
851
|
-
await stateStorage.deleteState(state);
|
|
925
|
+
if (!cachedState) {
|
|
926
|
+
throw new InvalidStateError("Invalid or expired state during account linking", { state });
|
|
852
927
|
}
|
|
928
|
+
codeVerifier = cachedState.codeVerifier;
|
|
929
|
+
await stateStorage.deleteState(state);
|
|
853
930
|
}
|
|
854
|
-
const tokens = await this.exchangeCodeForToken(
|
|
931
|
+
const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier || "");
|
|
855
932
|
const providerMetadata = {
|
|
856
933
|
name: providerType,
|
|
857
934
|
endpoints: {
|
|
@@ -889,7 +966,7 @@ var Lixa = class _Lixa {
|
|
|
889
966
|
* @returns Promise resolving to true on successful unlink
|
|
890
967
|
*/
|
|
891
968
|
async unlinkAccount(sessionId, providerToUnlink) {
|
|
892
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
969
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
893
970
|
const session = await sessionStorage.getSession(sessionId);
|
|
894
971
|
if (!session || !session.accounts) {
|
|
895
972
|
throw new AccountUnlinkError("Session not found or has no linked accounts.", { sessionId });
|
|
@@ -918,7 +995,7 @@ var Lixa = class _Lixa {
|
|
|
918
995
|
*/
|
|
919
996
|
async getResourceAuthUrl(params) {
|
|
920
997
|
const { sessionId, provider, scopes, state, prompt, extraConfig } = params;
|
|
921
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
998
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
922
999
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
923
1000
|
if (!activeSession) {
|
|
924
1001
|
throw new SessionNotFoundError("Authentication required. Active session must exist to connect resource providers.", { sessionId });
|
|
@@ -931,7 +1008,7 @@ var Lixa = class _Lixa {
|
|
|
931
1008
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
932
1009
|
const stateValue = state || randomBytes2(16).toString("hex");
|
|
933
1010
|
const codeVerifier = randomBytes2(32).toString("hex");
|
|
934
|
-
const storage = this.stateHandler.stateStorage ||
|
|
1011
|
+
const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
935
1012
|
await storage.saveState(
|
|
936
1013
|
stateValue,
|
|
937
1014
|
{
|
|
@@ -956,45 +1033,6 @@ var Lixa = class _Lixa {
|
|
|
956
1033
|
});
|
|
957
1034
|
return `${providerImpl.authorizationEndpoint}?${searchParams.toString()}`;
|
|
958
1035
|
}
|
|
959
|
-
/**
|
|
960
|
-
* Handles the OAuth callback for a connected resource provider and stores resource tokens on the session.
|
|
961
|
-
*
|
|
962
|
-
* @param params - Object containing sessionId, provider, code, state, and requested scopes
|
|
963
|
-
* @returns Updated Session containing stored resource tokens under session.resources[provider]
|
|
964
|
-
*/
|
|
965
|
-
static userResourceStore = /* @__PURE__ */ new Map();
|
|
966
|
-
static LOCAL_RESOURCE_HANDLER = {
|
|
967
|
-
resourceStorage: {
|
|
968
|
-
saveResource: async (userId, provider, resource) => {
|
|
969
|
-
let userMap = _Lixa.userResourceStore.get(userId);
|
|
970
|
-
if (!userMap) {
|
|
971
|
-
userMap = /* @__PURE__ */ new Map();
|
|
972
|
-
_Lixa.userResourceStore.set(userId, userMap);
|
|
973
|
-
}
|
|
974
|
-
userMap.set(provider.toLowerCase(), resource);
|
|
975
|
-
},
|
|
976
|
-
getResource: async (userId, provider) => {
|
|
977
|
-
const userMap = _Lixa.userResourceStore.get(userId);
|
|
978
|
-
return userMap?.get(provider.toLowerCase()) || null;
|
|
979
|
-
},
|
|
980
|
-
getUserResources: async (userId) => {
|
|
981
|
-
const userMap = _Lixa.userResourceStore.get(userId);
|
|
982
|
-
const result = {};
|
|
983
|
-
if (userMap) {
|
|
984
|
-
for (const [p, r] of userMap.entries()) {
|
|
985
|
-
result[p] = r;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
return result;
|
|
989
|
-
},
|
|
990
|
-
deleteResource: async (userId, provider) => {
|
|
991
|
-
const userMap = _Lixa.userResourceStore.get(userId);
|
|
992
|
-
if (userMap) {
|
|
993
|
-
userMap.delete(provider.toLowerCase());
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
};
|
|
998
1036
|
getUserKeyFromSession(session) {
|
|
999
1037
|
return session.userId || session.email || session.id || "anonymous";
|
|
1000
1038
|
}
|
|
@@ -1003,7 +1041,7 @@ var Lixa = class _Lixa {
|
|
|
1003
1041
|
*/
|
|
1004
1042
|
async handleResourceCallback(params) {
|
|
1005
1043
|
const { sessionId, provider, code, state, scopes } = params;
|
|
1006
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1044
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1007
1045
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1008
1046
|
if (!activeSession) {
|
|
1009
1047
|
throw new SessionNotFoundError("Authentication required. Active session not found for resource connection.", { sessionId });
|
|
@@ -1016,12 +1054,13 @@ var Lixa = class _Lixa {
|
|
|
1016
1054
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
1017
1055
|
let codeVerifier = randomBytes2(32).toString("hex");
|
|
1018
1056
|
if (state) {
|
|
1019
|
-
const stateStorage = this.stateHandler.stateStorage ||
|
|
1057
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
1020
1058
|
const cachedState = await stateStorage.getState(state);
|
|
1021
|
-
if (cachedState) {
|
|
1022
|
-
|
|
1023
|
-
await stateStorage.deleteState(state);
|
|
1059
|
+
if (!cachedState) {
|
|
1060
|
+
throw new InvalidStateError("Invalid or expired state during resource connection callback", { state });
|
|
1024
1061
|
}
|
|
1062
|
+
codeVerifier = cachedState.codeVerifier;
|
|
1063
|
+
await stateStorage.deleteState(state);
|
|
1025
1064
|
}
|
|
1026
1065
|
const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier);
|
|
1027
1066
|
const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
|
|
@@ -1035,7 +1074,7 @@ var Lixa = class _Lixa {
|
|
|
1035
1074
|
connectedAt: Date.now()
|
|
1036
1075
|
};
|
|
1037
1076
|
const userKey = this.getUserKeyFromSession(activeSession);
|
|
1038
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1077
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1039
1078
|
await resourceStorage.saveResource(userKey, providerType, connectedResource);
|
|
1040
1079
|
activeSession.resources = activeSession.resources || {};
|
|
1041
1080
|
activeSession.resources[providerType] = connectedResource;
|
|
@@ -1050,7 +1089,7 @@ var Lixa = class _Lixa {
|
|
|
1050
1089
|
* @param provider - Resource provider identifier (e.g. 'github', 'google')
|
|
1051
1090
|
*/
|
|
1052
1091
|
async getUserResource(userIdOrEmail, provider) {
|
|
1053
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1092
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1054
1093
|
const providerType = provider.toLowerCase();
|
|
1055
1094
|
const resource = await resourceStorage.getResource(userIdOrEmail, providerType);
|
|
1056
1095
|
if (!resource) return null;
|
|
@@ -1068,7 +1107,7 @@ var Lixa = class _Lixa {
|
|
|
1068
1107
|
* Retrieves all connected resources for a specific User ID / Email.
|
|
1069
1108
|
*/
|
|
1070
1109
|
async getUserResources(userIdOrEmail) {
|
|
1071
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1110
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1072
1111
|
return await resourceStorage.getUserResources(userIdOrEmail);
|
|
1073
1112
|
}
|
|
1074
1113
|
/**
|
|
@@ -1078,7 +1117,7 @@ var Lixa = class _Lixa {
|
|
|
1078
1117
|
* @param provider - Provider identifier (e.g. 'github', 'google')
|
|
1079
1118
|
*/
|
|
1080
1119
|
async getConnectedResource(sessionId, provider) {
|
|
1081
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1120
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1082
1121
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1083
1122
|
if (!activeSession) return null;
|
|
1084
1123
|
const providerType = provider.toLowerCase();
|
|
@@ -1103,12 +1142,29 @@ var Lixa = class _Lixa {
|
|
|
1103
1142
|
}
|
|
1104
1143
|
/**
|
|
1105
1144
|
* Refreshes a user's resource access token using its refresh token.
|
|
1145
|
+
* Deduplicates concurrent refresh requests via an in-flight promise mutex.
|
|
1106
1146
|
*
|
|
1107
1147
|
* @param userIdOrEmail - User identifier or email
|
|
1108
1148
|
* @param provider - Provider identifier (e.g. 'google', 'github')
|
|
1109
1149
|
*/
|
|
1110
1150
|
async refreshUserResourceToken(userIdOrEmail, provider, existingResource) {
|
|
1111
|
-
const
|
|
1151
|
+
const mutexKey = `${userIdOrEmail.toLowerCase()}:${provider.toLowerCase()}`;
|
|
1152
|
+
const existingPromise = this.refreshMutexes.get(mutexKey);
|
|
1153
|
+
if (existingPromise) {
|
|
1154
|
+
this.log("INFO", "Token", `Concurrent refresh request detected for '${mutexKey}'. Reusing in-flight promise.`);
|
|
1155
|
+
return existingPromise;
|
|
1156
|
+
}
|
|
1157
|
+
const refreshPromise = this.executeRefreshUserResourceToken(userIdOrEmail, provider, existingResource).finally(() => {
|
|
1158
|
+
this.refreshMutexes.delete(mutexKey);
|
|
1159
|
+
});
|
|
1160
|
+
this.refreshMutexes.set(mutexKey, refreshPromise);
|
|
1161
|
+
return refreshPromise;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Internal execution of refresh token exchange.
|
|
1165
|
+
*/
|
|
1166
|
+
async executeRefreshUserResourceToken(userIdOrEmail, provider, existingResource) {
|
|
1167
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1112
1168
|
const providerType = provider.toLowerCase();
|
|
1113
1169
|
let resource = existingResource || await resourceStorage.getResource(userIdOrEmail, providerType);
|
|
1114
1170
|
if (!resource || !resource.refreshToken) {
|
|
@@ -1161,7 +1217,7 @@ var Lixa = class _Lixa {
|
|
|
1161
1217
|
* Refreshes a connected resource access token for an active session.
|
|
1162
1218
|
*/
|
|
1163
1219
|
async refreshResourceToken(sessionId, provider) {
|
|
1164
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1220
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1165
1221
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1166
1222
|
if (!activeSession) {
|
|
1167
1223
|
throw new SessionNotFoundError("Active session not found.", { sessionId });
|
|
@@ -1179,7 +1235,7 @@ var Lixa = class _Lixa {
|
|
|
1179
1235
|
* Disconnects a resource provider for a specific User ID / Email.
|
|
1180
1236
|
*/
|
|
1181
1237
|
async disconnectUserResource(userIdOrEmail, provider) {
|
|
1182
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1238
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1183
1239
|
await resourceStorage.deleteResource(userIdOrEmail, provider.toLowerCase());
|
|
1184
1240
|
return true;
|
|
1185
1241
|
}
|
|
@@ -1187,7 +1243,7 @@ var Lixa = class _Lixa {
|
|
|
1187
1243
|
* Disconnects a resource provider from an active session and user account.
|
|
1188
1244
|
*/
|
|
1189
1245
|
async disconnectResource(sessionId, provider) {
|
|
1190
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1246
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1191
1247
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1192
1248
|
if (activeSession) {
|
|
1193
1249
|
const userKey = this.getUserKeyFromSession(activeSession);
|
|
@@ -1204,7 +1260,7 @@ var Lixa = class _Lixa {
|
|
|
1204
1260
|
* Retrieves active session details from session storage.
|
|
1205
1261
|
*/
|
|
1206
1262
|
async fetchSessionInfo(sessionId) {
|
|
1207
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1263
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1208
1264
|
return await sessionStorage.getSession(sessionId);
|
|
1209
1265
|
}
|
|
1210
1266
|
/**
|
|
@@ -1213,7 +1269,7 @@ var Lixa = class _Lixa {
|
|
|
1213
1269
|
* @param sessionId - Active session identifier
|
|
1214
1270
|
*/
|
|
1215
1271
|
async deleteSession(sessionId) {
|
|
1216
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1272
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1217
1273
|
await sessionStorage.deleteSession(sessionId);
|
|
1218
1274
|
}
|
|
1219
1275
|
async exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier) {
|
|
@@ -1269,7 +1325,7 @@ var Lixa = class _Lixa {
|
|
|
1269
1325
|
var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
|
|
1270
1326
|
var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
|
|
1271
1327
|
var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
|
|
1272
|
-
var DEFAULT_STATE_MAX_AGE_SECONDS =
|
|
1328
|
+
var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
|
|
1273
1329
|
function isProductionEnvironment() {
|
|
1274
1330
|
return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
|
|
1275
1331
|
}
|
|
@@ -1383,6 +1439,54 @@ function clearStateCookie(options) {
|
|
|
1383
1439
|
header
|
|
1384
1440
|
};
|
|
1385
1441
|
}
|
|
1442
|
+
|
|
1443
|
+
// src/express.ts
|
|
1444
|
+
function setSessionCookieOnResponse(res, sessionId, options) {
|
|
1445
|
+
const cookie = createSessionCookie(sessionId, options);
|
|
1446
|
+
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
1447
|
+
}
|
|
1448
|
+
function clearSessionCookieOnResponse(res, options) {
|
|
1449
|
+
const cookie = clearSessionCookie(options);
|
|
1450
|
+
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
1451
|
+
}
|
|
1452
|
+
function setStateCookieOnResponse(res, state, options) {
|
|
1453
|
+
const cookie = createStateCookie(state, options);
|
|
1454
|
+
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
1455
|
+
}
|
|
1456
|
+
function clearStateCookieOnResponse(res, options) {
|
|
1457
|
+
const cookie = clearStateCookie(options);
|
|
1458
|
+
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
1459
|
+
}
|
|
1460
|
+
function createRequireAuthMiddleware(lixa, options) {
|
|
1461
|
+
const cookieName = options?.sessionCookieName || DEFAULT_SESSION_COOKIE_NAME;
|
|
1462
|
+
return async (req, res, next) => {
|
|
1463
|
+
try {
|
|
1464
|
+
const sessionId = req.cookies?.[cookieName];
|
|
1465
|
+
if (!sessionId) {
|
|
1466
|
+
if (options?.unauthorizedHandler) {
|
|
1467
|
+
options.unauthorizedHandler(req, res);
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
res.status(401).json({ error: "Unauthorized: Missing session cookie" });
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
const session = await lixa.fetchSessionInfo(sessionId);
|
|
1474
|
+
if (!session) {
|
|
1475
|
+
if (options?.unauthorizedHandler) {
|
|
1476
|
+
options.unauthorizedHandler(req, res);
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
res.status(401).json({ error: "Unauthorized: Invalid or expired session" });
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
req.sessionInfo = session;
|
|
1483
|
+
req.sessionId = sessionId;
|
|
1484
|
+
next();
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
next(error);
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1386
1490
|
export {
|
|
1387
1491
|
AccountLinkingStrategy,
|
|
1388
1492
|
AccountUnlinkError,
|
|
@@ -1401,7 +1505,10 @@ export {
|
|
|
1401
1505
|
SessionNotFoundError,
|
|
1402
1506
|
TokenExchangeError,
|
|
1403
1507
|
clearSessionCookie,
|
|
1508
|
+
clearSessionCookieOnResponse,
|
|
1404
1509
|
clearStateCookie,
|
|
1510
|
+
clearStateCookieOnResponse,
|
|
1511
|
+
createRequireAuthMiddleware,
|
|
1405
1512
|
createSessionCookie,
|
|
1406
1513
|
createStateCookie,
|
|
1407
1514
|
decodeIdToken,
|
|
@@ -1409,6 +1516,8 @@ export {
|
|
|
1409
1516
|
extractUserInfo,
|
|
1410
1517
|
fetchUserInfo,
|
|
1411
1518
|
isProductionEnvironment,
|
|
1412
|
-
serializeCookie
|
|
1519
|
+
serializeCookie,
|
|
1520
|
+
setSessionCookieOnResponse,
|
|
1521
|
+
setStateCookieOnResponse
|
|
1413
1522
|
};
|
|
1414
1523
|
//# sourceMappingURL=index.js.map
|