From b8438fc0eb7ca51d8fa9e620a6770c8f82a2377c Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 9 Feb 2015 15:30:16 -0300 Subject: [PATCH 1/4] add express app --- TODO.txt | 2 +- app.js | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/server.js | 10 +++- 3 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 app.js diff --git a/TODO.txt b/TODO.txt index 7ec5061..b2d817b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -2,6 +2,6 @@ - Proposal with spent input should be tagged as invalid or removed - Cron job to broadcast accepted txps that failed to broadcast (we may need to track broadcast attempts for this). - Payment protocol - +- Automatically create ./db directory - check parameters for KEY at storage diff --git a/app.js b/app.js new file mode 100644 index 0000000..1c0f4fc --- /dev/null +++ b/app.js @@ -0,0 +1,157 @@ +'use strict'; + +var _ = require('lodash'); +var async = require('async'); +var log = require('npmlog'); +var CopayServer = require('./lib/server'); +var express = require('express'); +var querystring = require('querystring'); + +log.debug = log.verbose; +log.level = 'debug'; + +var POST_LIMIT = 1024 * 100 /* Max POST 100 kb */ ; + +CopayServer.initialize(); + + +var app = express(); +app.use(function(req, res, next) { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE'); + res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type,Authorization'); + next(); +}); +var allowCORS = function(req, res, next) { + if ('OPTIONS' == req.method) { + res.send(200); + res.end(); + return; + } + next(); +} +app.use(allowCORS); + +var port = process.env.COPAY_PORT || 3001; +var router = express.Router(); + +function returnError(err, res) { + if (err instanceof CopayServer.ClientError) { + var status = (err.code == 'NOTAUTHORIZED') ? 401 : 400; + res.status(status).json({ + code: err.code, + error: err.message, + }).end(); + } else { + var code, message; + if (_.isObject(err)) { + code = err.code; + message = err.message; + } + res.status(code || 500).json({ + error: message || err.toString(), + }).end(); + } +}; + +function getCredentials(req) { + var identity = req.header('x-identity'); + if (!identity) return; + + return { + copayerId: identity, + }; +}; + +function authenticate() { + return true; +}; + +function parsePost(req, res, cb) { + var queryData = ''; + req.on('data', function(data) { + queryData += data; + if (queryData.length > POST_LIMIT) { + queryData = ''; + res.writeHead(413, { + 'Content-Type': 'text/plain' + }); + res.end(); + req.connection.destroy(); + } + }).on('end', function() { + try { + var params = JSON.parse(queryData); + cb(params); + } catch (ex) { + returnError({ + code: 400, + message: 'Unable to parse request' + }, res); + } + }); +}; + +router.post('/v1/wallets/', function(req, res) { + parsePost(req, res, function(params) { + try { + var server = CopayServer.getInstance(); + server.createWallet(params, function(err, wallet) { + if (err) returnError(err, res); + + res.json(wallet); + }); + } catch (ex) { + returnError(ex, res); + } + }); +}); + +router.post('/v1/wallets/:id/join/', function(req, res) { + parsePost(req, res, function(params) { + params.walletId = req.params['id']; + try { + var server = CopayServer.getInstance(); + server.joinWallet(params, function(err) { + if (err) returnError(err, res); + + res.end(); + }); + } catch (ex) { + returnError(ex, res); + } + }); +}); + +router.get('/v1/wallets/', function(req, res) { + var credentials = getCredentials(req); + + CopayServer.getInstanceWithAuth({ + copayerId: credentials.copayerId, + message: 'hello world!', + signature: '3045022100addd20e5413865d65d561ad2979f2289a40d52594b1f804840babd9a63e4ebbf02204b86285e1fcab02df772e7a1325fc4b511ecad79a8f80a2bd1ad8bfa858ac3d4', + }, function(err, server) { + if (err) return returnError(err, res); + try { + server.getWallet({}, function(err, wallet) { + if (err) returnError(err, res); + res.json(wallet); + }); + } catch (ex) { + returnError(ex, res); + } + }); +}); + +// TODO: DEBUG only! +router.get('/v1/dump', function(req, res) { + var server = CopayServer.getInstance(); + server.storage._dump(function() { + res.end(); + }); +}); + +app.use('/copay/api', router); + +app.listen(port); +console.log('Copay service running on port ' + port); diff --git a/lib/server.js b/lib/server.js index ef4e0c1..73a0307 100644 --- a/lib/server.js +++ b/lib/server.js @@ -49,6 +49,10 @@ CopayServer.initialize = function(opts) { initialized = true; }; +CopayServer.getInstance = function() { + return new CopayServer(); +}; + /** * Gets an instance of the server after authenticating the copayer. * @param {Object} opts @@ -63,10 +67,10 @@ CopayServer.getInstanceWithAuth = function(opts, cb) { var server = new CopayServer(); server.storage.fetchCopayerLookup(opts.copayerId, function(err, copayer) { if (err) return cb(err); - if (!copayer) return cb('Copayer not found'); + if (!copayer) return cb(new ClientError('NOTAUTHORIZED', 'Copayer not found')); var isValid = server._verifySignature(opts.message, opts.signature, copayer.signingPubKey); - if (!isValid) return cb('Invalid signature'); + if (!isValid) return cb(new ClientError('NOTAUTHORIZED', 'Invalid signature')); server.copayerId = opts.copayerId; server.walletId = copayer.walletId; @@ -611,5 +615,5 @@ CopayServer.prototype.getTxs = function(opts, cb) { - module.exports = CopayServer; +module.exports.ClientError = ClientError; From a7e787704e9af8aae0262dc76c339436184a5296 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 9 Feb 2015 16:19:43 -0300 Subject: [PATCH 2/4] add request module --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 30ce41f..a9a3674 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "lodash": "^2.4.1", "npmlog": "^0.1.1", "preconditions": "^1.0.7", - "uuid":"*" + "request": "^2.53.0", + "uuid": "*" }, "devDependencies": { "chai": "^1.9.1", From 7df7f27d237e2d2fc81a07aaeff7d82d6a11b0c5 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 9 Feb 2015 16:19:54 -0300 Subject: [PATCH 3/4] add commander module --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a9a3674..06f0978 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "async": "^0.9.0", "bitcore": "*", "bitcore-explorers": "^0.9.1", + "commander": "^2.6.0", "express": "^4.10.0", "inherits": "^2.0.1", "leveldown": "^0.10.0", From b122eb49feecba610e49d9bd03c785b76eef8a5a Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 9 Feb 2015 17:22:53 -0300 Subject: [PATCH 4/4] more methods --- app.js | 80 ++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/app.js b/app.js index 1c0f4fc..85ba2e2 100644 --- a/app.js +++ b/app.js @@ -63,6 +63,19 @@ function getCredentials(req) { }; }; +function getServerWithAuth(req, res, cb) { + var credentials = getCredentials(req); + + CopayServer.getInstanceWithAuth({ + copayerId: credentials.copayerId, + message: 'hello world!', + signature: '3045022100addd20e5413865d65d561ad2979f2289a40d52594b1f804840babd9a63e4ebbf02204b86285e1fcab02df772e7a1325fc4b511ecad79a8f80a2bd1ad8bfa858ac3d4', + }, function(err, server) { + if (err) return returnError(err, res); + return cb(server); + }); +}; + function authenticate() { return true; }; @@ -94,52 +107,69 @@ function parsePost(req, res, cb) { router.post('/v1/wallets/', function(req, res) { parsePost(req, res, function(params) { - try { - var server = CopayServer.getInstance(); - server.createWallet(params, function(err, wallet) { - if (err) returnError(err, res); + var server = CopayServer.getInstance(); + server.createWallet(params, function(err, wallet) { + if (err) returnError(err, res); - res.json(wallet); - }); - } catch (ex) { - returnError(ex, res); - } + res.json(wallet); + }); }); }); router.post('/v1/wallets/:id/join/', function(req, res) { parsePost(req, res, function(params) { params.walletId = req.params['id']; - try { - var server = CopayServer.getInstance(); - server.joinWallet(params, function(err) { - if (err) returnError(err, res); + var server = CopayServer.getInstance(); + server.joinWallet(params, function(err) { + if (err) returnError(err, res); - res.end(); - }); - } catch (ex) { - returnError(ex, res); - } + res.end(); + }); }); }); router.get('/v1/wallets/', function(req, res) { var credentials = getCredentials(req); - CopayServer.getInstanceWithAuth({ + CopayServer.getInstanceWithAuth(getCredentials(req) { copayerId: credentials.copayerId, message: 'hello world!', signature: '3045022100addd20e5413865d65d561ad2979f2289a40d52594b1f804840babd9a63e4ebbf02204b86285e1fcab02df772e7a1325fc4b511ecad79a8f80a2bd1ad8bfa858ac3d4', }, function(err, server) { if (err) return returnError(err, res); - try { - server.getWallet({}, function(err, wallet) { + server.getWallet({}, function(err, wallet) { + if (err) returnError(err, res); + res.json(wallet); + }); + }); +}); + +router.post('/v1/addresses/', function(req, res) { + parsePost(req, res, function(params) { + getServerWithAuth(req, res, function(server) { + server.createAddress(params, function(err, address) { if (err) returnError(err, res); - res.json(wallet); + res.json(address); }); - } catch (ex) { - returnError(ex, res); - } + }); + }); +}); + +router.get('/v1/addresses/', function(req, res) { + getServerWithAuth(req, res, function(server) { + server.getAddresses({}, function(err, addresses) { + if (err) returnError(err, res); + res.json(addresses); + }); + }); +}); + +router.get('/v1/balance/', function(req, res) { + getServerWithAuth(req, res, function(server) { + server.getBalance({}, function(err, balance) { + if (err) returnError(err, res); + res.json(balance); + }); }); });