Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modified package to now use class based approach #10

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 70 additions & 103 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,63 @@
'use strict';

const funcs = {};
const queue = [];

/**
* Installs a compatible password hashing function.
* @public
* @param {string} name The name of the password hashing function.
* @param {Object} algorithm The password hashing function object.
* @param {Function} algorithm.hash A function that takes a password and returns
* a cryptographically secure password hash string.
* @param {Function} algorithm.verify A function that takes a secure password
* hash string and a password and returns whether or not the password is valid
* for the given hash string.
* @param {Function} algorithm.identifiers A function that returns the list of
* identifiers that this password hashing algorithm is able to generate / verify.
*/
function install(name, algorithm) {
if (typeof name !== 'string' || name === '') {
throw new TypeError('The algorithm name must be an non-empty string.');
}
if (
typeof algorithm !== 'object' ||
algorithm === null ||
Array.isArray(algorithm)
) {
throw new TypeError('The algorithm object must be an object.');
}

if (typeof algorithm.hash !== 'function') {
throw new TypeError(
'The hash property of the algorithm object should be a function.'
);
}

if (typeof algorithm.verify !== 'function') {
throw new TypeError(
'The verify property of the algorithm object should be a function.'
);
}
function Upash(algorithms, options) {
this.funcs = {};
this.queue = [];
this.default = options && (options.default || null);

const install = (name, algorithm) => {
if (typeof name !== 'string' || name === '') {
throw new TypeError('The algorithm name must be an non-empty string.');
}
if (
typeof algorithm !== 'object' ||
algorithm === null ||
Array.isArray(algorithm)
) {
throw new TypeError('The algorithm object must be an object.');
}

if (typeof algorithm.identifiers !== 'function') {
throw new TypeError(
'The identifiers property of the algorithm object should be a function.'
);
}
if (typeof algorithm.hash !== 'function') {
throw new TypeError(
'The hash property of the algorithm object should be a function.'
);
}

if (funcs[name] !== undefined) {
throw new TypeError(`The ${name} algorithm is already installed.`);
}
if (typeof algorithm.verify !== 'function') {
throw new TypeError(
'The verify property of the algorithm object should be a function.'
);
}

const idfs = algorithm.identifiers();
for (const an of queue) {
if (funcs[an].identifiers().some(idf => idfs.indexOf(idf) !== -1)) {
throw new Error(
'The identifiers property of the algorithm object clashes with the ones of another algorithm.'
if (typeof algorithm.identifiers !== 'function') {
throw new TypeError(
'The identifiers property of the algorithm object should be a function.'
);
}
}

funcs[name] = Object.assign({}, algorithm);
Object.freeze(funcs[name]);
queue.push(name);
}
if (this.funcs[name] !== undefined) {
throw new TypeError(`The ${name} algorithm is already installed.`);
}

/**
* Uninstalls a password hashing function previously installed.
* @public
* @param {string} name The name of the algorithm to uninstall or 'last' to
* uninstall the last one installed.
*/
function uninstall(name) {
if (typeof name !== 'string' || name === '') {
throw new TypeError('The algorithm name must be an non-empty string.');
}
const idfs = algorithm.identifiers();
for (const an of this.queue) {
if (this.funcs[an].identifiers().some(idf => idfs.indexOf(idf) !== -1)) {
throw new Error(
'The identifiers property of the algorithm object clashes with the ones of another algorithm.'
);
}
}

const hashFunc = funcs[name];
this.funcs[name] = Object.assign({}, algorithm);
Object.freeze(this.funcs[name]);
this.queue.push(name);
};

if (!hashFunc) {
throw new TypeError(`The ${name} algorithm is not installed`);
if (algorithms) {
Object.keys(algorithms).forEach(current => {
install(current, algorithms[current]);
});
}

delete funcs[name];
queue.splice(queue.indexOf(name), 1);
}

/**
Expand All @@ -91,9 +66,9 @@ function uninstall(name) {
* @return {string[]} The array of the available password hashing
* functions.
*/
function list() {
return queue.slice(0);
}
Upash.prototype.list = function() {
return this.queue.slice(0);
};

/**
* Selects manually which password hashing function to use.
Expand All @@ -102,32 +77,33 @@ function list() {
* @param {string|undefined} name The name of the algorithm to use.
* @return {Object} The password hashing function object.
*/
function use(name) {
Upash.prototype.use = function(name) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should set the default for the current instance

if (name === undefined) {
if (queue.length === 0) {
if (this.queue.length === 0 || !this.default) {
throw new Error('No algorithm installed.');
}
name = queue[queue.length - 1];
name = this.default;
} else if (typeof name !== 'string' || name === '') {
throw new TypeError('The algorithm name must be an non-empty string.');
}

const hashFunc = funcs[name];
const hashFunc = this.funcs[name];

if (!hashFunc) {
throw new TypeError(`The ${name} algorithm is not installed`);
}

this.default = name;
return hashFunc;
}
};

/**
* Returns the name of the algorithm that has generated the hash string.
* @public
* @param {string} hashstr Secure hash string generated from this package.
* @return {string|null} The name of password hashing algorithm.
*/
function which(hashstr) {
Upash.prototype.which = function(hashstr) {
if (typeof hashstr !== 'string' || hashstr === '') {
throw new TypeError('The hashstr param must be an non-empty string.');
}
Expand All @@ -140,16 +116,16 @@ function which(hashstr) {
}
const idf = fields[1];

if (queue.length === 0) {
if (this.queue.length === 0) {
throw new Error('No algorithm installed.');
}

for (const name of queue) {
if (funcs[name].identifiers().indexOf(idf) === -1) continue;
for (const name of this.queue) {
if (this.funcs[name].identifiers().indexOf(idf) === -1) continue;
return name;
}
return null;
}
};

/**
* Determines whether or not the hash provided matches the hash generated for
Expand All @@ -161,15 +137,15 @@ function which(hashstr) {
* @returns {Promise.<boolean>} A boolean that is true if the hash computed
* for the password matches.
*/
function verify(hashstr, password) {
const name = which(hashstr);
Upash.prototype.verify = function(hashstr, password) {
const name = this.which(hashstr);

if (name === null) {
throw new TypeError('No compatible algorithm installed.');
}

return use(name).verify(hashstr, password);
}
return this.use(name).verify(hashstr, password);
};

/**
* Computes the hash string of the given password using the 'last' algorithm
Expand All @@ -180,17 +156,8 @@ function verify(hashstr, password) {
* function. See the algorithm specific documentation for the options supported.
* @returns {Promise.<string>} The generated secure hash string.
*/
function hash(password, options) {
return use().hash(password, options);
}

module.exports = Object.freeze({
install,
uninstall,
list,
use,
which,
Upash.prototype.hash = function(password, options) {
return this.use().hash(password, options);
};

verify,
hash
});
module.exports = Upash;
Loading