@vunexa/lixa 0.0.1-alpha.20 → 0.0.1-alpha.22
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 +562 -79
- package/dist/dao/session-cache.d.ts +2 -2
- package/dist/dao/session-cache.d.ts.map +1 -1
- package/dist/dao/state-cache.d.ts +3 -3
- package/dist/dao/state-cache.d.ts.map +1 -1
- package/dist/dao/types.d.ts +246 -5
- package/dist/dao/types.d.ts.map +1 -1
- package/dist/export-types/index.d.ts +852 -49
- package/dist/index.cjs +282 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +853 -46
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +282 -46
- package/dist/index.js.map +1 -1
- package/dist/lixa.d.ts +187 -18
- package/dist/lixa.d.ts.map +1 -1
- package/dist/models/session.d.ts +163 -7
- package/dist/models/session.d.ts.map +1 -1
- package/dist/providers/IProvider.d.ts +121 -4
- package/dist/providers/IProvider.d.ts.map +1 -1
- package/dist/providers/index.d.ts +0 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/types.d.ts +131 -15
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -16
- package/dist/IProvider-2tDwRAnH.d.cts +0 -18
- package/dist/IProvider-2tDwRAnH.d.ts +0 -18
- package/dist/export-types/providers.d.ts +0 -61
- package/dist/providers/github.d.ts +0 -13
- package/dist/providers/github.d.ts.map +0 -1
- package/dist/providers/google.d.ts +0 -13
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers-entry.cjs +0 -48
- package/dist/providers-entry.cjs.map +0 -1
- package/dist/providers-entry.d.cts +0 -25
- package/dist/providers-entry.d.ts +0 -22
- package/dist/providers-entry.d.ts.map +0 -1
- package/dist/providers-entry.js +0 -20
- package/dist/providers-entry.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,10 +4,18 @@
|
|
|
4
4
|
* @remarks
|
|
5
5
|
* This package simplifies multi-provider authentication flows (e.g., Google, GitHub), supports extensible session management, and enables custom provider registration.
|
|
6
6
|
*
|
|
7
|
+
* Key features:
|
|
8
|
+
* - OAuth 2.0 authorization code flow with PKCE (RFC 6749, RFC 7636)
|
|
9
|
+
* - OpenID Connect support
|
|
10
|
+
* - Built-in providers available in \@vunexa/lixa-providers
|
|
11
|
+
* - Custom provider support via IProvider interface
|
|
12
|
+
* - Extensible session management via SessionStrategy
|
|
13
|
+
* - Pluggable state and session storage via StateDao and SessionDao
|
|
14
|
+
* - TypeScript-first with comprehensive type safety
|
|
15
|
+
*
|
|
7
16
|
* @packageDocumentation
|
|
8
17
|
*/
|
|
9
18
|
export { Lixa } from "./lixa";
|
|
10
|
-
export { type ProviderConfig, type LixaConfig, type SafeLixaConfig, } from "./types";
|
|
11
|
-
export {
|
|
12
|
-
export { type IProvider } from "./providers";
|
|
19
|
+
export { type ProviderConfig, type LixaConfig, type SafeLixaConfig, type IProvider, type SessionStrategy, type Session, type StateDao, type SessionDao, type StateData, } from "./types";
|
|
20
|
+
export { DefaultSessionStrategy } from "./models/session";
|
|
13
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -50,12 +50,11 @@ var DefaultSessionStrategy = class {
|
|
|
50
50
|
* @returns A Promise that resolves to a Session object
|
|
51
51
|
*/
|
|
52
52
|
async createSession(tokenData) {
|
|
53
|
-
|
|
54
|
-
if (!accessToken || typeof accessToken !== "string") {
|
|
53
|
+
if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
|
|
55
54
|
throw new Error("No valid access token found in OAuth response");
|
|
56
55
|
}
|
|
57
56
|
return {
|
|
58
|
-
token:
|
|
57
|
+
token: tokenData.access_token,
|
|
59
58
|
raw: tokenData
|
|
60
59
|
};
|
|
61
60
|
}
|
|
@@ -63,7 +62,9 @@ var DefaultSessionStrategy = class {
|
|
|
63
62
|
|
|
64
63
|
// src/lixa.ts
|
|
65
64
|
var Lixa = class _Lixa {
|
|
65
|
+
static DEFAULT_PROVIDERS = /* @__PURE__ */ new Map();
|
|
66
66
|
static CONFIGURED_PROVIDERS = /* @__PURE__ */ new Map();
|
|
67
|
+
// Legacy registry for backward compatibility
|
|
67
68
|
static LOCAL_STATE_CACHE = new LocalStateCache();
|
|
68
69
|
static LOCAL_SESSION_CACHE = new LocalSessionCache();
|
|
69
70
|
static DEFAULT_SESSION_STRATEGY = new DefaultSessionStrategy();
|
|
@@ -74,39 +75,122 @@ var Lixa = class _Lixa {
|
|
|
74
75
|
debug;
|
|
75
76
|
/**
|
|
76
77
|
* Creates a new Lixa instance with the provided configuration.
|
|
77
|
-
*
|
|
78
|
+
*
|
|
79
|
+
* @remarks
|
|
80
|
+
* Providers can be passed inline in the configuration using the `provider` field.
|
|
81
|
+
* Provider resolution priority: inline custom provider \> default providers \> legacy registry.
|
|
82
|
+
*
|
|
78
83
|
* @param config - The configuration object containing provider settings and optional session strategy
|
|
84
|
+
*
|
|
85
|
+
* @throws Error when provider configuration is missing required fields
|
|
86
|
+
* @throws Error when provider implementation is missing required properties
|
|
87
|
+
* @throws Error when provider is not available and no inline implementation is provided
|
|
79
88
|
*/
|
|
80
89
|
constructor(config) {
|
|
81
|
-
const configuredProviders = Object.keys(config.providers);
|
|
82
|
-
const registeredProviders = _Lixa.getRegisteredProviders();
|
|
83
|
-
for (const provider of configuredProviders) {
|
|
84
|
-
if (!registeredProviders.includes(provider.toLowerCase())) {
|
|
85
|
-
throw new Error(`Provider '${provider}' is not registered. Please register it first using Lixa.registerProvider()`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
90
|
this.config = config;
|
|
89
91
|
this.stateDao = config.stateDao || _Lixa.LOCAL_STATE_CACHE;
|
|
90
92
|
this.sesionDao = config.sessionDao || _Lixa.LOCAL_SESSION_CACHE;
|
|
91
93
|
this.sessionStrategy = config.sessionStrategy || _Lixa.DEFAULT_SESSION_STRATEGY;
|
|
92
94
|
this.debug = config.debug || false;
|
|
95
|
+
this.log("INFO", "Init", "Initializing Lixa instance", {
|
|
96
|
+
providers: Object.keys(config.providers),
|
|
97
|
+
debug: this.debug
|
|
98
|
+
});
|
|
99
|
+
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
100
|
+
const name = providerName.toLowerCase();
|
|
101
|
+
const typedConfig = providerConfig;
|
|
102
|
+
this.validateProviderConfig(providerName, typedConfig);
|
|
103
|
+
if (typedConfig.provider) {
|
|
104
|
+
this.validateProviderImplementation(providerName, typedConfig.provider);
|
|
105
|
+
this.log("INFO", "Init", `Registered inline provider: ${providerName}`);
|
|
106
|
+
} else {
|
|
107
|
+
if (!_Lixa.DEFAULT_PROVIDERS.has(name) && !_Lixa.CONFIGURED_PROVIDERS.has(name)) {
|
|
108
|
+
this.log("ERROR", "Init", `Provider '${providerName}' not available`);
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Provider '${providerName}' 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: '...', ... }`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
this.log("INFO", "Init", `Using registered provider: ${providerName}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
this.log("INFO", "Init", "Lixa instance initialized successfully");
|
|
93
117
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Validates that a provider configuration has all required credentials.
|
|
120
|
+
*
|
|
121
|
+
* @param name - The provider name
|
|
122
|
+
* @param config - The provider configuration
|
|
123
|
+
* @throws Error when required fields are missing or invalid
|
|
124
|
+
*/
|
|
125
|
+
validateProviderConfig(name, config) {
|
|
126
|
+
const requiredFields = ["clientId", "clientSecret", "redirectUri", "scopes"];
|
|
127
|
+
const missingFields = requiredFields.filter((field) => {
|
|
128
|
+
const value = config[field];
|
|
129
|
+
return value === void 0 || value === null || typeof value === "string" && value.trim() === "";
|
|
130
|
+
});
|
|
131
|
+
if (missingFields.length > 0) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Provider '${name}' configuration is missing required fields: ${missingFields.join(", ")}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (!Array.isArray(config.scopes)) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Provider '${name}' configuration error: 'scopes' must be an array of strings`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (config.scopes.length === 0) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Provider '${name}' configuration error: 'scopes' array cannot be empty`
|
|
144
|
+
);
|
|
97
145
|
}
|
|
98
146
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Validates that a provider implementation has all required properties.
|
|
149
|
+
*
|
|
150
|
+
* @param name - The provider name
|
|
151
|
+
* @param provider - The provider implementation
|
|
152
|
+
* @throws Error when required properties are missing
|
|
153
|
+
*/
|
|
154
|
+
validateProviderImplementation(name, provider) {
|
|
155
|
+
const requiredProps = ["authorizationEndpoint", "tokenEndpoint", "userInfoEndpoint"];
|
|
156
|
+
const missingProps = requiredProps.filter((prop) => {
|
|
157
|
+
const value = provider[prop];
|
|
158
|
+
return !value || typeof value !== "string" || value.trim() === "";
|
|
159
|
+
});
|
|
160
|
+
if (missingProps.length > 0) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Provider '${name}' implementation is missing required properties: ${missingProps.join(", ")}. All IProvider implementations must define: authorizationEndpoint, tokenEndpoint, and userInfoEndpoint.`
|
|
163
|
+
);
|
|
102
164
|
}
|
|
103
165
|
}
|
|
104
166
|
/**
|
|
105
|
-
*
|
|
167
|
+
* Structured debug logging with standardized format.
|
|
168
|
+
*
|
|
169
|
+
* @param level - Log level (INFO, WARN, ERROR)
|
|
170
|
+
* @param context - Context of the log (Init, Auth, Token, Session, State)
|
|
171
|
+
* @param message - Log message
|
|
172
|
+
* @param data - Optional data to log
|
|
173
|
+
*
|
|
174
|
+
* @remarks
|
|
175
|
+
* Format: [Lixa] [timestamp] [level] [context] message
|
|
176
|
+
* Only logs when debug mode is enabled.
|
|
177
|
+
*/
|
|
178
|
+
log(level, context, message, data) {
|
|
179
|
+
if (!this.debug) return;
|
|
180
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
181
|
+
const prefix = `[Lixa] [${timestamp}] [${level}] [${context}]`;
|
|
182
|
+
if (data !== void 0) {
|
|
183
|
+
console.log(`${prefix} ${message}`, data);
|
|
184
|
+
} else {
|
|
185
|
+
console.log(`${prefix} ${message}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Checks if a provider is configured for this instance.
|
|
106
190
|
* This is a type guard that narrows the provider type for use with getAuthUrl.
|
|
107
191
|
*
|
|
108
192
|
* @param provider - The provider name to check (case-insensitive)
|
|
109
|
-
* @returns True if the provider is
|
|
193
|
+
* @returns True if the provider is configured, false otherwise
|
|
110
194
|
*
|
|
111
195
|
* @example
|
|
112
196
|
* ```typescript
|
|
@@ -118,14 +202,55 @@ var Lixa = class _Lixa {
|
|
|
118
202
|
*/
|
|
119
203
|
isProviderConfigured(provider) {
|
|
120
204
|
const providerType = provider.toLowerCase();
|
|
121
|
-
return
|
|
205
|
+
return this.config.providers.hasOwnProperty(providerType);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Gets a provider implementation by name.
|
|
209
|
+
* Resolution priority: inline custom provider \> default providers \> legacy registry
|
|
210
|
+
*
|
|
211
|
+
* @param name - The provider name (case-insensitive)
|
|
212
|
+
* @param config - The provider configuration
|
|
213
|
+
* @returns The provider implementation
|
|
214
|
+
* @throws Error when provider is not found
|
|
215
|
+
*/
|
|
216
|
+
getProvider(name, config) {
|
|
217
|
+
if (config.provider) {
|
|
218
|
+
return config.provider;
|
|
219
|
+
}
|
|
220
|
+
const lowerName = name.toLowerCase();
|
|
221
|
+
const defaultProvider = _Lixa.DEFAULT_PROVIDERS.get(lowerName);
|
|
222
|
+
if (defaultProvider) {
|
|
223
|
+
return defaultProvider;
|
|
224
|
+
}
|
|
225
|
+
const legacyProvider = _Lixa.CONFIGURED_PROVIDERS.get(lowerName);
|
|
226
|
+
if (legacyProvider) {
|
|
227
|
+
return legacyProvider;
|
|
228
|
+
}
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Provider '${name}' not found. Ensure the provider is included in the configuration with a 'provider' field, or registered using Lixa.registerProvider().`
|
|
231
|
+
);
|
|
122
232
|
}
|
|
123
233
|
/**
|
|
124
234
|
* Registers custom OAuth providers for use with Lixa.
|
|
235
|
+
*
|
|
236
|
+
* @deprecated This method is maintained for backward compatibility.
|
|
237
|
+
* The recommended approach is to pass providers inline in the configuration:
|
|
238
|
+
* ```typescript
|
|
239
|
+
* const lixa = new Lixa({
|
|
240
|
+
* providers: {
|
|
241
|
+
* custom: {
|
|
242
|
+
* provider: new CustomProvider(),
|
|
243
|
+
* clientId: '...',
|
|
244
|
+
* // ...
|
|
245
|
+
* }
|
|
246
|
+
* }
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
125
249
|
*
|
|
126
250
|
* @param providerMap - A map of provider names to IProvider implementations
|
|
127
251
|
*
|
|
128
252
|
* @example
|
|
253
|
+
* Legacy usage (still supported):
|
|
129
254
|
* ```typescript
|
|
130
255
|
* class CustomProvider implements IProvider {
|
|
131
256
|
* authorizationEndpoint = 'https://custom.com/oauth/authorize';
|
|
@@ -150,19 +275,29 @@ var Lixa = class _Lixa {
|
|
|
150
275
|
return Array.from(_Lixa.CONFIGURED_PROVIDERS.keys());
|
|
151
276
|
}
|
|
152
277
|
/**
|
|
153
|
-
* Creates a type-safe configuration
|
|
278
|
+
* Creates a type-safe configuration.
|
|
279
|
+
*
|
|
280
|
+
* @deprecated This method is maintained for backward compatibility.
|
|
281
|
+
* You can now pass configuration directly to the Lixa constructor without this helper.
|
|
282
|
+
*
|
|
283
|
+
* @param config - Configuration object with provider settings
|
|
284
|
+
* @returns The same configuration object with type safety
|
|
154
285
|
*
|
|
155
|
-
* @
|
|
156
|
-
*
|
|
286
|
+
* @example
|
|
287
|
+
* New approach (recommended):
|
|
288
|
+
* ```typescript
|
|
289
|
+
* const lixa = new Lixa({
|
|
290
|
+
* providers: {
|
|
291
|
+
* google: {
|
|
292
|
+
* provider: new GoogleProvider(),
|
|
293
|
+
* clientId: '...',
|
|
294
|
+
* // ...
|
|
295
|
+
* }
|
|
296
|
+
* }
|
|
297
|
+
* });
|
|
298
|
+
* ```
|
|
157
299
|
*/
|
|
158
300
|
static createConfig(config) {
|
|
159
|
-
const configuredProviders = Object.keys(config.providers);
|
|
160
|
-
const registeredProviders = _Lixa.getRegisteredProviders();
|
|
161
|
-
for (const provider of configuredProviders) {
|
|
162
|
-
if (!registeredProviders.includes(provider.toLowerCase())) {
|
|
163
|
-
throw new Error(`Provider '${provider}' is not registered. Please register it first using Lixa.registerProvider()`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
301
|
return config;
|
|
167
302
|
}
|
|
168
303
|
/**
|
|
@@ -179,14 +314,83 @@ var Lixa = class _Lixa {
|
|
|
179
314
|
/**
|
|
180
315
|
* Generates a cryptographically secure code verifier for PKCE flows.
|
|
181
316
|
*
|
|
182
|
-
* @returns A 64-character hexadecimal string
|
|
317
|
+
* @returns A 64-character hexadecimal string (32 random bytes encoded as hex)
|
|
183
318
|
*
|
|
184
319
|
* @remarks
|
|
185
|
-
*
|
|
320
|
+
* This method implements the code verifier generation as specified in RFC 7636 (PKCE).
|
|
321
|
+
*
|
|
322
|
+
* **PKCE (Proof Key for Code Exchange)** is a security extension to OAuth 2.0 that
|
|
323
|
+
* prevents authorization code interception attacks. It's especially important for
|
|
324
|
+
* public clients (mobile apps, SPAs) but is recommended for all OAuth flows.
|
|
325
|
+
*
|
|
326
|
+
* **Generation methodology:**
|
|
327
|
+
* 1. Generate 32 cryptographically random bytes using Node.js crypto.randomBytes()
|
|
328
|
+
* 2. Encode the bytes as a hexadecimal string (64 characters)
|
|
329
|
+
* 3. The verifier is stored securely and used later in the token exchange
|
|
330
|
+
*
|
|
331
|
+
* **RFC 7636 Requirements:**
|
|
332
|
+
* - Minimum length: 43 characters
|
|
333
|
+
* - Maximum length: 128 characters
|
|
334
|
+
* - Character set: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
|
|
335
|
+
* - This implementation produces 64 hex characters, meeting the requirements
|
|
336
|
+
*
|
|
337
|
+
* The code verifier is:
|
|
338
|
+
* - Generated when creating the authorization URL
|
|
339
|
+
* - Stored in state cache with the state parameter
|
|
340
|
+
* - Retrieved during callback handling
|
|
341
|
+
* - Sent to the token endpoint to prove the client's identity
|
|
342
|
+
*
|
|
343
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}
|
|
344
|
+
* @see buildCodeChallenge for the corresponding challenge generation
|
|
345
|
+
*
|
|
346
|
+
* @internal
|
|
186
347
|
*/
|
|
187
348
|
static generateCodeVerifier() {
|
|
188
349
|
return randomBytes(32).toString("hex");
|
|
189
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Generates a code challenge from a code verifier for PKCE flows.
|
|
353
|
+
*
|
|
354
|
+
* @param codeVerifier - The code verifier string (64 hex characters)
|
|
355
|
+
* @returns A base64url-encoded SHA-256 hash of the code verifier
|
|
356
|
+
*
|
|
357
|
+
* @remarks
|
|
358
|
+
* This method implements the code challenge generation as specified in RFC 7636 (PKCE)
|
|
359
|
+
* using the S256 (SHA-256) transformation method.
|
|
360
|
+
*
|
|
361
|
+
* **Challenge generation methodology:**
|
|
362
|
+
* 1. Hash the code verifier using SHA-256
|
|
363
|
+
* 2. Encode the hash as base64
|
|
364
|
+
* 3. Convert to base64url format (RFC 4648):
|
|
365
|
+
* - Replace '+' with '-'
|
|
366
|
+
* - Replace '/' with '_'
|
|
367
|
+
* - Remove trailing '=' padding
|
|
368
|
+
*
|
|
369
|
+
* **PKCE Flow:**
|
|
370
|
+
* 1. Client generates code_verifier (random string)
|
|
371
|
+
* 2. Client creates code_challenge = BASE64URL(SHA256(code_verifier))
|
|
372
|
+
* 3. Client sends code_challenge to authorization endpoint
|
|
373
|
+
* 4. Authorization server stores the code_challenge
|
|
374
|
+
* 5. Client sends code_verifier to token endpoint
|
|
375
|
+
* 6. Authorization server verifies: SHA256(code_verifier) == code_challenge
|
|
376
|
+
*
|
|
377
|
+
* **Security Benefits:**
|
|
378
|
+
* - Prevents authorization code interception attacks
|
|
379
|
+
* - Even if an attacker intercepts the authorization code, they cannot
|
|
380
|
+
* exchange it for tokens without the original code_verifier
|
|
381
|
+
* - The challenge is sent in the authorization request (public)
|
|
382
|
+
* - The verifier is sent in the token request (should be kept secret)
|
|
383
|
+
*
|
|
384
|
+
* **RFC 7636 Transformation Methods:**
|
|
385
|
+
* - plain: code_challenge = code_verifier (not recommended)
|
|
386
|
+
* - S256: code_challenge = BASE64URL(SHA256(code_verifier)) (recommended, used here)
|
|
387
|
+
*
|
|
388
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7636 | RFC 7636 - PKCE}
|
|
389
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc4648#section-5 | RFC 4648 - Base64url Encoding}
|
|
390
|
+
* @see generateCodeVerifier for the verifier generation
|
|
391
|
+
*
|
|
392
|
+
* @internal
|
|
393
|
+
*/
|
|
190
394
|
static buildCodeChallenge(codeVerifier) {
|
|
191
395
|
const hash = crypto.createHash("sha256").update(codeVerifier).digest("base64");
|
|
192
396
|
return hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -209,13 +413,16 @@ var Lixa = class _Lixa {
|
|
|
209
413
|
*/
|
|
210
414
|
getAuthUrl(provider, state) {
|
|
211
415
|
const providerType = String(provider).toLowerCase();
|
|
416
|
+
this.log("INFO", "Auth", `Generating authorization URL for provider: ${providerType}`);
|
|
212
417
|
const providerConfig = this.findProviderByType(providerType);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
throw new Error(`Provider ${
|
|
418
|
+
if (!providerConfig) {
|
|
419
|
+
this.log("ERROR", "Auth", `Provider '${String(provider)}' is not configured`);
|
|
420
|
+
throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);
|
|
216
421
|
}
|
|
422
|
+
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
217
423
|
const codeVerifier = _Lixa.generateCodeVerifier();
|
|
218
424
|
const codeChallenge = _Lixa.buildCodeChallenge(codeVerifier);
|
|
425
|
+
this.log("INFO", "State", `Saving state for provider: ${providerType}`, { state });
|
|
219
426
|
this.stateDao.saveState(
|
|
220
427
|
state,
|
|
221
428
|
{
|
|
@@ -236,7 +443,12 @@ var Lixa = class _Lixa {
|
|
|
236
443
|
code_challenge_method: "S256",
|
|
237
444
|
...providerConfig.extraConfig
|
|
238
445
|
});
|
|
239
|
-
|
|
446
|
+
const authUrl = `${providerImpl.authorizationEndpoint}?${params.toString()}`;
|
|
447
|
+
this.log("INFO", "Auth", `Authorization URL generated successfully`, {
|
|
448
|
+
provider: providerType,
|
|
449
|
+
endpoint: providerImpl.authorizationEndpoint
|
|
450
|
+
});
|
|
451
|
+
return authUrl;
|
|
240
452
|
}
|
|
241
453
|
/**
|
|
242
454
|
* Handles the OAuth callback and creates a user session.
|
|
@@ -262,37 +474,50 @@ var Lixa = class _Lixa {
|
|
|
262
474
|
code,
|
|
263
475
|
state
|
|
264
476
|
}) {
|
|
477
|
+
const providerType = String(provider).toLowerCase();
|
|
478
|
+
this.log("INFO", "Auth", `Handling OAuth callback for provider: ${providerType}`);
|
|
265
479
|
if (!code || code.trim() === "") {
|
|
480
|
+
this.log("ERROR", "Auth", "Invalid or missing authorization code in callback");
|
|
266
481
|
throw new Error("Invalid or missing code in callback");
|
|
267
482
|
}
|
|
268
483
|
if (!state || state.trim() === "") {
|
|
484
|
+
this.log("ERROR", "Auth", "Invalid or missing state in callback");
|
|
269
485
|
throw new Error("Invalid or missing state in callback");
|
|
270
486
|
}
|
|
487
|
+
this.log("INFO", "State", "Validating state parameter", { state });
|
|
271
488
|
const cachedState = await this.stateDao.getState(state);
|
|
272
489
|
if (!cachedState) {
|
|
490
|
+
this.log("ERROR", "State", "State validation failed: state not found or expired", { state });
|
|
273
491
|
throw new Error("Invalid or expired state");
|
|
274
492
|
}
|
|
493
|
+
this.log("INFO", "State", "State validated successfully, removing from cache");
|
|
275
494
|
await this.stateDao.deleteState(state);
|
|
276
495
|
const codeVerifier = cachedState.codeVerifier;
|
|
277
|
-
const providerType = String(provider).toLowerCase();
|
|
278
496
|
const providerConfig = this.findProviderByType(providerType);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
throw new Error(`Provider ${String(provider)} not configured`);
|
|
497
|
+
if (!providerConfig) {
|
|
498
|
+
this.log("ERROR", "Auth", `Provider '${String(provider)}' is not configured`);
|
|
499
|
+
throw new Error(`Provider '${String(provider)}' is not configured in this Lixa instance`);
|
|
282
500
|
}
|
|
501
|
+
const providerImpl = this.getProvider(providerType, providerConfig);
|
|
502
|
+
this.log("INFO", "Token", `Exchanging authorization code for tokens`, { provider: providerType });
|
|
283
503
|
const tokens = await this.exchangeCodeForToken(
|
|
284
504
|
code,
|
|
285
505
|
providerConfig,
|
|
286
506
|
providerImpl,
|
|
287
507
|
codeVerifier
|
|
288
508
|
);
|
|
509
|
+
this.log("INFO", "Token", "Token exchange successful");
|
|
510
|
+
this.log("INFO", "Session", "Creating user session");
|
|
289
511
|
const session = await this.sessionStrategy.createSession(tokens);
|
|
290
512
|
const sessionId = randomBytes(32).toString("hex");
|
|
513
|
+
this.log("INFO", "Session", "Storing session", { sessionId });
|
|
291
514
|
await this.sesionDao.saveSession(sessionId, session, 86400);
|
|
515
|
+
this.log("INFO", "Session", "Session created successfully", { sessionId });
|
|
292
516
|
return sessionId;
|
|
293
517
|
}
|
|
294
|
-
fetchSessionInfo(sessionId) {
|
|
295
|
-
|
|
518
|
+
async fetchSessionInfo(sessionId) {
|
|
519
|
+
const sessionData = await this.sesionDao.getSession(sessionId);
|
|
520
|
+
return sessionData;
|
|
296
521
|
}
|
|
297
522
|
async exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier) {
|
|
298
523
|
const body = {
|
|
@@ -306,8 +531,9 @@ var Lixa = class _Lixa {
|
|
|
306
531
|
body.code_verifier = codeVerifier;
|
|
307
532
|
}
|
|
308
533
|
const params = new URLSearchParams(body);
|
|
309
|
-
this.log("Token
|
|
310
|
-
|
|
534
|
+
this.log("INFO", "Token", "Sending token exchange request", {
|
|
535
|
+
endpoint: providerImpl.tokenEndpoint
|
|
536
|
+
});
|
|
311
537
|
const response = await fetch(providerImpl.tokenEndpoint, {
|
|
312
538
|
method: "POST",
|
|
313
539
|
headers: {
|
|
@@ -318,15 +544,25 @@ var Lixa = class _Lixa {
|
|
|
318
544
|
});
|
|
319
545
|
if (!response.ok) {
|
|
320
546
|
const errorBody = await response.text();
|
|
321
|
-
this.
|
|
547
|
+
this.log("ERROR", "Token", "Token exchange failed", {
|
|
548
|
+
status: response.status,
|
|
549
|
+
statusText: response.statusText,
|
|
550
|
+
error: errorBody
|
|
551
|
+
});
|
|
322
552
|
throw new Error(
|
|
323
553
|
`Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`
|
|
324
554
|
);
|
|
325
555
|
}
|
|
556
|
+
this.log("INFO", "Token", "Token exchange response received successfully");
|
|
326
557
|
return response.json();
|
|
327
558
|
}
|
|
328
559
|
findProviderByType(providerType) {
|
|
329
|
-
|
|
560
|
+
for (const [key, value] of Object.entries(this.config.providers)) {
|
|
561
|
+
if (key.toLowerCase() === providerType.toLowerCase()) {
|
|
562
|
+
return value;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return void 0;
|
|
330
566
|
}
|
|
331
567
|
};
|
|
332
568
|
export {
|