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.
- package/package.json +1 -1
- package/dist/browser/shogun-core.js +0 -92128
- package/dist/browser/shogun-core.js.map +0 -1
- package/dist/config/simplified-config.js +0 -230
- package/dist/core.js +0 -338
- package/dist/gundb/crypto.js +0 -268
- package/dist/gundb/db.js +0 -1833
- package/dist/gundb/derive.js +0 -229
- package/dist/gundb/errors.js +0 -66
- package/dist/gundb/index.js +0 -6
- package/dist/gundb/restricted-put.js +0 -81
- package/dist/gundb/rxjs.js +0 -445
- package/dist/gundb/simple-api.js +0 -438
- package/dist/gundb/types.js +0 -4
- package/dist/index.js +0 -16
- package/dist/interfaces/common.js +0 -1
- package/dist/interfaces/events.js +0 -36
- package/dist/interfaces/plugin.js +0 -1
- package/dist/interfaces/shogun.js +0 -34
- package/dist/managers/AuthManager.js +0 -225
- package/dist/managers/CoreInitializer.js +0 -234
- package/dist/managers/EventManager.js +0 -67
- package/dist/managers/PluginManager.js +0 -296
- package/dist/migration-test.js +0 -91
- package/dist/plugins/base.js +0 -47
- package/dist/plugins/index.js +0 -15
- package/dist/plugins/nostr/index.js +0 -4
- package/dist/plugins/nostr/nostrConnector.js +0 -413
- package/dist/plugins/nostr/nostrConnectorPlugin.js +0 -446
- package/dist/plugins/nostr/nostrSigner.js +0 -313
- package/dist/plugins/nostr/types.js +0 -1
- package/dist/plugins/oauth/index.js +0 -3
- package/dist/plugins/oauth/oauthConnector.js +0 -753
- package/dist/plugins/oauth/oauthPlugin.js +0 -396
- package/dist/plugins/oauth/types.js +0 -1
- package/dist/plugins/web3/index.js +0 -4
- package/dist/plugins/web3/types.js +0 -1
- package/dist/plugins/web3/web3Connector.js +0 -528
- package/dist/plugins/web3/web3ConnectorPlugin.js +0 -448
- package/dist/plugins/web3/web3Signer.js +0 -308
- package/dist/plugins/webauthn/index.js +0 -3
- package/dist/plugins/webauthn/types.js +0 -11
- package/dist/plugins/webauthn/webauthn.js +0 -478
- package/dist/plugins/webauthn/webauthnPlugin.js +0 -398
- package/dist/plugins/webauthn/webauthnSigner.js +0 -304
- package/dist/storage/storage.js +0 -147
- package/dist/types/config/simplified-config.d.ts +0 -114
- package/dist/types/core.d.ts +0 -305
- package/dist/types/gundb/crypto.d.ts +0 -95
- package/dist/types/gundb/db.d.ts +0 -404
- package/dist/types/gundb/derive.d.ts +0 -21
- package/dist/types/gundb/errors.d.ts +0 -42
- package/dist/types/gundb/index.d.ts +0 -3
- package/dist/types/gundb/restricted-put.d.ts +0 -15
- package/dist/types/gundb/rxjs.d.ts +0 -110
- package/dist/types/gundb/simple-api.d.ts +0 -90
- package/dist/types/gundb/types.d.ts +0 -264
- package/dist/types/index.d.ts +0 -14
- package/dist/types/interfaces/common.d.ts +0 -85
- package/dist/types/interfaces/events.d.ts +0 -131
- package/dist/types/interfaces/plugin.d.ts +0 -162
- package/dist/types/interfaces/shogun.d.ts +0 -215
- package/dist/types/managers/AuthManager.d.ts +0 -72
- package/dist/types/managers/CoreInitializer.d.ts +0 -40
- package/dist/types/managers/EventManager.d.ts +0 -49
- package/dist/types/managers/PluginManager.d.ts +0 -145
- package/dist/types/migration-test.d.ts +0 -16
- package/dist/types/plugins/base.d.ts +0 -35
- package/dist/types/plugins/index.d.ts +0 -14
- package/dist/types/plugins/nostr/index.d.ts +0 -4
- package/dist/types/plugins/nostr/nostrConnector.d.ts +0 -119
- package/dist/types/plugins/nostr/nostrConnectorPlugin.d.ts +0 -163
- package/dist/types/plugins/nostr/nostrSigner.d.ts +0 -105
- package/dist/types/plugins/nostr/types.d.ts +0 -122
- 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/types/plugins/web3/index.d.ts +0 -4
- package/dist/types/plugins/web3/types.d.ts +0 -107
- package/dist/types/plugins/web3/web3Connector.d.ts +0 -129
- package/dist/types/plugins/web3/web3ConnectorPlugin.d.ts +0 -160
- package/dist/types/plugins/web3/web3Signer.d.ts +0 -114
- package/dist/types/plugins/webauthn/index.d.ts +0 -3
- package/dist/types/plugins/webauthn/types.d.ts +0 -162
- package/dist/types/plugins/webauthn/webauthn.d.ts +0 -129
- package/dist/types/plugins/webauthn/webauthnPlugin.d.ts +0 -158
- package/dist/types/plugins/webauthn/webauthnSigner.d.ts +0 -91
- package/dist/types/storage/storage.d.ts +0 -50
- package/dist/types/utils/errorHandler.d.ts +0 -119
- package/dist/types/utils/eventEmitter.d.ts +0 -39
- package/dist/types/utils/validation.d.ts +0 -27
- package/dist/utils/errorHandler.js +0 -241
- package/dist/utils/eventEmitter.js +0 -76
- 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;
|