diff --git a/customs.js b/customs.js index cb1869e3..bbe09544 100644 --- a/customs.js +++ b/customs.js @@ -3,28 +3,28 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ var P = require('./promise') - var request = require('./requestp') + var Pool = require('./pool') module.exports = function (log, error) { function Customs(url) { - this.url = url + if (url === 'none') { + this.pool = { post: function () { return P({ block: false })}} + } + else { + this.pool = new Pool(url, { timeout: 1000 }) + } } Customs.prototype.check = function (ip, agent, email, action) { log.trace({ op: 'customs.check', email: email, action: action }) - if (this.url === 'none') { return P() } - return request( + return this.pool.post( + '/check', { - method: 'POST', - url: this.url + '/check', - json: { - ip: ip, - email: email, - action: action, - agent: agent - }, - timeout: 1000 + ip: ip, + email: email, + action: action, + agent: agent } ) .then( @@ -42,16 +42,11 @@ Customs.prototype.flag = function (ip, email) { log.trace({ op: 'customs.flag', ip: ip, email: email }) - if (this.url === 'none') { return P() } - return request( + return this.pool.post( + '/failedLoginAttempt', { - method: 'POST', - url: this.url + '/failedLoginAttempt', - json: { - ip: ip, - email: email - }, - timeout: 1000 + ip: ip, + email: email } ) .then( diff --git a/db/httpdb.js b/db/httpdb.js index 0e01a1dd..89d2de64 100644 --- a/db/httpdb.js +++ b/db/httpdb.js @@ -2,7 +2,7 @@ * 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 request = require('../requestp') +var Pool = require('../pool') var butil = require('../crypto/butil') var unbuffer = butil.unbuffer @@ -19,19 +19,13 @@ module.exports = function ( PasswordChangeToken) { function DB(options) { - this.url = options.url + this.pool = new Pool(options.url) } DB.connect = function (options) { var db = new DB(options) - return request( - { - method: 'GET', - url: db.url, - json: true - } - ) + return db.pool.get('/') .then( function (api) { // TODO: transition to api version @@ -48,11 +42,11 @@ module.exports = function ( } DB.prototype.close = function () { - return P() + return P(this.pool.close()) } DB.prototype.ping = function () { - return request(this.url + '/__heartbeat__') + return this.pool.get('/__heartbeat__') } // CREATE @@ -67,12 +61,9 @@ module.exports = function ( ) data.createdAt = data.verifierSetAt = Date.now() data.normalizedEmail = data.email.toLowerCase() - return request( - { - method: 'PUT', - url: this.url + '/account/' + data.uid.toString('hex'), - json: unbuffer(data) - } + return this.pool.put( + '/account/' + data.uid.toString('hex'), + unbuffer(data) ) .then( function () { @@ -92,20 +83,17 @@ module.exports = function ( return SessionToken.create(authToken) .then( function (sessionToken) { - return request( - { - method: 'PUT', - url: this.url + '/sessionToken/' + sessionToken.id, - json: unbuffer( - { - tokenId: sessionToken.tokenId, - data: sessionToken.data, - uid: sessionToken.uid, - createdAt: sessionToken.createdAt - }, - 'inplace' - ) - } + return this.pool.put( + '/sessionToken/' + sessionToken.id, + unbuffer( + { + tokenId: sessionToken.tokenId, + data: sessionToken.data, + uid: sessionToken.uid, + createdAt: sessionToken.createdAt + }, + 'inplace' + ) ) .then( function () { @@ -121,21 +109,18 @@ module.exports = function ( return KeyFetchToken.create(authToken) .then( function (keyFetchToken) { - return request( - { - method: 'PUT', - url: this.url + '/keyFetchToken/' + keyFetchToken.id, - json: unbuffer( - { - tokenId: keyFetchToken.tokenId, - authKey: keyFetchToken.authKey, - uid: keyFetchToken.uid, - keyBundle: keyFetchToken.keyBundle, - createdAt: keyFetchToken.createdAt - }, - 'inplace' - ) - } + return this.pool.put( + '/keyFetchToken/' + keyFetchToken.id, + unbuffer( + { + tokenId: keyFetchToken.tokenId, + authKey: keyFetchToken.authKey, + uid: keyFetchToken.uid, + keyBundle: keyFetchToken.keyBundle, + createdAt: keyFetchToken.createdAt + }, + 'inplace' + ) ) .then( function () { @@ -151,20 +136,17 @@ module.exports = function ( return AccountResetToken.create(token) .then( function (accountResetToken) { - return request( - { - method: 'PUT', - url: this.url + '/accountResetToken/' + accountResetToken.id, - json: unbuffer( - { - tokenId: accountResetToken.tokenId, - data: accountResetToken.data, - uid: accountResetToken.uid, - createdAt: accountResetToken.createdAt - }, - 'inplace' - ) - } + return this.pool.put( + '/accountResetToken/' + accountResetToken.id, + unbuffer( + { + tokenId: accountResetToken.tokenId, + data: accountResetToken.data, + uid: accountResetToken.uid, + createdAt: accountResetToken.createdAt + }, + 'inplace' + ) ) .then( function () { @@ -180,22 +162,19 @@ module.exports = function ( return PasswordForgotToken.create(emailRecord) .then( function (passwordForgotToken) { - return request( - { - method: 'PUT', - url: this.url + '/passwordForgotToken/' + passwordForgotToken.id, - json: unbuffer( - { - tokenId: passwordForgotToken.tokenId, - data: passwordForgotToken.data, - uid: passwordForgotToken.uid, - passCode: passwordForgotToken.passCode, - createdAt: passwordForgotToken.createdAt, - tries: passwordForgotToken.tries - }, - 'inplace' - ) - } + return this.pool.put( + '/passwordForgotToken/' + passwordForgotToken.id, + unbuffer( + { + tokenId: passwordForgotToken.tokenId, + data: passwordForgotToken.data, + uid: passwordForgotToken.uid, + passCode: passwordForgotToken.passCode, + createdAt: passwordForgotToken.createdAt, + tries: passwordForgotToken.tries + }, + 'inplace' + ) ) .then( function () { @@ -211,20 +190,17 @@ module.exports = function ( return PasswordChangeToken.create(data) .then( function (passwordChangeToken) { - return request( - { - method: 'PUT', - url: this.url + '/passwordChangeToken/' + passwordChangeToken.id, - json: unbuffer( - { - tokenId: passwordChangeToken.tokenId, - data: passwordChangeToken.data, - uid: passwordChangeToken.uid, - createdAt: passwordChangeToken.createdAt - }, - 'inplace' - ) - } + return this.pool.put( + '/passwordChangeToken/' + passwordChangeToken.id, + unbuffer( + { + tokenId: passwordChangeToken.tokenId, + data: passwordChangeToken.data, + uid: passwordChangeToken.uid, + createdAt: passwordChangeToken.createdAt + }, + 'inplace' + ) ) .then( function () { @@ -239,211 +215,154 @@ module.exports = function ( DB.prototype.accountExists = function (email) { log.trace({ op: 'DB.accountExists', email: email }) - return request( - { - method: 'HEAD', - url: this.url + '/emailRecord/' + Buffer(email, 'utf8').toString('hex'), - json: true - } - ) - .then( - function () { - return true - }, - function (err) { - if (err.statusCode === 404) { - return false + return this.pool.head('/emailRecord/' + Buffer(email, 'utf8').toString('hex')) + .then( + function () { + return true + }, + function (err) { + if (err.statusCode === 404) { + return false + } + throw err } - throw err - } - ) + ) } DB.prototype.accountDevices = function (uid) { log.trace({ op: 'DB.accountDevices', uid: uid }) - return request( - { - method: 'GET', - url: this.url + '/account/' + uid.toString('hex') + '/devices', - json: true - } - ) + return this.pool.get('/account/' + uid.toString('hex') + '/devices') } DB.prototype.sessionToken = function (id) { log.trace({ op: 'DB.sessionToken', id: id }) - return request( - { - method: 'GET', - url: this.url + '/sessionToken/' + id.toString('hex'), - json: true - } - ) - .then( - function (body) { - var data = bufferize(body) - return SessionToken.fromHex(data.tokenData, data) - }, - function (err) { - if (err.statusCode === 404) { - err = error.invalidToken() + return this.pool.get('/sessionToken/' + id.toString('hex')) + .then( + function (body) { + var data = bufferize(body) + return SessionToken.fromHex(data.tokenData, data) + }, + function (err) { + if (err.statusCode === 404) { + err = error.invalidToken() + } + throw err } - throw err - } - ) + ) } DB.prototype.keyFetchToken = function (id) { log.trace({ op: 'DB.keyFetchToken', id: id }) - return request( - { - method: 'GET', - url: this.url + '/keyFetchToken/' + id.toString('hex'), - json: true - } - ) - .then( - function (body) { - var data = bufferize(body) - return KeyFetchToken.fromId(id, data) - }, - function (err) { - if (err.statusCode === 404) { - err = error.invalidToken() + return this.pool.get('/keyFetchToken/' + id.toString('hex')) + .then( + function (body) { + var data = bufferize(body) + return KeyFetchToken.fromId(id, data) + }, + function (err) { + if (err.statusCode === 404) { + err = error.invalidToken() + } + throw err } - throw err - } - ) + ) } DB.prototype.accountResetToken = function (id) { log.trace({ op: 'DB.accountResetToken', id: id }) - return request( - { - method: 'GET', - url: this.url + '/accountResetToken/' + id.toString('hex'), - json: true - } - ) - .then( - function (body) { - var data = bufferize(body) - return AccountResetToken.fromHex(data.tokenData, data) - }, - function (err) { - if (err.statusCode === 404) { - err = error.invalidToken() + return this.pool.get('/accountResetToken/' + id.toString('hex')) + .then( + function (body) { + var data = bufferize(body) + return AccountResetToken.fromHex(data.tokenData, data) + }, + function (err) { + if (err.statusCode === 404) { + err = error.invalidToken() + } + throw err } - throw err - } - ) + ) } DB.prototype.passwordForgotToken = function (id) { log.trace({ op: 'DB.passwordForgotToken', id: id }) - return request( - { - method: 'GET', - url: this.url + '/passwordForgotToken/' + id.toString('hex'), - json: true - } - ) - .then( - function (body) { - var data = bufferize(body) - return PasswordForgotToken.fromHex(data.tokenData, data) - }, - function (err) { - if (err.statusCode === 404) { - err = error.invalidToken() + return this.pool.get('/passwordForgotToken/' + id.toString('hex')) + .then( + function (body) { + var data = bufferize(body) + return PasswordForgotToken.fromHex(data.tokenData, data) + }, + function (err) { + if (err.statusCode === 404) { + err = error.invalidToken() + } + throw err } - throw err - } - ) + ) } DB.prototype.passwordChangeToken = function (id) { log.trace({ op: 'DB.passwordChangeToken', id: id }) - return request( - { - method: 'GET', - url: this.url + '/passwordChangeToken/' + id.toString('hex'), - json: true - } - ) - .then( - function (body) { - var data = bufferize(body) - return PasswordChangeToken.fromHex(data.tokenData, data) - }, - function (err) { - if (err.statusCode === 404) { - err = error.invalidToken() + return this.pool.get('/passwordChangeToken/' + id.toString('hex')) + .then( + function (body) { + var data = bufferize(body) + return PasswordChangeToken.fromHex(data.tokenData, data) + }, + function (err) { + if (err.statusCode === 404) { + err = error.invalidToken() + } + throw err } - throw err - } - ) + ) } DB.prototype.emailRecord = function (email) { log.trace({ op: 'DB.emailRecord', email: email }) - return request( - { - method: 'GET', - url: this.url + '/emailRecord/' + Buffer(email, 'utf8').toString('hex'), - json: true - } - ) - .then( - function (body) { - var data = bufferize(body) - data.emailVerified = !!data.emailVerified - return data - }, - function (err) { - if (err.statusCode === 404) { - err = error.unknownAccount(email) + return this.pool.get('/emailRecord/' + Buffer(email, 'utf8').toString('hex')) + .then( + function (body) { + var data = bufferize(body) + data.emailVerified = !!data.emailVerified + return data + }, + function (err) { + if (err.statusCode === 404) { + err = error.unknownAccount(email) + } + throw err } - throw err - } - ) + ) } DB.prototype.account = function (uid) { log.trace({ op: 'DB.account', uid: uid }) - return request( - { - method: 'GET', - url: this.url + '/account/' + uid.toString('hex'), - json: true - } - ) - .then( - function (body) { - var data = bufferize(body) - data.emailVerified = !!data.emailVerified - return data - }, - function (err) { - if (err.statusCode === 404) { - err = error.unknownAccount() + return this.pool.get('/account/' + uid.toString('hex')) + .then( + function (body) { + var data = bufferize(body) + data.emailVerified = !!data.emailVerified + return data + }, + function (err) { + if (err.statusCode === 404) { + err = error.unknownAccount() + } + throw err } - throw err - } - ) + ) } // UPDATE DB.prototype.updatePasswordForgotToken = function (token) { log.trace({ op: 'DB.udatePasswordForgotToken', uid: token && token.uid }) - return request( + return this.pool.post( + '/passwordForgotToken/' + token.id + '/update', { - method: 'POST', - url: this.url + '/passwordForgotToken/' + token.id + '/update', - json: { - tries: token.tries - } + tries: token.tries } ) } @@ -452,13 +371,7 @@ module.exports = function ( DB.prototype.deleteAccount = function (authToken) { log.trace({ op: 'DB.deleteAccount', uid: authToken && authToken.uid }) - return request( - { - method: 'DELETE', - url: this.url + '/account/' + authToken.uid.toString('hex'), - json: true - } - ) + return this.pool.del('/account/' + authToken.uid.toString('hex')) } DB.prototype.deleteSessionToken = function (sessionToken) { @@ -469,13 +382,7 @@ module.exports = function ( uid: sessionToken && sessionToken.uid } ) - return request( - { - method: 'DELETE', - url: this.url + '/sessionToken/' + sessionToken.id, - json: true - } - ) + return this.pool.del('/sessionToken/' + sessionToken.id) } DB.prototype.deleteKeyFetchToken = function (keyFetchToken) { @@ -486,13 +393,7 @@ module.exports = function ( uid: keyFetchToken && keyFetchToken.uid } ) - return request( - { - method: 'DELETE', - url: this.url + '/keyFetchToken/' + keyFetchToken.id, - json: true - } - ) + return this.pool.del('/keyFetchToken/' + keyFetchToken.id) } DB.prototype.deleteAccountResetToken = function (accountResetToken) { @@ -503,13 +404,7 @@ module.exports = function ( uid: accountResetToken && accountResetToken.uid } ) - return request( - { - method: 'DELETE', - url: this.url + '/accountResetToken/' + accountResetToken.id, - json: true - } - ) + return this.pool.del('/accountResetToken/' + accountResetToken.id) } DB.prototype.deletePasswordForgotToken = function (passwordForgotToken) { @@ -520,13 +415,7 @@ module.exports = function ( uid: passwordForgotToken && passwordForgotToken.uid } ) - return request( - { - method: 'DELETE', - url: this.url + '/passwordForgotToken/' + passwordForgotToken.id, - json: true - } - ) + return this.pool.del('/passwordForgotToken/' + passwordForgotToken.id) } DB.prototype.deletePasswordChangeToken = function (passwordChangeToken) { @@ -537,37 +426,22 @@ module.exports = function ( uid: passwordChangeToken && passwordChangeToken.uid } ) - return request( - { - method: 'DELETE', - url: this.url + '/passwordChangeToken/' + passwordChangeToken.id, - json: true - } - ) + return this.pool.del('/passwordChangeToken/' + passwordChangeToken.id) } // BATCH DB.prototype.resetAccount = function (accountResetToken, data) { log.trace({ op: 'DB.resetAccount', uid: accountResetToken && accountResetToken.uid }) - return request( - { - method: 'POST', - url: this.url + '/account/' + accountResetToken.uid.toString('hex') + '/reset', - json: unbuffer(data) - } + return this.pool.post( + '/account/' + accountResetToken.uid.toString('hex') + '/reset', + unbuffer(data) ) } DB.prototype.verifyEmail = function (account) { log.trace({ op: 'DB.verifyEmail', uid: account && account.uid }) - return request( - { - method: 'POST', - url: this.url + '/account/' + account.uid.toString('hex') + '/verifyEmail', - json: true - } - ) + return this.pool.post('/account/' + account.uid.toString('hex') + '/verifyEmail') } DB.prototype.forgotPasswordVerified = function (passwordForgotToken) { @@ -575,20 +449,17 @@ module.exports = function ( return AccountResetToken.create(passwordForgotToken) .then( function (accountResetToken) { - return request( - { - method: 'POST', - url: this.url + '/passwordForgotToken/' + passwordForgotToken.id + '/verified', - json: unbuffer( - { - tokenId: accountResetToken.tokenId, - data: accountResetToken.data, - uid: accountResetToken.uid, - createdAt: accountResetToken.createdAt - }, - 'inplace' - ) - } + return this.pool.post( + '/passwordForgotToken/' + passwordForgotToken.id + '/verified', + unbuffer( + { + tokenId: accountResetToken.tokenId, + data: accountResetToken.data, + uid: accountResetToken.uid, + createdAt: accountResetToken.createdAt + }, + 'inplace' + ) ) .then( function () { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 23629ec6..ae83a6bf 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "fxa-auth-server", - "version": "0.14.0", + "version": "0.14.1", "dependencies": { "ass": { "version": "0.0.4", @@ -1579,7 +1579,7 @@ }, "mime": { "version": "1.2.11", - "from": "mime@~1.2.11", + "from": "mime@1.2.11", "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz" }, "negotiator": { @@ -2873,6 +2873,18 @@ "from": "p-promise@0.2.5", "resolved": "https://registry.npmjs.org/p-promise/-/p-promise-0.2.5.tgz" }, + "poolee": { + "version": "0.4.13", + "from": "poolee@0.4.13", + "resolved": "https://registry.npmjs.org/poolee/-/poolee-0.4.13.tgz", + "dependencies": { + "keep-alive-agent": { + "version": "0.0.1", + "from": "keep-alive-agent@*", + "resolved": "https://registry.npmjs.org/keep-alive-agent/-/keep-alive-agent-0.0.1.tgz" + } + } + }, "request": { "version": "2.34.0", "from": "request@2.34.0", diff --git a/package.json b/package.json index 2385e9f8..3be253ea 100644 --- a/package.json +++ b/package.json @@ -36,19 +36,19 @@ "bunyan": "0.22.1", "compute-cluster": "git://github.com/dannycoates/node-compute-cluster.git#0222a742", "convict": "0.4.2", + "fxa-auth-mailer": "git://github.com/dannycoates/fxa-auth-mailer.git#35b7f29e0a", + "fxa-customs-server": "git://github.com/mozilla/fxa-customs-server.git#74d7b8d6", "hapi": "3.0.2", "hapi-auth-hawk": "git://github.com/dannycoates/hapi-auth-hawk.git#146758e444", "hkdf": "0.0.2", "jwcrypto": "0.4.4", "mysql": "2.1.1", "p-promise": "0.2.5", - "request": "2.34.0", + "poolee": "0.4.13", "scrypt-hash": "1.1.8", "through": "2.3.4", "toobusy": "0.2.4", - "uuid": "1.4.1", - "fxa-customs-server": "git://github.com/mozilla/fxa-customs-server.git#74d7b8d6", - "fxa-auth-mailer": "git://github.com/dannycoates/fxa-auth-mailer.git#35b7f29e0a" + "uuid": "1.4.1" }, "devDependencies": { "ass": "0.0.4", @@ -63,6 +63,7 @@ "lazysmtp": "git://github.com/dannycoates/node-lazysmtp.git#9bb3712992", "load-grunt-tasks": "0.4.0", "mailparser": "0.4.1", + "request": "2.34.0", "sjcl": "1.0.0", "tap": "0.4.8" } diff --git a/pool.js b/pool.js new file mode 100644 index 00000000..20514066 --- /dev/null +++ b/pool.js @@ -0,0 +1,99 @@ +/* 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 P = require('./promise') +var Poolee = require('poolee') + +function parseUrl(url) { + var match = /([a-zA-Z]+):\/\/(\S+)/.exec(url) + if (match) { + return { + protocol: match[1], + host: match[2] + } + } +} + +function Pool(url, options) { + options = options || {} + var foo = parseUrl(url) + var protocol = require(foo.protocol) + this.poolee = new Poolee( + protocol, + [foo.host], + { + timeout: options.timeout || 5000, + keepAlive: true, + maxRetries: 2 + } + ) +} + +Pool.prototype.request = function (method, path, data) { + var d = P.defer() + this.poolee.request( + { + method: method || 'GET', + path: path, + headers: { + "Content-Type": "application/json" + }, + data: data ? JSON.stringify(data) : undefined + }, + function (err, res, body) { + if (err || Math.floor(res && res.statusCode / 100) !== 2) { + return d.reject({ error: err, statusCode: res && res.statusCode, body: body }) + } + var json = null + try { + json = JSON.parse(body) + } + catch (e) {} + d.resolve(json) + } + ) + return d.promise +} + +Pool.prototype.post = function (path, data) { + return this.request('POST', path, data) +} + +Pool.prototype.put = function (path, data) { + return this.request('PUT', path, data) +} + +Pool.prototype.get = function (path) { + return this.request('GET', path) +} + +Pool.prototype.del = function (path) { + return this.request('DELETE', path) +} + +Pool.prototype.head = function (path) { + return this.request('HEAD', path) +} + +Pool.prototype.close = function () { + /*/ + This is a hack to coax the server to close its existing connections + /*/ + var socketCount = this.poolee.options.maxSockets || 20 + function noop() {} + for (var i = 0; i < socketCount; i++) { + this.poolee.request( + { + method: 'GET', + path: '/', + headers: { + "Connection": "close" + } + }, + noop + ) + } +} + +module.exports = Pool diff --git a/requestp.js b/requestp.js deleted file mode 100644 index 904e86a0..00000000 --- a/requestp.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/. */ - -var request = require('request') -var P = require('./promise') - -module.exports = function requestp(options) { - var d = P.defer() - var r = (typeof(options) === 'string') ? { url: options, method: 'GET'} : options - request( - r, - function (err, res, body) { - if (err || Math.floor(res && res.statusCode / 100) !== 2) { - return d.reject({ error: err, statusCode: res && res.statusCode, body: body }) - } - return d.resolve(body) - } - ) - return d.promise -}