diff --git a/Gruntfile.js b/Gruntfile.js index 9ccb9c6..8a27062 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -17,7 +17,7 @@ module.exports = function (grunt) { pattern: 'This Source Code Form is subject to the terms of the Mozilla Public' }, src: [ - '*.js', + '{,config/}*.js', '{bans/,bin/,scripts/,test/}*' ] }, @@ -37,7 +37,7 @@ module.exports = function (grunt) { reporter: require('jshint-stylish') }, app: [ - '{,bans/,bin/,scripts/,test/}*.js' + '{,bans/,bin/,config/,scripts/,test/}*.js' ] } }) diff --git a/bans/handler.js b/bans/handler.js index 6c2dc77..cc0ecc1 100644 --- a/bans/handler.js +++ b/bans/handler.js @@ -2,13 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -var config = require('../config') +var config = require('../config').root() -var LIFETIME = config.recordLifetimeSeconds -var BLOCK_INTERVAL_MS = config.blockIntervalSeconds * 1000 -var RATE_LIMIT_INTERVAL_MS = config.rateLimitIntervalSeconds * 1000 +var LIFETIME = config.memcache.recordLifetimeSeconds +var BLOCK_INTERVAL_MS = config.limits.blockIntervalSeconds * 1000 +var RATE_LIMIT_INTERVAL_MS = config.limits.rateLimitIntervalSeconds * 1000 -var EmailRecord = require('../email_record')(RATE_LIMIT_INTERVAL_MS, BLOCK_INTERVAL_MS, config.maxEmails) +var EmailRecord = require('../email_record')(RATE_LIMIT_INTERVAL_MS, BLOCK_INTERVAL_MS, config.limits.maxEmails) var IpRecord = require('../ip_record')(BLOCK_INTERVAL_MS) module.exports = function (mc, log) { diff --git a/bin/customs_server.js b/bin/customs_server.js index d6a9c99..18cb7d4 100644 --- a/bin/customs_server.js +++ b/bin/customs_server.js @@ -4,16 +4,16 @@ var Memcached = require('memcached') var restify = require('restify') -var config = require('../config') -var log = require('../log')(config.logLevel, 'customs-server') +var config = require('../config').root() +var log = require('../log')(config.log.level, 'customs-server') var packageJson = require('../package.json') -var LIFETIME = config.recordLifetimeSeconds -var BLOCK_INTERVAL_MS = config.blockIntervalSeconds * 1000 -var RATE_LIMIT_INTERVAL_MS = config.rateLimitIntervalSeconds * 1000 +var LIFETIME = config.memcache.recordLifetimeSeconds +var BLOCK_INTERVAL_MS = config.limits.blockIntervalSeconds * 1000 +var RATE_LIMIT_INTERVAL_MS = config.limits.rateLimitIntervalSeconds * 1000 -var IpEmailRecord = require('../ip_email_record')(RATE_LIMIT_INTERVAL_MS, config.maxBadLogins) -var EmailRecord = require('../email_record')(RATE_LIMIT_INTERVAL_MS, BLOCK_INTERVAL_MS, config.maxEmails) +var IpEmailRecord = require('../ip_email_record')(RATE_LIMIT_INTERVAL_MS, config.limits.maxBadLogins) +var EmailRecord = require('../email_record')(RATE_LIMIT_INTERVAL_MS, BLOCK_INTERVAL_MS, config.limits.maxEmails) var IpRecord = require('../ip_record')(BLOCK_INTERVAL_MS) var P = require('bluebird') @@ -28,7 +28,7 @@ if (process.env.ASS_CODE_COVERAGE) { } var mc = new Memcached( - config.memcached, + config.memcache.host + ':' + config.memcache.port, { timeout: 500, retries: 1, @@ -264,8 +264,9 @@ api.get( ) api.listen( - config.port, + config.listen.port, + config.listen.host, function () { - log.info({ op: 'listening', port: config.port }) + log.info({ op: 'listening', host: config.listen.host, port: config.listen.port }) } ) diff --git a/config.js b/config.js deleted file mode 100644 index 4abecd2..0000000 --- a/config.js +++ /dev/null @@ -1,21 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -module.exports = require('rc')( - 'fxa_customs', - { - logLevel: 'trace', - port: 7000, - memcached: '127.0.0.1:11211', - recordLifetimeSeconds: 900, // memcache record expiry - blockIntervalSeconds: 60 * 60 * 24, // duration of a manual ban - rateLimitIntervalSeconds: 60 * 15, // duration of automatic throttling - maxEmails: 3, // number of emails sent within rateLimitIntervalSeconds before throttling - maxBadLogins: 2, // number failed login attempts within rateLimitIntervalSeconds before throttling - bans: { - region: '', - queueUrl: '' - } - } -) diff --git a/config/config.js b/config/config.js new file mode 100644 index 0000000..04b8136 --- /dev/null +++ b/config/config.js @@ -0,0 +1,107 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +module.exports = function (fs, path, url, convict) { + + var conf = convict({ + env: { + doc: 'The current node.js environment', + default: 'prod', + format: [ 'dev', 'test', 'stage', 'prod' ], + env: 'NODE_ENV' + }, + log: { + level: { + default: 'trace', + env: 'LOG_LEVEL' + } + }, + publicUrl: { + format: 'url', + // the real url is set by awsbox + default: 'http://127.0.0.1:7000', + env: 'PUBLIC_URL' + }, + listen: { + host: { + doc: 'The ip address the server should bind', + default: '127.0.0.1', + format: 'ipaddress', + env: 'IP_ADDRESS' + }, + port: { + doc: 'The port the server should bind', + default: 7000, + format: 'port', + env: 'PORT' + } + }, + limits: { + blockIntervalSeconds: { + doc: 'Duration of a manual ban', + default: 60 * 60 * 24, + format: 'nat' + }, + rateLimitIntervalSeconds: { + doc: 'Duration of automatic throttling', + default: 60 * 15, + format: 'nat' + }, + maxEmails: { + doc: 'Number of emails sent within rateLimitIntervalSeconds before throttling', + default: 3, + format: 'nat' + }, + maxBadLogins: { + doc: 'Number failed login attempts within rateLimitIntervalSeconds before throttling', + default: 2, + format: 'nat' + } + }, + memcache: { + host: { + doc: 'Hostname / IP of the memcache server', + default: '127.0.0.1', + env: 'MEMCACHE_HOST' + }, + port: { + doc: 'Port of the memcache server', + default: '11211', + format: 'port', + env: 'MEMCACHE_PORT' + }, + recordLifetimeSeconds: { + doc: 'Memcache record expiry', + default: 900, + format: 'nat' + } + }, + bans: { + region: { + doc: 'The region where the queues live, most likely the same region we are sending email e.g. us-east-1, us-west-2, ap-southeast-2', + format: String, + default: '', + env: 'BANS_REGION' + }, + queueUrl: { + doc: 'The bounce queue URL to use (should include https://sqs..amazonaws.com//)', + format: String, + default: '', + env: 'BANS_QUEUE_URL' + } + } + }) + + // handle configuration files. you can specify a CSV list of configuration + // files to process, which will be overlayed in order, in the CONFIG_FILES + // environment variable. By default, the ./config/.json file is loaded. + + var envConfig = path.join(__dirname, conf.get('env') + '.json') + var files = (envConfig + ',' + process.env.CONFIG_FILES) + .split(',').filter(fs.existsSync) + conf.loadFile(files) + conf.validate() + + return conf +} diff --git a/config/index.js b/config/index.js new file mode 100644 index 0000000..b8e0bcc --- /dev/null +++ b/config/index.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var fs = require('fs') +var path = require('path') +var url = require('url') +var convict = require('convict') + +module.exports = require('./config')(fs, path, url, convict) diff --git a/package.json b/package.json index b054464..1c8484b 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "aws-sdk": "2.0.0-rc.20", "bluebird": "1.2.2", "bunyan": "0.23.1", + "convict": "0.4.2", "memcached": "0.2.8", - "rc": "0.3.4", "restify": "2.7.0" }, "devDependencies": { diff --git a/test/local/ban_tests.js b/test/local/ban_tests.js index a106ea1..aa8f05c 100644 --- a/test/local/ban_tests.js +++ b/test/local/ban_tests.js @@ -12,7 +12,9 @@ var log = { } var config = { - blockIntervalSeconds: 1 + limits: { + blockIntervalSeconds: 1 + } } var TEST_IP = '192.0.2.1' @@ -65,7 +67,7 @@ test( } ) }, - config.blockIntervalSeconds * 1000 + config.limits.blockIntervalSeconds * 1000 ) } ) @@ -105,7 +107,7 @@ test( } ) }, - config.blockIntervalSeconds * 1000 + config.limits.blockIntervalSeconds * 1000 ) } ) diff --git a/test/memcache-helper.js b/test/memcache-helper.js index 0e82999..cfcc9c7 100644 --- a/test/memcache-helper.js +++ b/test/memcache-helper.js @@ -5,15 +5,20 @@ var Memcached = require('memcached') var config = { - memcached: '127.0.0.1:11211', - blockIntervalSeconds: 1, - rateLimitIntervalSeconds: 1, - maxEmails: 3, - maxBadLogins: 2 + memcache: { + host: '127.0.0.1', + port: '11211' + }, + limits: { + blockIntervalSeconds: 1, + rateLimitIntervalSeconds: 1, + maxEmails: 3, + maxBadLogins: 2 + } } var mc = new Memcached( - config.memcached, + config.memcache.host + ':' + config.memcache.port, { timeout: 500, retries: 1, @@ -29,9 +34,9 @@ module.exports.mc = mc var TEST_EMAIL = 'test@example.com' var TEST_IP = '192.0.2.1' -var EmailRecord = require('../email_record')(config.rateLimitIntervalSeconds * 1000, config.blockIntervalSeconds * 1000, config.maxEmails) -var IpEmailRecord = require('../ip_email_record')(config.rateLimitIntervalSeconds * 1000, config.maxBadLogins) -var IpRecord = require('../ip_record')(config.blockIntervalSeconds * 1000) +var EmailRecord = require('../email_record')(config.limits.rateLimitIntervalSeconds * 1000, config.limits.blockIntervalSeconds * 1000, config.limits.maxEmails) +var IpEmailRecord = require('../ip_email_record')(config.limits.rateLimitIntervalSeconds * 1000, config.limits.maxBadLogins) +var IpRecord = require('../ip_record')(config.limits.blockIntervalSeconds * 1000) function blockedEmailCheck(cb) { setTimeout( // give memcache time to flush the writes diff --git a/test/remote/block_email_tests.js b/test/remote/block_email_tests.js index bc6753a..9807d85 100644 --- a/test/remote/block_email_tests.js +++ b/test/remote/block_email_tests.js @@ -10,7 +10,9 @@ var TEST_EMAIL = 'test@example.com' var TEST_IP = '192.0.2.1' var config = { - port: 7000 + listen: { + port: 7000 + } } var testServer = new TestServer(config) @@ -38,7 +40,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); test( diff --git a/test/remote/block_ip_tests.js b/test/remote/block_ip_tests.js index 0062142..84c4fba 100644 --- a/test/remote/block_ip_tests.js +++ b/test/remote/block_ip_tests.js @@ -10,7 +10,9 @@ var TEST_EMAIL = 'test@example.com' var TEST_IP = '192.0.2.1' var config = { - port: 7000 + listen: { + port: 7000 + } } var testServer = new TestServer(config) @@ -38,7 +40,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); test( diff --git a/test/remote/check_tests.js b/test/remote/check_tests.js index c8ebf2e..1b10768 100644 --- a/test/remote/check_tests.js +++ b/test/remote/check_tests.js @@ -9,7 +9,9 @@ var TEST_EMAIL = 'test@example.com' var TEST_IP = '192.0.2.1' var config = { - port: 7000 + listen: { + port: 7000 + } } var testServer = new TestServer(config) @@ -25,7 +27,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); ['accountCreate', 'accountLogin', 'passwordChange'].forEach(function (action) { diff --git a/test/remote/failed_login_attempt_tests.js b/test/remote/failed_login_attempt_tests.js index 45c5cda..5da08b4 100644 --- a/test/remote/failed_login_attempt_tests.js +++ b/test/remote/failed_login_attempt_tests.js @@ -9,7 +9,9 @@ var TEST_EMAIL = 'test@example.com' var TEST_IP = '192.0.2.1' var config = { - port: 7000 + listen: { + port: 7000 + } } var testServer = new TestServer(config) @@ -25,7 +27,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); test( diff --git a/test/remote/never_blocked.js b/test/remote/never_blocked.js index 2a70784..b119d43 100644 --- a/test/remote/never_blocked.js +++ b/test/remote/never_blocked.js @@ -10,7 +10,9 @@ var TEST_EMAIL = 'test@example.com' var TEST_IP = '192.0.2.1' var config = { - port: 7000 + listen: { + port: 7000 + } } var testServer = new TestServer(config) @@ -39,7 +41,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); test( diff --git a/test/remote/password_reset_clears_bad_logins.js b/test/remote/password_reset_clears_bad_logins.js index 7e99bb0..11a512d 100644 --- a/test/remote/password_reset_clears_bad_logins.js +++ b/test/remote/password_reset_clears_bad_logins.js @@ -10,8 +10,9 @@ var TEST_EMAIL = 'test@example.com' var TEST_IP = '192.0.2.1' var config = { - port: 7000, - rateLimitIntervalSeconds: 1 + listen: { + port: 7000 + } } var testServer = new TestServer(config) @@ -40,7 +41,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); test( diff --git a/test/remote/password_reset_tests.js b/test/remote/password_reset_tests.js index 2aeb7e4..ba74663 100644 --- a/test/remote/password_reset_tests.js +++ b/test/remote/password_reset_tests.js @@ -8,7 +8,9 @@ var TestServer = require('../test_server') var TEST_EMAIL = 'test@example.com' var config = { - port: 7000 + listen: { + port: 7000 + } } var testServer = new TestServer(config) @@ -24,7 +26,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); test( diff --git a/test/remote/root_tests.js b/test/remote/root_tests.js index 296c1ee..f437f1c 100644 --- a/test/remote/root_tests.js +++ b/test/remote/root_tests.js @@ -7,7 +7,9 @@ var TestServer = require('../test_server') var packageJson = require('../../package.json') var config = { - port: 7000 + listen: { + port: 7000 + } } var testServer = new TestServer(config) @@ -23,7 +25,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); test( diff --git a/test/remote/too_many_bad_logins.js b/test/remote/too_many_bad_logins.js index 8b4f0ad..6842a32 100644 --- a/test/remote/too_many_bad_logins.js +++ b/test/remote/too_many_bad_logins.js @@ -10,8 +10,12 @@ var TEST_EMAIL = 'test@example.com' var TEST_IP = '192.0.2.1' var config = { - port: 7000, - rateLimitIntervalSeconds: 1 + listen: { + port: 7000 + }, + limits: { + rateLimitIntervalSeconds: 1 + } } var testServer = new TestServer(config) @@ -40,7 +44,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); test( @@ -85,7 +89,7 @@ test( } ) }, - config.rateLimitIntervalSeconds * 1000 + config.limits.rateLimitIntervalSeconds * 1000 ) } ) diff --git a/test/remote/too_many_emails.js b/test/remote/too_many_emails.js index 5d6c9b1..330c545 100644 --- a/test/remote/too_many_emails.js +++ b/test/remote/too_many_emails.js @@ -10,8 +10,12 @@ var TEST_EMAIL = 'test@example.com' var TEST_IP = '192.0.2.1' var config = { - port: 7000, - rateLimitIntervalSeconds: 1 + listen: { + port: 7000 + }, + limits: { + rateLimitIntervalSeconds: 1 + } } var testServer = new TestServer(config) @@ -40,7 +44,7 @@ test( ) var client = restify.createJsonClient({ - url: 'http://127.0.0.1:' + config.port + url: 'http://127.0.0.1:' + config.listen.port }); test( @@ -95,7 +99,7 @@ test( } ) }, - config.rateLimitIntervalSeconds * 1000 + config.limits.rateLimitIntervalSeconds * 1000 ) } ) diff --git a/test/test_server.js b/test/test_server.js index 811c0b8..3b84d40 100644 --- a/test/test_server.js +++ b/test/test_server.js @@ -6,7 +6,7 @@ var cp = require('child_process') var request = require('request') function TestServer(config) { - this.url = 'http://127.0.0.1:' + config.port + this.url = 'http://127.0.0.1:' + config.listen.port this.server = null }