diff --git a/index.js b/index.js index fb4e362e..d4eb8274 100644 --- a/index.js +++ b/index.js @@ -35,6 +35,10 @@ const speechPassport = require('./server/speechPassport'); const { details: { port: PORT }, auth: { sessionKey }, + startup: { + performChecks, + performUpdates, + }, } = require('@config/siteConfig'); function Server () { @@ -97,31 +101,70 @@ function Server () { /* create server */ this.server = http.Server(this.app); }; + this.startServerListening = () => { + logger.info(`Starting server on ${PORT}...`); + return new Promise((resolve, reject) => { + this.server.listen(PORT, () => { + logger.info(`Server is listening on PORT ${PORT}`); + resolve(); + }) + }); + }; this.syncDatabase = () => { + logger.info(`Syncing database...`); return createDatabaseIfNotExists() .then(() => { db.sequelize.sync(); }) }; + this.performChecks = () => { + if (!performChecks) { + return; + } + logger.info(`Performing checks...`); + return Promise.all([ + getWalletBalance(), + ]) + .then(([walletBalance]) => { + logger.info('Starting LBC balance:', walletBalance); + }) + }; + this.performUpdates = () => { + if (!performUpdates) { + return; + } + logger.info(`Peforming updates...`); + return Promise.all([ + [], + db.Tor.refreshTable(), + ]) + .then(([updatedBlockedList, updatedTorList]) => { + logger.info('Blocked list updated, length:', updatedBlockedList.length); + logger.info('Tor list updated, length:', updatedTorList.length); + }) + }; this.start = () => { this.initialize(); this.createApp(); this.createServer(); - /* start the server */ - logger.info('getting LBC balance & syncing database...'); - Promise.all([ - this.syncDatabase(), - getWalletBalance(), - ]) - .then(([syncResult, walletBalance]) => { - logger.info('starting LBC balance:', walletBalance); - return this.server.listen(PORT, () => { - logger.info(`Server is listening on PORT ${PORT}`); - }) + this.syncDatabase() + .then(() => { + return this.startServerListening(); + }) + .then(() => { + return Promise.all([ + this.performChecks(), + this.performUpdates(), + ]) + }) + .then(() => { + logger.info('Spee.ch startup is complete'); }) .catch(error => { if (error.code === 'ECONNREFUSED') { return logger.error('Connection refused. The daemon may not be running.') + } else if (error.code === 'EADDRINUSE') { + return logger.error('Server could not start listening. The port is already in use.'); } else if (error.message) { logger.error(error.message); } diff --git a/server/controllers/api/claim/publish/index.js b/server/controllers/api/claim/publish/index.js index 7d42106a..b429e797 100644 --- a/server/controllers/api/claim/publish/index.js +++ b/server/controllers/api/claim/publish/index.js @@ -21,9 +21,9 @@ const authenticateUser = require('./authentication.js'); */ -const claimPublish = ({ body, files, headers, ip, originalUrl, user }, res) => { +const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res) => { // logging - logger.info('PUBLISH REQUEST:', { + logger.info('Publish request:', { ip, headers, body, diff --git a/server/controllers/api/tor/index.js b/server/controllers/api/tor/index.js new file mode 100644 index 00000000..df4c5831 --- /dev/null +++ b/server/controllers/api/tor/index.js @@ -0,0 +1,25 @@ +const logger = require('winston'); +const db = require('../../../models'); + +/* + + Route to update and return tor exit nodes that can connect to this ip address + +*/ + +const getTorList = (req, res) => { + db.Tor.refreshTable() + .then( result => { + logger.debug('number of records', result.length); + res.status(200).json(result); + }) + .catch((error) => { + logger.error(error); + res.status(500).json({ + success: false, + error, + }) + }); +}; + +module.exports = getTorList; diff --git a/server/routes/utils/multipartMiddleware.js b/server/middleware/multipartMiddleware.js similarity index 100% rename from server/routes/utils/multipartMiddleware.js rename to server/middleware/multipartMiddleware.js diff --git a/server/middleware/torCheckMiddleware.js b/server/middleware/torCheckMiddleware.js new file mode 100644 index 00000000..1b410331 --- /dev/null +++ b/server/middleware/torCheckMiddleware.js @@ -0,0 +1,32 @@ +const logger = require('winston'); +const db = require('../models'); + +const torCheck = (req, res, next) => { + const { ip } = req; + logger.debug(`tor check for: ${ip}`); + return db.Tor.findAll( + { + where: { + address: ip, + }, + raw: true, + }) + .then(result => { + logger.debug('tor check results:', result); + if (result.length >= 1) { + logger.info('Tor request blocked:', ip); + const failureResponse = { + success: false, + message: 'Unfortunately this api route is not currently available for tor users. We are working on a solution that will allow tor users to use this endpoint in the future.', + }; + res.status(403).json(failureResponse); + } else { + next(); + } + }) + .catch(error => { + logger.error(error); + }); +}; + +module.exports = torCheck; diff --git a/server/models/index.js b/server/models/index.js index e2011bc8..a13d58ec 100644 --- a/server/models/index.js +++ b/server/models/index.js @@ -8,6 +8,7 @@ const File = require('./file.js'); const Request = require('./request.js'); const User = require('./user.js'); const Blocked = require('./blocked.js'); +const Tor = require('./tor.js'); const {database, username, password} = require('@config/mysqlConfig'); if (!database || !username || !password) { @@ -50,6 +51,7 @@ db['File'] = sequelize.import('File', File); db['Request'] = sequelize.import('Request', Request); db['User'] = sequelize.import('User', User); db['Blocked'] = sequelize.import('Blocked', Blocked); +db['Tor'] = sequelize.import('Tor', Tor); // run model.association for each model in the db object that has an association logger.info('associating db models...'); diff --git a/server/models/tor.js b/server/models/tor.js new file mode 100644 index 00000000..51541545 --- /dev/null +++ b/server/models/tor.js @@ -0,0 +1,56 @@ +const { details: { ipAddress } } = require('@config/siteConfig'); + +module.exports = (sequelize, { STRING }) => { + const Tor = sequelize.define( + 'Tor', + { + address: { + type : STRING, + allowNull: false, + }, + fingerprint: { + type : STRING, + allowNull: true, + }, + }, + { + freezeTableName: true, + } + ); + + Tor.refreshTable = function () { + let torList = []; + return fetch(`https://check.torproject.org/api/bulk?ip=${ipAddress}&port=80`) + .then(response => { + return response.json(); + }) + .then(jsonResponse => { + for (let i = 0; i < jsonResponse.length; i++) { + torList.push({ + address : jsonResponse[i].Address, + fingerprint: jsonResponse[i].Fingerprint, + }); + } + // clear the table + return this.destroy({ + truncate: true, + }); + }) + .then(() => { + // fill the table + return this.bulkCreate(torList); + }) + .then(() => { + // return the new table + return this.findAll({ + attributes: ['address', 'fingerprint'], + raw : true, + }); + }) + .catch(error => { + throw error; + }); + }; + + return Tor; +}; diff --git a/server/routes/api/index.js b/server/routes/api/index.js index 6a89348d..b0d10a1a 100644 --- a/server/routes/api/index.js +++ b/server/routes/api/index.js @@ -14,29 +14,33 @@ const claimShortId = require('../../controllers/api/claim/shortId'); const fileAvailability = require('../../controllers/api/file/availability'); const userPassword = require('../../controllers/api/user/password'); const publishingConfig = require('../../controllers/api/config/site/publishing'); +const getTorList = require('../../controllers/api/tor'); -const multipartMiddleware = require('../utils/multipartMiddleware'); +const multipartMiddleware = require('../../middleware/multipartMiddleware'); +const torCheckMiddleware = require('../../middleware/torCheckMiddleware'); module.exports = (app) => { // channel routes - app.get('/api/channel/availability/:name', channelAvailability); - app.get('/api/channel/short-id/:longId/:name', channelShortId); - app.get('/api/channel/data/:channelName/:channelClaimId', channelData); - app.get('/api/channel/claims/:channelName/:channelClaimId/:page', channelClaims); + app.get('/api/channel/availability/:name', torCheckMiddleware, channelAvailability); + app.get('/api/channel/short-id/:longId/:name', torCheckMiddleware, channelShortId); + app.get('/api/channel/data/:channelName/:channelClaimId', torCheckMiddleware, channelData); + app.get('/api/channel/claims/:channelName/:channelClaimId/:page', torCheckMiddleware, channelClaims); // claim routes - app.get('/api/claim/availability/:name', claimAvailability); - app.get('/api/claim/blocked-list/', claimBlockedList); - app.get('/api/claim/data/:claimName/:claimId', claimData); - app.get('/api/claim/get/:name/:claimId', claimGet); - app.get('/api/claim/list/:name', claimList); - app.post('/api/claim/long-id', claimLongId); // should be a get - app.post('/api/claim/publish', multipartMiddleware, claimPublish); - app.get('/api/claim/resolve/:name/:claimId', claimResolve); - app.get('/api/claim/short-id/:longId/:name', claimShortId); + app.get('/api/claim/availability/:name', torCheckMiddleware, claimAvailability); + app.get('/api/claim/blocked-list/', torCheckMiddleware, claimBlockedList); + app.get('/api/claim/data/:claimName/:claimId', torCheckMiddleware, claimData); + app.get('/api/claim/get/:name/:claimId', torCheckMiddleware, claimGet); + app.get('/api/claim/list/:name', torCheckMiddleware, claimList); + app.post('/api/claim/long-id', torCheckMiddleware, claimLongId); // note: should be a 'get' + app.post('/api/claim/publish', torCheckMiddleware, multipartMiddleware, claimPublish); + app.get('/api/claim/resolve/:name/:claimId', torCheckMiddleware, claimResolve); + app.get('/api/claim/short-id/:longId/:name', torCheckMiddleware, claimShortId); // file routes - app.get('/api/file/availability/:name/:claimId', fileAvailability); + app.get('/api/file/availability/:name/:claimId', torCheckMiddleware, fileAvailability); // user routes - app.put('/api/user/password/', userPassword); + app.put('/api/user/password/', torCheckMiddleware, userPassword); // configs - app.get('/api/config/site/publishing', publishingConfig); + app.get('/api/config/site/publishing', torCheckMiddleware, publishingConfig); + // tor + app.get('/api/tor', torCheckMiddleware, getTorList); };