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.
- package/README.md +1378 -1176
- package/dist/browser/shogun-core.js +78019 -44874
- package/dist/browser/shogun-core.js.map +1 -1
- package/dist/core.js +1 -1
- package/dist/examples/zkproof-credentials-example.js +218 -0
- package/dist/examples/zkproof-example.js +206 -0
- package/dist/index.js +10 -1
- package/dist/interfaces/shogun.js +2 -2
- package/dist/managers/AuthManager.js +0 -2
- package/dist/managers/CoreInitializer.js +9 -12
- package/dist/plugins/index.js +9 -21
- package/dist/plugins/nostr/nostrConnectorPlugin.js +2 -2
- package/dist/plugins/webauthn/webauthn.js +20 -7
- package/dist/plugins/webauthn/webauthnPlugin.js +101 -17
- package/dist/plugins/zkproof/index.js +53 -0
- package/dist/plugins/zkproof/zkCredentials.js +213 -0
- package/dist/plugins/zkproof/zkProofConnector.js +198 -0
- package/dist/plugins/zkproof/zkProofPlugin.js +272 -0
- package/dist/types/examples/zkproof-credentials-example.d.ts +12 -0
- package/dist/types/examples/zkproof-example.d.ts +11 -0
- package/dist/types/gundb/types.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/interfaces/events.d.ts +3 -3
- package/dist/types/interfaces/shogun.d.ts +9 -24
- package/dist/types/plugins/index.d.ts +5 -3
- package/dist/types/plugins/webauthn/types.d.ts +22 -1
- package/dist/types/plugins/webauthn/webauthn.d.ts +1 -1
- package/dist/types/plugins/webauthn/webauthnPlugin.d.ts +23 -2
- package/dist/types/plugins/zkproof/index.d.ts +48 -0
- package/dist/types/plugins/zkproof/types.d.ts +123 -0
- package/dist/types/plugins/zkproof/zkCredentials.d.ts +112 -0
- package/dist/types/plugins/zkproof/zkProofConnector.d.ts +46 -0
- package/dist/types/plugins/zkproof/zkProofPlugin.d.ts +76 -0
- package/dist/types/utils/seedPhrase.d.ts +50 -0
- package/dist/types/utils/validation.d.ts +2 -2
- package/dist/utils/seedPhrase.js +97 -0
- package/dist/utils/validation.js +3 -1
- package/package.json +14 -1
- package/dist/migration-test.js +0 -96
- package/dist/plugins/oauth/index.js +0 -8
- package/dist/plugins/oauth/oauthConnector.js +0 -759
- package/dist/plugins/oauth/oauthPlugin.js +0 -400
- package/dist/types/migration-test.d.ts +0 -16
- package/dist/types/plugins/oauth/index.d.ts +0 -3
- package/dist/types/plugins/oauth/oauthConnector.d.ts +0 -110
- package/dist/types/plugins/oauth/oauthPlugin.d.ts +0 -91
- package/dist/types/plugins/oauth/types.d.ts +0 -114
- /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;
|