1 /*
  2  * telepathy
  3  * https://github.com/rummik/telepathy
  4  *
  5  * Copyright (c) 2012 rummik
  6  * Licensed under the MPL license.
  7  */
  8 
  9 var CryptoJS   = require('../crypto-js/hmac-sha512'),
 10     BD         = require('bigdecimal'),
 11     BigInteger = BD.BigInteger;
 12 
 13 /**
 14  * Telepathy constructor
 15  * @constructor
 16  * @param {object|string} [options]
 17  */
 18 function Telepathy(options) {
 19 	options = options || {};
 20 
 21 	if (typeof options == 'string')
 22 		options = { secret: options };
 23 
 24 	this.setSecret(options.secret);
 25 	delete options.secret;
 26 
 27 	this.user     = options.user || '';
 28 	this.alphabet = options.alphabet || Telepathy.alphabet.base62;
 29 	this.length   = options.length || 10;
 30 	this.domain   = options.domain || '';
 31 }
 32 
 33 /**
 34  * Set the private secret we'll be using in generating passwords
 35  * @param {string} secret
 36  */
 37 Telepathy.prototype.setSecret = function(secret) {
 38 	this.password = this.constructor.prototype._password.bind(this, secret || '');
 39 };
 40 
 41 /**
 42  * Generate a password
 43  * @param {object|string|number} [options]
 44  * @returns {string} Generated password
 45  * @example
 46  * var telepathy = new Telepathy('secret');
 47  * console.log(telepathy.password('google.com'));
 48  */
 49 Telepathy.prototype.password = function(options) {};
 50 
 51 /** @ignore */
 52 Telepathy.prototype._password = function(secret, options) {
 53 	options = options || {};
 54 
 55 	if (typeof options == 'string')
 56 		options = { domain: options };
 57 
 58 	if (typeof options == 'number')
 59 		options = { index: options };
 60 
 61 	var length   = parseInt(options.length || this.length || 10, 10),
 62 	    index    = Math.max(parseInt(options.index || 0, 10), 0),
 63 	    user     = options.user     || this.user,
 64 	    domain   = options.domain   || this.domain,
 65 	    alphabet = options.alphabet || this.alphabet,
 66 	    password = '',
 67 	    base     = new BigInteger(alphabet.length.toString()),
 68 	    data     = user + domain,
 69 	    len      = length * (index + 1),
 70 	    result, hash, remainder;
 71 
 72 	secret = options.secret || secret;
 73 
 74 	while (password.length < len) {
 75 		// create hash, and make it a big integer
 76 		hash = new BigInteger(CryptoJS.HmacSHA512(data + password, secret).toString(), 16);
 77 
 78 		// convert hash to an arbitrary base
 79 		while (hash.compareTo(base) >= 0 && password.length < len) {
 80 			result    = hash.divideAndRemainder(base);
 81 			hash      = result[0];
 82 			remainder = result[1];
 83 
 84 			password = alphabet[remainder] + password;
 85 
 86 			if (hash.compareTo(base) == -1 && password.length < len)
 87 				password = alphabet[hash] + password;
 88 		}
 89 	}
 90 
 91 	return password.substr(0, length);
 92 };
 93 
 94 /**
 95  * Output alphabets used in password generation (larger alphabets are better)
 96  * @readonly
 97  * @namespace
 98  * @property {string} base94 WARNING: this could break insecure login forms
 99  * @property {string} base62
100  * @property {string} base36
101  * @property {string} base16
102  * @property {string} base10
103  * @property {string} base8
104  * @property {string} base5
105  * @property {string} base2
106  */
107 Telepathy.alphabet = {
108 	base94: '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~',
109 	base62: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
110 	base36: '0123456789abcdefghijklmnopqrstuvwxyz',
111 	base16: '0123456789abcdef',
112 	base10: '0123456789',
113 	base8:  '01234567',
114 	base5:  '01234',
115 	base2:  '01'
116 };
117 
118 module.exports = Telepathy;
119