Merge pull request #200 from mozilla/pushonempty-rejection
fix(settings): pushOnMissing no longer updates on unexpected errors
This commit is contained in:
Коммит
3f03e431f3
|
@ -1,68 +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 = function (config, mc, log) {
|
||||
|
||||
var pollInterval = null
|
||||
|
||||
function AllowedEmailDomains(domains) {
|
||||
this.setAll(domains)
|
||||
}
|
||||
|
||||
AllowedEmailDomains.prototype.isAllowed = function (email) {
|
||||
var match = /^.+@(.+)$/.exec(email)
|
||||
return match ? this.domains[match[1]] : false
|
||||
}
|
||||
|
||||
AllowedEmailDomains.prototype.setAll = function (domains) {
|
||||
this.domains = {}
|
||||
for (var i = 0; i < domains.length; i++) {
|
||||
this.domains[domains[i]] = true
|
||||
}
|
||||
return Object.keys(this.domains)
|
||||
}
|
||||
|
||||
AllowedEmailDomains.prototype.push = function () {
|
||||
log.info({ op: 'allowedEmailDomains.push' })
|
||||
return mc.setAsync('allowedEmailDomains', Object.keys(this.domains), 0)
|
||||
.then(this.refresh.bind(this))
|
||||
}
|
||||
|
||||
AllowedEmailDomains.prototype.refresh = function (options) {
|
||||
log.info({ op: 'allowedEmailDomains.refresh' })
|
||||
var result = mc.getAsync('allowedEmailDomains').then(validate)
|
||||
|
||||
if (options && options.pushOnMissing) {
|
||||
result = result.catch(this.push.bind(this))
|
||||
}
|
||||
|
||||
return result.then(
|
||||
this.setAll.bind(this),
|
||||
function (err) {
|
||||
log.error({ op: 'allowedEmailDomains.refresh', err: err })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AllowedEmailDomains.prototype.pollForUpdates = function () {
|
||||
this.stopPolling()
|
||||
pollInterval = setInterval(this.refresh.bind(this), config.updatePollIntervalSeconds * 1000)
|
||||
pollInterval.unref()
|
||||
}
|
||||
|
||||
AllowedEmailDomains.prototype.stopPolling = function () {
|
||||
clearInterval(pollInterval)
|
||||
}
|
||||
|
||||
function validate(domains) {
|
||||
if (!Array.isArray(domains)) {
|
||||
log.error({ op: 'allowedEmailDomains.validate.invalid', data: domains })
|
||||
throw new Error('invalid allowedEmailDomains from memcache')
|
||||
}
|
||||
return domains
|
||||
}
|
||||
|
||||
var allowedEmailDomains = new AllowedEmailDomains(config.allowedEmailDomains || [])
|
||||
return allowedEmailDomains
|
||||
}
|
|
@ -1,70 +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/. */
|
||||
var net = require('net')
|
||||
|
||||
module.exports = function (config, mc, log) {
|
||||
|
||||
var pollInterval = null
|
||||
|
||||
function AllowedIPs(ips) {
|
||||
this.setAll(ips)
|
||||
}
|
||||
|
||||
AllowedIPs.prototype.setAll = function (ips) {
|
||||
this.ips = {}
|
||||
for (var i = 0; i < ips.length; i++) {
|
||||
this.ips[ips[i]] = true
|
||||
}
|
||||
return Object.keys(this.ips)
|
||||
}
|
||||
|
||||
AllowedIPs.prototype.push = function () {
|
||||
log.info({ op: 'allowedIPs.push' })
|
||||
return mc.setAsync('allowedIPs', Object.keys(this.ips), 0)
|
||||
.then(this.refresh.bind(this))
|
||||
}
|
||||
|
||||
AllowedIPs.prototype.refresh = function (options) {
|
||||
log.info({ op: 'allowedIPs.refresh' })
|
||||
var result = mc.getAsync('allowedIPs').then(validate)
|
||||
|
||||
if (options && options.pushOnMissing) {
|
||||
result = result.catch(this.push.bind(this))
|
||||
}
|
||||
|
||||
return result.then(
|
||||
this.setAll.bind(this),
|
||||
function (err) {
|
||||
log.error({ op: 'allowedIPs.refresh', err: err })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AllowedIPs.prototype.pollForUpdates = function () {
|
||||
this.stopPolling()
|
||||
pollInterval = setInterval(this.refresh.bind(this), config.updatePollIntervalSeconds * 1000)
|
||||
pollInterval.unref()
|
||||
}
|
||||
|
||||
AllowedIPs.prototype.stopPolling = function () {
|
||||
clearInterval(pollInterval)
|
||||
}
|
||||
|
||||
function validate(ips) {
|
||||
if (!Array.isArray(ips)) {
|
||||
log.error({ op: 'allowedIPs.validate.invalid', data: ips })
|
||||
throw new Error('invalid allowedIPs from memcache')
|
||||
}
|
||||
return ips.filter(function (ip) {
|
||||
var is = net.isIPv4(ip)
|
||||
if (!is) {
|
||||
log.error({ op: 'allowedIPs.validate.err', ip: ip })
|
||||
}
|
||||
return is
|
||||
})
|
||||
}
|
||||
|
||||
var allowedIPs = new AllowedIPs(config.allowedIPs || [])
|
||||
return allowedIPs
|
||||
}
|
|
@ -1,99 +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/. */
|
||||
|
||||
var deepEqual = require('deep-equal')
|
||||
|
||||
module.exports = function (config, mc, log) {
|
||||
|
||||
var pollInterval = null
|
||||
|
||||
function Limits(settings) {
|
||||
this.setAll(settings)
|
||||
}
|
||||
|
||||
Limits.prototype.setAll = function (settings) {
|
||||
this.blockIntervalSeconds = settings.blockIntervalSeconds
|
||||
this.blockIntervalMs = settings.blockIntervalSeconds * 1000
|
||||
this.rateLimitIntervalSeconds = settings.rateLimitIntervalSeconds
|
||||
this.rateLimitIntervalMs = settings.rateLimitIntervalSeconds * 1000
|
||||
this.maxEmails = settings.maxEmails
|
||||
this.maxBadLogins = settings.maxBadLogins
|
||||
this.maxBadLoginsPerIp = settings.maxBadLoginsPerIp
|
||||
this.maxUnblockAttempts = settings.maxUnblockAttempts
|
||||
this.maxVerifyCodes = settings.maxVerifyCodes
|
||||
this.ipRateLimitIntervalSeconds = settings.ipRateLimitIntervalSeconds
|
||||
this.ipRateLimitIntervalMs = settings.ipRateLimitIntervalSeconds * 1000
|
||||
this.ipRateLimitBanDurationSeconds = settings.ipRateLimitBanDurationSeconds
|
||||
this.ipRateLimitBanDurationMs = settings.ipRateLimitBanDurationSeconds * 1000
|
||||
this.maxAccountStatusCheck = settings.maxAccountStatusCheck
|
||||
this.badLoginErrnoWeights = settings.badLoginErrnoWeights || {}
|
||||
this.uidRateLimit = settings.uidRateLimit || {}
|
||||
this.maxChecksPerUid = this.uidRateLimit.maxChecks
|
||||
this.uidRateLimitBanDurationMs = this.uidRateLimit.banDurationSeconds * 1000
|
||||
this.uidRateLimitIntervalMs = this.uidRateLimit.limitIntervalSeconds * 1000
|
||||
this.smsRateLimit = settings.smsRateLimit || {}
|
||||
this.maxSms = settings.smsRateLimit.maxSms
|
||||
this.smsRateLimitIntervalSeconds = this.smsRateLimit.limitIntervalSeconds
|
||||
this.smsRateLimitIntervalMs = this.smsRateLimitIntervalSeconds * 1000
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Limits.prototype.push = function () {
|
||||
log.info({ op: 'limits.push' })
|
||||
return mc.setAsync('limits', this, 0)
|
||||
.then(this.refresh.bind(this))
|
||||
}
|
||||
|
||||
Limits.prototype.refresh = function (options) {
|
||||
log.info({ op: 'limits.refresh' })
|
||||
var result = mc.getAsync('limits').then(validate)
|
||||
|
||||
if (options && options.pushOnMissing) {
|
||||
result = result.catch(this.push.bind(this))
|
||||
}
|
||||
|
||||
return result.then(
|
||||
this.setAll.bind(this),
|
||||
function (err) {
|
||||
log.error({ op: 'limits.refresh', err: err })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Limits.prototype.pollForUpdates = function () {
|
||||
this.stopPolling()
|
||||
pollInterval = setInterval(this.refresh.bind(this), config.updatePollIntervalSeconds * 1000)
|
||||
pollInterval.unref()
|
||||
}
|
||||
|
||||
Limits.prototype.stopPolling = function () {
|
||||
clearInterval(pollInterval)
|
||||
}
|
||||
|
||||
var limits = new Limits(config.limits)
|
||||
|
||||
function validate(settings) {
|
||||
if (typeof(settings) !== 'object') {
|
||||
log.error({ op: 'limits.validate.invalid', data: settings })
|
||||
throw new Error('invalid limits from memcache')
|
||||
}
|
||||
var keys = Object.keys(config.limits)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
var current = limits[key]
|
||||
var future = settings[key]
|
||||
if (typeof(current) !== typeof(future)) {
|
||||
log.error({ op: 'limits.validate.err', key: key, message: 'types do not match'})
|
||||
settings[key] = current
|
||||
}
|
||||
else if (!deepEqual(current, future)) {
|
||||
log.info({ op: 'limits.validate.changed', key: key, current: current, future: future })
|
||||
}
|
||||
}
|
||||
return settings
|
||||
}
|
||||
|
||||
return limits
|
||||
}
|
|
@ -1,82 +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/. */
|
||||
|
||||
var deepEqual = require('deep-equal')
|
||||
|
||||
module.exports = function (config, mc, log) {
|
||||
|
||||
var requestChecks = null
|
||||
var pollInterval = null
|
||||
|
||||
function RequestChecks(settings) {
|
||||
this.setAll(settings)
|
||||
}
|
||||
|
||||
RequestChecks.prototype.setAll = function (settings) {
|
||||
this.treatEveryoneWithSuspicion = settings.treatEveryoneWithSuspicion
|
||||
// The private branch puts some additional private config here.
|
||||
return this
|
||||
}
|
||||
|
||||
RequestChecks.prototype.push = function () {
|
||||
log.info({ op: 'requestChecks.push' })
|
||||
return mc.setAsync('requestChecks', this, 0)
|
||||
.then(this.refresh.bind(this))
|
||||
}
|
||||
|
||||
RequestChecks.prototype.refresh = function (options) {
|
||||
log.info({ op: 'requestChecks.refresh' })
|
||||
var result = mc.getAsync('requestChecks').then(validateAndMerge)
|
||||
|
||||
if (options && options.pushOnMissing) {
|
||||
result = result.catch(this.push.bind(this))
|
||||
}
|
||||
|
||||
return result.then(
|
||||
this.setAll.bind(this),
|
||||
function (err) {
|
||||
log.error({ op: 'requestChecks.refresh', err: err })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
RequestChecks.prototype.pollForUpdates = function () {
|
||||
this.stopPolling()
|
||||
pollInterval = setInterval(this.refresh.bind(this), config.updatePollIntervalSeconds * 1000)
|
||||
pollInterval.unref()
|
||||
}
|
||||
|
||||
RequestChecks.prototype.stopPolling = function () {
|
||||
clearInterval(pollInterval)
|
||||
}
|
||||
|
||||
// Type-checks updates to the settings, and merges them
|
||||
// with the current values, modying its argument in-place.
|
||||
function validateAndMerge(settings) {
|
||||
if (typeof(settings) !== 'object') {
|
||||
log.error({ op: 'requestChecks.validate.invalid', data: settings })
|
||||
throw new Error('invalid requestChecks from memcache')
|
||||
}
|
||||
var keys = Object.keys(config.requestChecks)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
var current = requestChecks[key]
|
||||
var future = settings[key]
|
||||
if (typeof(future) === 'undefined') {
|
||||
settings[key] = current
|
||||
}
|
||||
else if (typeof(current) !== typeof(future)) {
|
||||
log.error({ op: 'requestChecks.validate.err', key: key, message: 'types do not match' })
|
||||
settings[key] = current
|
||||
}
|
||||
else if (!deepEqual(current, future)) {
|
||||
log.info({ op: 'requestChecks.validate.changed', key: key, current: current, future: future })
|
||||
}
|
||||
}
|
||||
return settings
|
||||
}
|
||||
|
||||
requestChecks = new RequestChecks(config.requestChecks)
|
||||
return requestChecks
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
var Memcached = require('memcached')
|
||||
var restify = require('restify')
|
||||
var safeJsonFormatter = require('restify-safe-json-formatter')
|
||||
|
@ -40,20 +42,22 @@ module.exports = function createServer(config, log) {
|
|||
)
|
||||
|
||||
var reputationService = require('./reputationService')(config, log)
|
||||
var limits = require('./limits')(config, mc, log)
|
||||
var allowedIPs = require('./allowed_ips')(config, mc, log)
|
||||
var allowedEmailDomains = require('./allowed_email_domains')(config, mc, log)
|
||||
var requestChecks = require('./requestChecks')(config, mc, log)
|
||||
const Settings = require('./settings/settings')(config, mc, log)
|
||||
var limits = require('./settings/limits')(config, Settings, log)
|
||||
var allowedIPs = require('./settings/allowed_ips')(config, Settings, log)
|
||||
var allowedEmailDomains = require('./settings/allowed_email_domains')(config, Settings, log)
|
||||
var requestChecks = require('./settings/requestChecks')(config, Settings, log)
|
||||
|
||||
if (config.updatePollIntervalSeconds) {
|
||||
limits.refresh({ pushOnMissing: true })
|
||||
limits.pollForUpdates()
|
||||
allowedIPs.refresh({ pushOnMissing: true })
|
||||
allowedIPs.pollForUpdates()
|
||||
allowedEmailDomains.refresh({ pushOnMissing: true })
|
||||
allowedEmailDomains.pollForUpdates()
|
||||
requestChecks.refresh({ pushOnMissing: true })
|
||||
requestChecks.pollForUpdates()
|
||||
[
|
||||
allowedEmailDomains,
|
||||
allowedIPs,
|
||||
limits,
|
||||
requestChecks
|
||||
].forEach(settings => {
|
||||
settings.refresh({ pushOnMissing: true }).catch(() => {})
|
||||
settings.pollForUpdates()
|
||||
})
|
||||
}
|
||||
|
||||
var IpEmailRecord = require('./ip_email_record')(limits)
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
module.exports = (config, Settings, log) => {
|
||||
|
||||
class AllowedEmailDomains extends Settings {
|
||||
constructor(domains) {
|
||||
super('allowedEmailDomains')
|
||||
this.setAll(domains)
|
||||
}
|
||||
|
||||
isAllowed(email) {
|
||||
var match = /^.+@(.+)$/.exec(email)
|
||||
return match ? this.domains[match[1]] : false
|
||||
}
|
||||
|
||||
setAll(domains) {
|
||||
this.domains = {}
|
||||
for (var i = 0; i < domains.length; i++) {
|
||||
this.domains[domains[i]] = true
|
||||
}
|
||||
return Object.keys(this.domains)
|
||||
}
|
||||
|
||||
validate(domains) {
|
||||
if (!Array.isArray(domains)) {
|
||||
log.error({ op: 'allowedEmailDomains.validate.invalid', data: domains })
|
||||
throw new Settings.Missing('invalid allowedEmailDomains from memcache')
|
||||
}
|
||||
return domains
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Object.keys(this.domains)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new AllowedEmailDomains(config.allowedEmailDomains || [])
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
const net = require('net')
|
||||
|
||||
module.exports = (config, Settings, log) => {
|
||||
|
||||
class AllowedIPs extends Settings {
|
||||
constructor(ips) {
|
||||
super('allowedIPs')
|
||||
this.setAll(ips)
|
||||
}
|
||||
|
||||
setAll(ips) {
|
||||
this.ips = {}
|
||||
for (var i = 0; i < ips.length; i++) {
|
||||
this.ips[ips[i]] = true
|
||||
}
|
||||
return Object.keys(this.ips)
|
||||
}
|
||||
|
||||
validate(ips) {
|
||||
if (!Array.isArray(ips)) {
|
||||
log.error({ op: 'allowedIPs.validate.invalid', data: ips })
|
||||
throw new Settings.Missing('invalid allowedIPs from memcache')
|
||||
}
|
||||
return ips.filter(function (ip) {
|
||||
var is = net.isIPv4(ip)
|
||||
if (!is) {
|
||||
log.error({ op: 'allowedIPs.validate.err', ip: ip })
|
||||
}
|
||||
return is
|
||||
})
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Object.keys(this.ips)
|
||||
}
|
||||
}
|
||||
|
||||
return new AllowedIPs(config.allowedIPs || [])
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
var deepEqual = require('deep-equal')
|
||||
|
||||
module.exports = (config, Settings, log) => {
|
||||
|
||||
class Limits extends Settings {
|
||||
constructor(settings) {
|
||||
super('limits')
|
||||
this.setAll(settings)
|
||||
}
|
||||
|
||||
setAll(settings) {
|
||||
this.blockIntervalSeconds = settings.blockIntervalSeconds
|
||||
this.blockIntervalMs = settings.blockIntervalSeconds * 1000
|
||||
this.rateLimitIntervalSeconds = settings.rateLimitIntervalSeconds
|
||||
this.rateLimitIntervalMs = settings.rateLimitIntervalSeconds * 1000
|
||||
this.maxEmails = settings.maxEmails
|
||||
this.maxBadLogins = settings.maxBadLogins
|
||||
this.maxBadLoginsPerIp = settings.maxBadLoginsPerIp
|
||||
this.maxUnblockAttempts = settings.maxUnblockAttempts
|
||||
this.maxVerifyCodes = settings.maxVerifyCodes
|
||||
this.ipRateLimitIntervalSeconds = settings.ipRateLimitIntervalSeconds
|
||||
this.ipRateLimitIntervalMs = settings.ipRateLimitIntervalSeconds * 1000
|
||||
this.ipRateLimitBanDurationSeconds = settings.ipRateLimitBanDurationSeconds
|
||||
this.ipRateLimitBanDurationMs = settings.ipRateLimitBanDurationSeconds * 1000
|
||||
this.maxAccountStatusCheck = settings.maxAccountStatusCheck
|
||||
this.badLoginErrnoWeights = settings.badLoginErrnoWeights || {}
|
||||
this.uidRateLimit = settings.uidRateLimit || {}
|
||||
this.maxChecksPerUid = this.uidRateLimit.maxChecks
|
||||
this.uidRateLimitBanDurationMs = this.uidRateLimit.banDurationSeconds * 1000
|
||||
this.uidRateLimitIntervalMs = this.uidRateLimit.limitIntervalSeconds * 1000
|
||||
this.smsRateLimit = settings.smsRateLimit || {}
|
||||
this.maxSms = settings.smsRateLimit.maxSms
|
||||
this.smsRateLimitIntervalSeconds = this.smsRateLimit.limitIntervalSeconds
|
||||
this.smsRateLimitIntervalMs = this.smsRateLimitIntervalSeconds * 1000
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
validate(settings) {
|
||||
if (typeof settings !== 'object') {
|
||||
log.error({ op: 'limits.validate.invalid', data: settings })
|
||||
throw new Settings.Missing('invalid limits from memcache')
|
||||
}
|
||||
var keys = Object.keys(config.limits)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
var current = this[key]
|
||||
var future = settings[key]
|
||||
if (typeof(current) !== typeof(future)) {
|
||||
log.error({ op: 'limits.validate.err', key: key, message: 'types do not match'})
|
||||
settings[key] = current
|
||||
}
|
||||
else if (!deepEqual(current, future)) {
|
||||
log.info({ op: 'limits.validate.changed', key: key, current: current, future: future })
|
||||
}
|
||||
}
|
||||
return settings
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return new Limits(config.limits)
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
const deepEqual = require('deep-equal')
|
||||
|
||||
module.exports = (config, Settings, log) => {
|
||||
|
||||
class RequestChecks extends Settings {
|
||||
constructor(settings) {
|
||||
super('requestChecks')
|
||||
this.setAll(settings)
|
||||
}
|
||||
|
||||
setAll(settings) {
|
||||
this.treatEveryoneWithSuspicion = settings.treatEveryoneWithSuspicion
|
||||
// The private branch puts some additional private config here.
|
||||
return this
|
||||
}
|
||||
|
||||
// Type-checks updates to the settings, and merges them
|
||||
// with the current values, modying its argument in-place.
|
||||
validate(settings) {
|
||||
if (typeof settings !== 'object') {
|
||||
log.error({ op: 'requestChecks.validate.invalid', data: settings })
|
||||
throw new Settings.Missing('invalid requestChecks from memcache')
|
||||
}
|
||||
const keys = Object.keys(config.requestChecks)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
const current = this[key]
|
||||
const future = settings[key]
|
||||
if (typeof future === 'undefined') {
|
||||
settings[key] = current
|
||||
}
|
||||
else if (typeof current !== typeof future) {
|
||||
log.error({ op: 'requestChecks.validate.err', key: key, message: 'types do not match' })
|
||||
settings[key] = current
|
||||
}
|
||||
else if (!deepEqual(current, future)) {
|
||||
log.info({ op: 'requestChecks.validate.changed', key: key, current: current, future: future })
|
||||
}
|
||||
}
|
||||
return settings
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new RequestChecks(config.requestChecks)
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
const assert = require('assert')
|
||||
|
||||
const KEY = Symbol()
|
||||
const POLL_INTERVAL = Symbol()
|
||||
|
||||
module.exports = (config, mc, log) => {
|
||||
|
||||
// A sentinel error for signaling that the result was invalid/missing,
|
||||
// and we should try to push our own representation to memcached.
|
||||
class Missing extends Error {}
|
||||
|
||||
// An abstract class that stores options in memcached.
|
||||
//
|
||||
// Others extend this class to hotload options from memcached.
|
||||
class Settings {
|
||||
|
||||
constructor(key) {
|
||||
assert(typeof key === 'string')
|
||||
this[KEY] = key
|
||||
}
|
||||
|
||||
// subclasses should provide their own implementation
|
||||
setAll(value) {
|
||||
return this
|
||||
}
|
||||
|
||||
// subclasses should provide their own implementation
|
||||
validate(value) {
|
||||
if (value == null) {
|
||||
throw new Missing('value was undefined or null')
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Pushes `this` as JSON to `key`
|
||||
//
|
||||
// Customize what is pushed with a toJSON method.
|
||||
push() {
|
||||
log.info({ op: this[KEY] + '.push' })
|
||||
return mc.setAsync(this[KEY], this, 0)
|
||||
.then(() => this.refresh())
|
||||
}
|
||||
|
||||
refresh(options) {
|
||||
log.info({ op: this[KEY] + '.refresh' })
|
||||
let result = mc.getAsync(this[KEY]).then(value => this.validate(value))
|
||||
|
||||
if (options && options.pushOnMissing) {
|
||||
result = result.catch(err => {
|
||||
if (err instanceof Missing) {
|
||||
log.info({ op: this[KEY] + '.refresh.pushOnMissing' })
|
||||
return this.push()
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
return result.then(
|
||||
value => this.setAll(value),
|
||||
err => {
|
||||
log.error({ op: this[KEY] + '.refresh', err: err })
|
||||
throw err
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pollForUpdates() {
|
||||
this.stopPolling()
|
||||
this[POLL_INTERVAL] = setInterval(() => {
|
||||
this.refresh()
|
||||
// refresh error should just log, but nothing else
|
||||
.catch(() => {})
|
||||
}, config.updatePollIntervalSeconds * 1000)
|
||||
this[POLL_INTERVAL].unref()
|
||||
}
|
||||
|
||||
stopPolling() {
|
||||
clearInterval(this[POLL_INTERVAL])
|
||||
}
|
||||
}
|
||||
|
||||
Settings.Missing = Missing
|
||||
|
||||
return Settings
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
'use strict'
|
||||
|
||||
const P = require('bluebird')
|
||||
const test = require('tap').test
|
||||
|
||||
const config = {}
|
||||
const mc = {}
|
||||
const log = {
|
||||
info() {},
|
||||
error() {}
|
||||
}
|
||||
const Settings = require('../../lib/settings/settings')(config, mc, log)
|
||||
|
||||
class TestSettings extends Settings {
|
||||
constructor() {
|
||||
super('tests')
|
||||
}
|
||||
|
||||
setAll(settings) {
|
||||
this.testOption = !!settings.testOption
|
||||
return this
|
||||
}
|
||||
|
||||
validate(other) {
|
||||
if (!other) {
|
||||
throw new Settings.Missing()
|
||||
}
|
||||
return other
|
||||
}
|
||||
}
|
||||
|
||||
test(
|
||||
'refresh without pushOnMissing does not call push',
|
||||
t => {
|
||||
let pushed
|
||||
mc.getAsync = () => P.resolve(pushed)
|
||||
mc.setAsync = (key, val) => {
|
||||
pushed = val
|
||||
return P.resolve(val)
|
||||
}
|
||||
const settings = new TestSettings()
|
||||
settings.setAll({ testOption: true })
|
||||
return settings.refresh()
|
||||
.then(
|
||||
t.fail,
|
||||
err => {
|
||||
t.equal(pushed, undefined)
|
||||
t.ok(err instanceof Settings.Missing)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'refresh pushOnMissing works on Missing error',
|
||||
t => {
|
||||
let pushed
|
||||
mc.getAsync = () => P.resolve(pushed)
|
||||
mc.setAsync = (key, val) => {
|
||||
pushed = val
|
||||
return P.resolve(val)
|
||||
}
|
||||
const settings = new TestSettings()
|
||||
settings.setAll({ testOption: true })
|
||||
return settings.refresh({ pushOnMissing: true })
|
||||
.then(() => {
|
||||
t.deepEqual(pushed, { testOption: true })
|
||||
}, t.fail)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'refresh pushOnMissing returns other Errors',
|
||||
t => {
|
||||
const mcError = new Error('memcached error')
|
||||
mc.getAsync = () => P.reject(mcError)
|
||||
mc.setAsync = (key, val) => {
|
||||
return P.reject(new Error('setAsync should not have been called'))
|
||||
}
|
||||
const settings = new TestSettings()
|
||||
settings.setAll({ testOption: true })
|
||||
return settings.refresh({ pushOnMissing: true })
|
||||
.then(
|
||||
t.fail,
|
||||
err => {
|
||||
t.equal(err, mcError)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
|
@ -2,6 +2,8 @@
|
|||
* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
var P = require('bluebird')
|
||||
var Memcached = require('memcached')
|
||||
P.promisifyAll(Memcached.prototype)
|
||||
|
@ -52,10 +54,11 @@ module.exports.mc = mc
|
|||
var TEST_EMAIL = 'test@example.com'
|
||||
var TEST_IP = '192.0.2.1'
|
||||
|
||||
var limits = require('../lib/limits')(config, mc, console)
|
||||
var allowedIPs = require('../lib/allowed_ips')(config, mc, console)
|
||||
var allowedEmailDomains = require('../lib/allowed_email_domains')(config, mc, console)
|
||||
var requestChecks = require('../lib/requestChecks')(config, mc, console)
|
||||
const Settings = require('../lib/settings/settings')(config, mc, console)
|
||||
var limits = require('../lib/settings/limits')(config, Settings, console)
|
||||
var allowedIPs = require('../lib/settings/allowed_ips')(config, Settings, console)
|
||||
var allowedEmailDomains = require('../lib/settings/allowed_email_domains')(config, Settings, console)
|
||||
var requestChecks = require('../lib/settings/requestChecks')(config, Settings, console)
|
||||
var EmailRecord = require('../lib/email_record')(limits)
|
||||
var IpEmailRecord = require('../lib/ip_email_record')(limits)
|
||||
var IpRecord = require('../lib/ip_record')(limits)
|
||||
|
|
Загрузка…
Ссылка в новой задаче