shogun-core 1.2.7 → 1.2.8
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 +0 -1
- package/dist/browser/shogun-core.js +1 -1
- package/dist/browser/shogun-core.js.LICENSE.txt +2 -0
- package/dist/browser/shogun-core.light.js +1 -1
- package/dist/browser/shogun-core.vendors.light.js +1 -1
- package/dist/core.js +31 -71
- package/dist/gundb/{instance.js → gunInstance.js} +135 -115
- package/dist/gundb/index.js +3 -20
- package/dist/plugins/index.js +23 -1
- package/dist/plugins/nostr/index.js +1 -0
- package/dist/plugins/nostr/nostrChain.js +128 -0
- package/dist/plugins/nostr/nostrConnector.js +42 -7
- package/dist/plugins/nostr/nostrConnectorPlugin.js +157 -1
- package/dist/plugins/nostr/nostrSigner.js +343 -0
- package/dist/plugins/oauth/index.js +13 -0
- package/dist/plugins/oauth/oauthChain.js +161 -0
- package/dist/plugins/oauth/oauthConnector.js +542 -0
- package/dist/plugins/oauth/oauthPlugin.js +302 -0
- package/dist/plugins/oauth/types.js +2 -0
- package/dist/plugins/web3/index.js +1 -0
- package/dist/plugins/web3/web3Chain.js +77 -2
- package/dist/plugins/web3/web3Connector.js +159 -37
- package/dist/plugins/web3/web3ConnectorPlugin.js +157 -1
- package/dist/plugins/web3/web3Signer.js +268 -0
- package/dist/plugins/webauthn/webauthnChain.js +78 -0
- package/dist/plugins/webauthn/webauthnPlugin.js +154 -1
- package/dist/plugins/webauthn/webauthnSigner.js +318 -0
- package/dist/storage/storage.js +0 -8
- package/dist/types/core.d.ts +10 -34
- package/dist/types/gundb/gun-es/gun-es.d.ts +1 -0
- package/dist/types/gundb/{instance.d.ts → gunInstance.d.ts} +2 -2
- package/dist/types/gundb/index.d.ts +1 -4
- package/dist/types/plugins/index.d.ts +4 -0
- package/dist/types/plugins/nostr/index.d.ts +1 -0
- package/dist/types/plugins/nostr/nostrConnector.d.ts +3 -2
- package/dist/types/plugins/nostr/nostrConnectorPlugin.d.ts +82 -0
- package/dist/types/plugins/nostr/nostrSigner.d.ts +104 -0
- package/dist/types/plugins/oauth/index.d.ts +4 -0
- package/dist/types/plugins/oauth/oauthChain.d.ts +2 -0
- package/dist/types/plugins/oauth/oauthConnector.d.ts +100 -0
- package/dist/types/plugins/oauth/oauthPlugin.d.ts +89 -0
- package/dist/types/plugins/oauth/types.d.ts +106 -0
- package/dist/types/plugins/web3/index.d.ts +1 -0
- package/dist/types/plugins/web3/types.d.ts +1 -0
- package/dist/types/plugins/web3/web3Connector.d.ts +8 -2
- package/dist/types/plugins/web3/web3ConnectorPlugin.d.ts +82 -0
- package/dist/types/plugins/web3/web3Signer.d.ts +93 -0
- package/dist/types/plugins/webauthn/webauthnPlugin.d.ts +81 -0
- package/dist/types/plugins/webauthn/webauthnSigner.d.ts +90 -0
- package/dist/types/shogun.js +1 -28
- package/dist/types/types/events.d.ts +2 -2
- package/dist/types/types/shogun.d.ts +13 -49
- package/package.json +2 -1
- package/dist/browser.js +0 -107
- package/dist/contracts/base.js +0 -152
- package/dist/contracts/entryPoint.js +0 -407
- package/dist/contracts/index.js +0 -47
- package/dist/contracts/registry.js +0 -259
- package/dist/contracts/relay.js +0 -494
- package/dist/contracts/utils.js +0 -582
- package/dist/types/browser.d.ts +0 -27
- package/dist/types/contracts/base.d.ts +0 -82
- package/dist/types/contracts/entryPoint.d.ts +0 -138
- package/dist/types/contracts/index.d.ts +0 -17
- package/dist/types/contracts/registry.d.ts +0 -97
- package/dist/types/contracts/relay.d.ts +0 -165
- package/dist/types/contracts/utils.d.ts +0 -173
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OAuthConnector = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* OAuth Connector - Simple version for GunDB user creation
|
|
6
|
+
*/
|
|
7
|
+
const ethers_1 = require("ethers");
|
|
8
|
+
const logger_1 = require("../../utils/logger");
|
|
9
|
+
const eventEmitter_1 = require("../../utils/eventEmitter");
|
|
10
|
+
/**
|
|
11
|
+
* OAuth Connector
|
|
12
|
+
*/
|
|
13
|
+
class OAuthConnector extends eventEmitter_1.EventEmitter {
|
|
14
|
+
DEFAULT_CONFIG = {
|
|
15
|
+
providers: {
|
|
16
|
+
google: {
|
|
17
|
+
clientId: "",
|
|
18
|
+
clientSecret: "",
|
|
19
|
+
redirectUri: `${this.getOrigin()}/auth/callback`,
|
|
20
|
+
scope: ["openid", "email", "profile"],
|
|
21
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
22
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
23
|
+
userInfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
24
|
+
},
|
|
25
|
+
github: {
|
|
26
|
+
clientId: "",
|
|
27
|
+
clientSecret: "",
|
|
28
|
+
redirectUri: `${this.getOrigin()}/auth/callback`,
|
|
29
|
+
scope: ["user:email"],
|
|
30
|
+
authUrl: "https://github.com/login/oauth/authorize",
|
|
31
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
32
|
+
userInfoUrl: "https://api.github.com/user",
|
|
33
|
+
},
|
|
34
|
+
discord: {
|
|
35
|
+
clientId: "",
|
|
36
|
+
clientSecret: "",
|
|
37
|
+
redirectUri: `${this.getOrigin()}/auth/callback`,
|
|
38
|
+
scope: ["identify", "email"],
|
|
39
|
+
authUrl: "https://discord.com/api/oauth2/authorize",
|
|
40
|
+
tokenUrl: "https://discord.com/api/oauth2/token",
|
|
41
|
+
userInfoUrl: "https://discord.com/api/users/@me",
|
|
42
|
+
},
|
|
43
|
+
twitter: {
|
|
44
|
+
clientId: "",
|
|
45
|
+
clientSecret: "",
|
|
46
|
+
redirectUri: `${this.getOrigin()}/auth/callback`,
|
|
47
|
+
scope: ["tweet.read", "users.read"],
|
|
48
|
+
authUrl: "https://twitter.com/i/oauth2/authorize",
|
|
49
|
+
tokenUrl: "https://api.twitter.com/2/oauth2/token",
|
|
50
|
+
userInfoUrl: "https://api.twitter.com/2/users/me",
|
|
51
|
+
},
|
|
52
|
+
custom: {
|
|
53
|
+
clientId: "",
|
|
54
|
+
clientSecret: "",
|
|
55
|
+
redirectUri: "",
|
|
56
|
+
scope: [],
|
|
57
|
+
authUrl: "",
|
|
58
|
+
tokenUrl: "",
|
|
59
|
+
userInfoUrl: "",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
usePKCE: true,
|
|
63
|
+
cacheDuration: 24 * 60 * 60 * 1000, // 24 hours
|
|
64
|
+
timeout: 60000,
|
|
65
|
+
maxRetries: 3,
|
|
66
|
+
retryDelay: 1000,
|
|
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
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Update the connector configuration
|
|
85
|
+
* @param config - New configuration options
|
|
86
|
+
*/
|
|
87
|
+
updateConfig(config) {
|
|
88
|
+
this.config = {
|
|
89
|
+
...this.config,
|
|
90
|
+
...config,
|
|
91
|
+
providers: {
|
|
92
|
+
...(this.config.providers || {}),
|
|
93
|
+
...(config.providers || {}),
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
(0, logger_1.logDebug)("OAuthConnector configuration updated", this.config);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get origin URL (browser or Node.js compatible)
|
|
100
|
+
*/
|
|
101
|
+
getOrigin() {
|
|
102
|
+
if (typeof window !== "undefined" && window.location) {
|
|
103
|
+
return window.location.origin;
|
|
104
|
+
}
|
|
105
|
+
// Fallback for Node.js environment
|
|
106
|
+
return "http://localhost:3000";
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Storage abstraction (browser sessionStorage or Node.js Map)
|
|
110
|
+
*/
|
|
111
|
+
setItem(key, value) {
|
|
112
|
+
if (typeof sessionStorage !== "undefined") {
|
|
113
|
+
sessionStorage.setItem(key, value);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.memoryStorage.set(key, value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
getItem(key) {
|
|
120
|
+
if (typeof sessionStorage !== "undefined") {
|
|
121
|
+
return sessionStorage.getItem(key);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
return this.memoryStorage.get(key) || null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
removeItem(key) {
|
|
128
|
+
if (typeof sessionStorage !== "undefined") {
|
|
129
|
+
sessionStorage.removeItem(key);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.memoryStorage.delete(key);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check if OAuth is supported
|
|
137
|
+
*/
|
|
138
|
+
isSupported() {
|
|
139
|
+
// In Node.js, we can still demonstrate the functionality
|
|
140
|
+
return typeof URLSearchParams !== "undefined";
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get available OAuth providers
|
|
144
|
+
*/
|
|
145
|
+
getAvailableProviders() {
|
|
146
|
+
return Object.keys(this.config.providers || {}).filter((provider) => this.config.providers[provider]?.clientId);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Generate PKCE challenge for secure OAuth flow
|
|
150
|
+
*/
|
|
151
|
+
async generatePKCEChallenge() {
|
|
152
|
+
const codeVerifier = this.generateRandomString(128);
|
|
153
|
+
const codeChallenge = await this.calculatePKCECodeChallenge(codeVerifier);
|
|
154
|
+
return { codeVerifier, codeChallenge };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Calculate the PKCE code challenge from a code verifier.
|
|
158
|
+
* Hashes the verifier using SHA-256 and then base64url encodes it.
|
|
159
|
+
* @param verifier The code verifier string.
|
|
160
|
+
* @returns The base64url-encoded SHA-256 hash of the verifier.
|
|
161
|
+
*/
|
|
162
|
+
async calculatePKCECodeChallenge(verifier) {
|
|
163
|
+
if (typeof window !== "undefined" &&
|
|
164
|
+
window.crypto &&
|
|
165
|
+
window.crypto.subtle) {
|
|
166
|
+
// Browser environment
|
|
167
|
+
const encoder = new TextEncoder();
|
|
168
|
+
const data = encoder.encode(verifier);
|
|
169
|
+
const hashBuffer = await window.crypto.subtle.digest("SHA-256", data);
|
|
170
|
+
return this.base64urlEncode(hashBuffer);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Node.js environment
|
|
174
|
+
const crypto = require("crypto");
|
|
175
|
+
const hash = crypto.createHash("sha256").update(verifier).digest();
|
|
176
|
+
return this.base64urlEncode(hash);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Encodes a buffer into a Base64URL-encoded string.
|
|
181
|
+
* @param buffer The buffer to encode.
|
|
182
|
+
* @returns The Base64URL-encoded string.
|
|
183
|
+
*/
|
|
184
|
+
base64urlEncode(buffer) {
|
|
185
|
+
let base64string;
|
|
186
|
+
// In Node.js, we can use the Buffer object. In the browser, we need a different approach.
|
|
187
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(buffer)) {
|
|
188
|
+
// Node.js path
|
|
189
|
+
base64string = buffer.toString("base64");
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Browser path (assuming ArrayBuffer)
|
|
193
|
+
const bytes = new Uint8Array(buffer);
|
|
194
|
+
let binary = "";
|
|
195
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
196
|
+
binary += String.fromCharCode(bytes[i]);
|
|
197
|
+
}
|
|
198
|
+
base64string = window.btoa(binary);
|
|
199
|
+
}
|
|
200
|
+
return base64string
|
|
201
|
+
.replace(/\+/g, "-")
|
|
202
|
+
.replace(/\//g, "_")
|
|
203
|
+
.replace(/=/g, "");
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Generate cryptographically secure random string
|
|
207
|
+
*/
|
|
208
|
+
generateRandomString(length) {
|
|
209
|
+
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
210
|
+
let randomValues;
|
|
211
|
+
if (typeof window !== "undefined" && window.crypto) {
|
|
212
|
+
// Browser environment
|
|
213
|
+
randomValues = new Uint8Array(length);
|
|
214
|
+
window.crypto.getRandomValues(randomValues);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
// Node.js environment
|
|
218
|
+
const crypto = require("crypto");
|
|
219
|
+
randomValues = new Uint8Array(crypto.randomBytes(length));
|
|
220
|
+
}
|
|
221
|
+
return Array.from(randomValues)
|
|
222
|
+
.map((value) => charset[value % charset.length])
|
|
223
|
+
.join("");
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Initiate OAuth flow with a provider
|
|
227
|
+
*/
|
|
228
|
+
async initiateOAuth(provider) {
|
|
229
|
+
try {
|
|
230
|
+
(0, logger_1.logDebug)(`Initiating OAuth flow with ${provider}`);
|
|
231
|
+
const providerConfig = this.config.providers[provider];
|
|
232
|
+
if (!providerConfig || !providerConfig.clientId) {
|
|
233
|
+
throw new Error(`Provider ${provider} not configured`);
|
|
234
|
+
}
|
|
235
|
+
// Generate state for CSRF protection
|
|
236
|
+
const state = this.generateRandomString(32);
|
|
237
|
+
this.setItem(`oauth_state_${provider}`, state);
|
|
238
|
+
// Generate PKCE challenge if enabled
|
|
239
|
+
let pkceParams = {};
|
|
240
|
+
if (this.config.usePKCE) {
|
|
241
|
+
const { codeVerifier, codeChallenge } = await this.generatePKCEChallenge();
|
|
242
|
+
this.setItem(`oauth_verifier_${provider}`, codeVerifier);
|
|
243
|
+
pkceParams = {
|
|
244
|
+
code_challenge: codeChallenge,
|
|
245
|
+
code_challenge_method: "S256",
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// Build authorization URL
|
|
249
|
+
const authParams = new URLSearchParams({
|
|
250
|
+
client_id: providerConfig.clientId,
|
|
251
|
+
redirect_uri: providerConfig.redirectUri,
|
|
252
|
+
scope: providerConfig.scope.join(" "),
|
|
253
|
+
response_type: "code",
|
|
254
|
+
state,
|
|
255
|
+
...pkceParams,
|
|
256
|
+
});
|
|
257
|
+
// Handle the authorization URL that might already contain query parameters
|
|
258
|
+
let authUrl = providerConfig.authUrl || "";
|
|
259
|
+
if (!authUrl) {
|
|
260
|
+
throw new Error(`Auth URL not configured for provider ${provider}`);
|
|
261
|
+
}
|
|
262
|
+
// If the authorization URL already contains query parameters, add the new parameters
|
|
263
|
+
if (authUrl.includes("?")) {
|
|
264
|
+
authUrl = `${authUrl}&${authParams.toString()}`;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
authUrl = `${authUrl}?${authParams.toString()}`;
|
|
268
|
+
}
|
|
269
|
+
this.emit("oauth_initiated", { provider, authUrl });
|
|
270
|
+
return {
|
|
271
|
+
success: true,
|
|
272
|
+
provider,
|
|
273
|
+
authUrl,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
(0, logger_1.logError)(`Error initiating OAuth with ${provider}:`, error);
|
|
278
|
+
return {
|
|
279
|
+
success: false,
|
|
280
|
+
error: error.message,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Complete OAuth flow
|
|
286
|
+
*/
|
|
287
|
+
async completeOAuth(provider, authCode, state) {
|
|
288
|
+
const providerConfig = this.config.providers?.[provider];
|
|
289
|
+
if (!providerConfig) {
|
|
290
|
+
const errorMsg = `Provider '${provider}' is not configured.`;
|
|
291
|
+
(0, logger_1.logError)(errorMsg);
|
|
292
|
+
return { success: false, error: errorMsg };
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const tokenData = await this.exchangeCodeForToken(provider, providerConfig, authCode, state);
|
|
296
|
+
if (!tokenData.access_token) {
|
|
297
|
+
const errorMsg = "No access token received from provider";
|
|
298
|
+
(0, logger_1.logError)(errorMsg, tokenData);
|
|
299
|
+
return { success: false, error: errorMsg };
|
|
300
|
+
}
|
|
301
|
+
const userInfo = await this.fetchUserInfo(provider, providerConfig, tokenData.access_token);
|
|
302
|
+
// Cache user info
|
|
303
|
+
this.cacheUserInfo(userInfo.id, provider, userInfo);
|
|
304
|
+
// Generate credentials
|
|
305
|
+
const credentials = await this.generateCredentials(userInfo, provider);
|
|
306
|
+
this.emit("oauth_completed", { provider, userInfo, credentials });
|
|
307
|
+
return {
|
|
308
|
+
success: true,
|
|
309
|
+
provider,
|
|
310
|
+
userInfo,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
(0, logger_1.logError)(`Error completing OAuth with ${provider}:`, error);
|
|
315
|
+
return {
|
|
316
|
+
success: false,
|
|
317
|
+
error: error.message,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Generate credentials from OAuth user info
|
|
323
|
+
*/
|
|
324
|
+
async generateCredentials(userInfo, provider) {
|
|
325
|
+
const providerConfig = this.config.providers?.[provider];
|
|
326
|
+
if (!providerConfig) {
|
|
327
|
+
throw new Error(`Provider ${provider} is not configured.`);
|
|
328
|
+
}
|
|
329
|
+
const salt = `${provider}@${userInfo.id}`;
|
|
330
|
+
const username = userInfo.email || `${userInfo.id}@${provider}.shogun`;
|
|
331
|
+
try {
|
|
332
|
+
(0, logger_1.logDebug)(`Generating credentials for ${provider} user: ${userInfo.id}`);
|
|
333
|
+
// Generate deterministic password
|
|
334
|
+
const password = await this.generateDeterministicPassword(userInfo, provider);
|
|
335
|
+
const credentials = {
|
|
336
|
+
username,
|
|
337
|
+
password,
|
|
338
|
+
provider,
|
|
339
|
+
};
|
|
340
|
+
// Cache the user info
|
|
341
|
+
this.cacheUserInfo(userInfo.id, provider, userInfo);
|
|
342
|
+
(0, logger_1.logDebug)("OAuth credentials generated successfully");
|
|
343
|
+
return credentials;
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
(0, logger_1.logError)("Error generating OAuth credentials:", error);
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Generate deterministic password
|
|
352
|
+
*/
|
|
353
|
+
async generateDeterministicPassword(userInfo, provider) {
|
|
354
|
+
const passwordBase = `${userInfo.id}_${provider}_${userInfo.email || ""}_shogun_oauth`;
|
|
355
|
+
const passwordHash = ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(passwordBase));
|
|
356
|
+
return passwordHash.slice(2, 34); // 32 character hex string
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Exchange authorization code for access token
|
|
360
|
+
*/
|
|
361
|
+
async exchangeCodeForToken(provider, providerConfig, code, state) {
|
|
362
|
+
const storedState = await this.getItem(`oauth_state_${provider}`);
|
|
363
|
+
if (state && storedState !== state) {
|
|
364
|
+
throw new Error("Invalid state parameter");
|
|
365
|
+
}
|
|
366
|
+
const tokenParams = {
|
|
367
|
+
client_id: providerConfig.clientId,
|
|
368
|
+
code: code,
|
|
369
|
+
redirect_uri: providerConfig.redirectUri,
|
|
370
|
+
grant_type: "authorization_code",
|
|
371
|
+
};
|
|
372
|
+
// Add client secret if available
|
|
373
|
+
if (providerConfig.clientSecret) {
|
|
374
|
+
tokenParams.client_secret = providerConfig.clientSecret;
|
|
375
|
+
}
|
|
376
|
+
if (this.config.usePKCE) {
|
|
377
|
+
const verifier = await this.getItem(`oauth_verifier_${provider}`);
|
|
378
|
+
if (verifier) {
|
|
379
|
+
tokenParams.code_verifier = verifier;
|
|
380
|
+
this.removeItem(`oauth_verifier_${provider}`);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
(0, logger_1.logWarn)(`PKCE is enabled, but no code verifier was found for ${provider}.`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const response = await fetch(providerConfig.tokenUrl, {
|
|
387
|
+
method: "POST",
|
|
388
|
+
headers: {
|
|
389
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
390
|
+
Accept: "application/json",
|
|
391
|
+
},
|
|
392
|
+
body: new URLSearchParams(tokenParams),
|
|
393
|
+
});
|
|
394
|
+
if (!response.ok) {
|
|
395
|
+
const errorData = await response
|
|
396
|
+
.json()
|
|
397
|
+
.catch(() => ({ error: response.statusText }));
|
|
398
|
+
throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
|
|
399
|
+
}
|
|
400
|
+
return await response.json();
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Get user info from OAuth provider
|
|
404
|
+
*/
|
|
405
|
+
async fetchUserInfo(provider, providerConfig, accessToken) {
|
|
406
|
+
const response = await fetch(providerConfig.userInfoUrl, {
|
|
407
|
+
headers: {
|
|
408
|
+
Authorization: `Bearer ${accessToken}`,
|
|
409
|
+
Accept: "application/json",
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
if (!response.ok) {
|
|
413
|
+
throw new Error(`Failed to get user info: ${response.statusText}`);
|
|
414
|
+
}
|
|
415
|
+
const userData = await response.json();
|
|
416
|
+
return this.normalizeUserInfo(userData, provider);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Normalize user info across different providers
|
|
420
|
+
*/
|
|
421
|
+
normalizeUserInfo(userData, provider) {
|
|
422
|
+
switch (provider) {
|
|
423
|
+
case "google":
|
|
424
|
+
return {
|
|
425
|
+
id: userData.id,
|
|
426
|
+
email: userData.email,
|
|
427
|
+
name: userData.name,
|
|
428
|
+
picture: userData.picture,
|
|
429
|
+
verified_email: userData.verified_email,
|
|
430
|
+
provider,
|
|
431
|
+
};
|
|
432
|
+
case "github":
|
|
433
|
+
return {
|
|
434
|
+
id: userData.id.toString(),
|
|
435
|
+
email: userData.email,
|
|
436
|
+
name: userData.name || userData.login,
|
|
437
|
+
picture: userData.avatar_url,
|
|
438
|
+
provider,
|
|
439
|
+
};
|
|
440
|
+
default:
|
|
441
|
+
return {
|
|
442
|
+
id: userData.id?.toString() || userData.sub,
|
|
443
|
+
email: userData.email,
|
|
444
|
+
name: userData.name || userData.username,
|
|
445
|
+
picture: userData.picture || userData.avatar_url,
|
|
446
|
+
provider,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Cache user info
|
|
452
|
+
*/
|
|
453
|
+
cacheUserInfo(userId, provider, userInfo) {
|
|
454
|
+
const cacheKey = `${provider}_${userId}`;
|
|
455
|
+
const cacheEntry = {
|
|
456
|
+
data: userInfo,
|
|
457
|
+
timestamp: Date.now(),
|
|
458
|
+
provider,
|
|
459
|
+
userId,
|
|
460
|
+
};
|
|
461
|
+
this.userCache.set(cacheKey, cacheEntry);
|
|
462
|
+
// Also store in localStorage for persistence
|
|
463
|
+
try {
|
|
464
|
+
localStorage.setItem(`shogun_oauth_user_${cacheKey}`, JSON.stringify(cacheEntry));
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
(0, logger_1.logError)("Error caching user info:", error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Get cached user info
|
|
472
|
+
*/
|
|
473
|
+
getCachedUserInfo(userId, provider) {
|
|
474
|
+
const cacheKey = `${provider}_${userId}`;
|
|
475
|
+
// Check memory cache first
|
|
476
|
+
const cached = this.userCache.get(cacheKey);
|
|
477
|
+
if (cached &&
|
|
478
|
+
cached.data &&
|
|
479
|
+
Date.now() - cached.timestamp <= this.config.cacheDuration) {
|
|
480
|
+
return cached.data;
|
|
481
|
+
}
|
|
482
|
+
// Check localStorage
|
|
483
|
+
try {
|
|
484
|
+
const localCached = localStorage.getItem(`shogun_oauth_user_${cacheKey}`);
|
|
485
|
+
if (localCached) {
|
|
486
|
+
const parsedCache = JSON.parse(localCached);
|
|
487
|
+
if (parsedCache.data &&
|
|
488
|
+
Date.now() - parsedCache.timestamp <= this.config.cacheDuration) {
|
|
489
|
+
this.userCache.set(cacheKey, parsedCache);
|
|
490
|
+
return parsedCache.data;
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
localStorage.removeItem(`shogun_oauth_user_${cacheKey}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
(0, logger_1.logError)("Error reading cached user info:", error);
|
|
499
|
+
}
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Clear user info cache
|
|
504
|
+
*/
|
|
505
|
+
clearUserCache(userId, provider) {
|
|
506
|
+
if (userId && provider) {
|
|
507
|
+
const cacheKey = `${provider}_${userId}`;
|
|
508
|
+
this.userCache.delete(cacheKey);
|
|
509
|
+
try {
|
|
510
|
+
localStorage.removeItem(`shogun_oauth_user_${cacheKey}`);
|
|
511
|
+
}
|
|
512
|
+
catch (error) {
|
|
513
|
+
(0, logger_1.logError)("Error clearing user cache:", error);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
// Clear all caches
|
|
518
|
+
this.userCache.clear();
|
|
519
|
+
try {
|
|
520
|
+
const keysToRemove = [];
|
|
521
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
522
|
+
const key = localStorage.key(i);
|
|
523
|
+
if (key && key.startsWith("shogun_oauth_user_")) {
|
|
524
|
+
keysToRemove.push(key);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
(0, logger_1.logError)("Error clearing all user caches:", error);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Cleanup resources
|
|
536
|
+
*/
|
|
537
|
+
cleanup() {
|
|
538
|
+
this.removeAllListeners();
|
|
539
|
+
this.userCache.clear();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
exports.OAuthConnector = OAuthConnector;
|