@vunexa/lixa 0.1.6-alpha.7 → 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/README.md +1 -2
- package/README.template.md +1 -2
- 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/errors.d.ts +90 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/export-types/index.d.ts +439 -95
- package/dist/express.d.ts +103 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/index.cjs +509 -129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +408 -96
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +483 -128
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +25 -21
- 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 +142 -0
- package/dist/utils/cookies.d.ts.map +1 -0
- 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
|
|
@@ -166,13 +187,91 @@ async function extractUserInfo(tokenData, providerMetadata) {
|
|
|
166
187
|
return { userInfo };
|
|
167
188
|
}
|
|
168
189
|
|
|
190
|
+
// src/errors.ts
|
|
191
|
+
var LixaError = class extends Error {
|
|
192
|
+
/**
|
|
193
|
+
* Standard error code string.
|
|
194
|
+
*/
|
|
195
|
+
code;
|
|
196
|
+
/**
|
|
197
|
+
* Additional error context data.
|
|
198
|
+
*/
|
|
199
|
+
details;
|
|
200
|
+
constructor(message, code = "LIXA_ERROR", details) {
|
|
201
|
+
super(message);
|
|
202
|
+
this.name = this.constructor.name;
|
|
203
|
+
this.code = code;
|
|
204
|
+
this.details = details;
|
|
205
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
var InvalidStateError = class extends LixaError {
|
|
209
|
+
constructor(message = "Invalid or expired state", details) {
|
|
210
|
+
super(message, "INVALID_STATE", details);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var ProviderNotConfiguredError = class extends LixaError {
|
|
214
|
+
constructor(provider, details) {
|
|
215
|
+
const message = details?.message && typeof details.message === "string" ? details.message : `Provider '${provider}' is not available. Either import it from '@vunexa/lixa-providers' and include it in the configuration, or provide a custom implementation using the 'provider' field: { provider: new CustomProvider(), clientId: '...', ... }`;
|
|
216
|
+
super(message, "PROVIDER_NOT_CONFIGURED", {
|
|
217
|
+
provider,
|
|
218
|
+
...details
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
var InvalidProviderConfigError = class extends LixaError {
|
|
223
|
+
constructor(message, details) {
|
|
224
|
+
super(message, "INVALID_PROVIDER_CONFIG", details);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
var InvalidOAuthCallbackError = class extends LixaError {
|
|
228
|
+
constructor(message, details) {
|
|
229
|
+
super(message, "INVALID_OAUTH_CALLBACK", details);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var TokenExchangeError = class extends LixaError {
|
|
233
|
+
status;
|
|
234
|
+
constructor(message, status, details) {
|
|
235
|
+
super(message, "TOKEN_EXCHANGE_FAILED", { status, ...details });
|
|
236
|
+
this.status = status;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var SessionNotFoundError = class extends LixaError {
|
|
240
|
+
constructor(message = "Active session not found or has expired", details) {
|
|
241
|
+
super(message, "SESSION_NOT_FOUND", details);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
var EmailNotVerifiedError = class extends LixaError {
|
|
245
|
+
constructor(email, details) {
|
|
246
|
+
super(
|
|
247
|
+
`Cannot link account: email '${email || "unknown"}' is not verified by the identity provider`,
|
|
248
|
+
"EMAIL_NOT_VERIFIED",
|
|
249
|
+
{ email, ...details }
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
var AccountUnlinkError = class extends LixaError {
|
|
254
|
+
constructor(message, details) {
|
|
255
|
+
super(message, "ACCOUNT_UNLINK_ERROR", details);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
var RefreshTokenError = class extends LixaError {
|
|
259
|
+
constructor(message, details) {
|
|
260
|
+
super(message, "REFRESH_TOKEN_ERROR", details);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
169
264
|
// src/lixa.ts
|
|
170
265
|
var Lixa = class _Lixa {
|
|
171
266
|
static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
|
|
172
267
|
static CONFIGURED_PROVIDERS = /* @__PURE__ */ new Map();
|
|
173
268
|
// Legacy registry for backward compatibility
|
|
174
|
-
|
|
175
|
-
|
|
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();
|
|
176
275
|
config;
|
|
177
276
|
stateHandler;
|
|
178
277
|
sessionHandler;
|
|
@@ -193,10 +292,44 @@ var Lixa = class _Lixa {
|
|
|
193
292
|
*/
|
|
194
293
|
constructor(config) {
|
|
195
294
|
this.config = config;
|
|
196
|
-
this.stateHandler = config.stateHandler || _Lixa.LOCAL_STATE_HANDLER;
|
|
197
|
-
this.sessionHandler = config.sessionHandler || _Lixa.LOCAL_SESSION_HANDLER;
|
|
198
|
-
this.resourceHandler = config.resourceHandler || _Lixa.LOCAL_RESOURCE_HANDLER;
|
|
199
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;
|
|
200
333
|
this.log("INFO", "Init", "Initializing Lixa instance", {
|
|
201
334
|
providers: Object.keys(config.providers),
|
|
202
335
|
debug: this.debug
|
|
@@ -211,8 +344,9 @@ var Lixa = class _Lixa {
|
|
|
211
344
|
} else {
|
|
212
345
|
if (!_Lixa.DEFAULT_PROVIDERS.has(name) && !_Lixa.CONFIGURED_PROVIDERS.has(name)) {
|
|
213
346
|
this.log("ERROR", "Init", `Provider '${providerName}' not available`);
|
|
214
|
-
throw new
|
|
215
|
-
|
|
347
|
+
throw new ProviderNotConfiguredError(
|
|
348
|
+
providerName,
|
|
349
|
+
{ hint: `Ensure the provider is registered via '@vunexa/lixa-providers' or provided inline.` }
|
|
216
350
|
);
|
|
217
351
|
}
|
|
218
352
|
this.log("INFO", "Init", `Using registered provider: ${providerName}`);
|
|
@@ -225,7 +359,7 @@ var Lixa = class _Lixa {
|
|
|
225
359
|
*
|
|
226
360
|
* @param name - The provider name
|
|
227
361
|
* @param config - The provider configuration
|
|
228
|
-
* @throws
|
|
362
|
+
* @throws InvalidProviderConfigError when required fields are missing or invalid
|
|
229
363
|
*/
|
|
230
364
|
validateProviderConfig(name, config) {
|
|
231
365
|
const requiredFields = ["clientId", "clientSecret", "redirectUri", "scopes"];
|
|
@@ -234,18 +368,21 @@ var Lixa = class _Lixa {
|
|
|
234
368
|
return value === void 0 || value === null || typeof value === "string" && value.trim() === "";
|
|
235
369
|
});
|
|
236
370
|
if (missingFields.length > 0) {
|
|
237
|
-
throw new
|
|
238
|
-
`Provider '${name}' configuration is missing required fields: ${missingFields.join(", ")}
|
|
371
|
+
throw new InvalidProviderConfigError(
|
|
372
|
+
`Provider '${name}' configuration is missing required fields: ${missingFields.join(", ")}`,
|
|
373
|
+
{ provider: name, missingFields }
|
|
239
374
|
);
|
|
240
375
|
}
|
|
241
376
|
if (!Array.isArray(config.scopes)) {
|
|
242
|
-
throw new
|
|
243
|
-
`Provider '${name}' configuration error: 'scopes' must be an array of strings
|
|
377
|
+
throw new InvalidProviderConfigError(
|
|
378
|
+
`Provider '${name}' configuration error: 'scopes' must be an array of strings`,
|
|
379
|
+
{ provider: name }
|
|
244
380
|
);
|
|
245
381
|
}
|
|
246
382
|
if (config.scopes.length === 0) {
|
|
247
|
-
throw new
|
|
248
|
-
`Provider '${name}' configuration error: 'scopes' array cannot be empty
|
|
383
|
+
throw new InvalidProviderConfigError(
|
|
384
|
+
`Provider '${name}' configuration error: 'scopes' array cannot be empty`,
|
|
385
|
+
{ provider: name }
|
|
249
386
|
);
|
|
250
387
|
}
|
|
251
388
|
}
|
|
@@ -254,7 +391,7 @@ var Lixa = class _Lixa {
|
|
|
254
391
|
*
|
|
255
392
|
* @param name - The provider name
|
|
256
393
|
* @param provider - The provider implementation
|
|
257
|
-
* @throws
|
|
394
|
+
* @throws InvalidProviderConfigError when required properties are missing
|
|
258
395
|
*/
|
|
259
396
|
validateProviderImplementation(name, provider) {
|
|
260
397
|
const requiredProps = ["authorizationEndpoint", "tokenEndpoint", "userInfoEndpoint"];
|
|
@@ -263,24 +400,27 @@ var Lixa = class _Lixa {
|
|
|
263
400
|
return !value || typeof value !== "string" || value.trim() === "";
|
|
264
401
|
});
|
|
265
402
|
if (missingProps.length > 0) {
|
|
266
|
-
throw new
|
|
267
|
-
`Provider '${name}' implementation is missing required properties: ${missingProps.join(", ")}. All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint
|
|
403
|
+
throw new InvalidProviderConfigError(
|
|
404
|
+
`Provider '${name}' implementation is missing required properties: ${missingProps.join(", ")}. All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint.`,
|
|
405
|
+
{ provider: name, missingProps }
|
|
268
406
|
);
|
|
269
407
|
}
|
|
270
408
|
}
|
|
271
409
|
/**
|
|
272
|
-
* Structured
|
|
410
|
+
* Structured logging with standardized format and custom logger support.
|
|
273
411
|
*
|
|
274
|
-
* @param level - Log level (INFO, WARN, ERROR)
|
|
275
|
-
* @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)
|
|
276
414
|
* @param message - Log message
|
|
277
415
|
* @param data - Optional data to log
|
|
278
|
-
*
|
|
279
|
-
* @remarks
|
|
280
|
-
* Format: [Lixa] [timestamp] [level] [context] message
|
|
281
|
-
* Only logs when debug mode is enabled.
|
|
282
416
|
*/
|
|
283
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
|
+
}
|
|
284
424
|
if (!this.debug) return;
|
|
285
425
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
286
426
|
const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;
|
|
@@ -331,8 +471,9 @@ var Lixa = class _Lixa {
|
|
|
331
471
|
if (legacyProvider) {
|
|
332
472
|
return legacyProvider;
|
|
333
473
|
}
|
|
334
|
-
throw new
|
|
335
|
-
|
|
474
|
+
throw new ProviderNotConfiguredError(
|
|
475
|
+
name,
|
|
476
|
+
{ hint: `Ensure the provider is included in the configuration with a 'provider' field, or registered using Lixa.registerProvider().` }
|
|
336
477
|
);
|
|
337
478
|
}
|
|
338
479
|
/**
|
|
@@ -522,23 +663,26 @@ var Lixa = class _Lixa {
|
|
|
522
663
|
const providerConfig = this.findProviderByType(providerType);
|
|
523
664
|
if (!providerConfig) {
|
|
524
665
|
this.log("ERROR", "Auth", `Provider '${String(provider)}' is not configured`);
|
|
525
|
-
throw new
|
|
666
|
+
throw new ProviderNotConfiguredError(String(provider), {
|
|
667
|
+
message: `Provider '${String(provider)}' is not configured in this Lixa instance`
|
|
668
|
+
});
|
|
526
669
|
}
|
|
527
670
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
528
671
|
let stateValue;
|
|
529
672
|
let codeVerifier;
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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);
|
|
533
677
|
stateValue = state || generated.state;
|
|
534
678
|
codeVerifier = generated.data.codeVerifier;
|
|
535
|
-
const storage = this.stateHandler.stateStorage ||
|
|
679
|
+
const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
536
680
|
await storage.saveState(stateValue, generated.data, 300);
|
|
537
681
|
} else {
|
|
538
682
|
this.log("INFO", "State", "Using default state generation");
|
|
539
683
|
stateValue = state || randomBytes2(16).toString("hex");
|
|
540
684
|
codeVerifier = randomBytes2(32).toString("hex");
|
|
541
|
-
const storage = this.stateHandler.stateStorage ||
|
|
685
|
+
const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
542
686
|
await storage.saveState(
|
|
543
687
|
stateValue,
|
|
544
688
|
{
|
|
@@ -547,12 +691,17 @@ var Lixa = class _Lixa {
|
|
|
547
691
|
codeVerifier
|
|
548
692
|
},
|
|
549
693
|
300
|
|
550
|
-
// 5 minutes in seconds
|
|
694
|
+
// 5 minutes in seconds (synchronized with cookie TTL)
|
|
551
695
|
);
|
|
552
696
|
}
|
|
553
697
|
const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
|
|
554
698
|
this.log("INFO", "State", `Saved state for provider: ${providerType}`, { state: stateValue });
|
|
555
|
-
const authNScopes = this.resolveAuthNScopes(
|
|
699
|
+
const authNScopes = this.resolveAuthNScopes(
|
|
700
|
+
providerType,
|
|
701
|
+
providerConfig.scopes,
|
|
702
|
+
providerImpl,
|
|
703
|
+
providerConfig.allowNonAuthScopes
|
|
704
|
+
);
|
|
556
705
|
const params = new URLSearchParams({
|
|
557
706
|
client_id: providerConfig.clientId,
|
|
558
707
|
redirect_uri: providerConfig.redirectUri,
|
|
@@ -571,9 +720,12 @@ var Lixa = class _Lixa {
|
|
|
571
720
|
return authUrl;
|
|
572
721
|
}
|
|
573
722
|
/**
|
|
574
|
-
* Restricts primary authentication scopes strictly to AuthN identity scopes.
|
|
723
|
+
* Restricts primary authentication scopes strictly to AuthN identity scopes unless allowNonAuthScopes is true.
|
|
575
724
|
*/
|
|
576
|
-
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
|
+
}
|
|
577
729
|
const defaultAuthScopes = {
|
|
578
730
|
google: ["openid", "email", "profile"],
|
|
579
731
|
github: ["read:user", "user:email"],
|
|
@@ -602,7 +754,7 @@ var Lixa = class _Lixa {
|
|
|
602
754
|
"Auth",
|
|
603
755
|
`Primary authentication is strictly limited to AuthN scopes. Excluded non-AuthN resource scopes: [${nonAuthNScopes.join(
|
|
604
756
|
", "
|
|
605
|
-
)}].
|
|
757
|
+
)}]. Set 'allowNonAuthScopes: true' on provider config to include them, or use lixa.getResourceAuthUrl() post-login to connect resource providers.`
|
|
606
758
|
);
|
|
607
759
|
}
|
|
608
760
|
if (validAuthNScopes.length > 0) {
|
|
@@ -638,18 +790,18 @@ var Lixa = class _Lixa {
|
|
|
638
790
|
this.log("INFO", "Auth", `Handling OAuth callback for provider: ${providerType}`);
|
|
639
791
|
if (!code || code.trim() === "") {
|
|
640
792
|
this.log("ERROR", "Auth", "Invalid or missing authorization code in callback");
|
|
641
|
-
throw new
|
|
793
|
+
throw new InvalidOAuthCallbackError("Invalid or missing code in callback");
|
|
642
794
|
}
|
|
643
795
|
if (!state || state.trim() === "") {
|
|
644
796
|
this.log("ERROR", "Auth", "Invalid or missing state in callback");
|
|
645
|
-
throw new
|
|
797
|
+
throw new InvalidOAuthCallbackError("Invalid or missing state in callback");
|
|
646
798
|
}
|
|
647
799
|
this.log("INFO", "State", "Validating state parameter", { state });
|
|
648
|
-
const stateStorage = this.stateHandler.stateStorage ||
|
|
800
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
649
801
|
const cachedState = await stateStorage.getState(state);
|
|
650
802
|
if (!cachedState) {
|
|
651
803
|
this.log("ERROR", "State", "State validation failed: state not found or expired", { state });
|
|
652
|
-
throw new
|
|
804
|
+
throw new InvalidStateError("Invalid or expired state", { state });
|
|
653
805
|
}
|
|
654
806
|
this.log("INFO", "State", "State validated successfully, removing from cache");
|
|
655
807
|
await stateStorage.deleteState(state);
|
|
@@ -657,7 +809,9 @@ var Lixa = class _Lixa {
|
|
|
657
809
|
const providerConfig = this.findProviderByType(providerType);
|
|
658
810
|
if (!providerConfig) {
|
|
659
811
|
this.log("ERROR", "Auth", `Provider '${String(provider)}' is not configured`);
|
|
660
|
-
throw new
|
|
812
|
+
throw new ProviderNotConfiguredError(String(provider), {
|
|
813
|
+
message: `Provider '${String(provider)}' is not configured in this Lixa instance`
|
|
814
|
+
});
|
|
661
815
|
}
|
|
662
816
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
663
817
|
this.log("INFO", "Token", `Exchanging authorization code for tokens`, { provider: providerType });
|
|
@@ -683,9 +837,12 @@ var Lixa = class _Lixa {
|
|
|
683
837
|
extractedUserInfo = userInfo;
|
|
684
838
|
} catch {
|
|
685
839
|
}
|
|
686
|
-
const generateSession = this.sessionHandler.generateSession ||
|
|
687
|
-
if (
|
|
688
|
-
|
|
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");
|
|
689
846
|
} else {
|
|
690
847
|
this.log("INFO", "Session", "Using default session generation");
|
|
691
848
|
}
|
|
@@ -694,7 +851,7 @@ var Lixa = class _Lixa {
|
|
|
694
851
|
if (extractedUserInfo?.email && !session.email) {
|
|
695
852
|
session.email = extractedUserInfo.email;
|
|
696
853
|
}
|
|
697
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
854
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
698
855
|
const linkingConfig = this.config.accountLinking;
|
|
699
856
|
const mode = String(linkingConfig?.mode || "");
|
|
700
857
|
const isLinkByEmail = mode === "AUTO_LINK_BY_VERIFIED_EMAIL" /* AUTO_LINK_BY_VERIFIED_EMAIL */ || mode === "AUTO_LINK_BY_VERIFIED_EMAIL" || mode === "linkByEmail";
|
|
@@ -747,27 +904,31 @@ var Lixa = class _Lixa {
|
|
|
747
904
|
*/
|
|
748
905
|
async linkAccount(params) {
|
|
749
906
|
const { sessionId, provider, code, state } = params;
|
|
750
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
907
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
751
908
|
const existingSession = await sessionStorage.getSession(sessionId);
|
|
752
909
|
if (!existingSession) {
|
|
753
|
-
throw new
|
|
910
|
+
throw new SessionNotFoundError("Invalid session ID. User must be authenticated to link an account.", { sessionId });
|
|
911
|
+
}
|
|
912
|
+
if (!code || code.trim() === "") {
|
|
913
|
+
throw new InvalidOAuthCallbackError("Invalid or missing code in linkAccount");
|
|
754
914
|
}
|
|
755
915
|
const providerType = String(provider).toLowerCase();
|
|
756
916
|
const providerConfig = this.findProviderByType(providerType);
|
|
757
917
|
if (!providerConfig) {
|
|
758
|
-
throw new
|
|
918
|
+
throw new ProviderNotConfiguredError(String(provider));
|
|
759
919
|
}
|
|
760
920
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
761
|
-
let codeVerifier
|
|
921
|
+
let codeVerifier;
|
|
762
922
|
if (state) {
|
|
763
|
-
const stateStorage = this.stateHandler.stateStorage ||
|
|
923
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
764
924
|
const cachedState = await stateStorage.getState(state);
|
|
765
|
-
if (cachedState) {
|
|
766
|
-
|
|
767
|
-
await stateStorage.deleteState(state);
|
|
925
|
+
if (!cachedState) {
|
|
926
|
+
throw new InvalidStateError("Invalid or expired state during account linking", { state });
|
|
768
927
|
}
|
|
928
|
+
codeVerifier = cachedState.codeVerifier;
|
|
929
|
+
await stateStorage.deleteState(state);
|
|
769
930
|
}
|
|
770
|
-
const tokens = await this.exchangeCodeForToken(
|
|
931
|
+
const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier || "");
|
|
771
932
|
const providerMetadata = {
|
|
772
933
|
name: providerType,
|
|
773
934
|
endpoints: {
|
|
@@ -805,14 +966,17 @@ var Lixa = class _Lixa {
|
|
|
805
966
|
* @returns Promise resolving to true on successful unlink
|
|
806
967
|
*/
|
|
807
968
|
async unlinkAccount(sessionId, providerToUnlink) {
|
|
808
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
969
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
809
970
|
const session = await sessionStorage.getSession(sessionId);
|
|
810
971
|
if (!session || !session.accounts) {
|
|
811
|
-
throw new
|
|
972
|
+
throw new AccountUnlinkError("Session not found or has no linked accounts.", { sessionId });
|
|
812
973
|
}
|
|
813
974
|
const linkedProviders = Object.keys(session.accounts);
|
|
814
975
|
if (linkedProviders.length <= 1) {
|
|
815
|
-
throw new
|
|
976
|
+
throw new AccountUnlinkError("Cannot unlink the only authentication provider for this account.", {
|
|
977
|
+
sessionId,
|
|
978
|
+
provider: providerToUnlink
|
|
979
|
+
});
|
|
816
980
|
}
|
|
817
981
|
delete session.accounts[providerToUnlink.toLowerCase()];
|
|
818
982
|
await sessionStorage.saveSession(sessionId, session, 86400);
|
|
@@ -831,20 +995,20 @@ var Lixa = class _Lixa {
|
|
|
831
995
|
*/
|
|
832
996
|
async getResourceAuthUrl(params) {
|
|
833
997
|
const { sessionId, provider, scopes, state, prompt, extraConfig } = params;
|
|
834
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
998
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
835
999
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
836
1000
|
if (!activeSession) {
|
|
837
|
-
throw new
|
|
1001
|
+
throw new SessionNotFoundError("Authentication required. Active session must exist to connect resource providers.", { sessionId });
|
|
838
1002
|
}
|
|
839
1003
|
const providerType = String(provider).toLowerCase();
|
|
840
1004
|
const providerConfig = this.findProviderByType(providerType);
|
|
841
1005
|
if (!providerConfig) {
|
|
842
|
-
throw new
|
|
1006
|
+
throw new ProviderNotConfiguredError(String(provider));
|
|
843
1007
|
}
|
|
844
1008
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
845
1009
|
const stateValue = state || randomBytes2(16).toString("hex");
|
|
846
1010
|
const codeVerifier = randomBytes2(32).toString("hex");
|
|
847
|
-
const storage = this.stateHandler.stateStorage ||
|
|
1011
|
+
const storage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
848
1012
|
await storage.saveState(
|
|
849
1013
|
stateValue,
|
|
850
1014
|
{
|
|
@@ -869,45 +1033,6 @@ var Lixa = class _Lixa {
|
|
|
869
1033
|
});
|
|
870
1034
|
return `${providerImpl.authorizationEndpoint}?${searchParams.toString()}`;
|
|
871
1035
|
}
|
|
872
|
-
/**
|
|
873
|
-
* Handles the OAuth callback for a connected resource provider and stores resource tokens on the session.
|
|
874
|
-
*
|
|
875
|
-
* @param params - Object containing sessionId, provider, code, state, and requested scopes
|
|
876
|
-
* @returns Updated Session containing stored resource tokens under session.resources[provider]
|
|
877
|
-
*/
|
|
878
|
-
static userResourceStore = /* @__PURE__ */ new Map();
|
|
879
|
-
static LOCAL_RESOURCE_HANDLER = {
|
|
880
|
-
resourceStorage: {
|
|
881
|
-
saveResource: async (userId, provider, resource) => {
|
|
882
|
-
let userMap = _Lixa.userResourceStore.get(userId);
|
|
883
|
-
if (!userMap) {
|
|
884
|
-
userMap = /* @__PURE__ */ new Map();
|
|
885
|
-
_Lixa.userResourceStore.set(userId, userMap);
|
|
886
|
-
}
|
|
887
|
-
userMap.set(provider.toLowerCase(), resource);
|
|
888
|
-
},
|
|
889
|
-
getResource: async (userId, provider) => {
|
|
890
|
-
const userMap = _Lixa.userResourceStore.get(userId);
|
|
891
|
-
return userMap?.get(provider.toLowerCase()) || null;
|
|
892
|
-
},
|
|
893
|
-
getUserResources: async (userId) => {
|
|
894
|
-
const userMap = _Lixa.userResourceStore.get(userId);
|
|
895
|
-
const result = {};
|
|
896
|
-
if (userMap) {
|
|
897
|
-
for (const [p, r] of userMap.entries()) {
|
|
898
|
-
result[p] = r;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
return result;
|
|
902
|
-
},
|
|
903
|
-
deleteResource: async (userId, provider) => {
|
|
904
|
-
const userMap = _Lixa.userResourceStore.get(userId);
|
|
905
|
-
if (userMap) {
|
|
906
|
-
userMap.delete(provider.toLowerCase());
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
};
|
|
911
1036
|
getUserKeyFromSession(session) {
|
|
912
1037
|
return session.userId || session.email || session.id || "anonymous";
|
|
913
1038
|
}
|
|
@@ -916,25 +1041,26 @@ var Lixa = class _Lixa {
|
|
|
916
1041
|
*/
|
|
917
1042
|
async handleResourceCallback(params) {
|
|
918
1043
|
const { sessionId, provider, code, state, scopes } = params;
|
|
919
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1044
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
920
1045
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
921
1046
|
if (!activeSession) {
|
|
922
|
-
throw new
|
|
1047
|
+
throw new SessionNotFoundError("Authentication required. Active session not found for resource connection.", { sessionId });
|
|
923
1048
|
}
|
|
924
1049
|
const providerType = String(provider).toLowerCase();
|
|
925
1050
|
const providerConfig = this.findProviderByType(providerType);
|
|
926
1051
|
if (!providerConfig) {
|
|
927
|
-
throw new
|
|
1052
|
+
throw new ProviderNotConfiguredError(String(provider));
|
|
928
1053
|
}
|
|
929
1054
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
930
1055
|
let codeVerifier = randomBytes2(32).toString("hex");
|
|
931
1056
|
if (state) {
|
|
932
|
-
const stateStorage = this.stateHandler.stateStorage ||
|
|
1057
|
+
const stateStorage = this.stateHandler.stateStorage || this.localStateHandler.stateStorage;
|
|
933
1058
|
const cachedState = await stateStorage.getState(state);
|
|
934
|
-
if (cachedState) {
|
|
935
|
-
|
|
936
|
-
await stateStorage.deleteState(state);
|
|
1059
|
+
if (!cachedState) {
|
|
1060
|
+
throw new InvalidStateError("Invalid or expired state during resource connection callback", { state });
|
|
937
1061
|
}
|
|
1062
|
+
codeVerifier = cachedState.codeVerifier;
|
|
1063
|
+
await stateStorage.deleteState(state);
|
|
938
1064
|
}
|
|
939
1065
|
const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier);
|
|
940
1066
|
const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
|
|
@@ -948,7 +1074,7 @@ var Lixa = class _Lixa {
|
|
|
948
1074
|
connectedAt: Date.now()
|
|
949
1075
|
};
|
|
950
1076
|
const userKey = this.getUserKeyFromSession(activeSession);
|
|
951
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1077
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
952
1078
|
await resourceStorage.saveResource(userKey, providerType, connectedResource);
|
|
953
1079
|
activeSession.resources = activeSession.resources || {};
|
|
954
1080
|
activeSession.resources[providerType] = connectedResource;
|
|
@@ -963,7 +1089,7 @@ var Lixa = class _Lixa {
|
|
|
963
1089
|
* @param provider - Resource provider identifier (e.g. 'github', 'google')
|
|
964
1090
|
*/
|
|
965
1091
|
async getUserResource(userIdOrEmail, provider) {
|
|
966
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1092
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
967
1093
|
const providerType = provider.toLowerCase();
|
|
968
1094
|
const resource = await resourceStorage.getResource(userIdOrEmail, providerType);
|
|
969
1095
|
if (!resource) return null;
|
|
@@ -981,7 +1107,7 @@ var Lixa = class _Lixa {
|
|
|
981
1107
|
* Retrieves all connected resources for a specific User ID / Email.
|
|
982
1108
|
*/
|
|
983
1109
|
async getUserResources(userIdOrEmail) {
|
|
984
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1110
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
985
1111
|
return await resourceStorage.getUserResources(userIdOrEmail);
|
|
986
1112
|
}
|
|
987
1113
|
/**
|
|
@@ -991,7 +1117,7 @@ var Lixa = class _Lixa {
|
|
|
991
1117
|
* @param provider - Provider identifier (e.g. 'github', 'google')
|
|
992
1118
|
*/
|
|
993
1119
|
async getConnectedResource(sessionId, provider) {
|
|
994
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1120
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
995
1121
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
996
1122
|
if (!activeSession) return null;
|
|
997
1123
|
const providerType = provider.toLowerCase();
|
|
@@ -1016,20 +1142,40 @@ var Lixa = class _Lixa {
|
|
|
1016
1142
|
}
|
|
1017
1143
|
/**
|
|
1018
1144
|
* Refreshes a user's resource access token using its refresh token.
|
|
1145
|
+
* Deduplicates concurrent refresh requests via an in-flight promise mutex.
|
|
1019
1146
|
*
|
|
1020
1147
|
* @param userIdOrEmail - User identifier or email
|
|
1021
1148
|
* @param provider - Provider identifier (e.g. 'google', 'github')
|
|
1022
1149
|
*/
|
|
1023
1150
|
async refreshUserResourceToken(userIdOrEmail, provider, existingResource) {
|
|
1024
|
-
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;
|
|
1025
1168
|
const providerType = provider.toLowerCase();
|
|
1026
1169
|
let resource = existingResource || await resourceStorage.getResource(userIdOrEmail, providerType);
|
|
1027
1170
|
if (!resource || !resource.refreshToken) {
|
|
1028
|
-
throw new
|
|
1171
|
+
throw new RefreshTokenError(
|
|
1172
|
+
`No refresh token available for user '${userIdOrEmail}' on connected resource '${provider}'`,
|
|
1173
|
+
{ userId: userIdOrEmail, provider }
|
|
1174
|
+
);
|
|
1029
1175
|
}
|
|
1030
1176
|
const providerConfig = this.findProviderByType(providerType);
|
|
1031
1177
|
if (!providerConfig) {
|
|
1032
|
-
throw new
|
|
1178
|
+
throw new ProviderNotConfiguredError(provider);
|
|
1033
1179
|
}
|
|
1034
1180
|
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
1035
1181
|
const body = {
|
|
@@ -1048,7 +1194,10 @@ var Lixa = class _Lixa {
|
|
|
1048
1194
|
});
|
|
1049
1195
|
if (!response.ok) {
|
|
1050
1196
|
const errorText = await response.text();
|
|
1051
|
-
throw new
|
|
1197
|
+
throw new RefreshTokenError(
|
|
1198
|
+
`Failed to refresh resource token for user '${userIdOrEmail}' on '${provider}': ${response.status} - ${errorText}`,
|
|
1199
|
+
{ userId: userIdOrEmail, provider, status: response.status }
|
|
1200
|
+
);
|
|
1052
1201
|
}
|
|
1053
1202
|
const tokens = await response.json();
|
|
1054
1203
|
const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
|
|
@@ -1068,10 +1217,10 @@ var Lixa = class _Lixa {
|
|
|
1068
1217
|
* Refreshes a connected resource access token for an active session.
|
|
1069
1218
|
*/
|
|
1070
1219
|
async refreshResourceToken(sessionId, provider) {
|
|
1071
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1220
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1072
1221
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1073
1222
|
if (!activeSession) {
|
|
1074
|
-
throw new
|
|
1223
|
+
throw new SessionNotFoundError("Active session not found.", { sessionId });
|
|
1075
1224
|
}
|
|
1076
1225
|
const providerType = provider.toLowerCase();
|
|
1077
1226
|
const userKey = this.getUserKeyFromSession(activeSession);
|
|
@@ -1086,7 +1235,7 @@ var Lixa = class _Lixa {
|
|
|
1086
1235
|
* Disconnects a resource provider for a specific User ID / Email.
|
|
1087
1236
|
*/
|
|
1088
1237
|
async disconnectUserResource(userIdOrEmail, provider) {
|
|
1089
|
-
const resourceStorage = this.resourceHandler.resourceStorage ||
|
|
1238
|
+
const resourceStorage = this.resourceHandler.resourceStorage || this.localResourceHandler.resourceStorage;
|
|
1090
1239
|
await resourceStorage.deleteResource(userIdOrEmail, provider.toLowerCase());
|
|
1091
1240
|
return true;
|
|
1092
1241
|
}
|
|
@@ -1094,7 +1243,7 @@ var Lixa = class _Lixa {
|
|
|
1094
1243
|
* Disconnects a resource provider from an active session and user account.
|
|
1095
1244
|
*/
|
|
1096
1245
|
async disconnectResource(sessionId, provider) {
|
|
1097
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1246
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1098
1247
|
const activeSession = await sessionStorage.getSession(sessionId);
|
|
1099
1248
|
if (activeSession) {
|
|
1100
1249
|
const userKey = this.getUserKeyFromSession(activeSession);
|
|
@@ -1107,10 +1256,22 @@ var Lixa = class _Lixa {
|
|
|
1107
1256
|
}
|
|
1108
1257
|
return false;
|
|
1109
1258
|
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Retrieves active session details from session storage.
|
|
1261
|
+
*/
|
|
1110
1262
|
async fetchSessionInfo(sessionId) {
|
|
1111
|
-
const sessionStorage = this.sessionHandler.sessionStorage ||
|
|
1263
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1112
1264
|
return await sessionStorage.getSession(sessionId);
|
|
1113
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Deletes a session from session storage (e.g. on logout).
|
|
1268
|
+
*
|
|
1269
|
+
* @param sessionId - Active session identifier
|
|
1270
|
+
*/
|
|
1271
|
+
async deleteSession(sessionId) {
|
|
1272
|
+
const sessionStorage = this.sessionHandler.sessionStorage || this.localSessionHandler.sessionStorage;
|
|
1273
|
+
await sessionStorage.deleteSession(sessionId);
|
|
1274
|
+
}
|
|
1114
1275
|
async exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier) {
|
|
1115
1276
|
const body = {
|
|
1116
1277
|
client_id: providerConfig.clientId,
|
|
@@ -1141,8 +1302,10 @@ var Lixa = class _Lixa {
|
|
|
1141
1302
|
statusText: response.statusText,
|
|
1142
1303
|
error: errorBody
|
|
1143
1304
|
});
|
|
1144
|
-
throw new
|
|
1145
|
-
`Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}
|
|
1305
|
+
throw new TokenExchangeError(
|
|
1306
|
+
`Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`,
|
|
1307
|
+
response.status,
|
|
1308
|
+
{ statusText: response.statusText, errorBody }
|
|
1146
1309
|
);
|
|
1147
1310
|
}
|
|
1148
1311
|
this.log("INFO", "Token", "Token exchange response received successfully");
|
|
@@ -1157,12 +1320,204 @@ var Lixa = class _Lixa {
|
|
|
1157
1320
|
return void 0;
|
|
1158
1321
|
}
|
|
1159
1322
|
};
|
|
1323
|
+
|
|
1324
|
+
// src/utils/cookies.ts
|
|
1325
|
+
var DEFAULT_SESSION_COOKIE_NAME = "lixa_session";
|
|
1326
|
+
var DEFAULT_STATE_COOKIE_NAME = "lixa_oauth_state";
|
|
1327
|
+
var DEFAULT_SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;
|
|
1328
|
+
var DEFAULT_STATE_MAX_AGE_SECONDS = 5 * 60;
|
|
1329
|
+
function isProductionEnvironment() {
|
|
1330
|
+
return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
|
|
1331
|
+
}
|
|
1332
|
+
function serializeCookie(name, value, options) {
|
|
1333
|
+
const isProd = isProductionEnvironment();
|
|
1334
|
+
const path = options?.path ?? "/";
|
|
1335
|
+
const httpOnly = options?.httpOnly ?? true;
|
|
1336
|
+
const secure = options?.secure ?? isProd;
|
|
1337
|
+
const sameSite = options?.sameSite ?? "lax";
|
|
1338
|
+
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
1339
|
+
if (path) {
|
|
1340
|
+
parts.push(`Path=${path}`);
|
|
1341
|
+
}
|
|
1342
|
+
if (typeof options?.maxAge === "number") {
|
|
1343
|
+
parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
|
|
1344
|
+
const expires = new Date(Date.now() + options.maxAge * 1e3).toUTCString();
|
|
1345
|
+
parts.push(`Expires=${expires}`);
|
|
1346
|
+
}
|
|
1347
|
+
if (options?.domain) {
|
|
1348
|
+
parts.push(`Domain=${options.domain}`);
|
|
1349
|
+
}
|
|
1350
|
+
if (httpOnly) {
|
|
1351
|
+
parts.push("HttpOnly");
|
|
1352
|
+
}
|
|
1353
|
+
if (secure) {
|
|
1354
|
+
parts.push("Secure");
|
|
1355
|
+
}
|
|
1356
|
+
if (sameSite) {
|
|
1357
|
+
const capitalized = sameSite.charAt(0).toUpperCase() + sameSite.slice(1).toLowerCase();
|
|
1358
|
+
parts.push(`SameSite=${capitalized}`);
|
|
1359
|
+
}
|
|
1360
|
+
return parts.join("; ");
|
|
1361
|
+
}
|
|
1362
|
+
function createSessionCookie(sessionId, options) {
|
|
1363
|
+
const isProd = isProductionEnvironment();
|
|
1364
|
+
const resolvedOptions = {
|
|
1365
|
+
name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
|
|
1366
|
+
path: options?.path ?? "/",
|
|
1367
|
+
maxAge: options?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
1368
|
+
httpOnly: options?.httpOnly ?? true,
|
|
1369
|
+
secure: options?.secure ?? isProd,
|
|
1370
|
+
sameSite: options?.sameSite ?? "lax",
|
|
1371
|
+
domain: options?.domain
|
|
1372
|
+
};
|
|
1373
|
+
const name = resolvedOptions.name;
|
|
1374
|
+
const header = serializeCookie(name, sessionId, resolvedOptions);
|
|
1375
|
+
return {
|
|
1376
|
+
name,
|
|
1377
|
+
value: sessionId,
|
|
1378
|
+
options: resolvedOptions,
|
|
1379
|
+
header
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function clearSessionCookie(options) {
|
|
1383
|
+
const isProd = isProductionEnvironment();
|
|
1384
|
+
const resolvedOptions = {
|
|
1385
|
+
name: options?.name || DEFAULT_SESSION_COOKIE_NAME,
|
|
1386
|
+
path: options?.path ?? "/",
|
|
1387
|
+
maxAge: 0,
|
|
1388
|
+
httpOnly: options?.httpOnly ?? true,
|
|
1389
|
+
secure: options?.secure ?? isProd,
|
|
1390
|
+
sameSite: options?.sameSite ?? "lax",
|
|
1391
|
+
domain: options?.domain
|
|
1392
|
+
};
|
|
1393
|
+
const name = resolvedOptions.name;
|
|
1394
|
+
const header = serializeCookie(name, "", resolvedOptions);
|
|
1395
|
+
return {
|
|
1396
|
+
name,
|
|
1397
|
+
value: "",
|
|
1398
|
+
options: resolvedOptions,
|
|
1399
|
+
header
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
function createStateCookie(state, options) {
|
|
1403
|
+
const isProd = isProductionEnvironment();
|
|
1404
|
+
const resolvedOptions = {
|
|
1405
|
+
name: options?.name || DEFAULT_STATE_COOKIE_NAME,
|
|
1406
|
+
path: options?.path ?? "/",
|
|
1407
|
+
maxAge: options?.maxAge ?? DEFAULT_STATE_MAX_AGE_SECONDS,
|
|
1408
|
+
httpOnly: options?.httpOnly ?? true,
|
|
1409
|
+
secure: options?.secure ?? isProd,
|
|
1410
|
+
sameSite: options?.sameSite ?? "lax",
|
|
1411
|
+
domain: options?.domain
|
|
1412
|
+
};
|
|
1413
|
+
const name = resolvedOptions.name;
|
|
1414
|
+
const header = serializeCookie(name, state, resolvedOptions);
|
|
1415
|
+
return {
|
|
1416
|
+
name,
|
|
1417
|
+
value: state,
|
|
1418
|
+
options: resolvedOptions,
|
|
1419
|
+
header
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
function clearStateCookie(options) {
|
|
1423
|
+
const isProd = isProductionEnvironment();
|
|
1424
|
+
const resolvedOptions = {
|
|
1425
|
+
name: options?.name || DEFAULT_STATE_COOKIE_NAME,
|
|
1426
|
+
path: options?.path ?? "/",
|
|
1427
|
+
maxAge: 0,
|
|
1428
|
+
httpOnly: options?.httpOnly ?? true,
|
|
1429
|
+
secure: options?.secure ?? isProd,
|
|
1430
|
+
sameSite: options?.sameSite ?? "lax",
|
|
1431
|
+
domain: options?.domain
|
|
1432
|
+
};
|
|
1433
|
+
const name = resolvedOptions.name;
|
|
1434
|
+
const header = serializeCookie(name, "", resolvedOptions);
|
|
1435
|
+
return {
|
|
1436
|
+
name,
|
|
1437
|
+
value: "",
|
|
1438
|
+
options: resolvedOptions,
|
|
1439
|
+
header
|
|
1440
|
+
};
|
|
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
|
+
}
|
|
1160
1490
|
export {
|
|
1161
1491
|
AccountLinkingStrategy,
|
|
1492
|
+
AccountUnlinkError,
|
|
1493
|
+
DEFAULT_SESSION_COOKIE_NAME,
|
|
1494
|
+
DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
1495
|
+
DEFAULT_STATE_COOKIE_NAME,
|
|
1496
|
+
DEFAULT_STATE_MAX_AGE_SECONDS,
|
|
1497
|
+
EmailNotVerifiedError,
|
|
1498
|
+
InvalidOAuthCallbackError,
|
|
1499
|
+
InvalidProviderConfigError,
|
|
1500
|
+
InvalidStateError,
|
|
1162
1501
|
Lixa,
|
|
1502
|
+
LixaError,
|
|
1503
|
+
ProviderNotConfiguredError,
|
|
1504
|
+
RefreshTokenError,
|
|
1505
|
+
SessionNotFoundError,
|
|
1506
|
+
TokenExchangeError,
|
|
1507
|
+
clearSessionCookie,
|
|
1508
|
+
clearSessionCookieOnResponse,
|
|
1509
|
+
clearStateCookie,
|
|
1510
|
+
clearStateCookieOnResponse,
|
|
1511
|
+
createRequireAuthMiddleware,
|
|
1512
|
+
createSessionCookie,
|
|
1513
|
+
createStateCookie,
|
|
1163
1514
|
decodeIdToken,
|
|
1164
1515
|
determineProviderFromIssuer,
|
|
1165
1516
|
extractUserInfo,
|
|
1166
|
-
fetchUserInfo
|
|
1517
|
+
fetchUserInfo,
|
|
1518
|
+
isProductionEnvironment,
|
|
1519
|
+
serializeCookie,
|
|
1520
|
+
setSessionCookieOnResponse,
|
|
1521
|
+
setStateCookieOnResponse
|
|
1167
1522
|
};
|
|
1168
1523
|
//# sourceMappingURL=index.js.map
|