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
package/dist/gundb/db.js DELETED
@@ -1,1833 +0,0 @@
1
- /**
2
- * GunDB class with enhanced features:
3
- * - Dynamic peer linking
4
- * - Support for remove/unset operations
5
- * - Direct authentication through Gun.user()
6
- */
7
- import Gun from "gun/gun";
8
- import SEA from "gun/sea";
9
- import "gun/lib/then";
10
- import "gun/lib/radix";
11
- import "gun/lib/radisk";
12
- import "gun/lib/store";
13
- import "gun/lib/rindexed";
14
- import "gun/lib/webrtc";
15
- import "gun/lib/wire";
16
- import "gun/lib/axe";
17
- import { restrictedPut } from "./restricted-put";
18
- import derive from "./derive";
19
- import { ErrorHandler, ErrorType } from "../utils/errorHandler";
20
- import { EventEmitter } from "../utils/eventEmitter";
21
- import { RxJS } from "./rxjs";
22
- import * as GunErrors from "./errors";
23
- import * as crypto from "./crypto";
24
- /**
25
- * Configuration constants for timeouts and security
26
- */
27
- const CONFIG = {
28
- TIMEOUTS: {
29
- USER_DATA_OPERATION: 5000,
30
- },
31
- PASSWORD: {
32
- MIN_LENGTH: 8,
33
- REQUIRE_UPPERCASE: false,
34
- REQUIRE_LOWERCASE: false,
35
- REQUIRE_NUMBERS: false,
36
- REQUIRE_SPECIAL_CHARS: false,
37
- },
38
- };
39
- class DataBase {
40
- gun;
41
- user = null;
42
- crypto;
43
- sea;
44
- node;
45
- onAuthCallbacks = [];
46
- eventEmitter;
47
- // Integrated modules
48
- _rxjs;
49
- constructor(gun, appScope = "shogun") {
50
- // Initialize event emitter
51
- this.eventEmitter = new EventEmitter();
52
- // Validate Gun instance
53
- if (!gun) {
54
- throw new Error("Gun instance is required but was not provided");
55
- }
56
- if (typeof gun !== "object") {
57
- throw new Error(`Gun instance must be an object, received: ${typeof gun}`);
58
- }
59
- if (typeof gun.user !== "function") {
60
- throw new Error(`Gun instance is invalid: gun.user is not a function. Received gun.user type: ${typeof gun.user}`);
61
- }
62
- if (typeof gun.get !== "function") {
63
- throw new Error(`Gun instance is invalid: gun.get is not a function. Received gun.get type: ${typeof gun.get}`);
64
- }
65
- if (typeof gun.on !== "function") {
66
- throw new Error(`Gun instance is invalid: gun.on is not a function. Received gun.on type: ${typeof gun.on}`);
67
- }
68
- this.gun = gun;
69
- this.user = this.gun.user().recall({ sessionStorage: true });
70
- this.subscribeToAuthEvents();
71
- this.crypto = crypto;
72
- this.sea = SEA;
73
- this.node = null;
74
- }
75
- /**
76
- * Initialize the GunInstance asynchronously
77
- * This method should be called after construction to perform async operations
78
- */
79
- async initialize(appScope = "shogun") {
80
- try {
81
- const sessionResult = this.restoreSession();
82
- this.node = this.gun.get(appScope);
83
- if (sessionResult.success) {
84
- // Session automatically restored
85
- }
86
- else {
87
- // No previous session to restore
88
- }
89
- }
90
- catch (error) {
91
- console.error("Error during automatic session restoration:", error);
92
- }
93
- }
94
- subscribeToAuthEvents() {
95
- this.gun.on("auth", (ack) => {
96
- // Auth event received
97
- if (ack.err) {
98
- ErrorHandler.handle(ErrorType.GUN, "AUTH_EVENT_ERROR", ack.err, new Error(ack.err));
99
- }
100
- else {
101
- this.notifyAuthListeners(ack.sea?.pub || "");
102
- }
103
- });
104
- }
105
- notifyAuthListeners(pub) {
106
- const user = this.gun.user();
107
- this.onAuthCallbacks.forEach((cb) => cb(user));
108
- }
109
- /**
110
- * Adds a new peer to the network
111
- * @param peer URL of the peer to add
112
- */
113
- addPeer(peer) {
114
- this.gun.opt({ peers: [peer] });
115
- console.log(`Added new peer: ${peer}`);
116
- }
117
- /**
118
- * Removes a peer from the network
119
- * @param peer URL of the peer to remove
120
- */
121
- removePeer(peer) {
122
- try {
123
- // Get current peers from Gun instance
124
- const gunOpts = this.gun._.opt;
125
- if (gunOpts && gunOpts.peers) {
126
- // Remove the peer from the peers object
127
- delete gunOpts.peers[peer];
128
- // Also try to close the connection if it exists
129
- const peerConnection = gunOpts.peers[peer];
130
- if (peerConnection && typeof peerConnection.close === "function") {
131
- peerConnection.close();
132
- }
133
- console.log(`Removed peer: ${peer}`);
134
- }
135
- else {
136
- console.log(`Peer not found in current connections: ${peer}`);
137
- }
138
- }
139
- catch (error) {
140
- console.error(`Error removing peer ${peer}:`, error);
141
- }
142
- }
143
- /**
144
- * Gets the list of currently connected peers
145
- * @returns Array of peer URLs
146
- */
147
- getCurrentPeers() {
148
- try {
149
- const gunOpts = this.gun._.opt;
150
- if (gunOpts && gunOpts.peers) {
151
- return Object.keys(gunOpts.peers).filter((peer) => {
152
- const peerObj = gunOpts.peers[peer];
153
- // Check if peer is actually connected (not just configured)
154
- return peerObj && peerObj.wire && peerObj.wire.hied !== "bye";
155
- });
156
- }
157
- return [];
158
- }
159
- catch (error) {
160
- console.error("Error getting current peers:", error);
161
- return [];
162
- }
163
- }
164
- /**
165
- * Gets the list of all configured peers (connected and disconnected)
166
- * @returns Array of peer URLs
167
- */
168
- getAllConfiguredPeers() {
169
- try {
170
- const gunOpts = this.gun._.opt;
171
- if (gunOpts && gunOpts.peers) {
172
- return Object.keys(gunOpts.peers);
173
- }
174
- return [];
175
- }
176
- catch (error) {
177
- console.error("Error getting configured peers:", error);
178
- return [];
179
- }
180
- }
181
- /**
182
- * Gets detailed information about all peers
183
- * @returns Object with peer information
184
- */
185
- getPeerInfo() {
186
- try {
187
- const gunOpts = this.gun._.opt;
188
- const peerInfo = {};
189
- if (gunOpts && gunOpts.peers) {
190
- Object.keys(gunOpts.peers).forEach((peer) => {
191
- const peerObj = gunOpts.peers[peer];
192
- const isConnected = peerObj && peerObj.wire && peerObj.wire.hied !== "bye";
193
- const status = isConnected
194
- ? "connected"
195
- : peerObj && peerObj.wire
196
- ? "disconnected"
197
- : "not_initialized";
198
- peerInfo[peer] = {
199
- connected: isConnected,
200
- status: status,
201
- };
202
- });
203
- }
204
- return peerInfo;
205
- }
206
- catch (error) {
207
- console.error("Error getting peer info:", error);
208
- return {};
209
- }
210
- }
211
- /**
212
- * Reconnects to a specific peer
213
- * @param peer URL of the peer to reconnect
214
- */
215
- reconnectToPeer(peer) {
216
- try {
217
- // First remove the peer
218
- this.removePeer(peer);
219
- // Add it back immediately instead of with timeout
220
- this.addPeer(peer);
221
- console.log(`Reconnected to peer: ${peer}`);
222
- }
223
- catch (error) {
224
- console.error(`Error reconnecting to peer ${peer}:`, error);
225
- }
226
- }
227
- /**
228
- * Clears all peers and optionally adds new ones
229
- * @param newPeers Optional array of new peers to add
230
- */
231
- resetPeers(newPeers) {
232
- try {
233
- const gunOpts = this.gun._.opt;
234
- if (gunOpts && gunOpts.peers) {
235
- // Clear all existing peers
236
- Object.keys(gunOpts.peers).forEach((peer) => {
237
- this.removePeer(peer);
238
- });
239
- // Add new peers if provided
240
- if (newPeers && newPeers.length > 0) {
241
- newPeers.forEach((peer) => {
242
- this.addPeer(peer);
243
- });
244
- }
245
- console.log(`Gun database reset with ${newPeers ? newPeers.length : 0} peers: ${newPeers ? newPeers.join(", ") : "none"}`);
246
- }
247
- }
248
- catch (error) {
249
- console.error("Error resetting peers:", error);
250
- }
251
- }
252
- /**
253
- * Registers an authentication callback
254
- * @param callback Function to call on auth events
255
- * @returns Function to unsubscribe the callback
256
- */
257
- onAuth(callback) {
258
- this.onAuthCallbacks.push(callback);
259
- const user = this.gun.user();
260
- if (user && user.is)
261
- callback(user);
262
- return () => {
263
- const i = this.onAuthCallbacks.indexOf(callback);
264
- if (i !== -1)
265
- this.onAuthCallbacks.splice(i, 1);
266
- };
267
- }
268
- /**
269
- * Helper method to navigate to a nested path by splitting and chaining .get() calls
270
- * @param node Starting Gun node
271
- * @param path Path string (e.g., "test/data/marco")
272
- * @returns Gun node at the specified path
273
- */
274
- navigateToPath(node, path) {
275
- if (!path || typeof path !== "string")
276
- return node;
277
- // Sanitize path to remove any control characters or invalid characters
278
- const sanitizedPath = path
279
- .replace(/[\x00-\x1F\x7F]/g, "") // Remove control characters
280
- .replace(/[^\w\-._/]/g, "") // Only allow alphanumeric, hyphens, dots, underscores, and slashes
281
- .trim();
282
- if (!sanitizedPath)
283
- return node;
284
- // Split path by '/' and filter out empty segments
285
- const pathSegments = sanitizedPath
286
- .split("/")
287
- .filter((segment) => segment.length > 0)
288
- .map((segment) => segment.trim())
289
- .filter((segment) => segment.length > 0);
290
- // Chain .get() calls for each path segment
291
- return pathSegments.reduce((currentNode, segment) => {
292
- return currentNode.get(segment);
293
- }, node);
294
- }
295
- /**
296
- * Gets the Gun instance
297
- * @returns Gun instance
298
- */
299
- getGun() {
300
- return this.gun;
301
- }
302
- /**
303
- * Gets the current user
304
- * @returns Current user object or null
305
- */
306
- getCurrentUser() {
307
- try {
308
- const user = this.gun.user();
309
- const pub = user?.is?.pub;
310
- return pub ? { pub, user } : null;
311
- }
312
- catch (error) {
313
- console.error("Error getting current user:", error);
314
- return null;
315
- }
316
- }
317
- /**
318
- * Gets the current user instance
319
- * @returns User instance
320
- */
321
- getUser() {
322
- return this.gun.user();
323
- }
324
- /**
325
- * Gets a node at the specified path
326
- * @param path Path to the node
327
- * @returns Gun node
328
- */
329
- get(path) {
330
- return this.navigateToPath(this.gun, path);
331
- }
332
- /**
333
- * Gets data at the specified path (one-time read)
334
- * @param path Path to get the data from
335
- * @returns Promise resolving to the data
336
- */
337
- async getData(path) {
338
- const node = await this.navigateToPath(this.gun, path).then();
339
- return node;
340
- }
341
- /**
342
- * Puts data at the specified path
343
- * @param path Path to store data
344
- * @param data Data to store
345
- * @returns Promise resolving to operation result
346
- */
347
- async put(path, data) {
348
- const ack = await this.navigateToPath(this.gun, path).put(data).then();
349
- const result = ack.err
350
- ? { success: false, error: ack.err }
351
- : { success: true };
352
- return result;
353
- }
354
- /**
355
- * Sets data at the specified path
356
- * @param path Path to store data
357
- * @param data Data to store
358
- * @returns Promise resolving to operation result
359
- */
360
- async set(path, data) {
361
- const ack = await this.navigateToPath(this.gun, path).set(data).then();
362
- const result = ack.err
363
- ? { success: false, error: ack.err }
364
- : { success: true };
365
- return result;
366
- }
367
- /**
368
- * Removes data at the specified path
369
- * @param path Path to remove
370
- * @returns Promise resolving to operation result
371
- */
372
- async remove(path) {
373
- const ack = await this.navigateToPath(this.gun, path).put(null).then();
374
- const result = ack.err
375
- ? { success: false, error: ack.err }
376
- : { success: true };
377
- return result;
378
- }
379
- /**
380
- * Checks if a user is currently logged in
381
- * @returns True if logged in
382
- */
383
- isLoggedIn() {
384
- try {
385
- const user = this.gun.user();
386
- return !!(user && user.is && user.is.pub);
387
- }
388
- catch (error) {
389
- console.error("Error checking login status:", error);
390
- return false;
391
- }
392
- }
393
- /**
394
- * Attempts to restore user session from local storage
395
- * @returns Promise resolving to session restoration result
396
- */
397
- restoreSession() {
398
- try {
399
- if (typeof localStorage === "undefined") {
400
- return { success: false, error: "localStorage not available" };
401
- }
402
- const sessionInfo = localStorage.getItem("gun/session");
403
- const pairInfo = localStorage.getItem("gun/pair");
404
- if (!sessionInfo || !pairInfo) {
405
- // No saved session found
406
- return { success: false, error: "No saved session" };
407
- }
408
- let session, pair;
409
- try {
410
- session = JSON.parse(sessionInfo);
411
- pair = JSON.parse(pairInfo);
412
- }
413
- catch (parseError) {
414
- console.error("Error parsing session data:", parseError);
415
- // Clear corrupted data
416
- localStorage.removeItem("gun/session");
417
- localStorage.removeItem("gun/pair");
418
- return { success: false, error: "Corrupted session data" };
419
- }
420
- // Validate session data structure
421
- if (!session.username || !session.pair || !session.userPub) {
422
- // Invalid session data, clearing storage
423
- localStorage.removeItem("gun/session");
424
- localStorage.removeItem("gun/pair");
425
- return { success: false, error: "Incomplete session data" };
426
- }
427
- // Check if session is expired
428
- if (session.expiresAt && Date.now() > session.expiresAt) {
429
- // Session expired, clearing storage
430
- localStorage.removeItem("gun/session");
431
- localStorage.removeItem("gun/pair");
432
- return { success: false, error: "Session expired" };
433
- }
434
- // Attempt to restore user session
435
- try {
436
- const userInstance = this.gun.user();
437
- if (!userInstance) {
438
- console.error("Gun user instance not available");
439
- localStorage.removeItem("gun/session");
440
- localStorage.removeItem("gun/pair");
441
- return { success: false, error: "Gun user instance not available" };
442
- }
443
- // Set the user pair
444
- try {
445
- userInstance._ = { ...userInstance._, sea: session.pair };
446
- }
447
- catch (pairError) {
448
- console.error("Error setting user pair:", pairError);
449
- }
450
- // Attempt to recall user session
451
- try {
452
- const recallResult = userInstance.recall({ sessionStorage: true });
453
- // console.log("recallResult", recallResult);
454
- }
455
- catch (recallError) {
456
- console.error("Error during recall:", recallError);
457
- }
458
- // Verify session restoration success
459
- if (userInstance.is && userInstance.is.pub === session.userPub) {
460
- this.user = userInstance;
461
- // Session restored successfully for user
462
- return {
463
- success: true,
464
- userPub: session.userPub,
465
- };
466
- }
467
- else {
468
- // Session restoration verification failed
469
- localStorage.removeItem("gun/session");
470
- localStorage.removeItem("gun/pair");
471
- return { success: false, error: "Session verification failed" };
472
- }
473
- }
474
- catch (error) {
475
- console.error(`Error restoring session: ${error}`);
476
- return {
477
- success: false,
478
- error: `Session restoration failed: ${error}`,
479
- };
480
- }
481
- }
482
- catch (mainError) {
483
- console.error(`Error in restoreSession: ${mainError}`);
484
- return {
485
- success: false,
486
- error: `Session restoration failed: ${mainError}`,
487
- };
488
- }
489
- return { success: false, error: "No session data available" };
490
- }
491
- logout() {
492
- try {
493
- const currentUser = this.gun.user();
494
- if (!currentUser || !currentUser.is) {
495
- console.log("No user logged in, skipping logout");
496
- return;
497
- }
498
- // Log out user
499
- try {
500
- currentUser.leave();
501
- }
502
- catch (gunError) {
503
- console.error("Error during Gun logout:", gunError);
504
- }
505
- // Clear user reference
506
- this.user = null;
507
- // Clear local session data
508
- try {
509
- // Clear session data if needed
510
- }
511
- catch (error) {
512
- console.error("Error clearing local session data:", error);
513
- }
514
- // Clear session storage
515
- try {
516
- if (typeof sessionStorage !== "undefined") {
517
- sessionStorage.removeItem("gunSessionData");
518
- // Session storage cleared
519
- }
520
- }
521
- catch (error) {
522
- console.error("Error clearing session storage:", error);
523
- }
524
- // Logout completed successfully
525
- }
526
- catch (error) {
527
- console.error("Error during logout:", error);
528
- }
529
- }
530
- /**
531
- * Accesses the RxJS module for reactive programming
532
- * @returns GunRxJS instance
533
- */
534
- rx() {
535
- if (!this._rxjs) {
536
- this._rxjs = new RxJS(this.gun);
537
- }
538
- return this._rxjs;
539
- }
540
- /**
541
- * Validates password strength according to security requirements
542
- */
543
- validatePasswordStrength(password) {
544
- if (password.length < CONFIG.PASSWORD.MIN_LENGTH) {
545
- return {
546
- valid: false,
547
- error: `Password must be at least ${CONFIG.PASSWORD.MIN_LENGTH} characters long`,
548
- };
549
- }
550
- const validations = [];
551
- if (CONFIG.PASSWORD.REQUIRE_UPPERCASE && !/[A-Z]/.test(password)) {
552
- validations.push("uppercase letter");
553
- }
554
- if (CONFIG.PASSWORD.REQUIRE_LOWERCASE && !/[a-z]/.test(password)) {
555
- validations.push("lowercase letter");
556
- }
557
- if (CONFIG.PASSWORD.REQUIRE_NUMBERS && !/\d/.test(password)) {
558
- validations.push("number");
559
- }
560
- if (CONFIG.PASSWORD.REQUIRE_SPECIAL_CHARS &&
561
- !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
562
- validations.push("special character");
563
- }
564
- if (validations.length > 0) {
565
- return {
566
- valid: false,
567
- error: `Password must contain at least one: ${validations.join(", ")}`,
568
- };
569
- }
570
- return { valid: true };
571
- }
572
- /**
573
- * Validates signup credentials with enhanced security
574
- */
575
- validateSignupCredentials(username, password, pair) {
576
- // Validate username
577
- if (!username || username.length < 1) {
578
- return {
579
- valid: false,
580
- error: "Username must be more than 0 characters long!",
581
- };
582
- }
583
- // Validate username format (alphanumeric and some special chars only)
584
- if (!/^[a-zA-Z0-9._-]+$/.test(username)) {
585
- return {
586
- valid: false,
587
- error: "Username can only contain letters, numbers, dots, underscores, and hyphens",
588
- };
589
- }
590
- // If using pair authentication, skip password validation
591
- if (pair) {
592
- if (!pair.pub ||
593
- !pair.priv ||
594
- !pair.epub ||
595
- !pair.epriv) {
596
- return {
597
- valid: false,
598
- error: "Invalid pair provided",
599
- };
600
- }
601
- if (!pair.pub || !pair.priv || !pair.epub || !pair.epriv) {
602
- return {
603
- valid: false,
604
- error: "Invalid pair provided",
605
- };
606
- }
607
- return { valid: true };
608
- }
609
- // Validate password strength
610
- return this.validatePasswordStrength(password);
611
- }
612
- /**
613
- * Creates a new user in Gun
614
- */
615
- async createNewUser(username, password) {
616
- return new Promise((resolve) => {
617
- // Validate inputs before creating user
618
- if (!username ||
619
- typeof username !== "string" ||
620
- username.trim().length === 0) {
621
- resolve({ success: false, error: "Invalid username provided" });
622
- return;
623
- }
624
- if (!password ||
625
- typeof password !== "string" ||
626
- password.length === 0) {
627
- resolve({ success: false, error: "Invalid password provided" });
628
- return;
629
- }
630
- // Normalize username
631
- const normalizedUsername = username.trim().toLowerCase();
632
- if (normalizedUsername.length === 0) {
633
- resolve({
634
- success: false,
635
- error: "Username cannot be empty",
636
- });
637
- return;
638
- }
639
- this.gun.user().create(normalizedUsername, password, (ack) => {
640
- if (ack.err) {
641
- console.error(`User creation error: ${ack.err}`);
642
- resolve({ success: false, error: ack.err });
643
- }
644
- else {
645
- // Validate that we got a userPub from creation
646
- const userPub = ack.pub;
647
- if (!userPub ||
648
- typeof userPub !== "string" ||
649
- userPub.trim().length === 0) {
650
- console.error("User creation successful but no userPub returned:", ack);
651
- resolve({
652
- success: false,
653
- error: "User creation successful but no userPub returned",
654
- });
655
- }
656
- else {
657
- console.log(`User created successfully with userPub: ${userPub}`);
658
- resolve({ success: true, userPub: userPub });
659
- }
660
- }
661
- });
662
- });
663
- }
664
- /**
665
- * Authenticates user after creation
666
- */
667
- async authenticateNewUser(username, password, pair) {
668
- return new Promise((resolve) => {
669
- // Validate inputs before authentication
670
- if (!username ||
671
- typeof username !== "string" ||
672
- username.trim().length === 0) {
673
- resolve({ success: false, error: "Invalid username provided" });
674
- return;
675
- }
676
- // Skip password validation when using pair authentication
677
- if (!pair &&
678
- (!password || typeof password !== "string" || password.length === 0)) {
679
- resolve({ success: false, error: "Invalid password provided" });
680
- return;
681
- }
682
- // Normalize username to match what was used in creation
683
- const normalizedUsername = username.trim().toLowerCase();
684
- if (normalizedUsername.length === 0) {
685
- resolve({
686
- success: false,
687
- error: "Username cannot be empty",
688
- });
689
- return;
690
- }
691
- if (pair) {
692
- this.gun.user().auth(pair, (ack) => {
693
- console.log(`Pair authentication after creation result:`, ack);
694
- if (ack.err) {
695
- console.error(`Authentication after creation failed: ${ack.err}`);
696
- resolve({ success: false, error: ack.err });
697
- }
698
- else {
699
- // Add a small delay to ensure user state is properly set
700
- setTimeout(() => {
701
- // Extract userPub from multiple possible sources
702
- const userPub = ack.pub || this.gun.user().is?.pub || ack.user?.pub;
703
- console.log(`Extracted userPub after pair auth: ${userPub}`);
704
- console.log(`User object after pair auth:`, this.gun.user());
705
- console.log(`User.is after pair auth:`, this.gun.user().is);
706
- if (!userPub) {
707
- console.error("Authentication successful but no userPub found");
708
- resolve({
709
- success: false,
710
- error: "No userPub returned from authentication",
711
- });
712
- }
713
- else {
714
- resolve({ success: true, userPub: userPub });
715
- }
716
- }, 100);
717
- }
718
- });
719
- }
720
- else {
721
- this.gun.user().auth(normalizedUsername, password, (ack) => {
722
- console.log(`Password authentication after creation result:`, ack);
723
- if (ack.err) {
724
- console.error(`Authentication after creation failed: ${ack.err}`);
725
- resolve({ success: false, error: ack.err });
726
- }
727
- else {
728
- // Add a small delay to ensure user state is properly set
729
- setTimeout(() => {
730
- // Extract userPub from multiple possible sources
731
- const userPub = ack.pub || this.gun.user().is?.pub || ack.user?.pub;
732
- console.log(`Extracted userPub after password auth: ${userPub}`);
733
- console.log(`User object after password auth:`, this.gun.user());
734
- console.log(`User.is after password auth:`, this.gun.user().is);
735
- if (!userPub) {
736
- console.error("Authentication successful but no userPub found");
737
- resolve({
738
- success: false,
739
- error: "No userPub returned from authentication",
740
- });
741
- }
742
- else {
743
- resolve({ success: true, userPub: userPub });
744
- }
745
- }, 100);
746
- }
747
- });
748
- }
749
- });
750
- }
751
- /**
752
- * Signs up a new user using direct Gun authentication
753
- * @param username Username
754
- * @param password Password
755
- * @param pair Optional SEA pair for Web3 login
756
- * @returns Promise resolving to signup result
757
- */
758
- async signUp(username, password, pair) {
759
- try {
760
- // Validate credentials with enhanced security
761
- const validation = this.validateSignupCredentials(username, password, pair);
762
- if (!validation.valid) {
763
- return { success: false, error: validation.error };
764
- }
765
- let createResult;
766
- if (pair) {
767
- // For Web3/plugin authentication, use pair-based creation
768
- createResult = await this.createNewUserWithPair(username, pair);
769
- }
770
- else {
771
- // For password authentication, use standard creation
772
- createResult = await this.createNewUser(username, password);
773
- }
774
- if (!createResult.success) {
775
- return { success: false, error: createResult.error };
776
- }
777
- // Add a small delay to ensure user is properly registered
778
- await new Promise((resolve) => setTimeout(resolve, 100));
779
- // Authenticate the newly created user
780
- const authResult = await this.authenticateNewUser(username, password, pair);
781
- if (!authResult.success) {
782
- return { success: false, error: authResult.error };
783
- }
784
- // Validate that we have a userPub
785
- if (!authResult.userPub ||
786
- typeof authResult.userPub !== "string" ||
787
- authResult.userPub.trim().length === 0) {
788
- console.error("Authentication successful but no valid userPub returned:", authResult);
789
- return {
790
- success: false,
791
- error: "Authentication successful but no valid userPub returned",
792
- };
793
- }
794
- // Set the user instance
795
- this.user = this.gun.user();
796
- // Run post-authentication tasks
797
- try {
798
- console.log(`Running post-auth setup with userPub: ${authResult.userPub}`);
799
- const postAuthResult = await this.runPostAuthOnAuthResult(username, authResult.userPub, authResult);
800
- // Return the post-auth result which includes the complete user data
801
- return postAuthResult;
802
- }
803
- catch (postAuthError) {
804
- console.error(`Post-auth error: ${postAuthError}`);
805
- // Even if post-auth fails, the user was created and authenticated successfully
806
- return {
807
- success: true,
808
- userPub: authResult.userPub,
809
- username: username,
810
- isNewUser: true,
811
- sea: this.gun.user()?._?.sea
812
- ? {
813
- pub: this.gun.user()._?.sea.pub,
814
- priv: this.gun.user()._?.sea.priv,
815
- epub: this.gun.user()._?.sea.epub,
816
- epriv: this.gun.user()._?.sea.epriv,
817
- }
818
- : undefined,
819
- };
820
- }
821
- }
822
- catch (error) {
823
- console.error(`Exception during signup for ${username}: ${error}`);
824
- return { success: false, error: `Signup failed: ${error}` };
825
- }
826
- }
827
- /**
828
- * Creates a new user in Gun with pair-based authentication (for Web3/plugins)
829
- */
830
- async createNewUserWithPair(username, pair) {
831
- return new Promise((resolve) => {
832
- // Validate inputs before creating user
833
- if (!username ||
834
- typeof username !== "string" ||
835
- username.trim().length === 0) {
836
- resolve({ success: false, error: "Invalid username provided" });
837
- return;
838
- }
839
- if (!pair || !pair.pub || !pair.priv) {
840
- resolve({ success: false, error: "Invalid pair provided" });
841
- return;
842
- }
843
- // Normalize username
844
- const normalizedUsername = username.trim().toLowerCase();
845
- if (normalizedUsername.length === 0) {
846
- resolve({
847
- success: false,
848
- error: "Username cannot be empty",
849
- });
850
- return;
851
- }
852
- // For pair-based authentication, we don't need to call gun.user().create()
853
- // because the pair already contains the cryptographic credentials
854
- // We just need to validate that the pair is valid and return success
855
- console.log(`User created successfully with pair for: ${normalizedUsername}`);
856
- resolve({ success: true, userPub: pair.pub });
857
- });
858
- }
859
- async runPostAuthOnAuthResult(username, userPub, authResult) {
860
- // Setting up user profile after authentication
861
- try {
862
- // Validate required parameters
863
- if (!username ||
864
- typeof username !== "string" ||
865
- username.trim().length === 0) {
866
- throw new Error("Invalid username provided");
867
- }
868
- if (!userPub ||
869
- typeof userPub !== "string" ||
870
- userPub.trim().length === 0) {
871
- console.error("Invalid userPub provided:", {
872
- userPub,
873
- type: typeof userPub,
874
- authResult,
875
- });
876
- throw new Error("Invalid userPub provided");
877
- }
878
- // Additional validation for userPub format
879
- if (!userPub.includes(".") || userPub.length < 10) {
880
- console.error("Invalid userPub format:", userPub);
881
- throw new Error("Invalid userPub format");
882
- }
883
- // Normalize username to prevent path issues
884
- const normalizedUsername = username.trim().toLowerCase();
885
- if (normalizedUsername.length === 0) {
886
- throw new Error("Username cannot be empty");
887
- }
888
- console.log(`Setting up user profile for ${normalizedUsername} with userPub: ${userPub}`);
889
- const existingUser = await this.gun.get(userPub).once().then();
890
- const isNewUser = !existingUser || !existingUser.username;
891
- // Get user's encryption public key (epub) for comprehensive tracking
892
- const userInstance = this.gun.user();
893
- const userSea = userInstance?._?.sea;
894
- const epub = userSea?.epub || null;
895
- // Enhanced user tracking system
896
- await this.setupComprehensiveUserTracking(normalizedUsername, userPub, epub, isNewUser);
897
- return {
898
- success: true,
899
- userPub: userPub,
900
- username: normalizedUsername,
901
- isNewUser: isNewUser,
902
- // Get the SEA pair from the user object
903
- sea: userSea
904
- ? {
905
- pub: userSea.pub,
906
- priv: userSea.priv,
907
- epub: userSea.epub,
908
- epriv: userSea.epriv,
909
- }
910
- : undefined,
911
- };
912
- }
913
- catch (error) {
914
- console.error(`Error in post-authentication setup: ${error}`);
915
- return {
916
- success: false,
917
- error: `Post-authentication setup failed: ${error}`,
918
- };
919
- }
920
- }
921
- /**
922
- * Sets up comprehensive user tracking system for agile user lookup
923
- * Creates multiple indexes for efficient user discovery
924
- */
925
- async setupComprehensiveUserTracking(username, userPub, epub, isNewUser) {
926
- try {
927
- // 1. Create alias index: ~@alias -> userPub (for GunDB compatibility)
928
- await this.createAliasIndex(username, userPub);
929
- // 2. Create username mapping: usernames/alias -> userPub
930
- await this.createUsernameMapping(username, userPub);
931
- // 3. Create user registry: users/userPub -> user data
932
- await this.createUserRegistry(username, userPub, epub);
933
- // 4. Create reverse lookup: userPub -> alias
934
- await this.createReverseLookup(username, userPub);
935
- // 5. Create epub index: epubKeys/epub -> userPub (for encryption lookups)
936
- if (epub) {
937
- await this.createEpubIndex(epub, userPub);
938
- }
939
- // 6. Create user metadata in user's own node
940
- await this.createUserMetadata(username, userPub, epub);
941
- console.log(`Comprehensive user tracking setup completed for ${username}`);
942
- }
943
- catch (error) {
944
- console.error(`Error in comprehensive user tracking setup: ${error}`);
945
- // Don't throw - continue with other operations
946
- }
947
- }
948
- /**
949
- * Creates alias index following GunDB pattern: ~@alias -> userPub
950
- */
951
- async createAliasIndex(username, userPub) {
952
- try {
953
- const aliasNode = this.gun.get(`~@${username}`);
954
- // For Gun.js alias validation to pass, the data must be exactly equal to the key
955
- // The key is `~@${username}`, so we store that as the data
956
- const ack = await aliasNode.put(`~@${username}`).then();
957
- if (ack.err) {
958
- console.error(`Error creating alias index: ${ack.err}`);
959
- }
960
- else {
961
- console.log(`Alias index created: ~@${username} -> ${userPub}`);
962
- }
963
- }
964
- catch (error) {
965
- console.error(`Error creating alias index: ${error}`);
966
- }
967
- }
968
- /**
969
- * Creates username mapping: usernames/alias -> userPub
970
- */
971
- async createUsernameMapping(username, userPub) {
972
- try {
973
- const ack = await this.node
974
- .get("usernames")
975
- .get(username)
976
- .put(userPub)
977
- .then();
978
- if (ack.err) {
979
- console.error(`Error creating username mapping: ${ack.err}`);
980
- }
981
- else {
982
- console.log(`Username mapping created: ${username} -> ${userPub}`);
983
- }
984
- }
985
- catch (error) {
986
- console.error(`Error creating username mapping: ${error}`);
987
- }
988
- }
989
- /**
990
- * Creates user registry: users/userPub -> user data
991
- */
992
- async createUserRegistry(username, userPub, epub) {
993
- try {
994
- const userData = {
995
- username: username,
996
- userPub: userPub,
997
- epub: epub,
998
- registeredAt: Date.now(),
999
- lastSeen: Date.now(),
1000
- };
1001
- const ack = await this.node
1002
- .get("users")
1003
- .get(userPub)
1004
- .put(userData)
1005
- .then();
1006
- if (ack.err) {
1007
- console.error(`Error creating user registry: ${ack.err}`);
1008
- }
1009
- else {
1010
- console.log(`User registry created: ${userPub}`);
1011
- }
1012
- }
1013
- catch (error) {
1014
- console.error(`Error creating user registry: ${error}`);
1015
- }
1016
- }
1017
- /**
1018
- * Creates reverse lookup: userPub -> alias
1019
- */
1020
- async createReverseLookup(username, userPub) {
1021
- try {
1022
- const ack = await this.node
1023
- .get("userAliases")
1024
- .get(userPub)
1025
- .put(username)
1026
- .then();
1027
- if (ack.err) {
1028
- console.error(`Error creating reverse lookup: ${ack.err}`);
1029
- }
1030
- else {
1031
- console.log(`Reverse lookup created: ${userPub} -> ${username}`);
1032
- }
1033
- }
1034
- catch (error) {
1035
- console.error(`Error creating reverse lookup: ${error}`);
1036
- }
1037
- }
1038
- /**
1039
- * Creates epub index: epubKeys/epub -> userPub
1040
- */
1041
- async createEpubIndex(epub, userPub) {
1042
- try {
1043
- const ack = await this.node.get("epubKeys").get(epub).put(userPub).then();
1044
- if (ack.err) {
1045
- console.error(`Error creating epub index: ${ack.err}`);
1046
- }
1047
- else {
1048
- console.log(`Epub index created: ${epub} -> ${userPub}`);
1049
- }
1050
- }
1051
- catch (error) {
1052
- console.error(`Error creating epub index: ${error}`);
1053
- }
1054
- }
1055
- /**
1056
- * Creates user metadata in user's own node
1057
- */
1058
- async createUserMetadata(username, userPub, epub) {
1059
- try {
1060
- const userMetadata = {
1061
- username: username,
1062
- epub: epub,
1063
- registeredAt: Date.now(),
1064
- lastSeen: Date.now(),
1065
- };
1066
- const ack = await this.gun.get(userPub).put(userMetadata).then();
1067
- if (ack.err) {
1068
- console.error(`Error creating user metadata: ${ack.err}`);
1069
- }
1070
- else {
1071
- console.log(`User metadata created for ${userPub}`);
1072
- }
1073
- }
1074
- catch (error) {
1075
- console.error(`Error creating user metadata: ${error}`);
1076
- }
1077
- }
1078
- /**
1079
- * Gets user information by alias using the comprehensive tracking system
1080
- * @param alias Username/alias to lookup
1081
- * @returns Promise resolving to user information or null if not found
1082
- */
1083
- async getUserByAlias(alias) {
1084
- try {
1085
- const normalizedAlias = alias.trim().toLowerCase();
1086
- if (!normalizedAlias) {
1087
- return null;
1088
- }
1089
- // Method 1: Try GunDB standard alias lookup (~@alias)
1090
- try {
1091
- const aliasData = await this.gun
1092
- .get(`~@${normalizedAlias}`)
1093
- .once()
1094
- .then();
1095
- if (aliasData && aliasData["~pubKeyOfUser"]) {
1096
- const userPub = aliasData["~pubKeyOfUser"]["#"] || aliasData["~pubKeyOfUser"];
1097
- if (userPub) {
1098
- const userData = await this.getUserDataByPub(userPub);
1099
- if (userData) {
1100
- return userData;
1101
- }
1102
- }
1103
- }
1104
- }
1105
- catch (error) {
1106
- console.log(`GunDB alias lookup failed for ${normalizedAlias}:`, error);
1107
- }
1108
- // Method 2: Try username mapping (usernames/alias -> userPub)
1109
- try {
1110
- const userPub = await this.node
1111
- .get("usernames")
1112
- .get(normalizedAlias)
1113
- .once()
1114
- .then();
1115
- if (userPub) {
1116
- const userData = await this.getUserDataByPub(userPub);
1117
- if (userData) {
1118
- return userData;
1119
- }
1120
- }
1121
- }
1122
- catch (error) {
1123
- console.log(`Username mapping lookup failed for ${normalizedAlias}:`, error);
1124
- }
1125
- return null;
1126
- }
1127
- catch (error) {
1128
- console.error(`Error looking up user by alias ${alias}:`, error);
1129
- return null;
1130
- }
1131
- }
1132
- /**
1133
- * Gets user information by public key
1134
- * @param userPub User's public key
1135
- * @returns Promise resolving to user information or null if not found
1136
- */
1137
- async getUserDataByPub(userPub) {
1138
- try {
1139
- if (!userPub || typeof userPub !== "string") {
1140
- return null;
1141
- }
1142
- // Method 1: Try user registry (users/userPub -> user data)
1143
- try {
1144
- const userData = await this.node
1145
- .get("users")
1146
- .get(userPub)
1147
- .once()
1148
- .then();
1149
- if (userData && userData.username) {
1150
- return {
1151
- userPub: userData.userPub || userPub,
1152
- epub: userData.epub || null,
1153
- username: userData.username,
1154
- registeredAt: userData.registeredAt || 0,
1155
- lastSeen: userData.lastSeen || 0,
1156
- };
1157
- }
1158
- }
1159
- catch (error) {
1160
- console.log(`User registry lookup failed for ${userPub}:`, error);
1161
- }
1162
- // Method 2: Try user's own node
1163
- try {
1164
- const userNodeData = await this.gun.get(userPub).once().then();
1165
- if (userNodeData && userNodeData.username) {
1166
- return {
1167
- userPub: userPub,
1168
- epub: userNodeData.epub || null,
1169
- username: userNodeData.username,
1170
- registeredAt: userNodeData.registeredAt || 0,
1171
- lastSeen: userNodeData.lastSeen || 0,
1172
- };
1173
- }
1174
- }
1175
- catch (error) {
1176
- console.log(`User node lookup failed for ${userPub}:`, error);
1177
- }
1178
- return null;
1179
- }
1180
- catch (error) {
1181
- console.error(`Error looking up user data by pub ${userPub}:`, error);
1182
- return null;
1183
- }
1184
- }
1185
- /**
1186
- * Gets user public key by encryption public key (epub)
1187
- * @param epub User's encryption public key
1188
- * @returns Promise resolving to user public key or null if not found
1189
- */
1190
- async getUserPubByEpub(epub) {
1191
- try {
1192
- if (!epub || typeof epub !== "string") {
1193
- return null;
1194
- }
1195
- const userPub = await this.node.get("epubKeys").get(epub).once().then();
1196
- return userPub || null;
1197
- }
1198
- catch (error) {
1199
- console.error(`Error looking up user pub by epub ${epub}:`, error);
1200
- return null;
1201
- }
1202
- }
1203
- /**
1204
- * Gets user alias by public key
1205
- * @param userPub User's public key
1206
- * @returns Promise resolving to user alias or null if not found
1207
- */
1208
- async getUserAliasByPub(userPub) {
1209
- try {
1210
- if (!userPub || typeof userPub !== "string") {
1211
- return null;
1212
- }
1213
- const alias = await this.node
1214
- .get("userAliases")
1215
- .get(userPub)
1216
- .once()
1217
- .then();
1218
- return alias || null;
1219
- }
1220
- catch (error) {
1221
- console.error(`Error looking up user alias by pub ${userPub}:`, error);
1222
- return null;
1223
- }
1224
- }
1225
- /**
1226
- * Gets all registered users (for admin purposes)
1227
- * @returns Promise resolving to array of user information
1228
- */
1229
- async getAllRegisteredUsers() {
1230
- try {
1231
- const users = [];
1232
- // Get all users from the users registry
1233
- const usersNode = this.node.get("users");
1234
- // Note: This is a simplified approach. In a real implementation,
1235
- // you might want to use Gun's map functionality or iterate through
1236
- // known user public keys
1237
- return users;
1238
- }
1239
- catch (error) {
1240
- console.error(`Error getting all registered users:`, error);
1241
- return [];
1242
- }
1243
- }
1244
- /**
1245
- * Updates user's last seen timestamp
1246
- * @param userPub User's public key
1247
- */
1248
- async updateUserLastSeen(userPub) {
1249
- try {
1250
- if (!userPub || typeof userPub !== "string") {
1251
- return;
1252
- }
1253
- const timestamp = Date.now();
1254
- // Update in user registry
1255
- try {
1256
- await this.node
1257
- .get("users")
1258
- .get(userPub)
1259
- .get("lastSeen")
1260
- .put(timestamp)
1261
- .then();
1262
- }
1263
- catch (error) {
1264
- console.log(`Failed to update lastSeen in user registry:`, error);
1265
- }
1266
- // Update in user's own node
1267
- try {
1268
- await this.gun.get(userPub).get("lastSeen").put(timestamp).then();
1269
- }
1270
- catch (error) {
1271
- console.log(`Failed to update lastSeen in user node:`, error);
1272
- }
1273
- }
1274
- catch (error) {
1275
- console.error(`Error updating user last seen for ${userPub}:`, error);
1276
- }
1277
- }
1278
- /**
1279
- * Performs authentication with Gun
1280
- */
1281
- async performAuthentication(username, password, pair) {
1282
- return new Promise((resolve) => {
1283
- console.log(`Attempting authentication for user: ${username}`);
1284
- if (pair) {
1285
- this.gun.user().auth(pair, (ack) => {
1286
- console.log(`Pair authentication result:`, ack);
1287
- if (ack.err) {
1288
- console.error(`Login error for ${username}: ${ack.err}`);
1289
- resolve({ success: false, error: ack.err });
1290
- }
1291
- else {
1292
- resolve({ success: true, ack });
1293
- }
1294
- });
1295
- }
1296
- else {
1297
- this.gun.user().auth(username, password, (ack) => {
1298
- console.log(`Password authentication result:`, ack);
1299
- if (ack.err) {
1300
- console.error(`Login error for ${username}: ${ack.err}`);
1301
- resolve({ success: false, error: ack.err });
1302
- }
1303
- else {
1304
- resolve({ success: true, ack });
1305
- }
1306
- });
1307
- }
1308
- });
1309
- }
1310
- /**
1311
- * Builds login result object
1312
- */
1313
- buildLoginResult(username, userPub) {
1314
- // Get the SEA pair from the user object
1315
- const seaPair = this.gun.user()?._?.sea;
1316
- return {
1317
- success: true,
1318
- userPub,
1319
- username,
1320
- // Include SEA pair for consistency with AuthResult interface
1321
- sea: seaPair
1322
- ? {
1323
- pub: seaPair.pub,
1324
- priv: seaPair.priv,
1325
- epub: seaPair.epub,
1326
- epriv: seaPair.epriv,
1327
- }
1328
- : undefined,
1329
- };
1330
- }
1331
- async login(username, password, pair) {
1332
- try {
1333
- const loginResult = await this.performAuthentication(username, password, pair);
1334
- if (!loginResult.success) {
1335
- return {
1336
- success: false,
1337
- error: `User '${username}' not found. Please check your username or register first.`,
1338
- };
1339
- }
1340
- // Add a small delay to ensure user state is properly set
1341
- await new Promise((resolve) => setTimeout(resolve, 100));
1342
- const userPub = this.gun.user().is?.pub;
1343
- console.log(`Login authentication successful, extracted userPub: ${userPub}`);
1344
- console.log(`User object:`, this.gun.user());
1345
- console.log(`User.is:`, this.gun.user().is);
1346
- if (!userPub) {
1347
- return {
1348
- success: false,
1349
- error: "Authentication failed: No user pub returned.",
1350
- };
1351
- }
1352
- // Pass the userPub to runPostAuthOnAuthResult
1353
- try {
1354
- await this.runPostAuthOnAuthResult(username, userPub, {
1355
- success: true,
1356
- userPub: userPub,
1357
- });
1358
- }
1359
- catch (postAuthError) {
1360
- console.error(`Post-auth error during login: ${postAuthError}`);
1361
- // Continue with login even if post-auth fails
1362
- }
1363
- // Update user's last seen timestamp
1364
- try {
1365
- await this.updateUserLastSeen(userPub);
1366
- }
1367
- catch (lastSeenError) {
1368
- console.error(`Error updating last seen: ${lastSeenError}`);
1369
- // Continue with login even if last seen update fails
1370
- }
1371
- // Save credentials for future sessions
1372
- try {
1373
- const userInfo = {
1374
- username,
1375
- pair: pair ?? null,
1376
- userPub: userPub,
1377
- };
1378
- this.saveCredentials(userInfo);
1379
- }
1380
- catch (saveError) {
1381
- console.error(`Error saving credentials:`, saveError);
1382
- }
1383
- return this.buildLoginResult(username, userPub);
1384
- }
1385
- catch (error) {
1386
- console.error(`Exception during login for ${username}: ${error}`);
1387
- return { success: false, error: String(error) };
1388
- }
1389
- }
1390
- /**
1391
- * Encrypts session data before storage
1392
- */
1393
- async encryptSessionData(data) {
1394
- try {
1395
- // Use a derived key from device fingerprint for encryption
1396
- const deviceInfo = navigator.userAgent +
1397
- (typeof screen !== "undefined"
1398
- ? screen.width + "x" + screen.height
1399
- : "");
1400
- const encryptionKey = await SEA.work(deviceInfo, null, null, {
1401
- name: "SHA-256",
1402
- });
1403
- if (!encryptionKey) {
1404
- throw new Error("Failed to generate encryption key");
1405
- }
1406
- const encryptedData = await SEA.encrypt(JSON.stringify(data), encryptionKey);
1407
- if (!encryptedData) {
1408
- throw new Error("Failed to encrypt session data");
1409
- }
1410
- return encryptedData;
1411
- }
1412
- catch (error) {
1413
- console.error("Error encrypting session data:", error);
1414
- throw error;
1415
- }
1416
- }
1417
- /**
1418
- * Decrypts session data from storage
1419
- */
1420
- async decryptSessionData(encryptedData) {
1421
- try {
1422
- // Use the same device fingerprint for decryption
1423
- const deviceInfo = navigator.userAgent +
1424
- (typeof screen !== "undefined"
1425
- ? screen.width + "x" + screen.height
1426
- : "");
1427
- const encryptionKey = await SEA.work(deviceInfo, null, null, {
1428
- name: "SHA-256",
1429
- });
1430
- if (!encryptionKey) {
1431
- throw new Error("Failed to generate decryption key");
1432
- }
1433
- const decryptedData = await SEA.decrypt(encryptedData, encryptionKey);
1434
- if (decryptedData === undefined) {
1435
- throw new Error("Failed to decrypt session data");
1436
- }
1437
- return JSON.parse(decryptedData);
1438
- }
1439
- catch (error) {
1440
- console.error("Error decrypting session data:", error);
1441
- throw error;
1442
- }
1443
- }
1444
- saveCredentials(userInfo) {
1445
- try {
1446
- const sessionInfo = {
1447
- username: userInfo.username,
1448
- pair: userInfo.pair,
1449
- userPub: userInfo.userPub,
1450
- timestamp: Date.now(),
1451
- expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days
1452
- };
1453
- if (typeof sessionStorage !== "undefined") {
1454
- // Encrypt session data before storage
1455
- this.encryptSessionData(sessionInfo)
1456
- .then((encryptedData) => {
1457
- sessionStorage.setItem("gunSessionData", encryptedData);
1458
- })
1459
- .catch((error) => {
1460
- console.error("Failed to encrypt and save session data:", error);
1461
- // Fallback to unencrypted storage (less secure)
1462
- sessionStorage.setItem("gunSessionData", JSON.stringify(sessionInfo));
1463
- });
1464
- }
1465
- }
1466
- catch (error) {
1467
- console.error(`Error saving credentials: ${error}`);
1468
- }
1469
- }
1470
- /**
1471
- * Sets up security questions and password hint
1472
- * @param username Username
1473
- * @param password Current password
1474
- * @param hint Password hint
1475
- * @param securityQuestions Array of security questions
1476
- * @param securityAnswers Array of answers to security questions
1477
- * @returns Promise resolving with the operation result
1478
- */
1479
- async setPasswordHintWithSecurity(username, password, hint, securityQuestions, securityAnswers) {
1480
- // Setting password hint for
1481
- // Verify that the user is authenticated with password
1482
- const loginResult = await this.login(username, password);
1483
- if (!loginResult.success) {
1484
- return { success: false, error: "Authentication failed" };
1485
- }
1486
- // Check if user was authenticated with password (not with other methods)
1487
- const currentUser = this.getCurrentUser();
1488
- if (!currentUser || !currentUser.pub) {
1489
- return { success: false, error: "User not authenticated" };
1490
- }
1491
- try {
1492
- // Generate a proof of work from security question answers
1493
- const answersText = securityAnswers.join("|");
1494
- let proofOfWork;
1495
- try {
1496
- // Use SEA directly if available
1497
- if (SEA && SEA.work) {
1498
- proofOfWork = await SEA.work(answersText, null, null, {
1499
- name: "SHA-256",
1500
- });
1501
- }
1502
- else if (this.crypto && this.crypto.hashText) {
1503
- proofOfWork = await this.crypto.hashText(answersText);
1504
- }
1505
- else {
1506
- throw new Error("Cryptographic functions not available");
1507
- }
1508
- if (!proofOfWork) {
1509
- throw new Error("Failed to generate hash");
1510
- }
1511
- }
1512
- catch (hashError) {
1513
- console.error("Error generating hash:", hashError);
1514
- return { success: false, error: "Failed to generate security hash" };
1515
- }
1516
- // Encrypt the password hint with the proof of work
1517
- let encryptedHint;
1518
- try {
1519
- if (SEA && SEA.encrypt) {
1520
- encryptedHint = await SEA.encrypt(hint, proofOfWork);
1521
- }
1522
- else if (this.crypto && this.crypto.encrypt) {
1523
- encryptedHint = await this.crypto.encrypt(hint, proofOfWork);
1524
- }
1525
- else {
1526
- throw new Error("Encryption functions not available");
1527
- }
1528
- if (!encryptedHint) {
1529
- throw new Error("Failed to encrypt hint");
1530
- }
1531
- }
1532
- catch (encryptError) {
1533
- console.error("Error encrypting hint:", encryptError);
1534
- return { success: false, error: "Failed to encrypt password hint" };
1535
- }
1536
- // Save to the public graph, readable by anyone but only decryptable with the right answers.
1537
- const userPub = currentUser.pub;
1538
- const securityPayload = {
1539
- questions: JSON.stringify(securityQuestions),
1540
- hint: encryptedHint,
1541
- };
1542
- const ack = await this.node.get(userPub)
1543
- .get("security")
1544
- .put(securityPayload)
1545
- .then();
1546
- if (ack.err) {
1547
- console.error("Error saving security data to public graph:", ack.err);
1548
- throw new Error(ack.err);
1549
- }
1550
- return { success: true };
1551
- }
1552
- catch (error) {
1553
- console.error("Error setting password hint:", error);
1554
- return { success: false, error: String(error) };
1555
- }
1556
- }
1557
- /**
1558
- * Recovers password hint using security question answers
1559
- * @param username Username
1560
- * @param securityAnswers Array of answers to security questions
1561
- * @returns Promise resolving with the password hint
1562
- */
1563
- async forgotPassword(username, securityAnswers) {
1564
- // Attempting password recovery for
1565
- try {
1566
- // Find the user's data using direct lookup
1567
- const normalizedUsername = username.trim().toLowerCase();
1568
- const userPub = (await this.node
1569
- .get("usernames")
1570
- .get(normalizedUsername)
1571
- .once()
1572
- .then()) || null;
1573
- if (!userPub) {
1574
- return { success: false, error: "User not found" };
1575
- }
1576
- // console.log(`Found user public key for password recovery: ${userPub}`);
1577
- // Access the user's security data directly from their public key node
1578
- const securityData = await this.node.get(userPub)
1579
- .get("security")
1580
- .once()
1581
- .then();
1582
- if (!securityData || !securityData.hint) {
1583
- return {
1584
- success: false,
1585
- error: "No password hint found",
1586
- };
1587
- }
1588
- // Generate hash from security answers
1589
- const answersText = securityAnswers.join("|");
1590
- let proofOfWork;
1591
- try {
1592
- // Use SEA directly if available
1593
- if (SEA && SEA.work) {
1594
- proofOfWork = await SEA.work(answersText, null, null, {
1595
- name: "SHA-256",
1596
- });
1597
- }
1598
- else if (this.crypto && this.crypto.hashText) {
1599
- proofOfWork = await this.crypto.hashText(answersText);
1600
- }
1601
- else {
1602
- throw new Error("Cryptographic functions not available");
1603
- }
1604
- if (!proofOfWork) {
1605
- throw new Error("Failed to generate hash");
1606
- }
1607
- }
1608
- catch (hashError) {
1609
- console.error("Error generating hash:", hashError);
1610
- return { success: false, error: "Failed to generate security hash" };
1611
- }
1612
- // Decrypt the password hint with the proof of work
1613
- let hint;
1614
- try {
1615
- if (SEA && SEA.decrypt) {
1616
- hint = await SEA.decrypt(securityData.hint, proofOfWork);
1617
- }
1618
- else if (this.crypto && this.crypto.decrypt) {
1619
- hint = await this.crypto.decrypt(securityData.hint, proofOfWork);
1620
- }
1621
- else {
1622
- throw new Error("Decryption functions not available");
1623
- }
1624
- }
1625
- catch (decryptError) {
1626
- return {
1627
- success: false,
1628
- error: "Incorrect answers to security questions",
1629
- };
1630
- }
1631
- if (hint === undefined) {
1632
- return {
1633
- success: false,
1634
- error: "Incorrect answers to security questions",
1635
- };
1636
- }
1637
- return { success: true, hint: hint };
1638
- }
1639
- catch (error) {
1640
- console.error("Error recovering password hint:", error);
1641
- return { success: false, error: String(error) };
1642
- }
1643
- }
1644
- /**
1645
- * Saves user data at the specified path
1646
- * @param path Path to save the data (supports nested paths like "test/data/marco")
1647
- * @param data Data to save
1648
- * @returns Promise that resolves when the data is saved
1649
- */
1650
- async putUserData(path, data) {
1651
- const user = this.gun.user();
1652
- if (!user.is) {
1653
- throw new Error("User not authenticated");
1654
- }
1655
- const ack = await this.navigateToPath(user, path).put(data).then();
1656
- if (ack.err) {
1657
- throw new Error(ack.err);
1658
- }
1659
- }
1660
- /**
1661
- * Gets user data from the specified path
1662
- * @param path Path to get the data from (supports nested paths like "test/data/marco")
1663
- * @returns Promise that resolves with the data
1664
- */
1665
- async getUserData(path) {
1666
- // Validazione del path
1667
- if (!path || typeof path !== "string") {
1668
- throw new Error("Path must be a non-empty string");
1669
- }
1670
- const user = this.gun.user();
1671
- if (!user.is) {
1672
- throw new Error("User not authenticated");
1673
- }
1674
- try {
1675
- const data = await this.navigateToPath(user, path).once().then();
1676
- // Gestisci i riferimenti GunDB
1677
- if (data && typeof data === "object" && data["#"]) {
1678
- // È un riferimento GunDB, carica i dati effettivi
1679
- const referencePath = data["#"];
1680
- const actualData = await this.navigateToPath(this.gun, referencePath)
1681
- .once()
1682
- .then();
1683
- return actualData;
1684
- }
1685
- else {
1686
- // Dati diretti, restituisci così come sono
1687
- return data;
1688
- }
1689
- }
1690
- catch (error) {
1691
- const errorMsg = error instanceof Error ? error.message : "Unknown error";
1692
- throw new Error(errorMsg);
1693
- }
1694
- }
1695
- // Errors
1696
- static Errors = GunErrors;
1697
- /**
1698
- * Adds an event listener
1699
- * @param event Event name
1700
- * @param listener Event listener function
1701
- */
1702
- on(event, listener) {
1703
- this.eventEmitter.on(event, listener);
1704
- }
1705
- /**
1706
- * Removes an event listener
1707
- * @param event Event name
1708
- * @param listener Event listener function
1709
- */
1710
- off(event, listener) {
1711
- this.eventEmitter.off(event, listener);
1712
- }
1713
- /**
1714
- * Adds a one-time event listener
1715
- * @param event Event name
1716
- * @param listener Event listener function
1717
- */
1718
- once(event, listener) {
1719
- this.eventEmitter.once(event, listener);
1720
- }
1721
- /**
1722
- * Emits an event
1723
- * @param event Event name
1724
- * @param data Event data
1725
- */
1726
- emit(event, data) {
1727
- return this.eventEmitter.emit(event, data);
1728
- }
1729
- /**
1730
- * Recall user session
1731
- */
1732
- recall() {
1733
- if (this.user) {
1734
- this.user.recall({ sessionStorage: true });
1735
- }
1736
- }
1737
- /**
1738
- * Leave user session
1739
- */
1740
- leave() {
1741
- if (this.user) {
1742
- this.user.leave();
1743
- }
1744
- }
1745
- /**
1746
- * Set user data
1747
- */
1748
- setUserData(data) {
1749
- if (this.user) {
1750
- this.user.put(data);
1751
- }
1752
- }
1753
- /**
1754
- * Set password hint
1755
- */
1756
- setPasswordHint(hint) {
1757
- if (this.user) {
1758
- try {
1759
- this.user.get("passwordHint").put(hint);
1760
- }
1761
- catch (error) {
1762
- // Handle case where user.get returns undefined
1763
- console.warn("Could not set password hint:", error);
1764
- }
1765
- }
1766
- }
1767
- /**
1768
- * Get password hint
1769
- */
1770
- getPasswordHint() {
1771
- if (this.user) {
1772
- // Access passwordHint from user data, not from is object
1773
- return this.user.passwordHint || null;
1774
- }
1775
- return null;
1776
- }
1777
- /**
1778
- * Save session to storage
1779
- */
1780
- saveSession(session) {
1781
- if (this.user) {
1782
- this.user.recall({ sessionStorage: true });
1783
- }
1784
- }
1785
- /**
1786
- * Load session from storage
1787
- */
1788
- loadSession() {
1789
- if (this.user) {
1790
- return this.user.recall({ sessionStorage: true });
1791
- }
1792
- return null;
1793
- }
1794
- /**
1795
- * Clear session
1796
- */
1797
- clearSession() {
1798
- if (this.user) {
1799
- this.user.leave();
1800
- }
1801
- }
1802
- /**
1803
- * Get app scope
1804
- */
1805
- getAppScope() {
1806
- return this.node?._?.soul || "shogun";
1807
- }
1808
- /**
1809
- * Get user public key
1810
- */
1811
- getUserPub() {
1812
- if (this.user) {
1813
- return this.user.is?.pub || null;
1814
- }
1815
- return null;
1816
- }
1817
- /**
1818
- * Check if user is authenticated
1819
- */
1820
- isAuthenticated() {
1821
- return this.user?.is?.pub ? true : false;
1822
- }
1823
- }
1824
- const createGun = (config) => {
1825
- console.log("Creating Gun instance with config:", config);
1826
- console.log("Config peers:", config?.peers);
1827
- const gunInstance = new Gun(config);
1828
- console.log("Created Gun instance:", gunInstance);
1829
- console.log("Gun instance opt after creation:", gunInstance?.opt);
1830
- return gunInstance;
1831
- };
1832
- export { Gun, DataBase, SEA, RxJS, crypto, GunErrors, derive, restrictedPut, createGun, };
1833
- export default Gun;