@anddev-oss/verdaccio-htpasswd-azure
Version:
htpasswd auth plugin for Verdaccio that saves in Azure Blob
189 lines • 7.15 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_BCRYPT_ROUNDS = void 0;
exports.parseHTPasswd = parseHTPasswd;
exports.verifyPassword = verifyPassword;
exports.generateHtpasswdLine = generateHtpasswdLine;
exports.addUserToHTPasswd = addUserToHTPasswd;
exports.sanityCheck = sanityCheck;
exports.changePasswordToHTPasswd = changePasswordToHTPasswd;
exports.stringToUtf8 = stringToUtf8;
const apache_md5_1 = __importDefault(require("apache-md5"));
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const crypto_1 = __importDefault(require("crypto"));
const debug_1 = __importDefault(require("debug"));
const http_errors_1 = __importDefault(require("http-errors"));
const core_1 = require("@verdaccio/core");
const crypt3_1 = __importDefault(require("./crypt3"));
const debug = (0, debug_1.default)('verdaccio:plugin:htpasswd:utils');
exports.DEFAULT_BCRYPT_ROUNDS = 10;
/**
* parseHTPasswd - convert htpasswd lines to object.
* @param {string} input
* @returns {object}
*/
function parseHTPasswd(input) {
// The input is split on line ending styles that are both windows and unix compatible
return input.split(/[\r]?[\n]/).reduce((result, line) => {
const args = line.split(':', 3).map((str) => str.trim());
if (args.length > 1) {
result[args[0]] = args[1];
}
return result;
}, {});
}
/**
* verifyPassword - matches password and it's hash.
* @param {string} passwd
* @param {string} hash
* @returns {Promise<boolean>}
*/
async function verifyPassword(passwd, hash) {
if (hash.match(/^\$2([aby])\$/)) {
return await bcryptjs_1.default.compare(passwd, hash);
}
else if (hash.indexOf('{PLAIN}') === 0) {
return passwd === hash.slice(7);
}
else if (hash.indexOf('{SHA}') === 0) {
return (crypto_1.default
.createHash('sha1')
// https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding
.update(passwd, 'utf8')
.digest('base64') === hash.slice(5));
}
// for backwards compatibility, first check md5 then check crypt3
return (0, apache_md5_1.default)(passwd, hash) === hash || (0, crypt3_1.default)(passwd, hash) === hash;
}
/**
* generateHtpasswdLine - generates line for htpasswd file.
* @param {string} user
* @param {string} passwd
* @param {HtpasswdHashConfig} hashConfig
* @returns {string}
*/
async function generateHtpasswdLine(user, passwd, hashConfig) {
let hash;
debug('algorithm %o', hashConfig.algorithm);
switch (hashConfig.algorithm) {
case core_1.constants.HtpasswdHashAlgorithm.bcrypt:
hash = await bcryptjs_1.default.hash(passwd, hashConfig.rounds || exports.DEFAULT_BCRYPT_ROUNDS);
break;
case core_1.constants.HtpasswdHashAlgorithm.crypt:
hash = (0, crypt3_1.default)(passwd);
break;
case core_1.constants.HtpasswdHashAlgorithm.md5:
hash = (0, apache_md5_1.default)(passwd);
break;
case core_1.constants.HtpasswdHashAlgorithm.sha1:
hash = '{SHA}' + crypto_1.default.createHash('sha1').update(passwd, 'utf8').digest('base64');
break;
default:
throw (0, http_errors_1.default)('Unexpected hash algorithm');
}
const comment = 'autocreated ' + new Date().toJSON();
return `${user}:${hash}:${comment}\n`;
}
/**
* addUserToHTPasswd - Generate a htpasswd format for .htpasswd
* @param {string} body
* @param {string} user
* @param {string} passwd
* @param {HtpasswdHashConfig} hashConfig
* @returns {string}
*/
async function addUserToHTPasswd(body, user, passwd, hashConfig) {
if (user !== encodeURIComponent(user)) {
const err = (0, http_errors_1.default)('username should not contain non-uri-safe characters');
err.status = core_1.HTTP_STATUS.CONFLICT;
throw err;
}
let newline = await generateHtpasswdLine(user, passwd, hashConfig);
if (body.length && body[body.length - 1] !== '\n') {
newline = '\n' + newline;
}
return body + newline;
}
/**
* Sanity check for a user
* @param {string} user
* @param {object} users
* @param {string} password
* @param {Callback} verifyFn
* @param {number} maxUsers
* @returns {object}
*/
async function sanityCheck(user, password, verifyFn, users, maxUsers) {
let err;
// check for user or password
if (!user || !password) {
debug('username or password is missing');
err = Error(core_1.API_ERROR.USERNAME_PASSWORD_REQUIRED);
err.status = core_1.HTTP_STATUS.BAD_REQUEST;
return err;
}
const hash = users[user];
if (maxUsers < 0) {
debug('registration is disabled');
err = Error(core_1.API_ERROR.REGISTRATION_DISABLED);
err.status = core_1.HTTP_STATUS.CONFLICT;
return err;
}
if (hash) {
const auth = await verifyFn(password, users[user]);
if (auth) {
debug(`user ${user} already exists`);
err = Error(core_1.API_ERROR.USERNAME_ALREADY_REGISTERED);
err.status = core_1.HTTP_STATUS.CONFLICT;
return err;
}
debug(`user ${user} exists but password is wrong`);
err = Error(core_1.API_ERROR.UNAUTHORIZED_ACCESS);
err.status = core_1.HTTP_STATUS.UNAUTHORIZED;
return err;
}
else if (Object.keys(users).length >= maxUsers) {
debug('maximum amount of users reached');
err = Error(core_1.API_ERROR.MAX_USERS_REACHED);
err.status = core_1.HTTP_STATUS.FORBIDDEN;
return err;
}
debug('sanity check passed');
return null;
}
/**
* /**
* changePasswordToHTPasswd - change password for existing user
* @param {string} body
* @param {string} user
* @param {string} passwd
* @param {string} newPasswd
* @param {HtpasswdHashConfig} hashConfig
* @returns {Promise<string>}
*/
async function changePasswordToHTPasswd(body, user, passwd, newPasswd, hashConfig) {
debug('change password for user %o', user);
let lines = body.split('\n');
const userLineIndex = lines.findIndex((line) => line.split(':', 1).shift() === user);
if (userLineIndex === -1) {
debug('user %o does not exist', user);
throw new Error(`Unable to change password for user '${user}': user does not currently exist`);
}
const [username, hash] = lines[userLineIndex].split(':', 2);
const passwordValid = await verifyPassword(passwd, hash);
if (!passwordValid) {
debug(`invalid old password`);
throw new Error(`Unable to change password for user '${user}': invalid old password`);
}
const updatedUserLine = await generateHtpasswdLine(username, newPasswd, hashConfig);
lines.splice(userLineIndex, 1, updatedUserLine);
debug('password changed');
return lines.join('\n');
}
function stringToUtf8(authentication) {
return (authentication || '').toString();
}
//# sourceMappingURL=utils.js.map