@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
JavaScript
"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;
}
}
}