@webdisrupt/persona
Version:
Store local data in a secure data vault.
538 lines • 29.7 kB
JavaScript
"use strict";
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;
return g = { next: verb(0), "throw": verb(1), "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 (_) 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 };
}
};
exports.__esModule = true;
exports.persona = exports.hashStrengthDetails = exports.hashStrength = void 0;
/**
* Persona - Personal information storage, privacy, and security
*/
var hash_strength_1 = require("./models/hash-strength");
exports.hashStrength = hash_strength_1.hashStrength;
exports.hashStrengthDetails = hash_strength_1.hashStrengthDetails;
var cypher_1 = require("./helpers/cypher");
var generic_1 = require("./helpers/generic");
var response_1 = require("./helpers/response");
var config_1 = require("./config");
/* Independant Modules */
var storage_block_1 = require("./modules/storage-block");
var storage_block_directory_1 = require("./modules/storage-block-directory");
var uuid = require('uuid-random');
var fs = require("fs");
var path = require("path");
var persona = /** @class */ (function () {
/**
* Constructor - Used to assign personaOptions.
* @param options
*/
function persona(options) {
if (options === void 0) { options = null; }
var _a;
this.appName = config_1.defaults.appName; // Your application name
this.path = config_1.defaults.path; // Current Personas folder location
this.current = null; // The currently loaded Persona (Encrypted/unusable)
this.recentList = []; // A list of all recently loaded personas
this.previous = null; // Last opened Persona (usable)
this.profile = null; // Stores version of profile data (usable)
this.username = null; // The current temp username (usable)
this.password = null; // The current temp password (usable)
this.key = null; // The key used for encryption
// Modules
this.module = {
storageBlock: null,
storageBlockDirectory: null
};
if ((options === null || options === void 0 ? void 0 : options.path) !== undefined)
this.path = options.path;
if ((options === null || options === void 0 ? void 0 : options.recentList) !== undefined)
this.recentList = options.recentList;
if ((options === null || options === void 0 ? void 0 : options.previous) !== undefined)
this.previous = options.previous;
if (((_a = options === null || options === void 0 ? void 0 : options.previous) === null || _a === void 0 ? void 0 : _a.username) !== undefined)
this.username = options.previous.username;
if ((options === null || options === void 0 ? void 0 : options.appName) !== undefined)
this.appName = options.appName;
// Create directory
if (!fs.existsSync(this.path))
fs.mkdirSync(this.path, { recursive: true });
}
/**
* Return null if user was never loggedIn, returns previous if there was a previous persona user.
*/
persona.prototype.isLoggedIn = function () {
return this.username != null && this.password != null ? response_1.response.success("".concat(this.username, " is currently logged in.")) :
this.previous !== null ? response_1.response.failed("".concat(this.previous.username, " is not currently logged in."), this.previous) : response_1.response.failed("No user is currently logged in.", null);
};
/**
* Returns the current Persona's id or null if not loggedIn
*/
persona.prototype.getId = function () {
return this.username != null && this.password != null ? response_1.response.success("".concat(this.username, "'s id was found."), this.current.id) :
response_1.response.failed("No user is currently logged in.", null);
};
/**
* Return the currently loaded username inside the standard response body
*/
persona.prototype.getUsername = function () {
return this.username != null ? response_1.response.success("".concat(this.username, " was found."), this.username) :
this.previous !== null ? response_1.response.failed("".concat(this.previous.username, " is not currently logged in.")) : response_1.response.failed("No user is currently logged in.");
};
/**
* Get the current Persona's profile details
* @returns
*/
persona.prototype.getProfile = function () {
return this.profile === null ? response_1.response.failed("No current profile exists.") : response_1.response.success("".concat(this.username, "'s profile has been loaded successfully."), this.profile);
};
/**
* Save Persona's profile details
* @returns
*/
persona.prototype.saveProfile = function (newProfile) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this.username === null || this.password === null)
return [2 /*return*/, response_1.response.failed("No current profile exists.")];
this.profile = newProfile;
this.current.profile = cypher_1.cypher.encrypt(JSON.stringify(newProfile), this.key);
return [4 /*yield*/, this.save()];
case 1:
_a.sent();
return [2 /*return*/, response_1.response.success("".concat(this.username, "'s profile was saved successfully."))];
}
});
});
};
/**
* Get all recently loaded profiles
* @returns
*/
persona.prototype.getRecentList = function () {
return this.recentList !== null ? response_1.response.success("Success, ".concat(this.recentList.length, " Prsonas found."), this.recentList) : response_1.response.failed("Could not find any recently loaded Prsonas.");
};
/**
* Add a new entry to the recently loaded list.
* @param recentlyLoadedPersona
*/
persona.prototype.addRecentListItem = function (recentlyLoadedPersona) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.recentList.includes(recentlyLoadedPersona)) {
this.recentList.push(recentlyLoadedPersona);
}
return [4 /*yield*/, this.systemSave()];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
/**
* Load temporal persona system data that can be used to house common data outside of the persona's
*/
persona.prototype.systemLoad = function () {
var _a;
return __awaiter(this, void 0, void 0, function () {
var filename, message, _b, _c, _d, _e, _f, _g;
return __generator(this, function (_h) {
switch (_h.label) {
case 0:
filename = "".concat(this.path, "\\").concat(config_1.defaults.system);
message = null;
if (!fs.existsSync(filename)) return [3 /*break*/, 5];
_h.label = 1;
case 1:
_h.trys.push([1, 3, , 4]);
_c = (_b = response_1.response).success;
_d = ["System data was loaded successfully."];
_f = (_e = JSON).parse;
return [4 /*yield*/, generic_1.generic.fileLoad(filename)];
case 2:
message = _c.apply(_b, _d.concat([_f.apply(_e, [_h.sent()])]));
return [3 /*break*/, 4];
case 3:
_g = _h.sent();
message = response_1.response.failed("Failed to load System data, file might be locked or is corrupted.");
return [3 /*break*/, 4];
case 4: return [3 /*break*/, 6];
case 5:
message = response_1.response.failed("No System data file exists.");
_h.label = 6;
case 6:
if (message.status) {
try {
this.recentList = message.data.recentList;
this.username = ((_a = message.data.previous) === null || _a === void 0 ? void 0 : _a.username) || null;
this.previous = message.data.previous;
}
catch (_j) {
message = response_1.response.failed("Failed to setup System data.");
}
}
return [2 /*return*/, message];
}
});
});
};
/**
* Save temporal persona system data that can be used to house common data outside of the persona's
*/
persona.prototype.systemSave = function () {
var _a;
return __awaiter(this, void 0, void 0, function () {
var systemData, wasSaved;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (this.current !== null) {
this.previous = {
id: this.current.id,
username: this.username,
avatar: ((_a = this.profile) === null || _a === void 0 ? void 0 : _a.avatar) != null ? this.profile.avatar : null
};
}
else {
this.previous = null;
}
systemData = {
previous: this.previous,
recentList: this.recentList
};
return [4 /*yield*/, generic_1.generic.fileUpdate(this.path, "".concat(config_1.defaults.system), JSON.stringify(systemData))];
case 1:
wasSaved = _b.sent();
return [2 /*return*/, wasSaved ? response_1.response.success("System data was saved successfully.") : response_1.response.failed("Failed to save system data.")];
}
});
});
};
/**
* Unloads all currently loaded data. Essentially the same as logging out.
* @returns
*/
persona.prototype.unload = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this.current === null)
return [2 /*return*/, response_1.response.failed("Persona cannot be unloaded because no Persona loaded.")];
return [4 /*yield*/, this.systemSave()];
case 1:
_a.sent();
this.current = null;
this.username = null;
this.key = null;
this.password = null;
this.profile = null;
this.loadModules();
return [2 /*return*/, response_1.response.success("Successfully logged out of the Persona ".concat(this.previous.username, "."))];
}
});
});
};
/**
* Login will handle all the init functions after a new user is created or loaded
*/
persona.prototype.login = function () {
this.loadModules();
};
/**
* Load modules - Refreshes modules with new data, and removes any stale data
* @returns
*/
persona.prototype.loadModules = function () {
if (this.current === null) {
this.module = null;
}
else {
this.module = {
storageBlock: new storage_block_1.StorageBlock({ path: this.path, appName: this.appName, personaId: this.current.id, key: this.key }),
storageBlockDirectory: new storage_block_directory_1.StorageBlockDirectory({ path: this.path, appName: this.appName, personaId: this.current.id, key: this.key })
};
}
};
/**
* Find Persona based on username. Use password to decrypt. Load additional storage blocks based on datamap parameter.
* @param username - Unique username associated with the Persona.
* @param password - Master password associated with the Persona.
* @param dataMap - Only pull back the sorage blocks you need to get started.
*/
persona.prototype.load = function (username, password) {
if (username === void 0) { username = null; }
if (password === void 0) { password = null; }
return __awaiter(this, void 0, void 0, function () {
var id;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.unload()];
case 1:
_a.sent();
return [4 /*yield*/, this.find(username, password)];
case 2:
id = _a.sent();
if (!(id !== null)) return [3 /*break*/, 4];
return [4 /*yield*/, generic_1.generic.fileLoad("".concat(this.path, "\\").concat(id, "\\").concat(config_1.defaults.root)).then(function (content) { return __awaiter(_this, void 0, void 0, function () {
var persona;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
persona = JSON.parse(content);
this.key = password + username;
return [4 /*yield*/, cypher_1.cypher.verify(this.key, persona.password)];
case 1:
if (!_b.sent()) return [3 /*break*/, 3];
this.password = password;
this.username = username;
this.current = persona;
this.profile = JSON.parse(cypher_1.cypher.decrypt(persona.profile, this.key));
return [4 /*yield*/, this.addRecentListItem({ id: id, username: username, avatar: ((_a = this.profile) === null || _a === void 0 ? void 0 : _a.avatar) || null, location: "".concat(this.path, "\\").concat(id) })];
case 2:
_b.sent();
this.login();
return [2 /*return*/, response_1.response.success("".concat(username, ", Welcome back!"))];
case 3: return [2 /*return*/, response_1.response.failed("The username or password is incorrect.")];
}
});
}); })];
case 3: return [2 /*return*/, _a.sent()];
case 4: return [2 /*return*/, response_1.response.failed("The username or password is incorrect.")];
}
});
});
};
/**
* Delete a Persona and all data storage.
* @param username (optional) - Needed for creating a new Persona, before saving.
* @param password (optional) - Needed for creating a new Persona, before saving.
*/
persona.prototype["delete"] = function (username, password) {
if (username === void 0) { username = null; }
if (password === void 0) { password = null; }
return __awaiter(this, void 0, void 0, function () {
var id, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
username = username === null ? this.username : username;
password = password === null ? this.password : password;
return [4 /*yield*/, this.find(username, password)];
case 1:
id = _b.sent();
if (!(id !== null)) return [3 /*break*/, 7];
_b.label = 2;
case 2:
_b.trys.push([2, 5, , 6]);
return [4 /*yield*/, fs.rmSync(this.path + "\\" + id, { recursive: true })];
case 3:
_b.sent();
return [4 /*yield*/, this.unload()];
case 4:
_b.sent();
return [2 /*return*/, response_1.response.success("".concat(username, "'s Persona has been deleted."))];
case 5:
_a = _b.sent();
return [2 /*return*/, response_1.response.failed("Something failed when deleting this Persona.")];
case 6: return [3 /*break*/, 8];
case 7: return [2 /*return*/, response_1.response.failed("Could not find a valid Persona or it has already been deleted.")];
case 8: return [2 /*return*/];
}
});
});
};
/**
* Allows users to create a new Persona.
* @param username - useranme required to create new Persona.
* @param password - password required to secure new Persona.
* @param strength - (optional) Passsword Hashing Strength.
* @returns response object contains a one-time recovery code as the data property
*/
persona.prototype.create = function (username, password, strength) {
if (strength === void 0) { strength = hash_strength_1.hashStrength.medium; }
return __awaiter(this, void 0, void 0, function () {
var checkIfPersonaExists, newID, recoveryId, location;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.unload()];
case 1:
_a.sent();
return [4 /*yield*/, this.find(username, password)];
case 2:
checkIfPersonaExists = _a.sent();
if (checkIfPersonaExists !== null)
return [2 /*return*/, response_1.response.failed("Persona ".concat(username, " already exists, please select a different username."))];
return [4 /*yield*/, this.generatePersonaId().then(function (id) { return id; })];
case 3:
newID = _a.sent();
recoveryId = cypher_1.cypher.generateRecoveryCode();
location = "".concat(this.path, "\\").concat(newID);
return [4 /*yield*/, cypher_1.cypher.hash(password + username, Number(strength.toString())).then(function (hash) { return __awaiter(_this, void 0, void 0, function () {
var newRes;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.username = username;
this.password = password;
this.key = password + username;
this.current = {
id: newID,
username: cypher_1.cypher.encrypt(username, this.key),
password: hash,
strength: strength,
profile: cypher_1.cypher.encrypt("{\"firstName\":\"\", \"lastName\":\"\"}", this.key),
mfa: "none",
recovery: cypher_1.cypher.encrypt(this.key, recoveryId),
link: []
};
return [4 /*yield*/, this.addRecentListItem({ id: newID, username: username, avatar: null, location: location })];
case 1:
_a.sent();
return [4 /*yield*/, generic_1.generic.fileUpdate(location, "".concat(config_1.defaults.root), JSON.stringify(this.current))];
case 2:
newRes = _a.sent();
this.login();
return [2 /*return*/, newRes ? response_1.response.success("Persona ".concat(this.username, " successfully created."), recoveryId) : response_1.response.failed("Persona ".concat(this.username, " failed to be created. Please check folder permissions."))];
}
});
}); })];
case 4: return [2 /*return*/, _a.sent()];
}
});
});
};
/**
* Save the currently loaded Persona.
* @param username (optional) - Needed for creating a new Persona, before saving.
* @param password (optional) - Needed for creating a new Persona, before saving.
*/
persona.prototype.save = function () {
return __awaiter(this, void 0, void 0, function () {
var newRes;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(this.current !== null && this.username !== null && this.password !== null)) return [3 /*break*/, 2];
return [4 /*yield*/, generic_1.generic.fileUpdate("".concat(this.path, "\\").concat(this.current.id), "".concat(config_1.defaults.root), JSON.stringify(this.current))];
case 1:
newRes = _a.sent();
return [2 /*return*/, newRes ? response_1.response.success("Persona ".concat(this.username, " successfully created.")) : response_1.response.failed("Persona ".concat(this.username, " failed to be created. Please check folder permissions."))];
case 2: return [2 /*return*/, response_1.response.failed('Persona failed to be saved. No Persona is active.')];
}
});
});
};
/**
* Find a Persona with username and password
* @param username
* @param password
* @returns
*/
persona.prototype.find = function (username, password) {
if (username === void 0) { username = null; }
if (password === void 0) { password = null; }
return __awaiter(this, void 0, void 0, function () {
var key, files, id;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
key = password + username;
if (this.current !== null && password === this.password && username === this.username)
return [2 /*return*/, this.current.id];
return [4 /*yield*/, fs.promises.readdir(this.path)];
case 1:
files = _a.sent();
id = null;
return [4 /*yield*/, generic_1.generic.asyncFind(files, function (personaId) { return __awaiter(_this, void 0, void 0, function () {
var personaFolder, rootFile, _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
personaFolder = "".concat(this.path, "\\").concat(personaId);
if (!fs.lstatSync(personaFolder).isDirectory()) return [3 /*break*/, 2];
_b = (_a = JSON).parse;
return [4 /*yield*/, generic_1.generic.fileLoad("".concat(personaFolder, "\\").concat(config_1.defaults.root))];
case 1:
rootFile = _b.apply(_a, [_c.sent()]);
return [2 /*return*/, username === cypher_1.cypher.decrypt(rootFile.username, key)];
case 2: return [2 /*return*/];
}
});
}); })];
case 2:
id = _a.sent();
return [2 /*return*/, id === null || id === undefined || typeof id !== 'string' ? null : id];
}
});
});
};
/**
* Checks if existing system IDs in path to check if ID is truly unique
* @param path - Location to check if unique ID is actually unique
* @returns
*/
persona.prototype.generatePersonaId = function () {
return __awaiter(this, void 0, void 0, function () {
var newId, files;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
newId = uuid();
if (!fs.existsSync(path)) return [3 /*break*/, 2];
return [4 /*yield*/, fs.promises.readdir(this.path)];
case 1:
files = _a.sent();
while (files.includes(newId)) {
newId = uuid();
}
_a.label = 2;
case 2: return [2 /*return*/, newId];
}
});
});
};
return persona;
}());
exports.persona = persona;
//# sourceMappingURL=index.js.map