shogun-core 3.3.8 → 4.0.0

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