remove plugins

This commit is contained in:
Manuel Araoz 2015-02-05 14:30:50 -03:00
parent 3e90fd420a
commit 777f17feb4
31 changed files with 1 additions and 3292 deletions

View File

@ -73,16 +73,8 @@ INSIGHT_PORT # insight api port
INSIGHT_DB # Path where to store insight's internal DB. (defaults to $HOME/.insight)
INSIGHT_SAFE_CONFIRMATIONS=6 # Nr. of confirmation needed to start caching transaction information
INSIGHT_IGNORE_CACHE # True to ignore cache of spents in transaction, with more than INSIGHT_SAFE_CONFIRMATIONS confirmations. This is useful for tracking double spents for old transactions.
ENABLE_MAILBOX # if "true" will enable mailbox plugin
ENABLE_CLEANER # if "true" will enable message db cleaner plugin
ENABLE_MONITOR # if "true" will enable message db monitor plugin
ENABLE_CURRENCYRATES # if "true" will enable a plugin to obtain historic conversion rates for various currencies
ENABLE_RATELIMITER # if "true" will enable the ratelimiter plugin
LOGGER_LEVEL # defaults to 'info', can be 'debug','verbose','error', etc.
ENABLE_HTTPS # if "true" it will server using SSL/HTTPS
ENABLE_EMAILSTORE # if "true" will enable a plugin to store data with a validated email address
INSIGHT_EMAIL_CONFIRM_HOST # Only meanfull if ENABLE_EMAILSTORE is enable. Hostname for the confirm URLs. E.g: 'https://insight.bitpay.com'
```
Make sure that bitcoind is configured to [accept incoming connections using 'rpcallowip'](https://en.bitcoin.it/wiki/Running_Bitcoin).

View File

@ -16,7 +16,7 @@ var version = JSON.parse(packageStr).version;
function getUserHome() {
return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
}
var home = process.env.INSIGHT_DB || (getUserHome() + '/.insight');
@ -79,38 +79,14 @@ var bitcoindConf = {
disableAgent: true
};
var enableMonitor = process.env.ENABLE_MONITOR === 'true';
var enableCleaner = process.env.ENABLE_CLEANER === 'true';
var enableMailbox = process.env.ENABLE_MAILBOX === 'true';
var enableRatelimiter = process.env.ENABLE_RATELIMITER === 'true';
var enableCredentialstore = process.env.ENABLE_CREDSTORE === 'true';
var enableEmailstore = process.env.ENABLE_EMAILSTORE === 'true';
var enablePublicInfo = process.env.ENABLE_PUBLICINFO === 'true';
var loggerLevel = process.env.LOGGER_LEVEL || 'info';
var enableHTTPS = process.env.ENABLE_HTTPS === 'true';
var enableCurrencyRates = process.env.ENABLE_CURRENCYRATES === 'true';
if (!fs.existsSync(db)) {
mkdirp.sync(db);
}
module.exports = {
enableMonitor: enableMonitor,
monitor: require('../plugins/config-monitor.js'),
enableCleaner: enableCleaner,
cleaner: require('../plugins/config-cleaner.js'),
enableMailbox: enableMailbox,
mailbox: require('../plugins/config-mailbox.js'),
enableRatelimiter: enableRatelimiter,
ratelimiter: require('../plugins/config-ratelimiter.js'),
enableCredentialstore: enableCredentialstore,
credentialstore: require('../plugins/config-credentialstore'),
enableEmailstore: enableEmailstore,
emailstore: require('../plugins/config-emailstore'),
enableCurrencyRates: enableCurrencyRates,
currencyrates: require('../plugins/config-currencyrates'),
enablePublicInfo: enablePublicInfo,
publicInfo: require('../plugins/publicInfo/config'),
loggerLevel: loggerLevel,
enableHTTPS: enableHTTPS,
version: version,
@ -126,8 +102,6 @@ module.exports = {
disableHistoricSync: false,
poolMatchFile: rootPath + '/etc/minersPoolStrings.json',
// Time to refresh the currency rate. In minutes
currencyRefresh: 10,
keys: {
segmentio: process.env.INSIGHT_SEGMENTIO_KEY
},

View File

@ -53,31 +53,6 @@ module.exports = function(app) {
var currency = require('../app/controllers/currency');
app.get(apiPrefix + '/currency', currency.index);
// Email store plugin
if (config.enableEmailstore) {
var emailPlugin = require('../plugins/emailstore');
app.post(apiPrefix + '/email/save', emailPlugin.save);
app.get(apiPrefix + '/email/retrieve', emailPlugin.retrieve);
app.post(apiPrefix + '/email/change_passphrase', emailPlugin.changePassphrase);
app.post(apiPrefix + '/email/validate', emailPlugin.validate);
app.get(apiPrefix + '/email/validate', emailPlugin.validate);
app.post(apiPrefix + '/email/register', emailPlugin.oldSave);
app.get(apiPrefix + '/email/retrieve/:email', emailPlugin.oldRetrieve);
app.post(apiPrefix + '/email/delete/profile', emailPlugin.eraseProfile);
app.get(apiPrefix + '/email/delete/item', emailPlugin.erase);
app.get(apiPrefix + '/email/resend_email', emailPlugin.resendEmail);
}
// Currency rates plugin
if (config.enableCurrencyRates) {
var currencyRatesPlugin = require('../plugins/currencyrates');
app.get(apiPrefix + '/rates/:code', currencyRatesPlugin.getRate);
}
// Address routes
var messages = require('../app/controllers/messages');
app.get(apiPrefix + '/messages/verify', messages.verify);

View File

@ -1,21 +0,0 @@
#!usr/bin/env node
var email = process.argv[2];
if (!email) {
console.log('\tdeleteWholeProfile.js <email>');
process.exit(-1);
}
console.log('\t Deleting email:', email, process.env.INSIGHT_NETWORK);
var p = require('../plugins/emailstore');
p.init({});
p.deleteWholeProfile(email, function(err) {
if (err)
console.log('[err:]', err);
else
console.log('done');
});

View File

@ -126,31 +126,6 @@ if (peerSync) peerSync.allowReorgs = true;
var ios = require('socket.io')(server, config);
require('./app/controllers/socket.js').init(ios);
// plugins
if (config.enableRatelimiter) {
require('./plugins/ratelimiter').init(expressApp, config.ratelimiter);
}
if (config.enableMailbox) {
require('./plugins/mailbox').init(ios, config.mailbox);
}
if (config.enableCleaner) {
require('./plugins/cleaner').init(config.cleaner);
}
if (config.enableMonitor) {
require('./plugins/monitor').init(config.monitor);
}
if (config.enableEmailstore) {
require('./plugins/emailstore').init(config.emailstore);
}
if (config.enableCurrencyRates) {
require('./plugins/currencyrates').init(config.currencyrates);
}
// express settings
require('./config/express')(expressApp, historicSync, peerSync);
require('./config/routes')(expressApp);

View File

@ -1,25 +0,0 @@
var mdb = require('../lib/MessageDb').default();
var logger = require('../lib/logger').logger;
var preconditions = require('preconditions').singleton();
var microtime = require('microtime');
var cron = require('cron');
var CronJob = cron.CronJob;
var Threshold = (process.env.CLEANER_THRESHOLD_DAYS || 30) *24*60*60; // in seconds
module.exports.init = function(config) {
var cronTime = config.cronTime || '0 * * * *';
logger.info('Using cleaner plugin with cronTime ' + cronTime + ' and threshold of ' + Threshold + ' seconds');
var onTick = function() {
var limit = microtime.now() - 1000 * 1000 * Threshold;
mdb.removeUpTo(limit, function(err, n) {
if (err) logger.error(err);
else logger.info('Ran cleaner task, removed ' + n);
});
};
var job = new CronJob({
cronTime: cronTime,
onTick: onTick
});
onTick();
job.start();
};

View File

@ -1,5 +0,0 @@
module.exports = {
cronTime: '0 * * * *', // run each hour
};

View File

@ -1,2 +0,0 @@
module.exports = {
};

View File

@ -1,4 +0,0 @@
module.exports = {
fetchIntervalInMinutes: 60,
defaultSource: 'BitPay',
};

View File

@ -1,9 +0,0 @@
module.exports = {
email: {
service: 'Gmail',
auth: {
user: '',
pass: ''
}
}
};

View File

@ -1,3 +0,0 @@
module.exports = {
};

View File

@ -1,3 +0,0 @@
module.exports = {
cronTime: '* * * * *', // run each minute
};

View File

@ -1,3 +0,0 @@
module.exports = {
};

View File

@ -1,160 +0,0 @@
/**
* Credentials storage service
*
* Allows users to store encrypted data on the server, useful to store the user's credentials.
*
* Steps for the user would be:
*
* 1. Choose an username
* 2. Choose a password
* 3. Create a strong key for encryption using PBKDF2 or scrypt with the username and password
* 4. Use that key to AES-CRT encrypt the private key
* 5. Take the double SHA256 hash of "salt"+"username"+"password" and use that as a secret
* 6. Send a POST request to resource /credentials with the params:
* username=johndoe
* secret=2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892
* record=YjU1MTI2YTM5ZjliMTE3MGEzMmU2ZjYxZTRhNjk0YzQ1MjM1ZTVhYzExYzA1ZWNkNmZm
* NjM5NWRlNmExMTE4NzIzYzYyYWMwODU1MTdkNWMyNjRiZTVmNmJjYTMxMGQyYmFiNjc4YzdiODV
* lZjg5YWIxYzQ4YjJmY2VkYWJjMDQ2NDYzODhkODFiYTU1NjZmMzgwYzhiODdiMzlmYjQ5ZTc1Nz
* FjYzQzYjk1YTEyYWU1OGMxYmQ3OGFhOTZmNGMz
*
* To retrieve data:
*
* 1. Recover the secret from the double sha256 of the salt, username, and password
* 2. Send a GET request to resource /credentials/username?secret=......
* 3. Decrypt the data received
*/
(function() {
'use strict';
var logger = require('../lib/logger').logger,
levelup = require('levelup'),
querystring = require('querystring');
var storePlugin = {};
/**
* Constant enum with the errors that the application may return
*/
var errors = {
MISSING_PARAMETER: {
code: 400,
message: 'Missing required parameter'
},
INVALID_REQUEST: {
code: 400,
message: 'Invalid request parameter'
},
NOT_FOUND: {
code: 404,
message: 'Credentials were not found'
}
};
var NAMESPACE = 'credentials-store-';
var MAX_ALLOWED_STORAGE = 1024 /* no more than 1 kb */;
/**
* Initializes the plugin
*
* @param {Express} expressApp
* @param {Object} config
*/
storePlugin.init = function(expressApp, config) {
var globalConfig = require('../config/config');
logger.info('Using credentialstore plugin');
var path = globalConfig.leveldb + '/credentialstore' + (globalConfig.name ? ('-' + globalConfig.name) : '');
storePlugin.db = config.db || globalConfig.db || levelup(path);
expressApp.post(globalConfig.apiPrefix + '/credentials', storePlugin.post);
expressApp.get(globalConfig.apiPrefix + '/credentials/:username', storePlugin.get);
};
/**
* Helper function that ends a requests showing the user an error. The response body will be a JSON
* encoded object with only one property with key "error" and value <tt>error.message</tt>, one of
* the parameters of the function
*
* @param {Object} error - The error that caused the request to be terminated
* @param {number} error.code - the HTTP code to return
* @param {string} error.message - the message to send in the body
* @param {Express.Response} response - the express.js response. the methods status, json, and end
* will be called, terminating the request.
*/
var returnError = function(error, response) {
response.status(error.code).json({error: error.message}).end();
};
/**
* Store a record in the database. The underlying database is merely a levelup instance (a key
* value store) that uses the username concatenated with the secret as a key to store the record.
* The request is expected to contain the parameters:
* * username
* * secret
* * record
*
* @param {Express.Request} request
* @param {Express.Response} response
*/
storePlugin.post = function(request, response) {
var queryData = '';
request.on('data', function(data) {
queryData += data;
if (queryData.length > MAX_ALLOWED_STORAGE) {
queryData = '';
response.writeHead(413, {'Content-Type': 'text/plain'}).end();
request.connection.destroy();
}
}).on('end', function() {
var params = querystring.parse(queryData);
var username = params.username;
var secret = params.secret;
var record = params.record;
if (!username || !secret || !record) {
return returnError(errors.MISSING_PARAMETER, response);
}
storePlugin.db.put(NAMESPACE + username + secret, record, function (err) {
if (err) {
return returnError({code: 500, message: err}, response);
}
response.json({success: true}).end();
});
});
};
/**
* Retrieve a record from the database.
*
* The request is expected to contain the parameters:
* * username
* * secret
*
* @param {Express.Request} request
* @param {Express.Response} response
*/
storePlugin.get = function(request, response) {
var username = request.param('username');
var secret = request.param('secret');
if (!username || !secret) {
return returnError(errors.MISSING_PARAMETER, response);
}
storePlugin.db.get(NAMESPACE + username + secret, function (err, value) {
if (err) {
if (err.notFound) {
return returnError(errors.NOT_FOUND, response);
}
return returnError({code: 500, message: err}, response);
}
response.send(value).end();
});
};
module.exports = storePlugin;
})();

View File

@ -1,15 +0,0 @@
var _ = require('lodash');
module.exports.id = 'BitPay';
module.exports.url = 'https://bitpay.com/api/rates/';
module.exports.parseFn = function(raw) {
var rates = _.compact(_.map(raw, function(d) {
if (!d.code || !d.rate) return null;
return {
code: d.code,
rate: d.rate,
};
}));
return rates;
};

View File

@ -1,11 +0,0 @@
var _ = require('lodash');
module.exports.id = 'Bitstamp';
module.exports.url = 'https://www.bitstamp.net/api/ticker/';
module.exports.parseFn = function(raw) {
return [{
code: 'USD',
rate: parseFloat(raw.last)
}];
};

View File

@ -1,199 +0,0 @@
(function() {
'use strict';
var _ = require('lodash');
var async = require('async');
var levelup = require('levelup');
var request = require('request');
var preconditions = require('preconditions').singleton();
var logger = require('../lib/logger').logger;
var globalConfig = require('../config/config');
var currencyRatesPlugin = {};
function getCurrentTs() {
return Math.floor(new Date() / 1000);
};
function getKey(sourceId, code, ts) {
var key = sourceId + '-' + code.toUpperCase();
if (ts) {
key += '-' + ts;
}
return key;
};
function returnError(error, res) {
res.status(error.code).json({
error: error.message,
}).end();
};
currencyRatesPlugin.init = function(config) {
logger.info('Using currencyrates plugin');
config = config || {};
var path = globalConfig.leveldb + '/currencyRates' + (globalConfig.name ? ('-' + globalConfig.name) : '');
currencyRatesPlugin.db = config.db || globalConfig.db || levelup(path);
if (_.isArray(config.sources)) {
currencyRatesPlugin.sources = config.sources;
} else {
currencyRatesPlugin.sources = [
require('./currencyRates/bitpay'),
require('./currencyRates/bitstamp'),
];
}
currencyRatesPlugin.request = config.request || request;
currencyRatesPlugin.defaultSource = config.defaultSource || globalConfig.defaultSource;
currencyRatesPlugin.initialized = true;
var interval = config.fetchIntervalInMinutes || globalConfig.fetchIntervalInMinutes;
if (interval) {
currencyRatesPlugin._fetch();
setInterval(function() {
currencyRatesPlugin._fetch();
}, interval * 60 * 1000);
}
};
currencyRatesPlugin._retrieve = function(source, cb) {
logger.debug('Fetching data for ' + source.id);
currencyRatesPlugin.request.get({
url: source.url,
json: true
}, function(err, res, body) {
if (err || !body) {
logger.warn('Error fetching data for ' + source.id, err);
return cb(err);
}
logger.debug('Data for ' + source.id + ' fetched successfully');
if (!source.parseFn) {
return cb('No parse function for source ' + source.id);
}
var rates = source.parseFn(body);
return cb(null, rates);
});
};
currencyRatesPlugin._store = function(source, rates, cb) {
logger.debug('Storing data for ' + source.id);
var ts = getCurrentTs();
var ops = _.map(rates, function(r) {
return {
type: 'put',
key: getKey(source.id, r.code, ts),
value: r.rate,
};
});
currencyRatesPlugin.db.batch(ops, function(err) {
if (err) {
logger.warn('Error storing data for ' + source.id, err);
return cb(err);
}
logger.debug('Data for ' + source.id + ' stored successfully');
return cb();
});
};
currencyRatesPlugin._dump = function(opts) {
var all = [];
currencyRatesPlugin.db.readStream(opts)
.on('data', console.log);
};
currencyRatesPlugin._fetch = function(cb) {
cb = cb || function() {};
preconditions.shouldNotBeFalsey(currencyRatesPlugin.initialized);
async.each(currencyRatesPlugin.sources, function(source, cb) {
currencyRatesPlugin._retrieve(source, function(err, res) {
if (err) {
logger.warn(err);
return cb();
}
currencyRatesPlugin._store(source, res, function(err, res) {
return cb();
});
});
}, function(err) {
return cb(err);
});
};
currencyRatesPlugin._getOneRate = function(sourceId, code, ts, cb) {
var result = null;
currencyRatesPlugin.db.createValueStream({
lte: getKey(sourceId, code, ts),
gte: getKey(sourceId, code) + '!',
reverse: true,
limit: 1,
})
.on('data', function(data) {
var num = parseFloat(data);
result = _.isNumber(num) && !_.isNaN(num) ? num : null;
})
.on('error', function(err) {
return cb(err);
})
.on('end', function() {
return cb(null, result);
});
};
currencyRatesPlugin._getRate = function(sourceId, code, ts, cb) {
preconditions.shouldNotBeFalsey(currencyRatesPlugin.initialized);
preconditions.shouldNotBeEmpty(code);
preconditions.shouldBeFunction(cb);
ts = ts || getCurrentTs();
if (!_.isArray(ts)) {
return currencyRatesPlugin._getOneRate(sourceId, code, ts, function(err, rate) {
if (err) return cb(err);
return cb(null, {
rate: rate
});
});
}
async.map(ts, function(ts, cb) {
currencyRatesPlugin._getOneRate(sourceId, code, ts, function(err, rate) {
if (err) return cb(err);
return cb(null, {
ts: parseInt(ts),
rate: rate
});
});
}, function(err, res) {
if (err) return cb(err);
return cb(null, res);
});
};
currencyRatesPlugin.getRate = function(req, res) {
var source = req.param('source') || currencyRatesPlugin.defaultSource;
var ts = req.param('ts');
if (_.isString(ts) && ts.indexOf(',') !== -1) {
ts = ts.split(',');
}
currencyRatesPlugin._getRate(source, req.param('code'), ts, function(err, result) {
if (err) returnError({
code: 500,
message: err,
});
res.json(result);
});
};
module.exports = currencyRatesPlugin;
})();

View File

@ -1,190 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title><%= title%></title>
<style type="text/css">
/* Based on The MailChimp Reset INLINE: Yes. */
/* Client-specific Styles */
#outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
/* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
.ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
/* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
#backgroundTable {margin:0; padding:0; width:100% !important; line-height: 100% !important;}
/* End reset */
/* Some sensible defaults for images
Bring inline: Yes. */
img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
a img {border:none;}
.image_fix {display:block;}
/* Yahoo paragraph fix
Bring inline: Yes. */
p {margin: 1em 0;}
/* Hotmail header color reset
Bring inline: Yes. */
h1, h2, h3, h4, h5, h6 {color: black !important;}
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color: blue !important;}
h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {
color: red !important; /* Preferably not the same color as the normal header link color. There is limited support for psuedo classes in email clients, this was added just for good measure. */
}
h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {
color: purple !important; /* Preferably not the same color as the normal header link color. There is limited support for psuedo classes in email clients, this was added just for good measure. */
}
/* Outlook 07, 10 Padding issue fix
Bring inline: No.*/
table td {border-collapse: collapse;}
/* Remove spacing around Outlook 07, 10 tables
Bring inline: Yes */
table { border-collapse:collapse; mso-table-lspace:0pt; mso-table-rspace:0pt; }
/* Styling your links has become much simpler with the new Yahoo. In fact, it falls in line with the main credo of styling in email and make sure to bring your styles inline. Your link colors will be uniform across clients when brought inline.
Bring inline: Yes. */
a {color: orange;}
/***************************************************
****************************************************
MOBILE TARGETING
****************************************************
***************************************************/
@media only screen and (max-device-width: 480px) {
/* Part one of controlling phone number linking for mobile. */
a[href^="tel"], a[href^="sms"] {
text-decoration: none;
color: blue; /* or whatever your want */
pointer-events: none;
cursor: default;
}
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
text-decoration: default;
color: orange !important;
pointer-events: auto;
cursor: default;
}
}
/* More Specific Targeting */
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
/* You guessed it, ipad (tablets, smaller screens, etc) */
/* repeating for the ipad */
a[href^="tel"], a[href^="sms"] {
text-decoration: none;
color: blue; /* or whatever your want */
pointer-events: none;
cursor: default;
}
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
text-decoration: default;
color: orange !important;
pointer-events: auto;
cursor: default;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
/* Put your iPhone 4g styles in here */
}
/* Android targeting */
@media only screen and (-webkit-device-pixel-ratio:.75){
/* Put CSS for low density (ldpi) Android layouts in here */
}
@media only screen and (-webkit-device-pixel-ratio:1){
/* Put CSS for medium density (mdpi) Android layouts in here */
}
@media only screen and (-webkit-device-pixel-ratio:1.5){
/* Put CSS for high density (hdpi) Android layouts in here */
}
/* end Android targeting */
</style>
<!-- Targeting Windows Mobile -->
<!--[if IEMobile 7]>
<style type="text/css">
</style>
<![endif]-->
<!-- ***********************************************
****************************************************
END MOBILE TARGETING
****************************************************
************************************************ -->
<!--[if gte mso 9]>
<style>
/* Target Outlook 2007 and 2010 */
</style>
<![endif]-->
</head>
<body>
<!-- Wrapper/Container Table: Use a wrapper table to control the width and the background color consistently of your email. Use this approach instead of setting attributes on the body tag. -->
<table cellpadding="0" cellspacing="0" border="0" id="backgroundTable">
<tr>
<td valign="top">
<!-- Tables are the most common way to format your email consistently. Set your table widths inside cells and in most cases reset cellpadding, cellspacing, and border to zero. Use nested tables as a way to space effectively in your message. -->
<table cellpadding="0" cellspacing="0" border="0" align="center">
<tr>
<td width="200" valign="top"></td>
<td width="200" valign="top"></td>
<td width="200" valign="top"></td>
</tr>
</table>
<!-- End example table -->
<p>Hi, <%= email %></p>
<p>You are now using Insight to store an encrypted Copay backup. This is a free
service that we are providing that you can turn off from Copay's preferences.</p>
<p>In order to prevent abuse, we need you to confirm that this email is valid and
that you requested this backup. Please follow this link if you agree on
backing up your Copay profile within our servers:</p>
<p>
<!-- Yahoo Link color fix updated: Simply bring your link styling inline. -->
<a href="<%= confirm_url %>" target ="_blank" title="Confirm your email" style="color: orange; text-decoration: none;">Confirm your email (click here).</a>
</p>
<p style="font-size: small">If the above link doesn't work, head to: <%= confirm_url %></p>
<p>We would also like you to know that:</p>
<ul>
<li>We only store information encrypted by your Copay app. We can't retrieve your private keys, help you remember the password, or collect any information about your wallet. </li>
<li>In case that one of our servers is compromised, intruders may be able to brute-force their way into your private keys unless you use a strong password.</li>
<li> Our code is open source and can be audited here:
<ul>
<li> https://github.com/bitpay/insight </li>
<li> https://github.com/bitpay/copay </li>
</ul>
</li>
</ul>
<p>Thanks!</p>
<p> The Copay Team </p>
</td>
</tr>
</table>
<!-- End of wrapper table -->
</body>
</html>

View File

@ -1,27 +0,0 @@
Hi, <%= email %>
You are now using Insight to store an encrypted Copay backup. This is a free
service that we are providing that you can turn off from Copay's preferences.
In order to prevent abuse, we need you to confirm that this email is valid and
that you requested this backup. Please follow this link if you agree on
backing up your Copay profile within our servers:
<%= confirm_url %>
We would also like you to take note of these:
* We only store information encrypted by your Copay app. We can't retrieve
your private keys, help you remember the password, or collect any information
about your wallet.
* In case of a service compromise, intruders may be able to brute-force their
way into your private keys. Please use a strong password to avoid this.
* Our code is open source and can be audited here:
https://github.com/bitpay/insight
https://github.com/bitpay/copay
Thanks!
The Copay Team

View File

@ -1,933 +0,0 @@
/**
* GIST: https://gist.github.com/eordano/3e80ee3383554e94a08e
*/
(function() {
'use strict';
var _ = require('lodash');
var async = require('async');
var bitcore = require('bitcore');
var crypto = require('crypto');
var fs = require('fs');
var levelup = require('levelup');
var nodemailer = require('nodemailer');
var querystring = require('querystring');
var moment = require('moment');
var logger = require('../lib/logger').logger;
var globalConfig = require('../config/config');
var emailPlugin = {};
/**
* Constant enum with the errors that the application may return
*/
emailPlugin.errors = {
MISSING_PARAMETER: {
code: 400,
message: 'Missing required parameter'
},
INVALID_REQUEST: {
code: 400,
message: 'Invalid request parameter'
},
NOT_FOUND: {
code: 404,
message: 'Email already confirmed or credentials not found'
},
INTERNAL_ERROR: {
code: 500,
message: 'Unable to save to database'
},
EMAIL_TAKEN: {
code: 409,
message: 'That email is already registered'
},
INVALID_CODE: {
code: 403,
message: 'The provided code is invalid'
},
OVER_QUOTA: {
code: 406,
message: 'User quota exceeded',
},
ERROR_SENDING_EMAIL: {
code: 501,
message: 'Could not send verification email',
},
};
var EMAIL_TO_PASSPHRASE = 'email-to-passphrase-';
var STORED_VALUE = 'emailstore-';
var ITEMS_COUNT = 'itemscount-';
var PENDING = 'pending-';
var VALIDATED = 'validated-';
var SEPARATOR = '#';
var UNCONFIRMED_PER_ITEM_QUOTA = 1024 * 150; /* 150 kb */
var CONFIRMED_PER_ITEM_QUOTA = 1024 * 300; /* 300 kb */
var UNCONFIRMED_ITEMS_LIMIT = 6;
var CONFIRMED_ITEMS_LIMIT = 11;
var POST_LIMIT = 1024 * 300 /* Max POST 300 kb */ ;
var valueKey = function(email, key) {
return STORED_VALUE + bitcore.util.twoSha256(email + SEPARATOR + key).toString('hex');
};
var countKey = function(email) {
return ITEMS_COUNT + bitcore.util.twoSha256(email).toString('hex');
};
var pendingKey = function(email) {
return PENDING + email;
};
var validatedKey = function(email) {
return VALIDATED + bitcore.util.twoSha256(email).toString('hex');
};
var emailToPassphrase = function(email) {
return EMAIL_TO_PASSPHRASE + bitcore.util.twoSha256(email).toString('hex');
};
/**
* Initializes the plugin
*
* @param {Object} config
*/
emailPlugin.init = function(config) {
logger.info('Using emailstore plugin');
var path = globalConfig.leveldb + '/emailstore' + (globalConfig.name ? ('-' + globalConfig.name) : '');
emailPlugin.db = config.db || globalConfig.db || levelup(path);
emailPlugin.email = config.emailTransport || nodemailer.createTransport(config.email);
emailPlugin.textTemplate = config.textTemplate || 'copay.plain';
emailPlugin.htmlTemplate = config.htmlTemplate || 'copay.html';
emailPlugin.crypto = config.crypto || crypto;
emailPlugin.confirmUrl = (
process.env.INSIGHT_EMAIL_CONFIRM_HOST || config.confirmUrl || 'https://insight.bitpay.com'
) + globalConfig.apiPrefix + '/email/validate';
emailPlugin.redirectUrl = (
config.redirectUrl || 'https://copay.io/in/app#!/confirmed'
);
};
/**
* Helper function that ends a requests showing the user an error. The response body will be a JSON
* encoded object with only one property with key "error" and value <tt>error.message</tt>, one of
* the parameters of the function
*
* @param {Object} error - The error that caused the request to be terminated
* @param {number} error.code - the HTTP code to return
* @param {string} error.message - the message to send in the body
* @param {Express.Response} response - the express.js response. the methods status, json, and end
* will be called, terminating the request.
*/
emailPlugin.returnError = function(error, response) {
response.status(error.code).json({
error: error.message
}).end();
};
/**
* Helper that sends a verification email.
*
* @param {string} email - the user's email
* @param {string} secret - the verification secret
*/
emailPlugin.sendVerificationEmail = function(email, secret, callback) {
var confirmUrl = emailPlugin.makeConfirmUrl(email, secret);
logger.debug('ConfirmUrl:',confirmUrl);
async.series([
function(callback) {
emailPlugin.makeEmailBody({
email: email,
confirm_url: confirmUrl
}, callback);
},
function(callback) {
emailPlugin.makeEmailHTMLBody({
email: email,
confirm_url: confirmUrl,
title: 'Your wallet backup needs confirmation'
}, callback);
}
], function(err, results) {
var emailBody = results[0];
var emailBodyHTML = results[1];
var mailOptions = {
from: 'copay@copay.io',
to: email,
subject: '[Copay] Your wallet backup needs confirmation',
text: emailBody,
html: emailBodyHTML
};
// send mail with defined transport object
emailPlugin.email.sendMail(mailOptions, function(err, info) {
if (err) {
logger.error('An error occurred when trying to send email to ' + email, err);
return callback(err);
}
logger.info('Message sent: ', info ? info : '');
return callback(err, info);
});
});
};
emailPlugin.makeConfirmUrl = function(email, secret) {
return emailPlugin.confirmUrl + (
'?email=' + encodeURIComponent(email) + '&verification_code=' + secret
);
};
/**
* Returns a function that reads an underscore template and uses the `opts` param
* to build an email body
*/
var applyTemplate = function(templateFilename) {
return function(opts, callback) {
fs.readFile(__dirname + '/emailTemplates/' + emailPlugin[templateFilename],
function(err, template) {
return callback(err, _.template(template, opts));
}
);
};
};
emailPlugin.makeEmailBody = applyTemplate('textTemplate');
emailPlugin.makeEmailHTMLBody = applyTemplate('htmlTemplate');
/**
* @param {string} email
* @param {Function(err, boolean)} callback
*/
emailPlugin.exists = function(email, callback) {
emailPlugin.db.get(emailToPassphrase(email), function(err, value) {
if (err && err.notFound) {
return callback(null, false);
} else if (err) {
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback(null, true);
});
};
/**
* @param {string} email
* @param {string} passphrase
* @param {Function(err, boolean)} callback
*/
emailPlugin.checkPassphrase = function(email, passphrase, callback) {
emailPlugin.db.get(emailToPassphrase(email), function(err, retrievedPassphrase) {
if (err) {
if (err.notFound) {
return callback(emailPlugin.errors.INVALID_CODE);
}
logger.error('error checking passphrase', email, err);
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback(err, passphrase === retrievedPassphrase);
});
};
/**
* @param {string} email
* @param {string} passphrase
* @param {Function(err)} callback
*/
emailPlugin.savePassphrase = function(email, passphrase, callback) {
emailPlugin.db.put(emailToPassphrase(email), passphrase, function(err) {
if (err) {
logger.error('error saving passphrase', err);
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback(null);
});
};
/**
* checkSizeQuota
*
* @param email
* @param size
* @param isConfirmed
* @param callback
*/
emailPlugin.checkSizeQuota = function(email, size, isConfirmed, callback) {
var err;
if (size > (isConfirmed ? CONFIRMED_PER_ITEM_QUOTA : UNCONFIRMED_PER_ITEM_QUOTA))
err = emailPlugin.errors.OVER_QUOTA;
logger.info('Storage size:', size);
return callback(err);
};
emailPlugin.checkAndUpdateItemCounter = function(email, isConfirmed, isAdd, callback) {
// this is a new item... Check User's Items quota.
emailPlugin.db.get(countKey(email), function(err, counter) {
if (err && !err.notFound) {
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
counter = (parseInt(counter) || 0)
if (isAdd) {
counter++;
logger.info('User counter quota:', counter);
if (counter > (isConfirmed ? CONFIRMED_ITEMS_LIMIT : UNCONFIRMED_ITEMS_LIMIT)) {
return callback(emailPlugin.errors.OVER_QUOTA);
}
} else {
if (counter > 0) counter--;
}
emailPlugin.db.put(countKey(email), counter, function(err) {
if (err) {
logger.error('error saving counter');
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback();
});
});
};
/**
* @param {string} email
* @param {string} key
* @param {Function(err)} callback
*/
emailPlugin.checkAndUpdateItemQuota = function(email, key, isConfirmed, callback) {
emailPlugin.db.get(valueKey(email, key), function(err) {
//existing item?
if (!err)
return callback();
if (err.notFound) {
//new item
return emailPlugin.checkAndUpdateItemCounter(email, isConfirmed, 1, callback);
} else {
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
});
};
/**
* @param {string} email
* @param {string} key
* @param {string} record
* @param {Function(err)} callback
*/
emailPlugin.saveEncryptedData = function(email, key, record, callback) {
emailPlugin.db.put(valueKey(email, key), record, function(err) {
if (err) {
logger.error('error saving encrypted data', email, key, record, err);
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback();
});
};
emailPlugin.createVerificationSecretAndSendEmail = function(email, callback) {
emailPlugin.createVerificationSecret(email, function(err, secret) {
if (err || !secret) {
logger.error('error saving verification secret', email, secret, err);
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
emailPlugin.sendVerificationEmail(email, secret, function (err, res) {
if (err) {
logger.error('error sending verification email', email, secret, err);
return callback(emailPlugin.errors.ERROR_SENDING_EMAIL);
}
return callback();
});
});
};
/**
* Creates and stores a verification secret in the database.
*
* @param {string} email - the user's email
* @param {Function} callback - will be called with params (err, secret)
*/
emailPlugin.createVerificationSecret = function(email, callback) {
emailPlugin.db.get(pendingKey(email), function(err, value) {
if (err && err.notFound) {
var secret = emailPlugin.crypto.randomBytes(16).toString('hex');
var value = {
secret: secret,
created: moment().unix(),
};
emailPlugin.db.put(pendingKey(email), JSON.stringify(value), function(err) {
if (err) {
logger.error('error saving pending data:', email, value);
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback(null, secret);
});
} else {
return callback(err);
}
});
};
/**
* @param {string} email
* @param {Function(err)} callback
*/
emailPlugin.retrieveByEmailAndKey = function(email, key, callback) {
emailPlugin.db.get(valueKey(email, key), function(error, value) {
if (error) {
if (error.notFound) {
return callback(emailPlugin.errors.NOT_FOUND);
}
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback(null, value);
});
};
emailPlugin.deleteByEmailAndKey = function deleteByEmailAndKey(email, key, callback) {
emailPlugin.db.del(valueKey(email, key), function(error) {
if (error) {
logger.error(error);
if (error.notFound) {
return callback(emailPlugin.errors.NOT_FOUND);
}
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return emailPlugin.checkAndUpdateItemCounter(email, null, null, callback);
});
};
emailPlugin.deleteWholeProfile = function deleteWholeProfile(email, callback) {
async.parallel([
function(cb) {
emailPlugin.db.del(emailToPassphrase(email), cb);
},
function(cb) {
emailPlugin.db.del(pendingKey(email), cb);
},
function(cb) {
emailPlugin.db.del(validatedKey(email), cb);
}
], function(err) {
if (err && !err.notFound) {
logger.error(err);
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback();
});
};
/**
* Store a record in the database. The underlying database is merely a levelup instance (a key
* value store) that uses the email concatenated with the secret as a key to store the record.
* The request is expected to contain the parameters:
* * email
* * secret
* * record
*
* @param {Express.Request} request
* @param {Express.Response} response
*/
emailPlugin.save = function(request, response) {
var queryData = '';
var credentials = emailPlugin.getCredentialsFromRequest(request);
if (credentials.code) {
return emailPlugin.returnError(credentials, response);
}
var email = credentials.email;
var passphrase = credentials.passphrase;
request.on('data', function(data) {
queryData += data;
if (queryData.length > POST_LIMIT) {
queryData = '';
response.writeHead(413, {
'Content-Type': 'text/plain'
});
response.end();
request.connection.destroy();
}
}).on('end', function() {
var params = querystring.parse(queryData);
var key = params.key;
var record = params.record;
if (!email || !passphrase || !record || !key) {
return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response);
}
emailPlugin.processPost(request, response, email, key, passphrase, record);
});
};
emailPlugin.processPost = function(request, response, email, key, passphrase, record) {
var isNewProfile = false;
var isConfirmed = true;
var errorCreating = false;
async.series([
/**
* Try to fetch this user's email. If it exists, check the secret is the same.
*/
function(callback) {
emailPlugin.exists(email, function(err, exists) {
if (err) return callback(err);
if (exists) {
emailPlugin.checkPassphrase(email, passphrase, function(err, match) {
if (err) return callback(err);
if (!match) return callback(emailPlugin.errors.EMAIL_TAKEN);
return callback();
});
} else {
isNewProfile = true;
emailPlugin.savePassphrase(email, passphrase, function(err) {
return callback(err);
});
}
});
},
function(callback) {
emailPlugin.isConfirmed(email, function(err, inIsConfirmed) {
if (err) return callback(err);
isConfirmed = inIsConfirmed;
return callback();
});
},
function(callback) {
emailPlugin.checkSizeQuota(email, record.length, isConfirmed, function(err) {
return callback(err);
});
},
function(callback) {
emailPlugin.checkAndUpdateItemQuota(email, key, isConfirmed, function(err) {
return callback(err);
});
},
/**
* Save the encrypted private key in the storage.
*/
function(callback) {
emailPlugin.saveEncryptedData(email, key, record, function(err) {
return callback(err);
});
},
/**
* Create and store the verification secret. If successful, send a verification email.
*/
function(callback) {
if (!isNewProfile || isConfirmed) return callback();
emailPlugin.createVerificationSecretAndSendEmail(email, function(err) {
if (err) {
errorCreating = true;
}
return callback(err);
});
},
],
function(err) {
if (err) {
if (isNewProfile && !isConfirmed && errorCreating) {
emailPlugin.deleteWholeProfile(email, function() {
return emailPlugin.returnError(err, response);
});
}
emailPlugin.returnError(err, response);
} else {
response.json({
success: true
}).end();
}
});
};
emailPlugin.getCredentialsFromRequest = function(request) {
var auth = request.header('authorization');
if (!auth) {
return emailPlugin.errors.INVALID_REQUEST;
}
var authHeader = new Buffer(auth, 'base64').toString('utf8');
var splitIndex = authHeader.indexOf(':');
if (splitIndex === -1) {
return emailPlugin.errors.INVALID_REQUEST;
}
var email = authHeader.substr(0, splitIndex);
var passphrase = authHeader.substr(splitIndex + 1);
return {
email: email,
passphrase: passphrase
};
};
/**
* @param {string} email
* @param {Function(err, boolean)} callback
*/
emailPlugin.isConfirmed = function(email, callback) {
emailPlugin.db.get(validatedKey(email), function(err, isConfirmed) {
if (err && err.notFound) {
return callback(null, false);
} else if (err) {
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback(null, !!isConfirmed);
});
};
/**
* addValidationAndQuotaHeader
*
* @param response
* @param email
* @param {Function(err, boolean)} callback
*/
emailPlugin.addValidationAndQuotaHeader = function(response, email, callback) {
emailPlugin.isConfirmed(email, function(err, isConfirmed) {
if (err) return callback(err);
if (!isConfirmed) {
response.set('X-Email-Needs-Validation', 'true');
}
response.set('X-Quota-Per-Item', isConfirmed ? CONFIRMED_PER_ITEM_QUOTA : UNCONFIRMED_PER_ITEM_QUOTA);
response.set('X-Quota-Items-Limit', isConfirmed ? CONFIRMED_ITEMS_LIMIT : UNCONFIRMED_ITEMS_LIMIT);
return callback();
});
};
emailPlugin.authorizeRequest = function(request, withKey, callback) {
var credentialsResult = emailPlugin.getCredentialsFromRequest(request);
if (_.contains(emailPlugin.errors, credentialsResult)) {
return callback(credentialsResult);
}
var email = credentialsResult.email;
var passphrase = credentialsResult.passphrase;
var key;
if (withKey) {
key = request.param('key');
}
if (!passphrase || !email || (withKey && !key)) {
return callback(emailPlugin.errors.MISSING_PARAMETER);
}
emailPlugin.checkPassphrase(email, passphrase, function(err, matches) {
if (err) {
return callback(err);
}
if (!matches) {
return callback(emailPlugin.errors.INVALID_CODE);
}
return callback(null, email, key);
});
};
emailPlugin.authorizeRequestWithoutKey = function(request, callback) {
emailPlugin.authorizeRequest(request, false, callback);
};
emailPlugin.authorizeRequestWithKey = function(request, callback) {
emailPlugin.authorizeRequest(request, true, callback);
};
/**
* Retrieve a record from the database
*/
emailPlugin.retrieve = function(request, response) {
emailPlugin.authorizeRequestWithKey(request, function(err, email, key) {
if (err)
return emailPlugin.returnError(err, response);
emailPlugin.retrieveByEmailAndKey(email, key, function(err, value) {
if (err)
return emailPlugin.returnError(err, response);
emailPlugin.addValidationAndQuotaHeader(response, email, function(err) {
if (err)
return emailPlugin.returnError(err, response);
response.send(value).end();
});
});
});
};
/**
* Remove a record from the database
*/
emailPlugin.erase = function(request, response) {
emailPlugin.authorizeRequestWithKey(request, function(err, email, key) {
if (err) {
return emailPlugin.returnError(err, response);
}
emailPlugin.deleteByEmailAndKey(email, key, function(err, value) {
if (err) {
return emailPlugin.returnError(err, response);
} else {
return response.json({
success: true
}).end();
};
});
});
};
/**
* Remove a whole profile from the database
*
* @TODO: This looks very similar to the method above
*/
emailPlugin.eraseProfile = function(request, response) {
emailPlugin.authorizeRequestWithoutKey(request, function(err, email) {
if (err) {
return emailPlugin.returnError(err, response);
}
emailPlugin.deleteWholeProfile(email, function(err, value) {
if (err) {
return emailPlugin.returnError(err, response);
} else {
return response.json({
success: true
}).end();
};
});
});
};
emailPlugin._parseSecret = function (value) {
var obj = null;
try {
obj = JSON.parse(value);
} catch (e) {}
if (obj && _.isObject(obj)) {
return obj.secret;
}
return value;
};
emailPlugin.resendEmail = function(request, response) {
emailPlugin.authorizeRequestWithoutKey(request, function(err, email) {
if (err) {
return emailPlugin.returnError(err, response);
}
emailPlugin.db.get(pendingKey(email), function(err, value) {
if (err) {
logger.error('error retrieving secret for email', email, err);
return emailPlugin.returnError(err, response);
}
var secret = emailPlugin._parseSecret(value);
emailPlugin.sendVerificationEmail(email, secret, function (err) {
if (err) {
logger.error('error resending verification email', email, secret, err);
return emailPlugin.returnError(emailPlugin.errors.ERROR_SENDING_EMAIL, response);
}
return response.json({
success: true
}).end();
});
});
});
};
/**
* Marks an email as validated
*
* The two expected params are:
* * email
* * verification_code
*
* @param {Express.Request} request
* @param {Express.Response} response
*/
emailPlugin.validate = function(request, response) {
var email = request.param('email');
var secret = request.param('verification_code');
if (!email || !secret) {
return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response);
}
emailPlugin.db.get(pendingKey(email), function(err, value) {
if (err) {
if (err.notFound) {
return emailPlugin.returnError(emailPlugin.errors.NOT_FOUND, response);
}
return emailPlugin.returnError({
code: 500,
message: err
}, response);
}
value = emailPlugin._parseSecret(value);
if (value !== secret) {
return emailPlugin.returnError(emailPlugin.errors.INVALID_CODE, response);
}
emailPlugin.db.put(validatedKey(email), true, function(err, value) {
if (err) {
return emailPlugin.returnError({
code: 500,
message: err
}, response);
} else {
emailPlugin.db.del(pendingKey(email), function(err, value) {
if (err) {
return emailPlugin.returnError({
code: 500,
message: err
}, response);
} else {
response.redirect(emailPlugin.redirectUrl);
}
});
}
});
});
};
/**
* Changes an user's passphrase
*
* @param {Express.Request} request
* @param {Express.Response} response
*/
emailPlugin.changePassphrase = function(request, response) {
emailPlugin.authorizeRequestWithoutKey(request, function(err, email) {
if (err) {
return emailPlugin.returnError(err, response);
}
var queryData = '';
request.on('data', function(data) {
queryData += data;
if (queryData.length > POST_LIMIT) {
queryData = '';
response.writeHead(413, {
'Content-Type': 'text/plain'
}).end();
request.connection.destroy();
}
}).on('end', function() {
var params = querystring.parse(queryData);
var newPassphrase = params.newPassphrase;
if (!newPassphrase) {
return emailPlugin.returnError(emailPlugin.errors.INVALID_REQUEST, response);
}
emailPlugin.savePassphrase(email, newPassphrase, function(error) {
if (error) {
return emailPlugin.returnError(error, response);
}
return response.json({
success: true
}).end();
});
});
});
};
//
// Backwards compatibility
//
emailPlugin.oldRetrieveDataByEmailAndPassphrase = function(email, key, passphrase, callback) {
emailPlugin.checkPassphrase(email, passphrase, function(err, matches) {
if (err) {
return callback(err);
}
if (matches) {
return emailPlugin.retrieveByEmailAndKey(email, key, callback);
} else {
return callback(emailPlugin.errors.INVALID_CODE);
}
});
};
emailPlugin.oldRetrieve = function(request, response) {
var email = request.param('email');
var key = request.param('key');
var secret = request.param('secret');
if (!secret) {
return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response);
}
emailPlugin.oldRetrieveDataByEmailAndPassphrase(email, key, secret, function(err, value) {
if (err) {
return emailPlugin.returnError(err, response);
}
response.send(value).end();
});
};
emailPlugin.oldSave = function(request, response) {
var queryData = '';
request.on('data', function(data) {
queryData += data;
if (queryData.length > UNCONFIRMED_PER_ITEM_QUOTA) {
queryData = '';
response.writeHead(413, {
'Content-Type': 'text/plain'
}).end();
request.connection.destroy();
}
}).on('end', function() {
var params = querystring.parse(queryData);
var email = params.email;
var passphrase = params.secret;
var key = params.key;
var record = params.record;
if (!email || !passphrase || !record || !key) {
return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response);
}
emailPlugin.processPost(request, response, email, key, passphrase, record);
});
};
module.exports = emailPlugin;
})();

View File

@ -1,65 +0,0 @@
var microtime = require('microtime');
var mdb = require('../lib/MessageDb').default();
var logger = require('../lib/logger').logger;
var preconditions = require('preconditions').singleton();
var io;
module.exports.init = function(ext_io, config) {
logger.info('Using mailbox plugin');
preconditions.checkArgument(ext_io);
io = ext_io;
io.sockets.on('connection', function(socket) {
// when it requests sync, send him all pending messages
socket.on('sync', function(ts) {
logger.verbose('Sync requested by ' + socket.id);
logger.debug(' from timestamp ' + ts);
var rooms = socket.rooms;
if (rooms.length !== 2) {
socket.emit('insight-error', 'Must subscribe with public key before syncing');
return;
}
var to = rooms[1];
var upper_ts = Math.round(microtime.now());
logger.debug(' to timestamp ' + upper_ts);
mdb.getMessages(to, ts, upper_ts, function(err, messages) {
// TODO: return error to the user
if (err) {
logger.debug('Couldn\'t get messages on sync request: ' + err);
}
logger.verbose('\tFound ' + messages.length + ' message' + (messages.length !== 1 ? 's' : ''));
if (messages.length) {
for (var i = 0; i < messages.length; i++) {
broadcastMessage(messages[i], socket);
}
} else {
socket.emit('no messages');
}
});
});
// when it sends a message, add it to db
socket.on('message', function(m) {
logger.debug('Message sent from ' + m.pubkey + ' to ' + m.to);
mdb.addMessage(m, function(err) {
// TODO: return error to the user
if (err) {
logger.debug('Couldn\'t add message to database: ' + err);
}
});
});
});
mdb.on('message', broadcastMessage);
};
var broadcastMessage = module.exports.broadcastMessage = function(message, socket) {
preconditions.checkState(io);
var s = socket || io.sockets.in(message.to);
logger.debug('sending message to ' + message.to);
s.emit('message', message);
}

View File

@ -1,26 +0,0 @@
var mdb = require('../lib/MessageDb').default();
var logger = require('../lib/logger').logger;
var preconditions = require('preconditions').singleton();
var microtime = require('microtime');
var cron = require('cron');
var CronJob = cron.CronJob;
module.exports.init = function(config) {
var cronTime = config.cronTime || '0 * * * *';
logger.info('Using monitor plugin with cronTime ' + cronTime);
var onTick = function() {
mdb.getAll(function(err, messages) {
if (err) logger.error(err);
else {
logger.info('Message db size = ' + messages.length);
}
});
};
var job = new CronJob({
cronTime: cronTime,
onTick: onTick
});
onTick();
job.start();
};

View File

@ -1,2 +0,0 @@
module.exports = {
};

View File

@ -1,144 +0,0 @@
/**
* Module to allow Copay users to publish public information about themselves
*
* It uses BitAuth to verify the authenticity of the request.
*
*/
(function() {
'use strict';
var logger = require('../../lib/logger').logger,
levelup = require('levelup'),
bitauth = require('bitauth'),
globalConfig = require('../../config/config'),
querystring = require('querystring');
var publicInfo = {};
/**
* Constant enum with the errors that the application may return
*/
var errors = {
MISSING_PARAMETER: {
code: 400,
message: 'Missing required parameter'
},
UNAUTHENTICATED: {
code: 401,
message: 'SIN validation error'
},
NOT_FOUND: {
code: 404,
message: 'There\'s no record of public information for the public key requested'
}
};
var NAMESPACE = 'public-info-';
var MAX_ALLOWED_STORAGE = 64 * 1024 /* no more than 64 kb of data is allowed to be stored */;
/**
* Initializes the plugin
*
* @param {Express} expressApp
* @param {Object} config
*/
publicInfo.init = function(expressApp, config) {
logger.info('Using publicInfo plugin');
var path = globalConfig.leveldb + '/publicinfo' + (globalConfig.name ? ('-' + globalConfig.name) : '');
publicInfo.db = config.db || globalConfig.db || levelup(path);
expressApp.post(globalConfig.apiPrefix + '/public', publicInfo.post);
expressApp.get(globalConfig.apiPrefix + '/public/:sin', publicInfo.get);
};
/**
* Helper function that ends a requests showing the user an error. The response body will be a JSON
* encoded object with only one property with key "error" and value <tt>error.message</tt>, one of
* the parameters of the function
*
* @param {Object} error - The error that caused the request to be terminated
* @param {number} error.code - the HTTP code to return
* @param {string} error.message - the message to send in the body
* @param {Express.Response} response - the express.js response. the methods status, json, and end
* will be called, terminating the request.
*/
var returnError = function(error, response) {
response.status(error.code).json({error: error.message}).end();
};
/**
* Store a record in the database. The underlying database is merely a levelup instance (a key
* value store) that uses the SIN to store the body of the message.
*
* @param {Express.Request} request
* @param {Express.Response} response
*/
publicInfo.post = function(request, response) {
var record = '';
request.on('data', function(data) {
record += data;
if (record.length > MAX_ALLOWED_STORAGE) {
record = '';
response.writeHead(413, {'Content-Type': 'text/plain'}).end();
request.connection.destroy();
}
}).on('end', function() {
var fullUrl = request.protocol + '://' + request.get('host') + request.url;
var data = fullUrl + record;
bitauth.verifySignature(data, request.headers['x-identity'], request.headers['x-signature'],
function(err, result) {
if(err || !result) {
return returnError(errors.UNAUTHENTICATED, response);
}
// Get the SIN from the public key
var sin = bitauth.getSinFromPublicKey(request.headers['x-identity']);
if (!sin) {
return returnError(errors.UNAUTHENTICATED, response);
}
publicInfo.db.put(NAMESPACE + sin, record, function (err) {
if (err) {
return returnError({code: 500, message: err}, response);
}
response.json({success: true}).end();
if (request.testCallback) {
request.testCallback();
}
});
}
);
});
};
/**
* Retrieve a record from the database.
*
* The request is expected to contain the parameter "sin"
*
* @param {Express.Request} request
* @param {Express.Response} response
*/
publicInfo.get = function(request, response) {
var sin = request.param('sin');
if (!sin) {
return returnError(errors.MISSING_PARAMETER, response);
}
publicInfo.db.get(NAMESPACE + sin, function (err, value) {
if (err) {
if (err.notFound) {
return returnError(errors.NOT_FOUND, response);
}
return returnError({code: 500, message: err}, response);
}
response.send(value).end();
});
};
module.exports = publicInfo;
})();

View File

@ -1,35 +0,0 @@
var logger = require('../lib/logger').logger;
var preconditions = require('preconditions').singleton();
var limiter = require('connect-ratelimit');
var ONE_HOUR = 60 * 60 * 1000;
module.exports.init = function(app, config) {
preconditions.checkArgument(app);
logger.info('Using ratelimiter plugin');
config = config || {};
config.whitelistRPH = config.whitelistRPH || 500000;
config.normalRPH = config.normalRPH || 10000;
config.blacklistRPH = config.blacklistRPH || 0;
app.use(limiter({
whitelist: [],
end: true,
blacklist: [], // 'example.com'
categories: {
whitelist: {
totalRequests: config.whitelistRPH,
every: ONE_HOUR
},
blacklist: {
totalRequests: config.blacklistRPH,
every: ONE_HOUR
},
normal: {
totalRequests: config.normalRPH,
every: ONE_HOUR
}
}
}));
};

View File

@ -1,101 +0,0 @@
'use strict';
var chai = require('chai');
var assert = require('assert');
var sinon = require('sinon');
var should = chai.should;
var expect = chai.expect;
describe('credentialstore test', function() {
var globalConfig = require('../config/config');
var leveldb_stub = sinon.stub();
leveldb_stub.post = sinon.stub();
leveldb_stub.get = sinon.stub();
var plugin = require('../plugins/credentialstore');
var express_mock = null;
var request = null;
var response = null;
beforeEach(function() {
express_mock = sinon.stub();
express_mock.post = sinon.stub();
express_mock.get = sinon.stub();
plugin.init(express_mock, {db: leveldb_stub});
request = sinon.stub();
request.on = sinon.stub();
request.param = sinon.stub();
response = sinon.stub();
response.send = sinon.stub();
response.status = sinon.stub();
response.json = sinon.stub();
response.end = sinon.stub();
});
it('initializes correctly', function() {
assert(plugin.db === leveldb_stub);
assert(express_mock.post.calledWith(
globalConfig.apiPrefix + '/credentials', plugin.post
));
assert(express_mock.get.calledWith(
globalConfig.apiPrefix + '/credentials/:username', plugin.get
));
});
it('writes a message correctly', function() {
var data = 'username=1&secret=2&record=3';
request.on.onFirstCall().callsArgWith(1, data);
request.on.onFirstCall().returnsThis();
request.on.onSecondCall().callsArg(1);
leveldb_stub.put = sinon.stub();
leveldb_stub.put.onFirstCall().callsArg(2);
response.json.returnsThis();
plugin.post(request, response);
assert(leveldb_stub.put.firstCall.args[0] === 'credentials-store-12');
assert(leveldb_stub.put.firstCall.args[1] === '3');
assert(response.json.calledWith({success: true}));
});
it('retrieves a message correctly', function() {
request.param.onFirstCall().returns('username');
request.param.onSecondCall().returns('secret');
var returnValue = '!@#$%';
leveldb_stub.get.onFirstCall().callsArgWith(1, null, returnValue);
response.send.returnsThis();
plugin.get(request, response);
assert(leveldb_stub.get.firstCall.args[0] === 'credentials-store-usernamesecret');
assert(response.send.calledWith(returnValue));
assert(response.end.calledOnce);
});
it('fails with messages that are too long', function() {
response.writeHead = sinon.stub();
request.connection = {};
request.connection.destroy = sinon.stub();
var data = 'blob';
for (var i = 0; i < 2048; i++) {
data += '----';
}
request.on.onFirstCall().callsArgWith(1, data);
request.on.onFirstCall().returnsThis();
response.writeHead.returnsThis();
plugin.post(request, response);
assert(response.writeHead.calledWith(413, {'Content-Type': 'text/plain'}));
assert(response.end.calledOnce);
assert(request.connection.destroy.calledOnce);
});
});

View File

@ -1,265 +0,0 @@
'use strict';
var chai = require('chai');
var assert = require('assert');
var sinon = require('sinon');
var should = chai.should;
var expect = chai.expect;
var levelup = require('levelup');
var memdown = require('memdown');
var logger = require('../lib/logger').logger;
logger.transports.console.level = 'non';
var rates = require('../plugins/currencyrates');
var db;
describe('Rates service', function() {
beforeEach(function() {
db = levelup(memdown);
});
describe('#getRate', function() {
beforeEach(function() {
rates.init({
db: db,
});
});
it('should get rate with exact ts', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 123.45
}, ]);
rates._getRate('bitpay', 'USD', 10, function(err, res) {
expect(err).to.not.exist;
res.rate.should.equal(123.45);
done();
});
});
it('should get rate with approximate ts', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 123.45,
}, {
type: 'put',
key: 'bitpay-USD-20',
value: 200.00,
}]);
rates._getRate('bitpay', 'USD', 25, function(err, res) {
res.rate.should.equal(200.00);
done();
});
});
it('should return null when no rate found', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-20',
value: 123.45,
}, {
type: 'put',
key: 'bitpay-USD-30',
value: 200.00,
}]);
rates._getRate('bitpay', 'USD', 10, function(err, res) {
expect(res.rate).to.be.null;
done();
});
});
it('should get rate from specified source', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 123.45,
}, {
type: 'put',
key: 'bitstamp-USD-10',
value: 200.00,
}]);
rates._getRate('bitpay', 'USD', 12, function(err, res) {
res.rate.should.equal(123.45);
done();
});
});
it('should get rate for specified currency', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 123.45,
}, {
type: 'put',
key: 'bitpay-EUR-10',
value: 200.00,
}]);
rates._getRate('bitpay', 'EUR', 12, function(err, res) {
res.rate.should.equal(200.00);
done();
});
});
it('should get multiple rates', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 100.00,
}, {
type: 'put',
key: 'bitpay-USD-20',
value: 200.00,
}, {
type: 'put',
key: 'bitstamp-USD-30',
value: 300.00,
}, {
type: 'put',
key: 'bitpay-USD-30',
value: 400.00,
}]);
rates._getRate('bitpay', 'USD', [10, 20, 35], function(err, res) {
expect(err).to.not.exist;
res.length.should.equal(3);
res[0].ts.should.equal(10);
res[1].ts.should.equal(20);
res[2].ts.should.equal(35);
res[0].rate.should.equal(100.00);
res[1].rate.should.equal(200.00);
res[2].rate.should.equal(400.00);
done();
});
});
});
describe('#fetch', function() {
it('should fetch from all sources', function(done) {
var sources = [];
sources.push({
id: 'id1',
url: 'http://dummy1',
parseFn: function(raw) {
return raw;
},
});
sources.push({
id: 'id2',
url: 'http://dummy2',
parseFn: function(raw) {
return raw;
},
});
var ds1 = [{
code: 'USD',
rate: 123.45,
}, {
code: 'EUR',
rate: 200.00,
}];
var ds2 = [{
code: 'USD',
rate: 126.39,
}];
var request = sinon.stub();
request.get = sinon.stub();
request.get.withArgs({
url: 'http://dummy1',
json: true
}).yields(null, null, ds1);
request.get.withArgs({
url: 'http://dummy2',
json: true
}).yields(null, null, ds2);
rates.init({
db: db,
sources: sources,
request: request,
});
var clock = sinon.useFakeTimers(1400000000 * 1000);
rates._fetch(function(err, res) {
clock.restore();
expect(err).to.not.exist;
var result = [];
db.readStream()
.on('data', function(data) {
result.push(data);
})
.on('close', function() {
result.length.should.equal(3);
result[0].key.should.equal('id1-EUR-1400000000');
result[1].key.should.equal('id1-USD-1400000000');
result[2].key.should.equal('id2-USD-1400000000');
parseFloat(result[0].value).should.equal(200.00);
parseFloat(result[1].value).should.equal(123.45);
parseFloat(result[2].value).should.equal(126.39);
done();
});
});
});
it('should not stop when failing to fetch source', function(done) {
var sources = [];
sources.push({
id: 'id1',
url: 'http://dummy1',
parseFn: function(raw) {
return raw;
},
});
sources.push({
id: 'id2',
url: 'http://dummy2',
parseFn: function(raw) {
return raw;
},
});
var ds2 = [{
code: 'USD',
rate: 126.39,
}];
var request = sinon.stub();
request.get = sinon.stub();
request.get.withArgs({
url: 'http://dummy1',
json: true
}).yields('dummy error', null, null);
request.get.withArgs({
url: 'http://dummy2',
json: true
}).yields(null, null, ds2);
rates.init({
db: db,
sources: sources,
request: request,
});
var clock = sinon.useFakeTimers(1400000000 * 1000);
rates._fetch(function(err, res) {
clock.restore();
expect(err).to.not.exist;
var result = [];
db.readStream()
.on('data', function(data) {
result.push(data);
})
.on('close', function() {
result.length.should.equal(1);
result[0].key.should.equal('id2-USD-1400000000');
parseFloat(result[0].value).should.equal(126.39);
done();
});
});
});
});
});

View File

@ -1,683 +0,0 @@
'use strict';
var chai = require('chai');
var assert = require('assert');
var sinon = require('sinon');
var crypto = require('crypto');
var bitcore = require('bitcore');
var logger = require('../lib/logger').logger;
var should = chai.should;
var expect = chai.expect;
var moment = require('moment');
logger.transports.console.level = 'non';
describe('emailstore test', function() {
var globalConfig = require('../config/config');
// Mock components of plugin
var leveldb_stub = sinon.stub();
leveldb_stub.put = sinon.stub();
leveldb_stub.get = sinon.stub();
leveldb_stub.del = sinon.stub();
var email_stub = sinon.stub();
email_stub.sendMail = sinon.stub().callsArg(1);
var cryptoMock = {
randomBytes: sinon.stub()
};
var plugin = require('../plugins/emailstore');
var express_mock = null;
var request = null;
var response = null;
beforeEach(function() {
plugin.init({
db: leveldb_stub,
emailTransport: email_stub,
crypto: cryptoMock
});
request = sinon.stub();
request.on = sinon.stub();
request.param = sinon.stub();
response = sinon.stub();
response.send = sinon.stub();
response.status = sinon.stub().returns({
json: function() {
return {
end: function() {
}
}
}
});
response.json = sinon.stub();
response.end = sinon.stub();
response.redirect = sinon.stub();
});
it('initializes correctly', function() {
assert(plugin.db === leveldb_stub);
});
describe('database queries', function() {
describe('exists', function() {
var fakeEmail = 'fake@email.com';
var fakeEmailKey = 'email-to-passphrase-' + bitcore.util.twoSha256(fakeEmail).toString('hex');
beforeEach(function() {
leveldb_stub.get.reset();
});
it('validates that an email is already registered', function(done) {
leveldb_stub.get.onFirstCall().callsArg(1);
plugin.exists(fakeEmail, function(err, exists) {
leveldb_stub.get.firstCall.args[0].should.equal(fakeEmailKey);
exists.should.equal(true);
done();
});
});
it('returns false when an email doesn\'t exist', function(done) {
leveldb_stub.get.onFirstCall().callsArgWith(1, {
notFound: true
});
plugin.exists(fakeEmail, function(err, exists) {
leveldb_stub.get.firstCall.args[0].should.equal(fakeEmailKey);
exists.should.equal(false);
done();
});
});
it('returns an internal error if database query couldn\'t be made', function(done) {
leveldb_stub.get.onFirstCall().callsArgWith(1, 'error');
plugin.exists(fakeEmail, function(err, exists) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
});
describe('passphrase', function() {
var fakeEmail = 'fake@email.com';
var fakePassphrase = 'secretPassphrase123';
beforeEach(function() {
leveldb_stub.get.reset();
leveldb_stub.put.reset();
});
it('returns true if passphrase matches', function(done) {
leveldb_stub.get.onFirstCall().callsArgWith(1, null, fakePassphrase);
plugin.checkPassphrase(fakeEmail, fakePassphrase, function(err, result) {
result.should.equal(true);
done();
});
});
it('returns false if passphrsase doesn\'t match', function(done) {
leveldb_stub.get.onFirstCall().callsArgWith(1, null, 'invalid passphrase');
plugin.checkPassphrase(fakeEmail, fakePassphrase, function(err, result) {
result.should.equal(false);
done();
});
});
it('returns an internal error if database query couldn\'t be made', function(done) {
leveldb_stub.get.onFirstCall().callsArgWith(1, 'error');
plugin.checkPassphrase(fakeEmail, fakePassphrase, function(err) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
it('stores passphrase correctly', function(done) {
leveldb_stub.put.onFirstCall().callsArg(2);
plugin.savePassphrase(fakeEmail, fakePassphrase, function(err) {
expect(err).to.equal(null);
done();
});
});
it('doesn\'t store the email in the key', function(done) {
leveldb_stub.put.onFirstCall().callsArg(2);
plugin.savePassphrase(fakeEmail, fakePassphrase, function(err) {
leveldb_stub.put.firstCall.args[0].should.not.contain(fakeEmail);
done();
});
});
it('returns internal error on database error', function(done) {
leveldb_stub.put.onFirstCall().callsArgWith(2, 'error');
plugin.savePassphrase(fakeEmail, fakePassphrase, function(err) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
});
describe('saving encrypted data', function() {
var fakeEmail = 'fake@email.com';
var fakeKey = 'nameForData';
var fakeRecord = 'fakeRecord';
var expectedKey = 'emailstore-' + bitcore.util.twoSha256(fakeEmail + '#' + fakeKey).toString('hex');
beforeEach(function() {
leveldb_stub.get.reset();
leveldb_stub.put.reset();
});
it('saves data under the expected key', function(done) {
leveldb_stub.put.onFirstCall().callsArgWith(2);
plugin.saveEncryptedData(fakeEmail, fakeKey, fakeRecord, function(err) {
leveldb_stub.put.firstCall.args[0].should.equal(expectedKey);
done();
});
});
it('fails with INTERNAL_ERROR on database error', function(done) {
leveldb_stub.put.onFirstCall().callsArgWith(2, 'error');
plugin.saveEncryptedData(fakeEmail, fakeKey, fakeRecord, function(err) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
});
describe('creating verification secret', function() {
var fakeEmail = 'fake@email.com';
var fakeRandom = 'fakerandom';
var randomBytes = {
toString: function() {
return fakeRandom;
}
};
beforeEach(function() {
leveldb_stub.get.reset();
leveldb_stub.put.reset();
plugin.email.sendMail.reset();
cryptoMock.randomBytes = sinon.stub();
cryptoMock.randomBytes.onFirstCall().returns(randomBytes);
});
var setupLevelDb = function() {
leveldb_stub.get.onFirstCall().callsArgWith(1, {
notFound: true
});
leveldb_stub.put.onFirstCall().callsArg(2);
};
it('saves data under the expected key', function(done) {
setupLevelDb();
var clock = sinon.useFakeTimers();
plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) {
var arg = JSON.parse(leveldb_stub.put.firstCall.args[1]);
arg.secret.should.equal(fakeRandom);
arg.created.should.equal(moment().unix());
clock.restore();
done();
});
});
it('sends verification email', function(done) {
setupLevelDb();
plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) {
plugin.email.sendMail.calledOnce.should.be.true;
done();
});
});
it('returns internal error on put database error', function(done) {
leveldb_stub.get.onFirstCall().callsArgWith(1, {
notFound: true
});
leveldb_stub.put.onFirstCall().callsArgWith(2, 'error');
plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
it('returns internal error on get database error', function(done) {
leveldb_stub.get.onFirstCall().callsArgWith(1, 'error');
plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
});
});
describe('on registration', function() {
var emailParam = 'email';
var secretParam = 'secret';
var keyParam = 'key';
var recordParam = 'record';
beforeEach(function() {
var data = ('email=' + emailParam + '&secret=' + secretParam + '&record=' + recordParam + '&key=' + keyParam);
request.on.onFirstCall().callsArgWith(1, data);
request.on.onFirstCall().returnsThis();
request.on.onSecondCall().callsArg(1);
response.json.returnsThis();
});
it('should allow new registrations', function() {
var originalCredentials = plugin.getCredentialsFromRequest;
plugin.getCredentialsFromRequest = sinon.mock();
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: emailParam,
passphrase: secretParam
});
plugin.exists = sinon.stub();
plugin.exists.onFirstCall().callsArgWith(1, null, false);
plugin.savePassphrase = sinon.stub();
plugin.savePassphrase.onFirstCall().callsArg(2);
plugin.isConfirmed = sinon.stub();
plugin.isConfirmed.onFirstCall().callsArgWith(1, null, false);
plugin.checkSizeQuota = sinon.stub();
plugin.checkSizeQuota.onFirstCall().callsArgWith(3, null);
plugin.checkAndUpdateItemQuota = sinon.stub();
plugin.checkAndUpdateItemQuota.onFirstCall().callsArgWith(3, null);
plugin.saveEncryptedData = sinon.stub();
plugin.saveEncryptedData.onFirstCall().callsArg(3);
plugin.createVerificationSecretAndSendEmail = sinon.stub();
plugin.createVerificationSecretAndSendEmail.onFirstCall().callsArg(1);
response.send.onFirstCall().returnsThis();
plugin.save(request, response);
assert(plugin.exists.firstCall.args[0] === emailParam);
assert(plugin.savePassphrase.firstCall.args[0] === emailParam);
assert(plugin.savePassphrase.firstCall.args[1] === secretParam);
assert(plugin.saveEncryptedData.firstCall.args[0] === emailParam);
assert(plugin.saveEncryptedData.firstCall.args[1] === keyParam);
assert(plugin.saveEncryptedData.firstCall.args[2] === recordParam);
assert(plugin.createVerificationSecretAndSendEmail.firstCall.args[0] === emailParam);
plugin.getCredentialsFromRequest = originalCredentials;
});
it('should allow to overwrite data', function() {
var originalCredentials = plugin.getCredentialsFromRequest;
plugin.getCredentialsFromRequest = sinon.mock();
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: emailParam,
passphrase: secretParam
});
plugin.exists = sinon.stub();
plugin.exists.onFirstCall().callsArgWith(1, null, true);
plugin.checkPassphrase = sinon.stub();
plugin.checkPassphrase.onFirstCall().callsArgWith(2, null, true);
plugin.isConfirmed = sinon.stub();
plugin.isConfirmed.onFirstCall().callsArgWith(1, null, false);
plugin.checkSizeQuota = sinon.stub();
plugin.checkSizeQuota.onFirstCall().callsArgWith(3, null);
plugin.checkAndUpdateItemQuota = sinon.stub();
plugin.checkAndUpdateItemQuota.onFirstCall().callsArgWith(3, null);
plugin.saveEncryptedData = sinon.stub();
plugin.saveEncryptedData.onFirstCall().callsArg(3);
plugin.createVerificationSecretAndSendEmail = sinon.stub();
response.send.onFirstCall().returnsThis();
plugin.save(request, response);
assert(plugin.exists.firstCall.args[0] === emailParam);
assert(plugin.checkPassphrase.firstCall.args[0] === emailParam);
assert(plugin.checkPassphrase.firstCall.args[1] === secretParam);
assert(plugin.saveEncryptedData.firstCall.args[0] === emailParam);
assert(plugin.saveEncryptedData.firstCall.args[1] === keyParam);
assert(plugin.saveEncryptedData.firstCall.args[2] === recordParam);
plugin.createVerificationSecretAndSendEmail.called.should.be.false;
plugin.getCredentialsFromRequest = originalCredentials;
});
it('should delete profile on error sending verification email', function() {
var originalCredentials = plugin.getCredentialsFromRequest;
plugin.getCredentialsFromRequest = sinon.mock();
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: emailParam,
passphrase: secretParam
});
plugin.exists = sinon.stub();
plugin.exists.onFirstCall().callsArgWith(1, null, false);
plugin.savePassphrase = sinon.stub();
plugin.savePassphrase.onFirstCall().callsArg(2);
plugin.isConfirmed = sinon.stub();
plugin.isConfirmed.onFirstCall().callsArgWith(1, null, false);
plugin.checkSizeQuota = sinon.stub();
plugin.checkSizeQuota.onFirstCall().callsArgWith(3, null);
plugin.checkAndUpdateItemQuota = sinon.stub();
plugin.checkAndUpdateItemQuota.onFirstCall().callsArgWith(3, null);
plugin.saveEncryptedData = sinon.stub();
plugin.saveEncryptedData.onFirstCall().callsArg(3);
plugin.createVerificationSecretAndSendEmail = sinon.stub();
plugin.createVerificationSecretAndSendEmail.onFirstCall().callsArgWith(1, 'error');
var deleteWholeProfile = sinon.stub(plugin, 'deleteWholeProfile');
deleteWholeProfile.onFirstCall().callsArg(1);
response.send.onFirstCall().returnsThis();
plugin.save(request, response);
assert(plugin.exists.firstCall.args[0] === emailParam);
assert(plugin.savePassphrase.firstCall.args[0] === emailParam);
assert(plugin.savePassphrase.firstCall.args[1] === secretParam);
assert(plugin.saveEncryptedData.firstCall.args[0] === emailParam);
assert(plugin.saveEncryptedData.firstCall.args[1] === keyParam);
assert(plugin.saveEncryptedData.firstCall.args[2] === recordParam);
assert(plugin.createVerificationSecretAndSendEmail.firstCall.args[0] === emailParam);
assert(deleteWholeProfile.firstCall.args[0] === emailParam);
plugin.getCredentialsFromRequest = originalCredentials;
});
after(function () {
plugin.deleteWholeProfile.restore();
});
});
describe('when validating email', function() {
var email = '1';
var secret = '2';
beforeEach(function() {
request.param.onFirstCall().returns(email);
request.param.onSecondCall().returns(secret);
leveldb_stub.put = sinon.stub();
leveldb_stub.get = sinon.stub();
leveldb_stub.put.onFirstCall().callsArg(2);
leveldb_stub.del.onFirstCall().callsArg(1);
response.json.returnsThis();
});
it('should validate correctly an email if the secret matches (secret only)', function() {
leveldb_stub.get.onFirstCall().callsArgWith(1, null, secret);
leveldb_stub.del = sinon.stub().yields(null);
response.redirect = sinon.stub();
plugin.validate(request, response);
assert(response.redirect.firstCall.calledWith(plugin.redirectUrl));
});
it('should validate correctly an email if the secret matches (secret + creation date)', function() {
leveldb_stub.get.onFirstCall().callsArgWith(1, null, JSON.stringify({
secret: secret,
created: moment().unix(),
}));
leveldb_stub.del = sinon.stub().yields(null);
response.redirect = sinon.stub();
plugin.validate(request, response);
assert(response.redirect.firstCall.calledWith(plugin.redirectUrl));
});
it('should fail to validate an email if the secret doesn\'t match', function() {
var invalid = '3';
leveldb_stub.get.onFirstCall().callsArgWith(1, null, invalid);
response.status.returnsThis();
response.json.returnsThis();
plugin.validate(request, response);
assert(response.status.firstCall.calledWith(plugin.errors.INVALID_CODE.code));
assert(response.json.firstCall.calledWith({
error: 'The provided code is invalid'
}));
assert(response.end.calledOnce);
});
});
describe('resend validation email', function () {
var email = 'fake@email.com';
var secret = '123';
beforeEach(function() {
leveldb_stub.get.reset();
request.param.onFirstCall().returns(email);
response.json.returnsThis();
response.redirect = sinon.stub();
});
it('should resend validation email when pending', function () {
plugin.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1, null, email);
leveldb_stub.get.onFirstCall().callsArgWith(1, null, JSON.stringify({ secret: secret, created: new Date() }));
plugin.sendVerificationEmail = sinon.spy();
plugin.resendEmail(request, response);
plugin.sendVerificationEmail.calledOnce.should.be.true;
plugin.sendVerificationEmail.calledWith(email, secret).should.be.true;
});
it('should resend validation email when pending (old style secret)', function () {
plugin.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1, null, email);
leveldb_stub.get.onFirstCall().callsArgWith(1, null, secret);
plugin.sendVerificationEmail = sinon.spy();
plugin.resendEmail(request, response);
plugin.sendVerificationEmail.calledOnce.should.be.true;
plugin.sendVerificationEmail.calledWith(email, secret).should.be.true;
});
it('should not resend when email is no longer pending', function () {
plugin.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1, null, email);
leveldb_stub.get.onFirstCall().callsArgWith(1, { notFound: true });
plugin.sendVerificationEmail = sinon.spy();
plugin.resendEmail(request, response);
plugin.sendVerificationEmail.should.not.be.called;
});
});
describe('removing items', function() {
var fakeEmail = 'fake@email.com';
var fakeKey = 'nameForData';
beforeEach(function() {
leveldb_stub.del = sinon.stub();
});
it('deletes a stored element (key)', function(done) {
leveldb_stub.del.onFirstCall().callsArg(1);
plugin.checkAndUpdateItemCounter = sinon.stub();
plugin.checkAndUpdateItemCounter.onFirstCall().callsArg(3);
plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) {
expect(err).to.be.undefined;
done();
});
});
it('returns NOT FOUND if trying to delete a stored element by key', function(done) {
leveldb_stub.del.onFirstCall().callsArgWith(1, {notFound: true});
plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) {
err.should.equal(plugin.errors.NOT_FOUND);
done();
});
});
it('returns INTERNAL_ERROR if an unexpected error ocurrs', function(done) {
leveldb_stub.del.onFirstCall().callsArgWith(1, {unexpected: true});
plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
it('can delete a whole profile (validation data and passphrase)', function(done) {
leveldb_stub.del.callsArg(1);
plugin.deleteWholeProfile(fakeEmail, function(err) {
expect(err).to.be.undefined;
leveldb_stub.del.callCount.should.equal(3);
done();
});
});
it('dismisses not found errors', function(done) {
leveldb_stub.del.callsArg(1);
leveldb_stub.del.onSecondCall().callsArgWith(1, {notFound: true});
plugin.deleteWholeProfile(fakeEmail, function(err) {
expect(err).to.be.undefined;
done();
});
});
it('returns internal error if something goes awry', function(done) {
leveldb_stub.del.callsArg(1);
leveldb_stub.del.onSecondCall().callsArgWith(1, {unexpected: true});
plugin.deleteWholeProfile(fakeEmail, function(err) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
});
describe('when retrieving data', function() {
it('should validate the secret and return the data', function() {
request.param.onFirstCall().returns('key');
plugin.authorizeRequestWithKey = sinon.stub().callsArgWith(1,null, 'email','key');
plugin.retrieveByEmailAndKey = sinon.stub().yields(null, 'encrypted');
response.send.onFirstCall().returnsThis();
plugin.addValidationHeader = sinon.stub().callsArg(2);
plugin.addValidationAndQuotaHeader = sinon.stub().callsArg(2);
plugin.retrieve(request, response);
response.send.calledOnce.should.equal(true);
assert(plugin.retrieveByEmailAndKey.firstCall.args[0] === 'email');
assert(plugin.retrieveByEmailAndKey.firstCall.args[1] === 'key');
assert(response.send.firstCall.args[0] === 'encrypted');
assert(response.end.calledOnce);
});
});
describe('authorizing requests', function() {
var originalCredentials;
beforeEach(function() {
originalCredentials = plugin.getCredentialsFromRequest;
plugin.getCredentialsFromRequest = sinon.mock();
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: 'email',
passphrase: 'pass'
});
request.param.onFirstCall().returns('key');
request.on = sinon.stub();
request.on.onFirstCall().callsArgWith(1, 'newPassphrase=newPassphrase');
request.on.onFirstCall().returns(request);
request.on.onSecondCall().callsArg(1);
plugin.checkPassphrase = sinon.stub().callsArgWith(2,null, true);
});
it('should authorize a request', function(done){
plugin.authorizeRequest(request, false, function(err, email, key) {
expect(err).to.be.null;
expect(key).to.be.undefined;
email.should.be.equal('email');
done();
});
});
it('should authorize a request with key', function(done){
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: 'email',
passphrase: 'pass',
});
plugin.authorizeRequest(request, true, function(err, email, key) {
expect(err).to.be.null;
email.should.be.equal('email');
key.should.be.equal('key');
done();
});
});
it('should not authorize a request when param are missing', function(done){
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: 'email',
});
plugin.authorizeRequest(request, false, function(err, email, key) {
expect(err).not.to.be.null;
expect(key).to.be.undefined;
expect(email).to.be.undefined;
done();
});
});
it('should not authorize a request when param are missing (case2)', function(done){
plugin.getCredentialsFromRequest.onFirstCall().returns({
passphrase: 'pass'
});
plugin.authorizeRequest(request, false, function(err, email, key) {
expect(err).not.to.be.null;
expect(key).to.be.undefined;
expect(email).to.be.undefined;
done();
});
});
it('should not authorize a request when param are missing (case3)', function(done){
request.param.onFirstCall().returns(undefined);
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: 'email',
passphrase: 'pass'
});
plugin.authorizeRequest(request, true, function(err, email, key) {
expect(err).not.to.be.null;
expect(key).to.be.undefined;
expect(email).to.be.undefined;
done();
});
});
after(function() {
plugin.getCredentialsFromRequest = originalCredentials;
});
});
describe('changing the user password', function() {
beforeEach(function() {
request.on = sinon.stub();
request.on.onFirstCall().callsArgWith(1, 'newPassphrase=newPassphrase');
request.on.onFirstCall().returns(request);
request.on.onSecondCall().callsArg(1);
response.status.onFirstCall().returnsThis();
plugin.checkPassphrase = sinon.stub();
plugin.savePassphrase = sinon.stub();
});
it('should validate the previous passphrase', function() {
response.status.onFirstCall().returnsThis();
response.json.onFirstCall().returnsThis();
plugin.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1,'error');
plugin.changePassphrase(request, response);
assert(response.status.calledOnce);
assert(response.json.calledOnce);
assert(response.end.calledOnce);
});
it('should change the passphrase', function() {
response.json.onFirstCall().returnsThis();
plugin.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1,null, 'email');
plugin.checkPassphrase.onFirstCall().callsArgWith(2, null);
plugin.savePassphrase.onFirstCall().callsArgWith(2, null);
plugin.changePassphrase(request, response);
assert(response.json.calledOnce);
assert(response.end.calledOnce);
});
});
});

View File

@ -1,133 +0,0 @@
'use strict';
var chai = require('chai');
var should = chai.should;
var expect = chai.expect;
var levelup = require('levelup');
var memdown = require('memdown');
var microtime = require('microtime');
var MessageDb = require('../lib/MessageDb');
var bitcore = require('bitcore');
var SIN = bitcore.SIN;
var Key = bitcore.Key;
var AuthMessage = bitcore.AuthMessage;
describe('MessageDb', function() {
var opts = {
name: 'test-MessageDb',
db: levelup({
db: memdown,
sync: true,
valueEncoding: 'json'
})
};
it('should be able to create instance', function() {
var mdb = new MessageDb(opts);
expect(mdb).to.exist;
});
it('should be able to create default instance', function() {
var mdb = MessageDb.default();
expect(mdb).to.exist;
});
var fpk = Key.generateSync();
var tpk = Key.generateSync();
var from = fpk.public.toString('hex');
var to = tpk.public.toString('hex');
var messageData = {
a: 1,
b: 2
};
var messageData2 = {};
var messageData3 = ['a', 'b'];
var message = AuthMessage.encode(to, fpk, messageData);
var message2 = AuthMessage.encode(to, fpk, messageData2);
var message3 = AuthMessage.encode(to, fpk, messageData3);
it('should be able to add and read a message', function(done) {
var mdb = new MessageDb(opts);
var lower_ts = microtime.now();
mdb.addMessage(message, function(err) {
expect(err).to.not.exist;
var upper_ts = microtime.now();
mdb.getMessages(to, lower_ts, upper_ts, function(err, messages) {
expect(err).to.not.exist;
messages.length.should.equal(1);
messages[0].ts.should.be.below(upper_ts);
messages[0].ts.should.be.above(lower_ts);
var m = AuthMessage.decode(tpk, messages[0]).payload;
m.a.should.equal(1);
m.b.should.equal(2);
done();
});
});
});
var sharedMDB;
it('should be able to add many messages and read some', function(done) {
var mdb = new MessageDb(opts);
sharedMDB = mdb;
var lower_ts = microtime.now();
mdb.addMessage(message, function(err) {
expect(err).to.not.exist;
mdb.addMessage(message2, function(err) {
expect(err).to.not.exist;
var upper_ts = microtime.now();
setTimeout(function() {
mdb.addMessage(message3, function(err) {
expect(err).to.not.exist;
mdb.getMessages(to, lower_ts, upper_ts, function(err, messages) {
expect(err).to.not.exist;
messages.length.should.equal(2);
messages[0].ts.should.be.below(upper_ts);
messages[0].ts.should.be.above(lower_ts);
var m0 = AuthMessage.decode(tpk, messages[0]).payload;
JSON.stringify(m0).should.equal('{"a":1,"b":2}');
messages[1].ts.should.be.below(upper_ts);
messages[1].ts.should.be.above(lower_ts);
var m1 = AuthMessage.decode(tpk, messages[1]).payload;
JSON.stringify(m1).should.equal('{}');
done();
});
});
}, 10);
});
});
});
it('should be able to add many messages and read all', function(done) {
var mdb = sharedMDB;
mdb.getMessages(to, null, null, function(err, messages) {
expect(err).to.not.exist;
messages.length.should.equal(4);
var m0 = AuthMessage.decode(tpk, messages[0]).payload;
JSON.stringify(m0).should.equal('{"a":1,"b":2}');
var m1 = AuthMessage.decode(tpk, messages[1]).payload;
JSON.stringify(m1).should.equal('{"a":1,"b":2}');
var m2 = AuthMessage.decode(tpk, messages[2]).payload;
JSON.stringify(m2).should.equal('{}');
var m3 = AuthMessage.decode(tpk, messages[3]).payload;
JSON.stringify(m3).should.equal('["a","b"]');
done();
});
});
it('should be able #removeUpTo', function(done) {
var mdb = sharedMDB;
var upper_ts = microtime.now();
mdb.addMessage(message, function(err) {
expect(err).to.not.exist;
mdb.removeUpTo(upper_ts, function(err, n) {
expect(err).to.not.exist;
n.should.equal(4);
mdb.getAll(function(error, all) {
expect(error).to.not.exist;
all.length.should.equal(1);
done();
});
});
});
});
it('should be able to close instance', function() {
var mdb = new MessageDb(opts);
mdb.close();
expect(mdb).to.exist;
});
});

View File

@ -1,113 +0,0 @@
'use strict';
var chai = require('chai');
var assert = require('assert');
var sinon = require('sinon');
var should = chai.should;
var expect = chai.expect;
var bitauth = require('bitauth');
describe('public profile test', function() {
var globalConfig = require('../config/config');
var leveldb_stub = sinon.stub();
leveldb_stub.put = sinon.stub();
leveldb_stub.get = sinon.stub();
var plugin = require('../plugins/publicInfo/publicInfo.js');
var express_mock = null;
var request = null;
var response = null;
beforeEach(function() {
express_mock = sinon.stub();
express_mock.post = sinon.stub();
express_mock.get = sinon.stub();
plugin.init(express_mock, {db: leveldb_stub});
request = sinon.stub();
request.on = sinon.stub();
request.param = sinon.stub();
response = sinon.stub();
response.send = sinon.stub();
response.status = sinon.stub();
response.json = sinon.stub();
response.end = sinon.stub();
});
it('initializes correctly', function() {
assert(plugin.db === leveldb_stub);
assert(express_mock.post.calledWith(
globalConfig.apiPrefix + '/public', plugin.post
));
assert(express_mock.get.calledWith(
globalConfig.apiPrefix + '/public/:sin', plugin.get
));
});
it('writes a message correctly', function(done) {
var privateKey = bitauth.generateSin();
var protocol = 'https';
var dataToSign = protocol + '://hosturlSTUFF';
var signature = bitauth.sign(dataToSign, privateKey.priv);
request.get = function() { return 'host'; };
request.protocol = protocol;
request.url = 'url';
request.headers = {
'x-identity': privateKey.pub,
'x-signature': signature
};
request.on.onFirstCall().callsArgWith(1, 'STUFF');
request.on.onFirstCall().returnsThis();
request.on.onSecondCall().callsArg(1);
leveldb_stub.put.onFirstCall().callsArg(2);
response.status.returns(response);
response.json.returns(response);
request.testCallback = function() {
assert(leveldb_stub.put.firstCall.args[0] === 'public-info-' + privateKey.sin);
assert(leveldb_stub.put.firstCall.args[1] === 'STUFF');
assert(response.json.calledOnce);
assert(response.end.calledOnce);
done();
};
plugin.post(request, response);
});
it('fails if the signature is invalid', function() {
var data = 'uecord3';
request.get = function() { return ''; };
request.headers = {};
request.on.onFirstCall().callsArgWith(1, data);
request.on.onFirstCall().returnsThis();
request.on.onSecondCall().callsArg(1);
leveldb_stub.put = sinon.stub();
leveldb_stub.put.onFirstCall().callsArg(2);
response.json.returnsThis();
response.status.returnsThis();
plugin.post(request, response);
assert(response.end.calledOnce);
});
it('retrieves a message correctly', function() {
request.param.onFirstCall().returns('SIN');
var returnValue = '!@#$%';
leveldb_stub.get.onFirstCall().callsArgWith(1, null, returnValue);
response.send.returnsThis();
plugin.get(request, response);
assert(leveldb_stub.get.firstCall.args[0] === 'public-info-SIN');
assert(response.send.calledWith(returnValue));
assert(response.end.calledOnce);
});
});

View File

@ -1,30 +0,0 @@
'use strict';
var chai = require('chai');
var should = chai.should;
var expect = chai.expect;
var sinon = require('sinon');
var socket = require('../app/controllers/socket');
var bitcore = require('bitcore');
var EventEmitter = require('events').EventEmitter;
describe('socket server', function() {
it('should be able to call init with no args', function() {
socket.init.should.not.throw();
});
it('should register socket handlers', function() {
var io = {
sockets: new EventEmitter(),
}
socket.init(io);
var mockSocket = {};
mockSocket.on = sinon.spy();
io.sockets.emit('connection', mockSocket);
mockSocket.on.calledWith('subscribe');
mockSocket.on.calledWith('sync');
mockSocket.on.calledWith('message');
});
});