shogun-core 3.0.3 → 3.0.4

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.
Files changed (95) hide show
  1. package/package.json +1 -1
  2. package/dist/browser/shogun-core.js +0 -92128
  3. package/dist/browser/shogun-core.js.map +0 -1
  4. package/dist/config/simplified-config.js +0 -230
  5. package/dist/core.js +0 -338
  6. package/dist/gundb/crypto.js +0 -268
  7. package/dist/gundb/db.js +0 -1833
  8. package/dist/gundb/derive.js +0 -229
  9. package/dist/gundb/errors.js +0 -66
  10. package/dist/gundb/index.js +0 -6
  11. package/dist/gundb/restricted-put.js +0 -81
  12. package/dist/gundb/rxjs.js +0 -445
  13. package/dist/gundb/simple-api.js +0 -438
  14. package/dist/gundb/types.js +0 -4
  15. package/dist/index.js +0 -16
  16. package/dist/interfaces/common.js +0 -1
  17. package/dist/interfaces/events.js +0 -36
  18. package/dist/interfaces/plugin.js +0 -1
  19. package/dist/interfaces/shogun.js +0 -34
  20. package/dist/managers/AuthManager.js +0 -225
  21. package/dist/managers/CoreInitializer.js +0 -234
  22. package/dist/managers/EventManager.js +0 -67
  23. package/dist/managers/PluginManager.js +0 -296
  24. package/dist/migration-test.js +0 -91
  25. package/dist/plugins/base.js +0 -47
  26. package/dist/plugins/index.js +0 -15
  27. package/dist/plugins/nostr/index.js +0 -4
  28. package/dist/plugins/nostr/nostrConnector.js +0 -413
  29. package/dist/plugins/nostr/nostrConnectorPlugin.js +0 -446
  30. package/dist/plugins/nostr/nostrSigner.js +0 -313
  31. package/dist/plugins/nostr/types.js +0 -1
  32. package/dist/plugins/oauth/index.js +0 -3
  33. package/dist/plugins/oauth/oauthConnector.js +0 -753
  34. package/dist/plugins/oauth/oauthPlugin.js +0 -396
  35. package/dist/plugins/oauth/types.js +0 -1
  36. package/dist/plugins/web3/index.js +0 -4
  37. package/dist/plugins/web3/types.js +0 -1
  38. package/dist/plugins/web3/web3Connector.js +0 -528
  39. package/dist/plugins/web3/web3ConnectorPlugin.js +0 -448
  40. package/dist/plugins/web3/web3Signer.js +0 -308
  41. package/dist/plugins/webauthn/index.js +0 -3
  42. package/dist/plugins/webauthn/types.js +0 -11
  43. package/dist/plugins/webauthn/webauthn.js +0 -478
  44. package/dist/plugins/webauthn/webauthnPlugin.js +0 -398
  45. package/dist/plugins/webauthn/webauthnSigner.js +0 -304
  46. package/dist/storage/storage.js +0 -147
  47. package/dist/types/config/simplified-config.d.ts +0 -114
  48. package/dist/types/core.d.ts +0 -305
  49. package/dist/types/gundb/crypto.d.ts +0 -95
  50. package/dist/types/gundb/db.d.ts +0 -404
  51. package/dist/types/gundb/derive.d.ts +0 -21
  52. package/dist/types/gundb/errors.d.ts +0 -42
  53. package/dist/types/gundb/index.d.ts +0 -3
  54. package/dist/types/gundb/restricted-put.d.ts +0 -15
  55. package/dist/types/gundb/rxjs.d.ts +0 -110
  56. package/dist/types/gundb/simple-api.d.ts +0 -90
  57. package/dist/types/gundb/types.d.ts +0 -264
  58. package/dist/types/index.d.ts +0 -14
  59. package/dist/types/interfaces/common.d.ts +0 -85
  60. package/dist/types/interfaces/events.d.ts +0 -131
  61. package/dist/types/interfaces/plugin.d.ts +0 -162
  62. package/dist/types/interfaces/shogun.d.ts +0 -215
  63. package/dist/types/managers/AuthManager.d.ts +0 -72
  64. package/dist/types/managers/CoreInitializer.d.ts +0 -40
  65. package/dist/types/managers/EventManager.d.ts +0 -49
  66. package/dist/types/managers/PluginManager.d.ts +0 -145
  67. package/dist/types/migration-test.d.ts +0 -16
  68. package/dist/types/plugins/base.d.ts +0 -35
  69. package/dist/types/plugins/index.d.ts +0 -14
  70. package/dist/types/plugins/nostr/index.d.ts +0 -4
  71. package/dist/types/plugins/nostr/nostrConnector.d.ts +0 -119
  72. package/dist/types/plugins/nostr/nostrConnectorPlugin.d.ts +0 -163
  73. package/dist/types/plugins/nostr/nostrSigner.d.ts +0 -105
  74. package/dist/types/plugins/nostr/types.d.ts +0 -122
  75. package/dist/types/plugins/oauth/index.d.ts +0 -3
  76. package/dist/types/plugins/oauth/oauthConnector.d.ts +0 -110
  77. package/dist/types/plugins/oauth/oauthPlugin.d.ts +0 -91
  78. package/dist/types/plugins/oauth/types.d.ts +0 -114
  79. package/dist/types/plugins/web3/index.d.ts +0 -4
  80. package/dist/types/plugins/web3/types.d.ts +0 -107
  81. package/dist/types/plugins/web3/web3Connector.d.ts +0 -129
  82. package/dist/types/plugins/web3/web3ConnectorPlugin.d.ts +0 -160
  83. package/dist/types/plugins/web3/web3Signer.d.ts +0 -114
  84. package/dist/types/plugins/webauthn/index.d.ts +0 -3
  85. package/dist/types/plugins/webauthn/types.d.ts +0 -162
  86. package/dist/types/plugins/webauthn/webauthn.d.ts +0 -129
  87. package/dist/types/plugins/webauthn/webauthnPlugin.d.ts +0 -158
  88. package/dist/types/plugins/webauthn/webauthnSigner.d.ts +0 -91
  89. package/dist/types/storage/storage.d.ts +0 -50
  90. package/dist/types/utils/errorHandler.d.ts +0 -119
  91. package/dist/types/utils/eventEmitter.d.ts +0 -39
  92. package/dist/types/utils/validation.d.ts +0 -27
  93. package/dist/utils/errorHandler.js +0 -241
  94. package/dist/utils/eventEmitter.js +0 -76
  95. package/dist/utils/validation.js +0 -72
@@ -1,753 +0,0 @@
1
- /**
2
- * OAuth Connector - Secure version for GunDB user creation
3
- */
4
- import { EventEmitter } from "../../utils/eventEmitter";
5
- import derive from "../../gundb/derive";
6
- import { generateUsernameFromIdentity, generateDeterministicPassword, } from "../../utils/validation";
7
- import { ethers } from "ethers";
8
- /**
9
- * OAuth Connector
10
- */
11
- export class OAuthConnector extends EventEmitter {
12
- DEFAULT_CONFIG = {
13
- providers: {
14
- google: {
15
- clientId: "",
16
- redirectUri: `${this.getOrigin()}/auth/callback`,
17
- scope: ["openid", "email", "profile"],
18
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
19
- tokenUrl: "https://oauth2.googleapis.com/token",
20
- userInfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
21
- usePKCE: true, // Forza PKCE per Google
22
- },
23
- github: {
24
- clientId: "",
25
- redirectUri: `${this.getOrigin()}/auth/callback`,
26
- scope: ["user:email"],
27
- authUrl: "https://github.com/login/oauth/authorize",
28
- tokenUrl: "https://github.com/login/oauth/access_token",
29
- userInfoUrl: "https://api.github.com/user",
30
- usePKCE: true,
31
- },
32
- discord: {
33
- clientId: "",
34
- redirectUri: `${this.getOrigin()}/auth/callback`,
35
- scope: ["identify", "email"],
36
- authUrl: "https://discord.com/api/oauth2/authorize",
37
- tokenUrl: "https://discord.com/api/oauth2/token",
38
- userInfoUrl: "https://discord.com/api/users/@me",
39
- usePKCE: true,
40
- },
41
- twitter: {
42
- clientId: "",
43
- redirectUri: `${this.getOrigin()}/auth/callback`,
44
- scope: ["tweet.read", "users.read"],
45
- authUrl: "https://twitter.com/i/oauth2/authorize",
46
- tokenUrl: "https://api.twitter.com/2/oauth2/token",
47
- userInfoUrl: "https://api.twitter.com/2/users/me",
48
- usePKCE: true,
49
- },
50
- custom: {
51
- clientId: "",
52
- redirectUri: "",
53
- scope: [],
54
- authUrl: "",
55
- tokenUrl: "",
56
- userInfoUrl: "",
57
- usePKCE: true,
58
- },
59
- },
60
- usePKCE: true, // PKCE abilitato di default per sicurezza
61
- cacheDuration: 24 * 60 * 60 * 1000, // 24 hours
62
- timeout: 60000,
63
- maxRetries: 3,
64
- retryDelay: 1000,
65
- allowUnsafeClientSecret: false, // Disabilitato per sicurezza
66
- stateTimeout: 10 * 60 * 1000, // 10 minuti per il timeout dello state
67
- };
68
- config;
69
- userCache = new Map();
70
- // Fallback storage for Node.js environment
71
- memoryStorage = new Map();
72
- constructor(config = {}) {
73
- super();
74
- this.config = {
75
- ...this.DEFAULT_CONFIG,
76
- ...config,
77
- providers: {
78
- ...(this.DEFAULT_CONFIG.providers || {}),
79
- ...(config.providers || {}),
80
- },
81
- };
82
- // Validazione di sicurezza post-costruzione
83
- this.validateSecurityConfig();
84
- }
85
- /**
86
- * Validates security configuration
87
- */
88
- validateSecurityConfig() {
89
- const providers = this.config.providers || {};
90
- for (const [providerName, providerConfig] of Object.entries(providers)) {
91
- if (!providerConfig)
92
- continue;
93
- // Verify that PKCE is enabled for all providers in browser
94
- if (typeof window !== "undefined" && !providerConfig.usePKCE) {
95
- console.warn(`Provider ${providerName} does not have PKCE enabled - not secure for browser`);
96
- // Force PKCE for all providers in browser, except if already configured differently
97
- providerConfig.usePKCE = true;
98
- }
99
- // Verify that there is no client_secret in browser (except Google with PKCE)
100
- if (typeof window !== "undefined" && providerConfig.clientSecret) {
101
- if (providerName === "google" && providerConfig.usePKCE) {
102
- console.log(`Provider ${providerName} has client_secret configured - OK for Google with PKCE`);
103
- }
104
- else {
105
- console.error(`Provider ${providerName} has client_secret configured in browser - REMOVE IMMEDIATELY`);
106
- // Remove client_secret for security in browser
107
- delete providerConfig.clientSecret;
108
- console.log(`Provider ${providerName} client_secret removed for security in browser`);
109
- }
110
- }
111
- }
112
- }
113
- /**
114
- * Update the connector configuration
115
- * @param config - New configuration options
116
- */
117
- updateConfig(config) {
118
- this.config = {
119
- ...this.config,
120
- ...config,
121
- providers: {
122
- ...(this.config.providers || {}),
123
- ...(config.providers || {}),
124
- },
125
- };
126
- console.log("OAuthConnector configuration updated", this.config);
127
- }
128
- /**
129
- * Get origin URL (browser or Node.js compatible)
130
- */
131
- getOrigin() {
132
- if (typeof window !== "undefined" && window.location) {
133
- return window.location.origin;
134
- }
135
- // Fallback for Node.js environment
136
- return "http://localhost:3000";
137
- }
138
- /**
139
- * Storage abstraction (browser sessionStorage or Node.js Map)
140
- */
141
- setItem(key, value) {
142
- if (typeof window !== "undefined" &&
143
- typeof sessionStorage !== "undefined") {
144
- sessionStorage.setItem(key, value);
145
- }
146
- else {
147
- this.memoryStorage.set(key, value);
148
- }
149
- }
150
- getItem(key) {
151
- if (typeof window !== "undefined" &&
152
- typeof sessionStorage !== "undefined") {
153
- return sessionStorage.getItem(key);
154
- }
155
- else {
156
- return this.memoryStorage.get(key) || null;
157
- }
158
- }
159
- removeItem(key) {
160
- if (typeof window !== "undefined" &&
161
- typeof sessionStorage !== "undefined") {
162
- sessionStorage.removeItem(key);
163
- }
164
- else {
165
- this.memoryStorage.delete(key);
166
- }
167
- }
168
- /**
169
- * Check if OAuth is supported
170
- */
171
- isSupported() {
172
- // In Node.js, we can still demonstrate the functionality
173
- return typeof URLSearchParams !== "undefined";
174
- }
175
- /**
176
- * Get available OAuth providers
177
- */
178
- getAvailableProviders() {
179
- return Object.keys(this.config.providers || {}).filter((provider) => this.config.providers[provider]?.clientId);
180
- }
181
- /**
182
- * Generate PKCE challenge for secure OAuth flow
183
- */
184
- async generatePKCEChallenge() {
185
- const codeVerifier = this.generateRandomString(128);
186
- const codeChallenge = await this.calculatePKCECodeChallenge(codeVerifier);
187
- return { codeVerifier, codeChallenge };
188
- }
189
- /**
190
- * Calculate the PKCE code challenge from a code verifier.
191
- * Hashes the verifier using SHA-256 and then base64url encodes it.
192
- * @param verifier The code verifier string.
193
- * @returns The base64url-encoded SHA-256 hash of the verifier.
194
- */
195
- async calculatePKCECodeChallenge(verifier) {
196
- if (typeof window !== "undefined" &&
197
- window.crypto &&
198
- window.crypto.subtle) {
199
- // Browser environment
200
- const encoder = new TextEncoder();
201
- const data = encoder.encode(verifier);
202
- const hashBuffer = await window.crypto.subtle.digest("SHA-256", data);
203
- return this.base64urlEncode(hashBuffer);
204
- }
205
- else {
206
- // Node.js environment
207
- const crypto = require("crypto");
208
- const hash = crypto.createHash("sha256").update(verifier).digest();
209
- return this.base64urlEncode(hash);
210
- }
211
- }
212
- /**
213
- * Encodes a buffer into a Base64URL-encoded string.
214
- * @param buffer The buffer to encode.
215
- * @returns The Base64URL-encoded string.
216
- */
217
- base64urlEncode(buffer) {
218
- let base64string;
219
- // In Node.js, we can use the Buffer object. In the browser, we need a different approach.
220
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(buffer)) {
221
- // Node.js path
222
- base64string = buffer.toString("base64");
223
- }
224
- else {
225
- // Browser path (assuming ArrayBuffer)
226
- const bytes = new Uint8Array(buffer);
227
- let binary = "";
228
- for (let i = 0; i < bytes.length; i++) {
229
- binary += String.fromCharCode(bytes[i]);
230
- }
231
- base64string = window.btoa(binary);
232
- }
233
- return base64string
234
- .replace(/\+/g, "-")
235
- .replace(/\//g, "_")
236
- .replace(/=/g, "");
237
- }
238
- /**
239
- * Generate cryptographically secure random string
240
- */
241
- generateRandomString(length) {
242
- const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
243
- let randomValues;
244
- if (typeof window !== "undefined" && window.crypto) {
245
- // Browser environment
246
- randomValues = new Uint8Array(length);
247
- window.crypto.getRandomValues(randomValues);
248
- }
249
- else {
250
- // Node.js environment
251
- const crypto = require("crypto");
252
- randomValues = new Uint8Array(crypto.randomBytes(length));
253
- }
254
- return Array.from(randomValues)
255
- .map((value) => charset[value % charset.length])
256
- .join("");
257
- }
258
- /**
259
- * Initiate OAuth flow
260
- */
261
- async initiateOAuth(provider) {
262
- const providerConfig = this.config.providers?.[provider];
263
- if (!providerConfig) {
264
- const errorMsg = `Provider '${provider}' is not configured.`;
265
- console.error(errorMsg);
266
- return { success: false, error: errorMsg };
267
- }
268
- // Validazione di sicurezza pre-inizializzazione
269
- if (typeof window !== "undefined" && providerConfig.clientSecret) {
270
- // Google OAuth richiede client_secret anche con PKCE
271
- if (provider === "google" && providerConfig.usePKCE) {
272
- console.log(`Provider ${provider} has client_secret configured - OK for Google with PKCE`);
273
- }
274
- else {
275
- const errorMsg = `Client secret cannot be used in browser for ${provider}`;
276
- console.error(errorMsg);
277
- return { success: false, error: errorMsg };
278
- }
279
- }
280
- try {
281
- const state = this.generateRandomString(32);
282
- const stateTimestamp = Date.now();
283
- // Salva state con timestamp per validazione timeout
284
- this.setItem(`oauth_state_${provider}`, state);
285
- this.setItem(`oauth_state_timestamp_${provider}`, stateTimestamp.toString());
286
- let authUrl = providerConfig.authUrl;
287
- const authParams = new URLSearchParams({
288
- client_id: providerConfig.clientId,
289
- redirect_uri: providerConfig.redirectUri,
290
- response_type: "code",
291
- state,
292
- });
293
- // Add scope if configured
294
- if (providerConfig.scope && providerConfig.scope.length > 0) {
295
- authParams.set("scope", providerConfig.scope.join(" "));
296
- }
297
- // Add Google-specific parameters for better UX
298
- if (provider === "google") {
299
- authParams.set("prompt", "select_account"); // Force account selection
300
- authParams.set("access_type", "offline"); // Get refresh token
301
- authParams.set("include_granted_scopes", "true"); // Include previously granted scopes
302
- }
303
- // PKCE è obbligatorio per sicurezza
304
- const isPKCEEnabled = providerConfig.usePKCE ?? this.config.usePKCE ?? true;
305
- if (!isPKCEEnabled && typeof window !== "undefined") {
306
- const errorMsg = `PKCE is required for ${provider} in browser for security reasons`;
307
- console.error(errorMsg);
308
- return { success: false, error: errorMsg };
309
- }
310
- if (isPKCEEnabled) {
311
- console.log("PKCE is enabled, generating challenge...");
312
- const { codeVerifier, codeChallenge } = await this.generatePKCEChallenge();
313
- console.log(`Generated code verifier: ${codeVerifier.substring(0, 10)}... (length: ${codeVerifier.length})`);
314
- console.log(`Generated code challenge: ${codeChallenge.substring(0, 10)}... (length: ${codeChallenge.length})`);
315
- this.setItem(`oauth_verifier_${provider}`, codeVerifier);
316
- this.setItem(`oauth_verifier_timestamp_${provider}`, stateTimestamp.toString());
317
- console.log(`Saved code verifier to storage with key: oauth_verifier_${provider}`);
318
- authParams.set("code_challenge", codeChallenge);
319
- authParams.set("code_challenge_method", "S256");
320
- console.log("Added PKCE parameters to auth URL");
321
- }
322
- // If the authorization URL already contains query parameters, add the new parameters
323
- if (authUrl.includes("?")) {
324
- authUrl = `${authUrl}&${authParams.toString()}`;
325
- }
326
- else {
327
- authUrl = `${authUrl}?${authParams.toString()}`;
328
- }
329
- this.emit("oauth_initiated", { provider, authUrl });
330
- return {
331
- success: true,
332
- provider,
333
- authUrl,
334
- };
335
- }
336
- catch (error) {
337
- console.error(`Error initiating OAuth with ${provider}:`, error);
338
- return {
339
- success: false,
340
- error: error.message,
341
- };
342
- }
343
- }
344
- /**
345
- * Complete OAuth flow
346
- */
347
- async completeOAuth(provider, authCode, state) {
348
- const providerConfig = this.config.providers?.[provider];
349
- if (!providerConfig) {
350
- const errorMsg = `Provider '${provider}' is not configured.`;
351
- console.error(errorMsg);
352
- return { success: false, error: errorMsg };
353
- }
354
- try {
355
- const tokenData = await this.exchangeCodeForToken(provider, providerConfig, authCode, state);
356
- if (!tokenData.access_token) {
357
- const errorMsg = "No access token received from provider";
358
- console.error(errorMsg, tokenData);
359
- return { success: false, error: errorMsg };
360
- }
361
- const userInfo = await this.fetchUserInfo(provider, providerConfig, tokenData.access_token);
362
- // Cache user info
363
- this.cacheUserInfo(userInfo.id, provider, userInfo);
364
- // Generate credentials
365
- const credentials = await this.generateCredentials(userInfo, provider);
366
- this.emit("oauth_completed", { provider, userInfo, credentials });
367
- return {
368
- success: true,
369
- provider,
370
- userInfo,
371
- };
372
- }
373
- catch (error) {
374
- console.error(`Error completing OAuth with ${provider}:`, error);
375
- return {
376
- success: false,
377
- error: error.message,
378
- };
379
- }
380
- }
381
- /**
382
- * Generate credentials from OAuth user info
383
- * Ora restituisce anche la chiave GunDB derivata (key)
384
- */
385
- async generateCredentials(userInfo, provider) {
386
- const providerConfig = this.config.providers?.[provider];
387
- if (!providerConfig) {
388
- throw new Error(`Provider ${provider} is not configured.`);
389
- }
390
- // Username uniforme
391
- const username = generateUsernameFromIdentity(provider, userInfo);
392
- try {
393
- console.log(`Generating credentials for ${provider} user: ${userInfo.id}`);
394
- const saltData = `${userInfo.id}_${provider}_${userInfo.email || "no-email"}`;
395
- const salt = ethers.keccak256(ethers.toUtf8Bytes(saltData));
396
- // Password deterministica (compatibilità)
397
- const password = generateDeterministicPassword(salt);
398
- // Deriva la chiave GunDB
399
- const key = await derive(password, salt, { includeP256: true });
400
- const credentials = {
401
- username,
402
- password,
403
- provider,
404
- key,
405
- };
406
- this.cacheUserInfo(userInfo.id, provider, userInfo);
407
- console.log("OAuth credentials generated successfully");
408
- return credentials;
409
- }
410
- catch (error) {
411
- console.error("Error generating OAuth credentials:", error);
412
- throw error;
413
- }
414
- }
415
- /**
416
- * Exchange authorization code for access token
417
- */
418
- async exchangeCodeForToken(provider, providerConfig, code, state) {
419
- const storedState = this.getItem(`oauth_state_${provider}`);
420
- const storedStateTimestamp = this.getItem(`oauth_state_timestamp_${provider}`);
421
- if (!state || !storedState || state !== storedState) {
422
- this.removeItem(`oauth_state_${provider}`);
423
- this.removeItem(`oauth_state_timestamp_${provider}`);
424
- throw new Error("Invalid state parameter or expired");
425
- }
426
- // Validazione del timestamp dello state
427
- if (storedStateTimestamp) {
428
- const stateTimestamp = parseInt(storedStateTimestamp, 10);
429
- const stateTimeout = this.config.stateTimeout || 10 * 60 * 1000; // Default 10 minuti
430
- if (Date.now() - stateTimestamp > stateTimeout) {
431
- this.removeItem(`oauth_state_${provider}`);
432
- this.removeItem(`oauth_state_timestamp_${provider}`);
433
- throw new Error("State parameter expired");
434
- }
435
- }
436
- this.removeItem(`oauth_state_${provider}`);
437
- this.removeItem(`oauth_state_timestamp_${provider}`);
438
- const tokenParams = {
439
- client_id: providerConfig.clientId,
440
- code: code,
441
- redirect_uri: providerConfig.redirectUri,
442
- grant_type: "authorization_code",
443
- };
444
- // Check for PKCE first
445
- const isPKCEEnabled = providerConfig.usePKCE ?? this.config.usePKCE;
446
- if (isPKCEEnabled) {
447
- console.log("PKCE enabled, retrieving code verifier...");
448
- // Debug: Show all oauth-related keys in sessionStorage
449
- if (typeof sessionStorage !== "undefined") {
450
- const oauthKeys = [];
451
- for (let i = 0; i < sessionStorage.length; i++) {
452
- const key = sessionStorage.key(i);
453
- if (key && key.startsWith("oauth_")) {
454
- oauthKeys.push(key);
455
- }
456
- }
457
- console.log("OAuth keys in sessionStorage:", oauthKeys);
458
- }
459
- const verifier = this.getItem(`oauth_verifier_${provider}`);
460
- const verifierTimestamp = this.getItem(`oauth_verifier_timestamp_${provider}`);
461
- console.log(`Looking for key: oauth_verifier_${provider}, found:`, !!verifier);
462
- if (verifier && verifierTimestamp) {
463
- const verifierTimestampInt = parseInt(verifierTimestamp, 10);
464
- const stateTimeout = this.config.stateTimeout || 10 * 60 * 1000; // Default 10 minuti
465
- if (Date.now() - verifierTimestampInt > stateTimeout) {
466
- console.warn(`Code verifier expired for PKCE flow for ${provider}`);
467
- this.removeItem(`oauth_verifier_${provider}`);
468
- this.removeItem(`oauth_verifier_timestamp_${provider}`);
469
- throw new Error("Code verifier expired");
470
- }
471
- console.log(`Found code verifier for PKCE flow: ${verifier.substring(0, 10)}... (length: ${verifier.length})`);
472
- tokenParams.code_verifier = verifier;
473
- }
474
- else {
475
- // Fallback: prova a generare un nuovo verifier (non ideale ma funziona per test)
476
- console.warn("PKCE enabled but no code verifier found. Attempting fallback...");
477
- try {
478
- const { codeVerifier } = await this.generatePKCEChallenge();
479
- tokenParams.code_verifier = codeVerifier;
480
- console.log("Generated fallback code verifier");
481
- }
482
- catch (fallbackError) {
483
- throw new Error("PKCE enabled but no code verifier found and fallback failed");
484
- }
485
- }
486
- }
487
- else {
488
- // PKCE non abilitato - non sicuro per browser
489
- if (typeof window !== "undefined") {
490
- throw new Error("PKCE is required for browser applications. Client secret cannot be used in browser.");
491
- }
492
- // Solo per ambiente Node.js con client_secret
493
- if (providerConfig.clientSecret &&
494
- providerConfig.clientSecret.trim() !== "") {
495
- tokenParams.client_secret = providerConfig.clientSecret;
496
- console.log("Using client_secret for server-side OAuth flow");
497
- }
498
- else {
499
- throw new Error("Client secret is required when PKCE is not enabled for server-side flows.");
500
- }
501
- }
502
- // Google OAuth richiede client_secret anche con PKCE
503
- // Questo è un comportamento specifico di Google, non una vulnerabilità
504
- if (provider === "google" &&
505
- providerConfig.clientSecret &&
506
- providerConfig.clientSecret.trim() !== "") {
507
- tokenParams.client_secret = providerConfig.clientSecret;
508
- console.log("Adding client_secret for Google OAuth (required even with PKCE)");
509
- }
510
- // Clean up verifier
511
- this.removeItem(`oauth_verifier_${provider}`);
512
- this.removeItem(`oauth_verifier_timestamp_${provider}`);
513
- const urlParams = new URLSearchParams(tokenParams);
514
- console.log("Request body keys:", Array.from(urlParams.keys()));
515
- const response = await fetch(providerConfig.tokenUrl, {
516
- method: "POST",
517
- headers: {
518
- "Content-Type": "application/x-www-form-urlencoded",
519
- },
520
- body: urlParams.toString(),
521
- });
522
- if (!response.ok) {
523
- const errorData = await response.json().catch(() => ({}));
524
- throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
525
- }
526
- return await response.json();
527
- }
528
- /**
529
- * Fetch user info from provider
530
- */
531
- async fetchUserInfo(provider, providerConfig, accessToken) {
532
- const response = await fetch(providerConfig.userInfoUrl, {
533
- headers: {
534
- Authorization: `Bearer ${accessToken}`,
535
- },
536
- });
537
- if (!response.ok) {
538
- throw new Error(`Failed to fetch user info: ${response.status} ${response.statusText}`);
539
- }
540
- const userData = await response.json();
541
- return this.normalizeUserInfo(userData, provider);
542
- }
543
- /**
544
- * Normalize user info from different providers
545
- */
546
- normalizeUserInfo(userData, provider) {
547
- switch (provider) {
548
- case "google":
549
- return {
550
- id: userData.id,
551
- email: userData.email,
552
- name: userData.name,
553
- picture: userData.picture,
554
- provider,
555
- };
556
- case "github":
557
- return {
558
- id: userData.id.toString(),
559
- email: userData.email,
560
- name: userData.name || userData.login,
561
- picture: userData.avatar_url,
562
- provider,
563
- };
564
- case "discord":
565
- return {
566
- id: userData.id,
567
- email: userData.email,
568
- name: userData.username,
569
- picture: `https://cdn.discordapp.com/avatars/${userData.id}/${userData.avatar}.png`,
570
- provider,
571
- };
572
- case "twitter":
573
- return {
574
- id: userData.data.id,
575
- email: userData.data.email,
576
- name: userData.data.name,
577
- picture: userData.data.profile_image_url,
578
- provider,
579
- };
580
- default:
581
- return {
582
- id: userData.id?.toString() || "",
583
- email: userData.email || "",
584
- name: userData.name || "",
585
- picture: userData.picture || userData.avatar_url || "",
586
- provider,
587
- };
588
- }
589
- }
590
- /**
591
- * Cache user info
592
- */
593
- cacheUserInfo(userId, provider, userInfo) {
594
- const cacheKey = `${provider}_${userId}`;
595
- const cacheEntry = {
596
- data: userInfo,
597
- provider,
598
- userId,
599
- timestamp: Date.now(),
600
- };
601
- this.userCache.set(cacheKey, cacheEntry);
602
- // Salva solo dati minimi in localStorage (solo se disponibile)
603
- try {
604
- if (typeof window !== "undefined" &&
605
- typeof localStorage !== "undefined") {
606
- const minimalCacheEntry = {
607
- userId: userInfo.id,
608
- provider,
609
- timestamp: Date.now(),
610
- };
611
- localStorage.setItem(`shogun_oauth_user_${cacheKey}`, JSON.stringify(minimalCacheEntry));
612
- }
613
- }
614
- catch (error) {
615
- console.warn("Failed to persist user info in localStorage:", error);
616
- }
617
- }
618
- /**
619
- * Get cached user info
620
- */
621
- getCachedUserInfo(userId, provider) {
622
- const cacheKey = `${provider}_${userId}`;
623
- // First check memory cache
624
- const cached = this.userCache.get(cacheKey);
625
- if (cached) {
626
- // Check if cache is still valid
627
- if (this.config.cacheDuration &&
628
- Date.now() - cached.timestamp <= this.config.cacheDuration) {
629
- return cached.data || null;
630
- }
631
- }
632
- // Then check localStorage (solo se disponibile)
633
- try {
634
- if (typeof window !== "undefined" &&
635
- typeof localStorage !== "undefined") {
636
- const localCached = localStorage.getItem(`shogun_oauth_user_${cacheKey}`);
637
- if (localCached) {
638
- const parsedCache = JSON.parse(localCached);
639
- if (this.config.cacheDuration &&
640
- Date.now() - parsedCache.timestamp <= this.config.cacheDuration) {
641
- // Update memory cache
642
- this.userCache.set(cacheKey, parsedCache);
643
- return parsedCache.userInfo;
644
- }
645
- }
646
- }
647
- }
648
- catch (error) {
649
- console.warn("Failed to read user info from localStorage:", error);
650
- }
651
- return null;
652
- }
653
- /**
654
- * Clear user cache
655
- */
656
- clearUserCache(userId, provider) {
657
- if (userId && provider) {
658
- const cacheKey = `${provider}_${userId}`;
659
- this.userCache.delete(cacheKey);
660
- try {
661
- if (typeof window !== "undefined" &&
662
- typeof localStorage !== "undefined") {
663
- localStorage.removeItem(`shogun_oauth_user_${cacheKey}`);
664
- }
665
- }
666
- catch (error) {
667
- console.warn("Failed to remove user info from localStorage:", error);
668
- }
669
- }
670
- else {
671
- // Clear all cache
672
- this.userCache.clear();
673
- try {
674
- if (typeof window !== "undefined" &&
675
- typeof localStorage !== "undefined") {
676
- const keysToRemove = [];
677
- for (let i = 0; i < localStorage.length; i++) {
678
- const key = localStorage.key(i);
679
- if (key && key.startsWith("shogun_oauth_user_")) {
680
- keysToRemove.push(key);
681
- }
682
- }
683
- keysToRemove.forEach((key) => localStorage.removeItem(key));
684
- }
685
- }
686
- catch (error) {
687
- console.warn("Failed to clear user info from localStorage:", error);
688
- }
689
- }
690
- }
691
- /**
692
- * Cleanup
693
- */
694
- cleanup() {
695
- this.removeAllListeners();
696
- this.userCache.clear();
697
- this.cleanupExpiredOAuthData();
698
- }
699
- /**
700
- * Clean up expired OAuth data from storage
701
- */
702
- cleanupExpiredOAuthData() {
703
- const stateTimeout = this.config.stateTimeout || 10 * 60 * 1000;
704
- const currentTime = Date.now();
705
- // Clean sessionStorage
706
- if (typeof sessionStorage !== "undefined") {
707
- const keysToRemove = [];
708
- for (let i = 0; i < sessionStorage.length; i++) {
709
- const key = sessionStorage.key(i);
710
- if (key && key.startsWith("oauth_state_timestamp_")) {
711
- const timestamp = sessionStorage.getItem(key);
712
- if (timestamp) {
713
- const stateTime = parseInt(timestamp, 10);
714
- if (currentTime - stateTime > stateTimeout) {
715
- const stateKey = key.replace("_timestamp", "");
716
- keysToRemove.push(key, stateKey);
717
- }
718
- }
719
- }
720
- if (key && key.startsWith("oauth_verifier_timestamp_")) {
721
- const timestamp = sessionStorage.getItem(key);
722
- if (timestamp) {
723
- const verifierTime = parseInt(timestamp, 10);
724
- if (currentTime - verifierTime > stateTimeout) {
725
- const verifierKey = key.replace("_timestamp", "");
726
- keysToRemove.push(key, verifierKey);
727
- }
728
- }
729
- }
730
- }
731
- keysToRemove.forEach((key) => sessionStorage.removeItem(key));
732
- if (keysToRemove.length > 0) {
733
- console.log(`Cleaned up ${keysToRemove.length} expired OAuth entries`);
734
- }
735
- }
736
- // Clean memoryStorage (Node.js)
737
- const memoryKeysToRemove = [];
738
- for (const [key, value] of this.memoryStorage.entries()) {
739
- if (key.startsWith("oauth_state_timestamp_") ||
740
- key.startsWith("oauth_verifier_timestamp_")) {
741
- const timestamp = parseInt(value, 10);
742
- if (currentTime - timestamp > stateTimeout) {
743
- const baseKey = key.replace("_timestamp", "");
744
- memoryKeysToRemove.push(key, baseKey);
745
- }
746
- }
747
- }
748
- memoryKeysToRemove.forEach((key) => this.memoryStorage.delete(key));
749
- if (memoryKeysToRemove.length > 0) {
750
- console.log(`Cleaned up ${memoryKeysToRemove.length} expired OAuth entries from memory`);
751
- }
752
- }
753
- }