UNPKG

@magic.batua/account

Version:

The Account modules powers the user account management features of the Magic Batua platform.

374 lines (353 loc) 14.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Source/Account.js - Postman Documentation</title> <script src="scripts/prettify/prettify.js"></script> <script src="scripts/prettify/lang-css.js"></script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/ionicons.min.css"> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <input type="checkbox" id="nav-trigger" class="nav-trigger" /> <label for="nav-trigger" class="navicon-button x"> <div class="navicon"></div> </label> <label for="nav-trigger" class="overlay"></label> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Account.html">Account</a><ul class='methods'><li data-type='method'><a href="Account.html#AddPoints">AddPoints</a></li><li data-type='method'><a href="Account.html#AddReferral">AddReferral</a></li><li data-type='method'><a href="Account.html#CanAuthenticateUsing">CanAuthenticateUsing</a></li><li data-type='method'><a href="Account.html#Delete">Delete</a></li><li data-type='method'><a href="Account.html#Export">Export</a></li><li data-type='method'><a href="Account.html#RedeemPoints">RedeemPoints</a></li><li data-type='method'><a href="Account.html#RemoveReferral">RemoveReferral</a></li><li data-type='method'><a href="Account.html#ResetPassword">ResetPassword</a></li><li data-type='method'><a href="Account.html#SetOTP">SetOTP</a></li><li data-type='method'><a href="Account.html#SetReferrer">SetReferrer</a></li><li data-type='method'><a href="Account.html#Undelete">Undelete</a></li><li data-type='method'><a href="Account.html#UnsetOTP">UnsetOTP</a></li></ul></li><li><a href="Registry.html">Registry</a><ul class='methods'><li data-type='method'><a href="Registry.html#Create">Create</a></li><li data-type='method'><a href="Registry.html#DidResetPassword">DidResetPassword</a></li><li data-type='method'><a href="Registry.html#DidSendOTP">DidSendOTP</a></li><li data-type='method'><a href="Registry.html#HasVerified">HasVerified</a></li><li data-type='method'><a href="Registry.html#IsDuplicate">IsDuplicate</a></li><li data-type='method'><a href="Registry.html#Modify">Modify</a></li><li data-type='method'><a href="Registry.html#Remove">Remove</a></li><li data-type='method'><a href="Registry.html#Retrieve">Retrieve</a></li></ul></li></ul><h3>Modules</h3><ul><li><a href="module-Database.html">Database</a><ul class='methods'><li data-type='method'><a href="module-Database.html#~Find">Find</a></li><li data-type='method'><a href="module-Database.html#~FindAndReplace">FindAndReplace</a></li><li data-type='method'><a href="module-Database.html#~GetAccountByID">GetAccountByID</a></li><li data-type='method'><a href="module-Database.html#~GetReferrer">GetReferrer</a></li><li data-type='method'><a href="module-Database.html#~Insert">Insert</a></li><li data-type='method'><a href="module-Database.html#~IsDuplicate">IsDuplicate</a></li><li data-type='method'><a href="module-Database.html#~UpdateInPlace">UpdateInPlace</a></li></ul></li><li><a href="module-Source.html">Source</a></li></ul> </nav> <div id="main"> <h1 class="page-title">Source/Account.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>"use strict"; /** * @module Account * @overview This module defines the `Account` class that models a Magic Batua account. * * @author Animesh Mishra &lt;hello@animesh.ltd> * @copyright © 2018 Animesh Ltd. All Rights Reserved. */ Object.defineProperty(exports, "__esModule", { value: true }); const Crypto = require("crypto"); const bson_1 = require("bson"); const Source_1 = require("./Source"); const Points = require("@magic.batua/points"); /** * The `Account` class models the data and functions supported by Magic Batua * accounts. Besides the standard profile information — name, email, phone, etc. — * it also takes care of things such as referrals and loyalty points. * * Business logic used for password resets, salt generation, hashing and other * such operations are also defined by the `Account` class. */ class Account { /** * In a server-side application, there are two instantiation scenarios: one where the input * is provided by the client, and the other where the input is provided by the database. * * Client-side input is always lighter than database-side input, since auto-generated * properties such as `_id` are missing in the client-side variant. Moreover, client-side * input may contain less information than database-side input as some sensitive properties * such as password, salt, etc. are never revealed to the client side. * * As such, we need a way to track the source of input and proceed with instantiation * accordingly. This `source` parameter serves exactly this purpose. * * @param {any} json Input JSON * @param {Source} source Input source. See `Source/Source.ts` for definition of `Source`. * * @class */ constructor(json, source = Source_1.Source.Database) { /** * Flag used to soft-delete an account. Accounts are never deleted permanently. This * is for administrative and data analytics reasons. When a user submits a account * deletion request, this flag is set to true and the `recoverBy` date is set to 14 * days ahead. * * If a user logs into their account within those 14 days, the deletion hold is lifted * and this flag is set to `false` again. */ this.isDeleted = false; this.name = json.name; this.phone = json.phone; this.email = json.email; // Source is client only for signup requests. So we will need to // hash the password and generate a referral code for the new // account. if (source == Source_1.Source.Client) { this._id = new bson_1.ObjectId(); this.password = this.hash(json.password); this.referralCode = this.generateReferralCode(); // Issue points for signup and initialise the ledger let signup = { points: Points.Points.ToSelfFor("Signup"), type: "Issue", notes: "Signup" }; this.pointsLedger = new Points.Ledger([signup]); } else { this._id = json._id; this.isEmailVerified = json.isEmailVerified; this.isPhoneVerified = json.isPhoneVerified; this.referralCode = json.referralCode; this.referredBy = json.referredBy; this.referrals = json.referrals; this.pointsLedger = new Points.Ledger(json.pointsLedger.transactions); this.password = json.password; this.salt = json.salt; this.isDeleted = json.isDeleted; this.recoverBy = json.recoverBy; this.otp = json.otp; } } /** * Salts the given `password` using the `salt` used at account creation, * and then compares the hash with the hashed password stored in the * database. * * @param {string} password Client-provided password * * @returns {boolean} `true` if the password is correct, otherwise `false` * * @function CanAuthenticateUsing * @memberof Account * @instance */ CanAuthenticateUsing(password) { return this.password == this.hash(password, this.salt); } /** * Sets `referredBy` to the `_id` of the user who referred this user to * Magic Batua. * * @param {string} id User `_id` of the referrer * * @function SetReferrer * @memberof Account * @instance */ SetReferrer(id) { this.referredBy = id; } /** * Adds a referral to the account's referrals list * * @param {string} id User `_id` of the referral * * @function AddReferral * @memberof Account * @instance */ AddReferral(id) { if (!this.referrals) { this.referrals = new Array(); } this.referrals.push(id); } /** * Adds loyalty points to the account. * * @param {number} points Number of points to be awarded * @param {string} reason Reason for the generosity * * @function AddPoints * @memberof Account * @instance */ AddPoints(points, reason) { this.pointsLedger.Issue(points, reason); } /** * Redeems the given number of points. Throws an error if available * points are fewer than the `points` requested. * * @param {number} points Numbe of points to be redeemed * @param {string} reason Purpose of redemption * * @function RedeemPoints * @memberof Account * @instance */ RedeemPoints(points, reason) { this.pointsLedger.Redeem(points, reason); } /** * Removes a referral from the account's referral list. This is used * only when a referral decides to permanently delete their account. * * @param {string} id User `_id` of the referral to be removed * * @function RemoveReferral * @memberof Account * @instance */ RemoveReferral(id) { if (this.referrals) { this.referrals = this.referrals.filter(element => element != id); } } /** * Changes the existing account password to `newPass`. This also changes the * `salt` used before hashing. * * @param {string} newPass New password * @returns A tuple of the form (salt, newPassword) * * @function ResetPassword * @memberof Account * @instance */ ResetPassword(newPass) { this.password = this.hash(newPass); } /** * Generates a random 6-digit number and stores it in database as a verification code. * * @function SetOTP * @memberof Account * @instance */ SetOTP() { this.otp = Math.floor(Math.random() * 900000) + 100000; } /** * Sets the verification code `otp` to `undefined` after a successful verification * * @function UnsetOTP * @memberof Account * @instance */ UnsetOTP() { this.otp = undefined; } /** * Puts the account into a 14-day deletion hold by setting `isDeleted` to `true` * and setting the account `recoverBy` date to 14-days from now. * * @function Delete * @memberof Account * @instance */ Delete() { this.isDeleted = true; const millisecondsInAFortnight = 1209600000; this.recoverBy = Date.now() + millisecondsInAFortnight; } /** * Removes the deletion hold on the account by setting `isDeleted` to `false` and * setting the account `recoverBy` date to `undefined`. * * @function Undelete * @memberof Account * @instance */ Undelete() { this.isDeleted = false; this.recoverBy = undefined; } /** * Exports the `Account` object to a shareable JSON string. Used to compose * HTTP response bodies. Prevents sensitive information such as password, salt etc. * from leaking onto client-side. * * @returns {string} A stringified, sanitised version of the `Account` instance * * @function Export * @memberof Account * @instance */ Export() { // Copy the object so changes made here don't affect `this` object let exportable = JSON.parse(JSON.stringify(this)); // Convert ObjectId to string exportable._id = exportable._id.toString(); // Remove sensitive information delete exportable.otp; delete exportable.password; delete exportable.salt; delete exportable.isDeleted; delete exportable.recoverBy; return JSON.stringify(exportable); } // // Private methods // /** * Storing plain-text password is poor security practice. When client sends a plain-text password, * it must be salted, i.e. a random bytes must be attached to it, and then hashed to make it harder * for hackers to hack into someone's account. This method takes in a plaintext UTF-8 password and * returns a salted and hashed Base64 string which is written to the database. * * @param {string} password Plaintext to be hashed * @param {string} salt A Base64 string denoting the random data to be attached to the password. * If a value is not provided, one is generated randomly at runtime using * Node's `Crypto.randomBytes()`. * * @returns {string} A salted and hashed Base64 string * * @function hash * @memberof Account * @private * @instance */ hash(password, salt = Crypto.randomBytes(128).toString("base64")) { this.salt = salt; return Crypto.pbkdf2Sync(password, salt, 5000, 512, "sha512").toString("base64"); } /** * Generates a unique referral code using Node's `crypto` module. * * @returns {string} The referral code * * @function generateReferralCode * @memberof Account * @private * @instance * */ generateReferralCode() { return Crypto.randomBytes(5).toString("hex").toLowerCase(); } // // Static methods // /** * Changes user's password. The newPassword uses a unique salt and the salted * string is hashed for better security. * * @param {string} password The new password * * @returns {any} A tuple of the form (salt, newPassword) * * @function ResetPassword * @memberof Account * @private * @static */ static ResetPassword(newPassword) { let salt = Crypto.randomBytes(128).toString("base64"); let hashed = Crypto.pbkdf2Sync(newPassword, salt, 5000, 512, "sha512").toString("base64"); return [salt, hashed]; } } exports.Account = Account; //# sourceMappingURL=Account.js.map</code></pre> </article> </section> </div> <br class="clear"> <footer> Documentation generated at Mon Mar 19 2018 22:39:23 GMT+0530 (IST) </footer> <script>prettyPrint();</script> <script src="scripts/linenumber.js"></script> </body> </html>