UNPKG

shogun-core

Version:

SHOGUN CORE - Core library for Shogun Ecosystem

966 lines (965 loc) 80.8 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; import { EventEmitter } from '../utils/eventEmitter.js'; import * as GunErrors from './errors.js'; import * as crypto from './crypto.js'; /** * GunDB configuration constants. * @internal */ var CONFIG = { PASSWORD: { MIN_LENGTH: 8, MAX_LENGTH: 1024, }, USERNAME: { MAX_LENGTH: 64, }, }; /** * DataBase * * Manages GunDB user authentication and various utility helpers for * session, alias/username, SEA cryptography, event handling, and reactive streams. */ var DataBase = /** @class */ (function () { /** * Constructs a new DataBase instance connected to a GunDB instance. * @param gun The main GunDB instance. * @param core Optionally, the root Gun instance (unused in this context). * @param sea Optional cryptography (Gun SEA) instance; will be auto-discovered if not provided. * @throws If gun or gun.user() is not provided. */ function DataBase(gun, core, sea) { var _a; /** Cached user instance or `null` if not logged in */ this.user = null; /** Registered callbacks for auth state changes */ this.onAuthCallbacks = []; /** Whether the database instance has been destroyed */ this._isDestroyed = false; /** Node prefix for Firegun compatibility */ this.prefix = ''; /** Event handlers for Firegun compatibility */ this.ev = {}; this.eventEmitter = new EventEmitter(); this.core = core; if (!gun) { throw new Error('Gun instance is required but was not provided'); } if (typeof gun.user !== 'function') { throw new Error('Gun instance is invalid: gun.user is not a function'); } this.gun = gun; this.user = this.gun.user().recall({ sessionStorage: true }); this.subscribeToAuthEvents(); this.crypto = crypto; this.sea = sea || null; if (!this.sea) { if (this.gun.SEA) { this.sea = this.gun.SEA; } else if ((_a = globalThis.Gun) === null || _a === void 0 ? void 0 : _a.SEA) { this.sea = globalThis.Gun.SEA; } else if (globalThis.SEA) { this.sea = globalThis.SEA; } } this.usernamesNode = this.gun.get('usernames'); console.log('[DB] DataBase initialization completed'); } /** * Initialize the database instance. */ DataBase.prototype.initialize = function () { // Database is already initialized in constructor }; /** * Internal: subscribe to GunDB "auth" events and notify listeners. * Listeners are invoked on authentication status change. * @internal */ DataBase.prototype.subscribeToAuthEvents = function () { var _this = this; this.gun.on('auth', function (ack) { var _a; if (ack.err) { console.error('[DB] Auth event error:', ack.err); } else { _this.notifyAuthListeners(((_a = ack.sea) === null || _a === void 0 ? void 0 : _a.pub) || ''); } }); }; /** * Internal: notify all onAuth callbacks with current user. * @param pub User's public key (pub). * @internal */ DataBase.prototype.notifyAuthListeners = function (pub) { var user = this.gun.user(); this.onAuthCallbacks.forEach(function (cb) { return cb(user); }); }; /** * Listen for authentication/sign-in events (login, logout, etc). * @param callback Function to call with new user instance. * @returns Function to remove the registered callback. */ DataBase.prototype.onAuth = function (callback) { var _this = this; this.onAuthCallbacks.push(callback); var user = this.gun.user(); if (user && user.is) callback(user); return function () { var i = _this.onAuthCallbacks.indexOf(callback); if (i !== -1) _this.onAuthCallbacks.splice(i, 1); }; }; /** * Check if a user is currently logged in (there is a valid session). * @returns `true` if logged in; otherwise `false`. */ DataBase.prototype.isLoggedIn = function () { try { var user = this.gun.user(); return !!(user && user.is && user.is.pub); } catch (error) { return false; } }; /** * Attempt to restore a previously saved session from sessionStorage. * @returns Object indicating success, error, and userPub if restored. */ DataBase.prototype.restoreSession = function () { return __awaiter(this, void 0, void 0, function () { var sessionData, session, integrityCheck, key, decryptedData, user, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 4, , 5]); if (typeof sessionStorage === 'undefined') { return [2 /*return*/, { success: false, error: 'sessionStorage not available' }]; } sessionData = sessionStorage.getItem('gunSessionData'); if (!sessionData) { return [2 /*return*/, { success: false, error: 'No saved session' }]; } session = JSON.parse(sessionData); // Handle legacy plaintext sessions (no version or version < 2) if (!session.version || session.version < 2) { sessionStorage.removeItem('gunSessionData'); return [2 /*return*/, { success: false, error: 'Legacy session expired' }]; } // Check required fields for encrypted session if (!session.encrypted || !session.integrity || !session.salt || !session.pub) { return [2 /*return*/, { success: false, error: 'Invalid encrypted session format' }]; } return [4 /*yield*/, this.sea.work(session.encrypted, null, null, { name: 'SHA-256' })]; case 1: integrityCheck = _a.sent(); if (integrityCheck !== session.integrity) { sessionStorage.removeItem('gunSessionData'); return [2 /*return*/, { success: false, error: 'Session integrity check failed' }]; } return [4 /*yield*/, this.deriveSessionKey(session.username, session.salt, session.pub)]; case 2: key = _a.sent(); return [4 /*yield*/, this.sea.decrypt(session.encrypted, key)]; case 3: decryptedData = _a.sent(); if (!decryptedData) { sessionStorage.removeItem('gunSessionData'); return [2 /*return*/, { success: false, error: 'Session decryption failed' }]; } // 4. Validate session content if (decryptedData.pub !== session.pub) { return [2 /*return*/, { success: false, error: 'Session public key mismatch' }]; } // Check if session is expired if (decryptedData.expiresAt && Date.now() > decryptedData.expiresAt) { sessionStorage.removeItem('gunSessionData'); return [2 /*return*/, { success: false, error: 'Session expired' }]; } user = this.gun.user(); if (user.is && user.is.pub === session.pub) { this.user = user; return [2 /*return*/, { success: true, userPub: session.pub }]; } return [2 /*return*/, { success: false, error: 'Session verification failed' }]; case 4: error_1 = _a.sent(); console.error('[DB] Restore session error:', error_1); return [2 /*return*/, { success: false, error: String(error_1) }]; case 5: return [2 /*return*/]; } }); }); }; /** * Log out the current user, clear local state and remove session from storage. */ DataBase.prototype.logout = function () { try { var wasLoggedIn = !!this.user; var currentUser = this.gun.user(); if (currentUser && currentUser.is) { currentUser.leave(); } this.user = null; if (typeof sessionStorage !== 'undefined') { sessionStorage.removeItem('gunSessionData'); } // Emit auth:logout event if core is available and user was logged in if (wasLoggedIn && this.core && typeof this.core.emit === 'function') { this.core.emit('auth:logout', undefined); } } catch (error) { console.error('[DB] Error during logout:', error); } }; /** * Validate that a provided password meets minimum length requirements. * @param password Password string to validate. * @returns Object indicating validity and, if invalid, an error. */ DataBase.prototype.validatePasswordStrength = function (password) { if (password.length < CONFIG.PASSWORD.MIN_LENGTH) { return { valid: false, error: "Password must be at least ".concat(CONFIG.PASSWORD.MIN_LENGTH, " characters long"), }; } if (password.length > CONFIG.PASSWORD.MAX_LENGTH) { return { valid: false, error: "Password must be ".concat(CONFIG.PASSWORD.MAX_LENGTH, " characters or fewer"), }; } if (!/[A-Z]/.test(password)) { return { valid: false, error: 'Password must contain at least one uppercase letter', }; } if (!/[a-z]/.test(password)) { return { valid: false, error: 'Password must contain at least one lowercase letter', }; } if (!/[0-9]/.test(password)) { return { valid: false, error: 'Password must contain at least one number', }; } if (!/[!@#$%^&*()_+\-=[\]{}|;':",./<>?]/.test(password)) { return { valid: false, error: 'Password must contain at least one special character', }; } return { valid: true }; }; /** * Validate a signup request's username, password, and/or cryptographic pair. * @param username Username string. * @param password Password string. * @param pair Optional cryptographic SEA pair. * @returns Object with validation status and optional error. */ DataBase.prototype.validateSignupCredentials = function (username, password, pair) { if (!username || username.length < 1) { return { valid: false, error: 'Username must be more than 0 characters long', }; } if (username.length > CONFIG.USERNAME.MAX_LENGTH) { return { valid: false, error: "Username must be ".concat(CONFIG.USERNAME.MAX_LENGTH, " characters or fewer"), }; } if (!/^[a-zA-Z0-9._-]+$/.test(username)) { return { valid: false, error: 'Username can only contain letters, numbers, dots, underscores, and hyphens', }; } if (pair) { if (!pair.pub || !pair.priv || !pair.epub || !pair.epriv) { return { valid: false, error: 'Invalid pair provided' }; } return { valid: true }; } return this.validatePasswordStrength(password); }; /** * Ensures that an alias/username is available in GunDB for registration. * @param alias Username to check. * @param timeout Timeout in milliseconds (default 5000ms). * @throws If the alias is already taken. */ DataBase.prototype.ensureAliasAvailable = function (alias_1) { return __awaiter(this, arguments, void 0, function (alias, timeout) { var available; if (timeout === void 0) { timeout = 5000; } return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.isAliasAvailable(alias, timeout)]; case 1: available = _a.sent(); if (!available) { throw new Error("Alias \"".concat(alias, "\" is already registered in Gun")); } return [2 /*return*/]; } }); }); }; /** * Checks if a given alias/username is available on GunDB. * @param alias Username to check for availability. * @param timeout Timeout in ms (default: 5000). * @returns Promise resolving to `true` if available; otherwise `false`. * @throws If alias is invalid or on I/O error. */ DataBase.prototype.isAliasAvailable = function (alias_1) { return __awaiter(this, arguments, void 0, function (alias, timeout) { var normalizedAlias; var _this = this; if (timeout === void 0) { timeout = 5000; } return __generator(this, function (_a) { if (typeof alias !== 'string' || !alias.trim()) { throw new Error('Alias must be a non-empty string'); } normalizedAlias = alias.trim().toLowerCase(); return [2 /*return*/, new Promise(function (resolve, reject) { var settled = false; var timer = setTimeout(function () { if (settled) return; settled = true; reject(new Error('Timeout while checking alias availability')); }, timeout); _this.usernamesNode.get(normalizedAlias).once(function (existingPub) { if (settled) return; settled = true; clearTimeout(timer); resolve(!existingPub); }); })]; }); }); }; /** * Checks if a given alias/username is taken on GunDB. * @param alias Username to check for availability. * @returns Promise resolving to `true` if taken; otherwise `false`. * @throws If alias is invalid or on I/O error. */ DataBase.prototype.isAliasTaken = function (alias) { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve, reject) { // Check if username exists by looking up ~@username _this.gun.get("~@".concat(alias)).once(function (user) { // If user exists, alias is taken (return true) // If user is null/undefined, alias is available (return false) resolve(!!user); }); })]; }); }); }; /** * Register a new alias (username) → public key mapping on GunDB. * @param alias The username/alias to register. * @param userPub The user's public key. * @param timeout Timeout in ms (default 5000). * @throws If alias/userPub is invalid or the alias cannot be registered. */ DataBase.prototype.registerAlias = function (alias_1, userPub_1) { return __awaiter(this, arguments, void 0, function (alias, userPub, timeout) { var normalizedAlias, available, taken; var _this = this; if (timeout === void 0) { timeout = 5000; } return __generator(this, function (_a) { switch (_a.label) { case 0: if (!alias || !alias.trim()) { throw new Error('Alias must be provided for registration'); } if (!userPub) { throw new Error('userPub must be provided for alias registration'); } normalizedAlias = alias.trim().toLowerCase(); return [4 /*yield*/, this.isAliasAvailable(normalizedAlias, timeout).catch(function (error) { console.error('[DB] Alias availability check failed:', error); throw error; })]; case 1: available = _a.sent(); return [4 /*yield*/, this.isAliasTaken(normalizedAlias)]; case 2: taken = _a.sent(); if (taken) { throw new Error("Alias \"".concat(normalizedAlias, "\" is already taken")); } if (!available) { throw new Error("Alias \"".concat(normalizedAlias, "\" is no longer available for registration")); } return [4 /*yield*/, new Promise(function (resolve, reject) { var settled = false; var timer = setTimeout(function () { if (settled) return; settled = true; reject(new Error('Timeout while registering alias')); }, timeout); _this.usernamesNode.get(normalizedAlias).put(userPub, function (ack) { if (settled) return; settled = true; clearTimeout(timer); if (ack && ack.err) { reject(new Error(String(ack.err))); return; } resolve(); }); }).catch(function (error) { console.error('[DB] Failed to register alias:', error); throw error; })]; case 3: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Reset gun.user() authentication state and clear cached user. * @internal */ DataBase.prototype.resetAuthState = function () { try { var user = this.gun.user(); if (user && user._) { var cat = user._; cat.ing = false; cat.auth = null; cat.act = null; if (cat.auth) { cat.auth = null; } } try { user.leave(); } catch (leaveError) { // Ignore leave errors } this.user = null; } catch (e) { // Ignore } }; /** * Assemble a standard AuthResult object after a successful login. * @param username Resulting username. * @param userPub Public key (pub) for logged-in user. * @returns AuthResult. * @internal */ DataBase.prototype.buildLoginResult = function (username, userPub) { var _a, _b; var seaPair = (_b = (_a = this.gun.user()) === null || _a === void 0 ? void 0 : _a._) === null || _b === void 0 ? void 0 : _b.sea; return { success: true, userPub: userPub, username: username, sea: seaPair ? { pub: seaPair.pub, priv: seaPair.priv, epub: seaPair.epub, epriv: seaPair.epriv, } : undefined, }; }; /** * Internal: Get or create a device-specific secret stored in localStorage. * This binds the session key to the device, preventing decryption of stolen sessionStorage * on other devices. */ DataBase.prototype.getDeviceSecret = function () { try { if (typeof localStorage === 'undefined') return ''; var KEY = 'shogun_device_secret'; var secret = localStorage.getItem(KEY); if (!secret) { secret = this.crypto.randomUUID(); try { localStorage.setItem(KEY, secret); } catch (e) { console.warn('[DB] Failed to save device secret to localStorage', e); // If we can't save it, we return it anyway so current session works. // Next reload will fail to decrypt, which is secure fail. } } return secret; } catch (e) { return ''; } }; /** * Derive a unique encryption key for the session. * @param username Username to derive key from * @param salt Random salt for this session * @param pub User's public key */ DataBase.prototype.deriveSessionKey = function (username, salt, pub) { return __awaiter(this, void 0, void 0, function () { var deviceSecret, input; return __generator(this, function (_a) { switch (_a.label) { case 0: // We use SEA.work to derive a key from username + salt + pub // This makes the key unique per session (due to salt) and user if (!this.sea) throw new Error('SEA not available'); deviceSecret = this.getDeviceSecret(); input = "".concat(username, ":").concat(salt, ":").concat(pub, ":").concat(deviceSecret); return [4 /*yield*/, this.sea.work(input, null, null, { name: 'SHA-256' })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); }; /** * Save credentials for the current session to sessionStorage, if available. * Encrypts sensitive data using a derived session key. * @param userInfo The credentials and user identity to store. */ DataBase.prototype.saveCredentials = function (userInfo) { return __awaiter(this, void 0, void 0, function () { var salt, key, payload, encrypted, integrity, sessionInfo, error_2; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 6, , 7]); if (!(typeof sessionStorage !== 'undefined')) return [3 /*break*/, 5]; if (!this.sea) return [2 /*return*/]; return [4 /*yield*/, this.sea.work(this.crypto.randomUUID(), null, null, { name: 'SHA-256', })]; case 1: salt = _a.sent(); return [4 /*yield*/, this.deriveSessionKey(userInfo.alias, salt, userInfo.userPub)]; case 2: key = _a.sent(); payload = { username: userInfo.alias, pair: userInfo.pair, pub: userInfo.userPub, expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days }; return [4 /*yield*/, this.sea.encrypt(payload, key)]; case 3: encrypted = _a.sent(); return [4 /*yield*/, this.sea.work(encrypted, null, null, { name: 'SHA-256', })]; case 4: integrity = _a.sent(); sessionInfo = { version: 2, username: userInfo.alias, pub: userInfo.userPub, salt: salt, encrypted: encrypted, integrity: integrity, }; sessionStorage.setItem('gunSessionData', JSON.stringify(sessionInfo)); _a.label = 5; case 5: return [3 /*break*/, 7]; case 6: error_2 = _a.sent(); console.error('[DB] Error saving credentials:', error_2); return [3 /*break*/, 7]; case 7: return [2 /*return*/]; } }); }); }; /** * Register and authenticate a new user account. * @param username The username to create/account for. * @param password The user's password. * @param pair Optional cryptographic pair (for `auth` instead of password). * @returns SignUpResult Promise. */ DataBase.prototype.signUp = function (username, password, pair) { return __awaiter(this, void 0, void 0, function () { var validation, normalizedUsername, user, loginResult, e_1, aliasError_1, result; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: validation = this.validateSignupCredentials(username, password, pair); if (!validation.valid) { return [2 /*return*/, { success: false, error: validation.error }]; } this.resetAuthState(); normalizedUsername = username.trim().toLowerCase(); user = this.gun.user(); if (!pair) return [3 /*break*/, 4]; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, new Promise(function (resolve) { var callbackInvoked = false; user.auth(pair, function (ack) { return __awaiter(_this, void 0, void 0, function () { var userPub, alias, userPair; var _a, _b, _c; return __generator(this, function (_d) { switch (_d.label) { case 0: if (callbackInvoked) { return [2 /*return*/]; } callbackInvoked = true; if (ack.err) { resolve({ success: false, error: ack.err }); return [2 /*return*/]; } userPub = (_a = user === null || user === void 0 ? void 0 : user.is) === null || _a === void 0 ? void 0 : _a.pub; if (!userPub) { this.resetAuthState(); resolve({ success: false, error: 'No userPub available' }); return [2 /*return*/]; } this.user = user; alias = (_b = user === null || user === void 0 ? void 0 : user.is) === null || _b === void 0 ? void 0 : _b.alias; userPair = (_c = user === null || user === void 0 ? void 0 : user._) === null || _c === void 0 ? void 0 : _c.sea; return [4 /*yield*/, this.saveCredentials({ alias: alias || normalizedUsername, pair: pair !== null && pair !== void 0 ? pair : userPair, userPub: userPub, })]; case 1: _d.sent(); // Emit auth:signup event if core is available (pair-based signup) if (this.core && typeof this.core.emit === 'function') { this.core.emit('auth:signup', { userPub: userPub, username: normalizedUsername, method: 'pair', }); } resolve(this.buildLoginResult(alias || normalizedUsername, userPub)); return [2 /*return*/]; } }); }); }); })]; case 2: loginResult = _a.sent(); if (loginResult && loginResult.success) { return [2 /*return*/, loginResult]; } return [3 /*break*/, 4]; case 3: e_1 = _a.sent(); return [3 /*break*/, 4]; case 4: _a.trys.push([4, 6, , 7]); return [4 /*yield*/, this.ensureAliasAvailable(normalizedUsername)]; case 5: _a.sent(); return [3 /*break*/, 7]; case 6: aliasError_1 = _a.sent(); return [2 /*return*/, { success: false, error: aliasError_1 instanceof Error ? aliasError_1.message : String(aliasError_1), }]; case 7: return [4 /*yield*/, new Promise(function (resolve) { var callbackInvoked = false; user.create(normalizedUsername, password, function (createAck) { if (callbackInvoked) { return; } if (createAck.err || (createAck.ok !== undefined && createAck.ok !== 0)) { callbackInvoked = true; _this.resetAuthState(); resolve({ success: false, error: createAck.err || 'Signup failed' }); return; } var userPub = createAck.pub; if (!userPub) { callbackInvoked = true; _this.resetAuthState(); resolve({ success: false, error: 'No userPub available from signup', }); return; } user.auth(normalizedUsername, password, function (authAck) { return __awaiter(_this, void 0, void 0, function () { var authenticatedUserPub, alias, userPair, saveError_1, registerError_1, sea; var _a, _b, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: if (callbackInvoked) { return [2 /*return*/]; } callbackInvoked = true; if (authAck.err) { this.resetAuthState(); resolve({ success: false, error: authAck.err || 'Authentication after signup failed', }); return [2 /*return*/]; } authenticatedUserPub = (_a = user === null || user === void 0 ? void 0 : user.is) === null || _a === void 0 ? void 0 : _a.pub; if (!authenticatedUserPub) { this.resetAuthState(); resolve({ success: false, error: 'User not authenticated after signup', }); return [2 /*return*/]; } this.user = user; alias = (_b = user === null || user === void 0 ? void 0 : user.is) === null || _b === void 0 ? void 0 : _b.alias; userPair = (_c = user === null || user === void 0 ? void 0 : user._) === null || _c === void 0 ? void 0 : _c.sea; _e.label = 1; case 1: _e.trys.push([1, 3, , 4]); return [4 /*yield*/, this.saveCredentials({ alias: alias || normalizedUsername, pair: pair !== null && pair !== void 0 ? pair : userPair, userPub: authenticatedUserPub, })]; case 2: _e.sent(); return [3 /*break*/, 4]; case 3: saveError_1 = _e.sent(); return [3 /*break*/, 4]; case 4: _e.trys.push([4, 6, , 7]); return [4 /*yield*/, this.registerAlias(alias || normalizedUsername, authenticatedUserPub)]; case 5: _e.sent(); return [3 /*break*/, 7]; case 6: registerError_1 = _e.sent(); console.error('[DB] Alias registration failed:', registerError_1); return [3 /*break*/, 7]; case 7: // Emit auth:signup event if core is available if (this.core && typeof this.core.emit === 'function') { this.core.emit('auth:signup', { userPub: authenticatedUserPub, username: normalizedUsername, method: pair ? 'pair' : 'password', }); } sea = (_d = user === null || user === void 0 ? void 0 : user._) === null || _d === void 0 ? void 0 : _d.sea; resolve({ success: true, userPub: authenticatedUserPub, username: normalizedUsername, isNewUser: true, sea: sea ? { pub: sea.pub, priv: sea.priv, epub: sea.epub, epriv: sea.epriv, } : undefined, }); return [2 /*return*/]; } }); }); }); }); })]; case 8: result = _a.sent(); return [2 /*return*/, result]; } }); }); }; /** * Sign in (authenticate) as an existing user by username/password or SEA pair. * @param username Username to log in as. * @param password User's password (or "" if using pair). * @param pair Optional cryptographic SEA pair. * @returns AuthResult Promise. */ DataBase.prototype.login = function (username, password, pair) { return __awaiter(this, void 0, void 0, function () { var normalizedUsername, user; var _this = this; return __generator(this, function (_a) { this.resetAuthState(); normalizedUsername = username.trim().toLowerCase(); user = this.gun.user(); return [2 /*return*/, new Promise(function (resolve) { if (pair) { user.auth(pair, function (ack) { return __awaiter(_this, void 0, void 0, function () { var userPub, alias, userPair, saveError_2; var _a, _b, _c; return __generator(this, function (_d) { switch (_d.label) { case 0: if (ack.err) { this.resetAuthState(); resolve({ success: false, error: ack.err }); return [2 /*return*/]; } userPub = (_a = user === null || user === void 0 ? void 0 : user.is) === null || _a === void 0 ? void 0 : _a.pub; if (!userPub) { this.resetAuthState(); resolve({ success: false, error: 'No userPub available' }); return [2 /*return*/]; } this.user = user; alias = (_b = user === null || user === void 0 ? void 0 : user.is) === null || _b === void 0 ? void 0 : _b.alias; userPair = (_c = user === null || user === void 0 ? void 0 : user._) === null || _c === void 0 ? void 0 : _c.sea; _d.label = 1; case 1: _d.trys.push([1, 3, , 4]); return [4 /*yield*/, this.saveCredentials({ alias: alias || normalizedUsername, pair: pair !== null && pair !== void 0 ? pair : userPair, userPub: userPub, })]; case 2: _d.sent(); return [3 /*break*/, 4]; case 3: saveError_2 = _d.sent(); return [3 /*break*/, 4]; case 4: // Emit auth:login event if core is available (pair-based login) if (this.core && typeof this.core.emit === 'function') { this.core.emit('auth:login', { userPub: userPub, username: alias || normalizedUsername, method: 'pair', }); } resolve(this.buildLoginResult(alias || normalizedUsername, userPub)); return [2 /*return*/]; } }); }); }); } else { user.auth(normalizedUsername, password, function (ack) { return __awaiter(_this, void 0, void 0, function () { var userPub, alias, userPair, saveError_3; var _a, _b, _c; return __generator(this, function (_d) { switch (_d.label) { case 0: if (ack.err) { this.resetAuthState(); resolve({ success: false, error: ack.err }); return [2 /*return*/]; } userPub = (_a = user === null || user === void 0 ? void 0 : user.is) === null || _a === void 0 ? void 0 : _a.pub; if (!userPub) { this.resetAuthState(); resolve({ success: false, error: 'No userPub available' }); return [2 /*return*/]; } this.user = user; alias = (_b = user === null || user === void 0 ? void 0 : user.is) === null || _b === void 0 ? void 0 : _b.alias; userPair = (_c = user === null || user === void 0 ? void 0 : user._) === null || _c === void 0 ? void 0 : _c.sea; _d.label = 1; case 1: _d.trys.push([1, 3, , 4]); return [4 /*yield*/, this.saveCredentials({ alias: alias || normalizedUsername, pair: pair !== null && pair !== void 0 ? pair : userPair, userPub: userPub, })]; case 2: _d.sent(); return [3 /*break*/, 4]; case 3: saveError_3 = _d.sent();