mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
refactor: remove internal plugin
This commit is contained in:
parent
d3d8d182ec
commit
f972585f39
3 changed files with 0 additions and 268 deletions
|
@ -1,59 +0,0 @@
|
||||||
/* eslint require-jsdoc: off */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/** Node.js Crypt(3) Library
|
|
||||||
|
|
||||||
Inspired by (and intended to be compatible with) sendanor/crypt3
|
|
||||||
|
|
||||||
see https://github.com/sendanor/node-crypt3
|
|
||||||
|
|
||||||
The key difference is the removal of the dependency on the unix crypt(3) function
|
|
||||||
which is not platform independent, and requires compilation. Instead, a pure
|
|
||||||
javascript version is used.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const crypt = require('unix-crypt-td-js');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
function createSalt(type) {
|
|
||||||
type = type || 'sha512';
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
|
|
||||||
case 'md5':
|
|
||||||
return '$1$' + crypto.randomBytes(10).toString('base64');
|
|
||||||
|
|
||||||
case 'blowfish':
|
|
||||||
return '$2a$' + crypto.randomBytes(10).toString('base64');
|
|
||||||
|
|
||||||
case 'sha256':
|
|
||||||
return '$5$' + crypto.randomBytes(10).toString('base64');
|
|
||||||
|
|
||||||
case 'sha512':
|
|
||||||
return '$6$' + crypto.randomBytes(10).toString('base64');
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new TypeError('Unknown salt type at crypt3.createSalt: ' + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function crypt3(key, salt) {
|
|
||||||
salt = salt || createSalt();
|
|
||||||
return crypt(key, salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Crypt(3) password and data encryption.
|
|
||||||
* @param {string} key user's typed password
|
|
||||||
* @param {string} salt Optional salt, for example SHA-512 use "$6$salt$".
|
|
||||||
* @returns {string} A generated hash in format $id$salt$encrypted
|
|
||||||
* @see https://en.wikipedia.org/wiki/Crypt_(C)
|
|
||||||
*/
|
|
||||||
module.exports = crypt3;
|
|
||||||
|
|
||||||
/** Create salt
|
|
||||||
* @param {string} type The type of salt: md5, blowfish (only some linux distros), sha256 or sha512. Default is sha512.
|
|
||||||
* @returns {string} Generated salt string
|
|
||||||
*/
|
|
||||||
module.exports.createSalt = createSalt;
|
|
|
@ -1,137 +0,0 @@
|
||||||
/* eslint require-jsdoc: off */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let fs = require('fs');
|
|
||||||
let Path = require('path');
|
|
||||||
let utils = require('./utils');
|
|
||||||
|
|
||||||
module.exports = HTPasswd;
|
|
||||||
|
|
||||||
function HTPasswd(config, stuff) {
|
|
||||||
let self = Object.create(HTPasswd.prototype);
|
|
||||||
self._users = {};
|
|
||||||
|
|
||||||
// config for this module
|
|
||||||
self._config = config;
|
|
||||||
|
|
||||||
// verdaccio logger
|
|
||||||
self._logger = stuff.logger;
|
|
||||||
|
|
||||||
// verdaccio main config object
|
|
||||||
self._verdaccio_config = stuff.config;
|
|
||||||
|
|
||||||
// all this "verdaccio_config" stuff is for b/w compatibility only
|
|
||||||
self._maxusers = self._config.max_users;
|
|
||||||
if (!self._maxusers) self._maxusers = self._verdaccio_config.max_users;
|
|
||||||
// set maxusers to Infinity if not specified
|
|
||||||
if (!self._maxusers) self._maxusers = Infinity;
|
|
||||||
|
|
||||||
self._last_time = null;
|
|
||||||
let file = self._config.file;
|
|
||||||
if (!file) file = self._verdaccio_config.users_file;
|
|
||||||
if (!file) throw new Error('should specify "file" in config');
|
|
||||||
self._path = Path.resolve(Path.dirname(self._verdaccio_config.self_path), file);
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTPasswd.prototype.authenticate = function(user, password, cb) {
|
|
||||||
let self = this;
|
|
||||||
self._reload(function(err) {
|
|
||||||
if (err) return cb(err.code === 'ENOENT' ? null : err);
|
|
||||||
if (!self._users[user]) return cb(null, false);
|
|
||||||
if (!utils.verify_password(user, password, self._users[user])) return cb(null, false);
|
|
||||||
|
|
||||||
// authentication succeeded!
|
|
||||||
// return all usergroups this user has access to;
|
|
||||||
// (this particular package has no concept of usergroups, so just return user herself)
|
|
||||||
return cb(null, [user]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// hopefully race-condition-free way to add users:
|
|
||||||
// 1. lock file for writing (other processes can still read)
|
|
||||||
// 2. reload .htpasswd
|
|
||||||
// 3. write new data into .htpasswd.tmp
|
|
||||||
// 4. move .htpasswd.tmp to .htpasswd
|
|
||||||
// 5. reload .htpasswd
|
|
||||||
// 6. unlock file
|
|
||||||
HTPasswd.prototype.adduser = function(user, password, real_cb) {
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
function sanity_check() {
|
|
||||||
let err = null;
|
|
||||||
if (self._users[user]) {
|
|
||||||
err = Error('this user already exists');
|
|
||||||
} else if (Object.keys(self._users).length >= self._maxusers) {
|
|
||||||
err = Error('maximum amount of users reached');
|
|
||||||
}
|
|
||||||
if (err) err.status = 403;
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// preliminary checks, just to ensure that file won't be reloaded if it's not needed
|
|
||||||
let s_err = sanity_check();
|
|
||||||
if (s_err) return real_cb(s_err, false);
|
|
||||||
|
|
||||||
utils.lock_and_read(self._path, function(err, res) {
|
|
||||||
let locked = false;
|
|
||||||
|
|
||||||
// callback that cleans up lock first
|
|
||||||
function cb(err) {
|
|
||||||
if (locked) {
|
|
||||||
utils.unlock_file(self._path, function() {
|
|
||||||
// ignore any error from the unlock
|
|
||||||
real_cb(err, !err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
real_cb(err, !err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!err) {
|
|
||||||
locked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore ENOENT errors, we'll just create .htpasswd in that case
|
|
||||||
if (err && err.code !== 'ENOENT') return cb(err);
|
|
||||||
|
|
||||||
let body = (res || '').toString('utf8');
|
|
||||||
self._users = utils.parse_htpasswd(body);
|
|
||||||
|
|
||||||
// real checks, to prevent race conditions
|
|
||||||
let s_err = sanity_check();
|
|
||||||
if (s_err) return cb(s_err);
|
|
||||||
|
|
||||||
try {
|
|
||||||
body = utils.add_user_to_htpasswd(body, user, password);
|
|
||||||
} catch (err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
fs.writeFile(self._path, body, function(err) {
|
|
||||||
if (err) return cb(err);
|
|
||||||
self._reload(function() {
|
|
||||||
cb(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
HTPasswd.prototype._reload = function(_callback) {
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
fs.stat(self._path, function(err, stats) {
|
|
||||||
if (err) return _callback(err);
|
|
||||||
|
|
||||||
if (self._last_time === stats.mtime) return _callback();
|
|
||||||
self._last_time = stats.mtime;
|
|
||||||
|
|
||||||
fs.readFile(self._path, 'utf8', function(err, buffer) {
|
|
||||||
if (err) return _callback(err);
|
|
||||||
|
|
||||||
self._users = utils.parse_htpasswd(buffer);
|
|
||||||
|
|
||||||
_callback();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,72 +0,0 @@
|
||||||
/* eslint require-jsdoc: off */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let crypto = require('crypto');
|
|
||||||
let crypt3 = require('./crypt3');
|
|
||||||
let md5 = require('apache-md5');
|
|
||||||
let locker = require('@verdaccio/file-locking');
|
|
||||||
|
|
||||||
// this function neither unlocks file nor closes it
|
|
||||||
// it'll have to be done manually later
|
|
||||||
function lock_and_read(name, cb) {
|
|
||||||
locker.readFile(name, {lock: true}, function(err, res) {
|
|
||||||
if (err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
return cb(null, res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// close and unlock file
|
|
||||||
function unlock_file(name, cb) {
|
|
||||||
locker.unlockFile(name, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse_htpasswd(input) {
|
|
||||||
let result = {};
|
|
||||||
input.split('\n').forEach(function(line) {
|
|
||||||
let args = line.split(':', 3);
|
|
||||||
if (args.length > 1) result[args[0]] = args[1];
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function verify_password(user, passwd, hash) {
|
|
||||||
if (hash.indexOf('{PLAIN}') === 0) {
|
|
||||||
return passwd === hash.substr(7);
|
|
||||||
} else if (hash.indexOf('{SHA}') === 0) {
|
|
||||||
return crypto.createHash('sha1').update(passwd, 'binary').digest('base64') === hash.substr(5);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
// for backwards compatibility, first check md5 then check crypt3
|
|
||||||
md5(passwd, hash) === hash ||
|
|
||||||
crypt3(passwd, hash) === hash
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_user_to_htpasswd(body, user, passwd) {
|
|
||||||
if (user !== encodeURIComponent(user)) {
|
|
||||||
let err = Error('username should not contain non-uri-safe characters');
|
|
||||||
err.status = 409;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (crypt3) {
|
|
||||||
passwd = crypt3(passwd);
|
|
||||||
} else {
|
|
||||||
passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64');
|
|
||||||
}
|
|
||||||
let comment = 'autocreated ' + (new Date()).toJSON();
|
|
||||||
|
|
||||||
let newline = user + ':' + passwd + ':' + comment + '\n';
|
|
||||||
if (body.length && body[body.length-1] !== '\n') newline = '\n' + newline;
|
|
||||||
return body + newline;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.parse_htpasswd = parse_htpasswd;
|
|
||||||
module.exports.verify_password = verify_password;
|
|
||||||
module.exports.add_user_to_htpasswd = add_user_to_htpasswd;
|
|
||||||
module.exports.lock_and_read = lock_and_read;
|
|
||||||
module.exports.unlock_file = unlock_file;
|
|
Loading…
Reference in a new issue