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