UNPKG

@rnaga/wp-node

Version:

👉 **[View Full Documentation at rnaga.github.io/wp-node →](https://rnaga.github.io/wp-node/)**

295 lines (294 loc) • 11 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generatePassword = exports.checkPassword = exports.hashPassword = void 0; const crypto = __importStar(require("crypto")); const bcryptjs_1 = __importDefault(require("bcryptjs")); const hashPassword = (plainText) => { const passwordHash = new PasswordHash(8, true); return passwordHash.hashPassword(plainText); }; exports.hashPassword = hashPassword; const checkPassword = (plainText, storedHash) => { const passwordHash = new PasswordHash(8, true); return passwordHash.checkPassword(plainText, storedHash); }; exports.checkPassword = checkPassword; const generatePassword = (length = 12, specialChars = true, extraSpecialChars = false) => { let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; if (specialChars) { chars += "!@#$%^&*()"; } if (extraSpecialChars) { chars += "-_ []{}<>~`+=,.;:/?|"; } let password = ""; for (let i = 0; i < length; i++) { password += chars.charAt(Math.floor(Math.random() * chars.length)); } return password; }; exports.generatePassword = generatePassword; const CRYPT_BLOWFISH = 1; class PasswordHash { itoa64; iterationCountLog2; portableHashes; randomState; constructor(iterationCountLog2, portableHashes) { this.itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; if (iterationCountLog2 < 4 || iterationCountLog2 > 31) { iterationCountLog2 = 8; } this.iterationCountLog2 = iterationCountLog2; this.portableHashes = portableHashes; this.randomState = Date.now().toString(); if (typeof process !== "undefined" && typeof process.pid !== "undefined") { this.randomState += process.pid.toString(); } } getRandomBytes(count) { let output = ""; try { const randomBytes = crypto.randomBytes(count); output = randomBytes.toString("hex"); } catch (err) { for (let i = 0; i < count; i += 16) { this.randomState = crypto .createHash("md5") .update(this.randomState + Math.random()) .digest("binary"); output += crypto .createHash("md5") .update(this.randomState, "binary") .digest("binary"); } output = output.substring(0, count); } return output; } encode64(input, count) { let output = ""; let i = 0; do { let value = input.charCodeAt(i++); output += this.itoa64[value & 0x3f]; if (i < count) { value |= input.charCodeAt(i) << 8; } output += this.itoa64[(value >> 6) & 0x3f]; if (i++ >= count) { break; } if (i < count) { value |= input.charCodeAt(i) << 16; } output += this.itoa64[(value >> 12) & 0x3f]; if (i++ >= count) { break; } output += this.itoa64[(value >> 18) & 0x3f]; } while (i < count); return output; } genSaltPrivate(input) { let output = "$P$"; output += this.itoa64[Math.min(this.iterationCountLog2 + (parseInt(process.versions.node, 10) >= 5 ? 5 : 3), 30)]; output += this.encode64(input, 6); return output; } cryptPrivate(password, setting) { let output = "*0"; if (setting.substring(0, 2) === output) { output = "*1"; } const id = setting.substring(0, 3); if (id !== "$P$" && id !== "$H$") { return output; } const countLog2 = this.itoa64.indexOf(setting[3]); if (countLog2 < 7 || countLog2 > 30) { return output; } let count = 1 << countLog2; const salt = setting.substring(4, 12); if (salt.length !== 8) { return output; } let hash = crypto .createHash("md5") .update(salt + password, "binary") .digest("binary"); do { hash = crypto .createHash("md5") .update(hash + password, "binary") .digest("binary"); } while (--count); output = setting.substring(0, 12) + this.encode64(hash, 16); return output; } genSaltBlowfish(input) { const itoa64 = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let output = "$2a$"; output += String.fromCharCode(parseInt("0", 10) + Math.floor(this.iterationCountLog2 / 10)); output += String.fromCharCode(parseInt("0", 10) + (this.iterationCountLog2 % 10)); output += "$"; let i = 0; do { const c1 = input.charCodeAt(i++); output += itoa64[c1 >> 2]; const c2 = input.charCodeAt(i++); output += itoa64[((c1 & 0x03) << 4) | (c2 >> 4)]; if (i >= 16) { output += itoa64[c2 & 0x0f]; break; } const c3 = input.charCodeAt(i++); output += itoa64[((c2 & 0x0f) << 2) | (c3 >> 6)]; output += itoa64[c3 & 0x3f]; // eslint-disable-next-line no-constant-condition } while (true); return output; } hashPassword(password) { if (password.length > 4096) { return "*"; } let random = ""; if (CRYPT_BLOWFISH === 1 && !this.portableHashes) { random = this.getRandomBytes(16); const hash = this.cryptPrivate(password, this.genSaltBlowfish(random)); if (hash.length === 60) { return hash; } } if (random.length < 6) { random = this.getRandomBytes(6); } const hash = this.cryptPrivate(password, this.genSaltPrivate(random)); if (hash.length === 34) { return hash; } return "*"; } // https://github.com/WordPress/wordpress-develop/blob/063a74f93f0a89d1d92fac1f25c49a379ab3476b/src/wp-includes/pluggable.php#L2740 checkPassword(password, storedHash) { // Passwords longer than 4096 characters are not supported if (password.length > 4096) { return false; } // Check the hash using md5 regardless of the current hashing mechanism (legacy support) if (storedHash.length <= 32) { const md5Hash = crypto.createHash("md5").update(password).digest("hex"); return this.hashEquals(storedHash, md5Hash); } // Check the password using the current WordPress prefixed hash ($wp$ prefix) if (storedHash.startsWith("$wp$")) { try { // WordPress 6.8+ uses SHA384 HMAC preprocessing before bcrypt const passwordToVerify = crypto .createHmac("sha384", "wp-sha384") .update(password) .digest("base64"); const bcryptHash = storedHash.substring(3); // Remove "$wp" prefix return this.verifyPassword(passwordToVerify, bcryptHash); } catch (err) { console.error("Error verifying WordPress prefixed hash:", err); return false; } } // Check the password using phpass ($P$ prefix) if (storedHash.startsWith("$P$")) { let hash = this.cryptPrivate(password, storedHash); if (hash[0] === "*") { hash = crypto .createHash("md5") .update(password, "binary") .digest("binary"); hash = this.cryptPrivate(password, storedHash); } return hash === storedHash; } // Check the password using compat support for any non-prefixed hash (bcrypt, Argon2, etc.) try { return this.verifyPassword(password, storedHash); } catch (err) { console.error("Error verifying non-prefixed hash:", err); return false; } } /** * Secure hash comparison to prevent timing attacks */ hashEquals(hash1, hash2) { if (hash1.length !== hash2.length) { return false; } let result = 0; for (let i = 0; i < hash1.length; i++) { result |= hash1.charCodeAt(i) ^ hash2.charCodeAt(i); } return result === 0; } /** * Verify password using modern hashing algorithms (bcrypt, Argon2, etc.) */ verifyPassword(password, hash) { try { // Try bcrypt first (most common) if (hash.match(/^\$2[axyb]\$/)) { return bcryptjs_1.default.compareSync(password, hash); } // For other algorithms, we would need additional libraries // For now, we'll just try bcrypt and return false for unsupported formats console.warn(`Unsupported hash format: ${hash.substring(0, 10)}...`); return false; } catch (err) { console.error("Error in password verification:", err); return false; } } }