diff --git a/lib/bounces.js b/lib/bounces.js index 02268a9e..974670f1 100644 --- a/lib/bounces.js +++ b/lib/bounces.js @@ -67,6 +67,8 @@ module.exports = function (log, error) { function handleBounce(message) { + const currentTime = Date.now() + var recipients = [] if (message.bounce && message.bounce.bounceType === 'Permanent') { recipients = message.bounce.bouncedRecipients @@ -108,6 +110,26 @@ module.exports = function (log, error) { } } + // Log flow metrics if `flowId` and `flowBeginTime` specified in headers + const flowId = getHeaderValue('X-Flow-Id', message) + const flowBeginTime = getHeaderValue('X-Flow-Begin-Time', message) + const elapsedTime = currentTime - flowBeginTime + + if (flowId && flowBeginTime && (elapsedTime > 0)) { + const eventName = `email.${templateName}.bounced` + + // Flow events have a specific event and structure that must be emitted. + // Ref `gather` in https://github.com/mozilla/fxa-auth-server/blob/master/lib/metrics/context.js + const flowEventInfo = { + event: eventName, + time: currentTime, + flow_id: flowId, + flow_time: elapsedTime + } + + log.info('flowEvent', flowEventInfo) + } + log.info(logData) log.increment('account.email_bounced') diff --git a/lib/mailer.js b/lib/mailer.js index 965e36ae..b23ac2c9 100644 --- a/lib/mailer.js +++ b/lib/mailer.js @@ -22,6 +22,8 @@ module.exports = function (config, log) { return P.resolve(mailer.verifyEmail( { email: account.email, + flowId: opts.flowId, + flowBeginTime: opts.flowBeginTime, uid: account.uid.toString('hex'), code: code.toString('hex'), service: opts.service, @@ -44,6 +46,8 @@ module.exports = function (config, log) { code: code.toString('hex'), email: account.email, ip: opts.ip, + flowId: opts.flowId, + flowBeginTime: opts.flowBeginTime, location: opts.location, redirectTo: opts.redirectTo, resume: opts.resume, @@ -61,6 +65,8 @@ module.exports = function (config, log) { return P.resolve(mailer.recoveryEmail( { email: token.email, + flowId: opts.flowId, + flowBeginTime: opts.flowBeginTime, token: token.data.toString('hex'), code: code.toString('hex'), service: opts.service, @@ -95,7 +101,9 @@ module.exports = function (config, log) { return P.resolve(mailer.passwordResetEmail( { email: email, - acceptLanguage: opts.acceptLanguage || defaultLanguage + acceptLanguage: opts.acceptLanguage || defaultLanguage, + flowId: opts.flowId, + flowBeginTime: opts.flowBeginTime, } )) } @@ -103,6 +111,8 @@ module.exports = function (config, log) { return P.resolve(mailer.newDeviceLoginEmail( { acceptLanguage: opts.acceptLanguage || defaultLanguage, + flowId: opts.flowId, + flowBeginTime: opts.flowBeginTime, email: email, ip: opts.ip, location: opts.location, @@ -126,6 +136,8 @@ module.exports = function (config, log) { return P.resolve(mailer.unblockCodeEmail( { acceptLanguage: opts.acceptLanguage || defaultLanguage, + flowId: opts.flowId, + flowBeginTime: opts.flowBeginTime, email: account.email, ip: opts.ip, location: opts.location, diff --git a/lib/routes/account.js b/lib/routes/account.js index 22987ab8..8782c222 100644 --- a/lib/routes/account.js +++ b/lib/routes/account.js @@ -104,6 +104,13 @@ module.exports = function ( request.validateMetricsContext() + // Store flowId and flowBeginTime to send in email + let flowId, flowBeginTime + if (request.payload.metricsContext) { + flowId = request.payload.metricsContext.flowId + flowBeginTime = request.payload.metricsContext.flowBeginTime + } + customs.check(request, email, 'accountCreate') .then(db.emailRecord.bind(db, email)) .then(deleteAccountIfUnverified, ignoreUnknownAccountError) @@ -281,6 +288,8 @@ module.exports = function ( redirectTo: form.redirectTo, resume: form.resume, acceptLanguage: request.app.acceptLanguage, + flowId: flowId, + flowBeginTime: flowBeginTime, ip: ip, location: geoData.location, uaBrowser: sessionToken.uaBrowser, @@ -420,6 +429,13 @@ module.exports = function ( request.validateMetricsContext() + // Store flowId and flowBeginTime to send in email + let flowId, flowBeginTime + if (request.payload.metricsContext) { + flowId = request.payload.metricsContext.flowId + flowBeginTime = request.payload.metricsContext.flowBeginTime + } + checkIsBlockForced() .then(() => customs.check(request, email, 'accountLogin')) .catch(extractUnblockCode) @@ -835,6 +851,8 @@ module.exports = function ( redirectTo: redirectTo, resume: resume, acceptLanguage: request.app.acceptLanguage, + flowId: flowId, + flowBeginTime: flowBeginTime, ip: ip, location: geoData.location, uaBrowser: sessionToken.uaBrowser, @@ -867,6 +885,8 @@ module.exports = function ( emailRecord.email, { acceptLanguage: request.app.acceptLanguage, + flowId: flowId, + flowBeginTime: flowBeginTime, ip: ip, location: geoData.location, timeZone: geoData.timeZone, @@ -898,6 +918,8 @@ module.exports = function ( tokenVerificationId, { acceptLanguage: request.app.acceptLanguage, + flowId: flowId, + flowBeginTime: flowBeginTime, ip: ip, location: geoData.location, redirectTo: redirectTo, @@ -1790,6 +1812,15 @@ module.exports = function ( var ip = request.app.clientAddress var emailRecord + request.validateMetricsContext() + + // Store flowId and flowBeginTime to send in email + let flowId, flowBeginTime + if (request.payload.metricsContext) { + flowId = request.payload.metricsContext.flowId + flowBeginTime = request.payload.metricsContext.flowBeginTime + } + return customs.check(request, email, 'sendUnblockCode') .then(lookupAccount) .then(createUnblockCode) @@ -1821,6 +1852,8 @@ module.exports = function ( .then((geoData) => { return mailer.sendUnblockCode(emailRecord, code, userAgent.call({ acceptLanguage: request.app.acceptLanguage, + flowId: flowId, + flowBeginTime: flowBeginTime, ip: ip, location: geoData.location, timeZone: geoData.timeZone diff --git a/lib/routes/password.js b/lib/routes/password.js index f5460259..aebf2fa3 100644 --- a/lib/routes/password.js +++ b/lib/routes/password.js @@ -366,6 +366,13 @@ module.exports = function ( request.validateMetricsContext() + // Store flowId and flowBeginTime to send in email + let flowId, flowBeginTime + if (request.payload.metricsContext) { + flowId = request.payload.metricsContext.flowId + flowBeginTime = request.payload.metricsContext.flowBeginTime + } + request.emitMetricsEvent('password.forgot.send_code.start') .then( customs.check.bind( @@ -396,6 +403,8 @@ module.exports = function ( redirectTo: request.payload.redirectTo, resume: request.payload.resume, acceptLanguage: request.app.acceptLanguage, + flowId: flowId, + flowBeginTime: flowBeginTime, ip: ip, location: geoData.location, timeZone: geoData.timeZone @@ -463,6 +472,13 @@ module.exports = function ( request.validateMetricsContext() + // Store flowId and flowBeginTime to send in email + let flowId, flowBeginTime + if (request.payload.metricsContext) { + flowId = request.payload.metricsContext.flowId + flowBeginTime = request.payload.metricsContext.flowBeginTime + } + request.emitMetricsEvent('password.forgot.resend_code.start') .then( customs.check.bind( @@ -484,6 +500,8 @@ module.exports = function ( redirectTo: request.payload.redirectTo, resume: request.payload.resume, acceptLanguage: request.app.acceptLanguage, + flowId: flowId, + flowBeginTime: flowBeginTime, ip: ip, location: geoData.location, timeZone: geoData.timeZone @@ -539,6 +557,13 @@ module.exports = function ( request.validateMetricsContext() + // Store flowId and flowBeginTime to send in email + let flowId, flowBeginTime + if (request.payload.metricsContext) { + flowId = request.payload.metricsContext.flowId + flowBeginTime = request.payload.metricsContext.flowBeginTime + } + request.emitMetricsEvent('password.forgot.verify_code.start') .then( customs.check.bind( @@ -557,7 +582,9 @@ module.exports = function ( return mailer.sendPasswordResetNotification( passwordForgotToken.email, { - acceptLanguage: request.app.acceptLanguage + acceptLanguage: request.app.acceptLanguage, + flowId: flowId, + flowBeginTime: flowBeginTime } ) .then( diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c139219b..d982515d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -65,7 +65,7 @@ }, "typedarray": { "version": "0.0.6", - "from": "typedarray@>=0.0.5 <0.1.0", + "from": "typedarray@>=0.0.6 <0.0.7", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" }, "readable-stream": { @@ -1289,31 +1289,31 @@ "from": "abbrev@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz" }, - "ansi-styles": { - "version": "2.2.1", - "from": "ansi-styles@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" - }, "ansi-regex": { "version": "2.0.0", "from": "ansi-regex@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" }, + "ansi-styles": { + "version": "2.2.1", + "from": "ansi-styles@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + }, "aproba": { "version": "1.0.4", "from": "aproba@>=1.0.3 <2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.0.4.tgz" }, - "are-we-there-yet": { - "version": "1.1.2", - "from": "are-we-there-yet@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz" - }, "asn1": { "version": "0.2.3", "from": "asn1@>=0.2.3 <0.3.0", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" }, + "are-we-there-yet": { + "version": "1.1.2", + "from": "are-we-there-yet@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz" + }, "assert-plus": { "version": "0.2.0", "from": "assert-plus@>=0.2.0 <0.3.0", @@ -1329,16 +1329,16 @@ "from": "async@>=1.5.2 <2.0.0", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" }, - "aws4": { - "version": "1.4.1", - "from": "aws4@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz" - }, "balanced-match": { "version": "0.4.2", "from": "balanced-match@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" }, + "aws4": { + "version": "1.4.1", + "from": "aws4@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz" + }, "block-stream": { "version": "0.0.9", "from": "block-stream@*", @@ -1359,6 +1359,11 @@ "from": "buffer-shims@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" }, + "chalk": { + "version": "1.1.3", + "from": "chalk@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" + }, "caseless": { "version": "0.11.0", "from": "caseless@>=0.11.0 <0.12.0", @@ -1369,25 +1374,25 @@ "from": "code-point-at@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz" }, - "chalk": { - "version": "1.1.3", - "from": "chalk@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" - }, "combined-stream": { "version": "1.0.5", "from": "combined-stream@>=1.0.5 <1.1.0", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" }, + "commander": { + "version": "2.9.0", + "from": "commander@>=2.9.0 <3.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" + }, "console-control-strings": { "version": "1.1.0", "from": "console-control-strings@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" }, - "commander": { - "version": "2.9.0", - "from": "commander@>=2.9.0 <3.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" }, "concat-map": { "version": "0.0.1", @@ -1399,36 +1404,31 @@ "from": "cryptiles@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" }, - "core-util-is": { - "version": "1.0.2", - "from": "core-util-is@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + "deep-extend": { + "version": "0.4.1", + "from": "deep-extend@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz" }, "debug": { "version": "2.2.0", "from": "debug@>=2.2.0 <2.3.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" }, - "deep-extend": { - "version": "0.4.1", - "from": "deep-extend@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz" - }, "delayed-stream": { "version": "1.0.0", "from": "delayed-stream@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" }, - "ecc-jsbn": { - "version": "0.1.1", - "from": "ecc-jsbn@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" - }, "delegates": { "version": "1.0.0", "from": "delegates@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" }, + "ecc-jsbn": { + "version": "0.1.1", + "from": "ecc-jsbn@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" + }, "escape-string-regexp": { "version": "1.0.5", "from": "escape-string-regexp@>=1.0.2 <2.0.0", @@ -1444,16 +1444,16 @@ "from": "extsprintf@1.0.2", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" }, - "forever-agent": { - "version": "0.6.1", - "from": "forever-agent@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - }, "form-data": { "version": "1.0.0-rc4", "from": "form-data@>=1.0.0-rc4 <1.1.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" }, + "forever-agent": { + "version": "0.6.1", + "from": "forever-agent@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + }, "fs.realpath": { "version": "1.0.0", "from": "fs.realpath@>=1.0.0 <2.0.0", @@ -1469,50 +1469,45 @@ "from": "fstream@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz" }, - "generate-function": { - "version": "2.0.0", - "from": "generate-function@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" - }, "gauge": { "version": "2.6.0", "from": "gauge@>=2.6.0 <2.7.0", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.6.0.tgz" }, + "generate-function": { + "version": "2.0.0", + "from": "generate-function@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" + }, "generate-object-property": { "version": "1.2.0", "from": "generate-object-property@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" }, - "glob": { - "version": "7.0.5", - "from": "glob@>=7.0.5 <8.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz" - }, "graceful-fs": { "version": "4.1.4", "from": "graceful-fs@>=4.1.2 <5.0.0", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.4.tgz" }, + "glob": { + "version": "7.0.5", + "from": "glob@>=7.0.5 <8.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz" + }, "graceful-readlink": { "version": "1.0.1", "from": "graceful-readlink@>=1.0.0", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" }, - "har-validator": { - "version": "2.0.6", - "from": "har-validator@>=2.0.6 <2.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" - }, "has-ansi": { "version": "2.0.0", "from": "has-ansi@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" }, - "has-color": { - "version": "0.1.7", - "from": "has-color@>=0.1.7 <0.2.0", - "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" + "har-validator": { + "version": "2.0.6", + "from": "har-validator@>=2.0.6 <2.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" }, "has-unicode": { "version": "2.0.1", @@ -1524,21 +1519,26 @@ "from": "hoek@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" }, + "has-color": { + "version": "0.1.7", + "from": "has-color@>=0.1.7 <0.2.0", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" + }, "hawk": { "version": "3.1.3", "from": "hawk@>=3.1.3 <3.2.0", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" }, - "inflight": { - "version": "1.0.5", - "from": "inflight@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz" - }, "http-signature": { "version": "1.1.1", "from": "http-signature@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" }, + "inflight": { + "version": "1.0.5", + "from": "inflight@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz" + }, "inherits": { "version": "2.0.1", "from": "inherits@>=2.0.1 <2.1.0", @@ -1554,41 +1554,46 @@ "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" }, - "is-my-json-valid": { - "version": "2.13.1", - "from": "is-my-json-valid@>=2.12.4 <3.0.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" + "is-typedarray": { + "version": "1.0.0", + "from": "is-typedarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" }, "is-property": { "version": "1.0.2", "from": "is-property@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" }, - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - }, - "is-typedarray": { - "version": "1.0.0", - "from": "is-typedarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - }, - "isstream": { - "version": "0.1.2", - "from": "isstream@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + "is-my-json-valid": { + "version": "2.13.1", + "from": "is-my-json-valid@>=2.12.4 <3.0.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" }, "jodid25519": { "version": "1.0.2", "from": "jodid25519@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" }, + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, "jsbn": { "version": "0.1.0", "from": "jsbn@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" }, + "jsonpointer": { + "version": "2.0.0", + "from": "jsonpointer@2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" + }, "json-schema": { "version": "0.2.2", "from": "json-schema@0.2.2", @@ -1599,30 +1604,15 @@ "from": "json-stringify-safe@>=5.0.1 <5.1.0", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" }, - "jsonpointer": { - "version": "2.0.0", - "from": "jsonpointer@2.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" - }, - "mime-db": { - "version": "1.23.0", - "from": "mime-db@>=1.23.0 <1.24.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz" - }, "jsprim": { "version": "1.3.0", "from": "jsprim@>=1.2.2 <2.0.0", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.0.tgz" }, - "mime-types": { - "version": "2.1.11", - "from": "mime-types@>=2.1.7 <2.2.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz" - }, - "minimist": { - "version": "0.0.8", - "from": "minimist@0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + "mime-db": { + "version": "1.23.0", + "from": "mime-db@>=1.23.0 <1.24.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz" }, "minimatch": { "version": "3.0.2", @@ -1634,6 +1624,16 @@ "from": "mkdirp@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" }, + "mime-types": { + "version": "2.1.11", + "from": "mime-types@>=2.1.7 <2.2.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz" + }, + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + }, "ms": { "version": "0.7.1", "from": "ms@0.7.1", @@ -1659,6 +1659,11 @@ "from": "number-is-nan@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" }, + "oauth-sign": { + "version": "0.8.2", + "from": "oauth-sign@>=0.8.1 <0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" + }, "object-assign": { "version": "4.1.0", "from": "object-assign@>=4.1.0 <5.0.0", @@ -1669,11 +1674,6 @@ "from": "once@>=1.3.0 <2.0.0", "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" }, - "oauth-sign": { - "version": "0.8.2", - "from": "oauth-sign@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" - }, "path-is-absolute": { "version": "1.0.0", "from": "path-is-absolute@>=1.0.0 <2.0.0", @@ -1684,16 +1684,16 @@ "from": "pinkie@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" }, - "pinkie-promise": { - "version": "2.0.1", - "from": "pinkie-promise@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" - }, "process-nextick-args": { "version": "1.0.7", "from": "process-nextick-args@>=1.0.6 <1.1.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" }, + "pinkie-promise": { + "version": "2.0.1", + "from": "pinkie-promise@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + }, "qs": { "version": "6.2.0", "from": "qs@>=6.2.0 <6.3.0", @@ -1709,16 +1709,16 @@ "from": "request@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/request/-/request-2.73.0.tgz" }, - "rimraf": { - "version": "2.5.3", - "from": "rimraf@>=2.5.0 <2.6.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.3.tgz" - }, "semver": { "version": "5.2.0", "from": "semver@>=5.2.0 <5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.2.0.tgz" }, + "rimraf": { + "version": "2.5.3", + "from": "rimraf@>=2.5.0 <2.6.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.3.tgz" + }, "set-blocking": { "version": "2.0.0", "from": "set-blocking@>=2.0.0 <2.1.0", @@ -1754,16 +1754,16 @@ "from": "strip-ansi@>=3.0.1 <4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" }, - "supports-color": { - "version": "2.0.0", - "from": "supports-color@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" - }, "strip-json-comments": { "version": "1.0.4", "from": "strip-json-comments@>=1.0.4 <1.1.0", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" }, + "supports-color": { + "version": "2.0.0", + "from": "supports-color@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + }, "tar": { "version": "2.2.1", "from": "tar@>=2.2.0 <2.3.0", @@ -1779,6 +1779,11 @@ "from": "tough-cookie@>=2.2.0 <2.3.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" }, + "tunnel-agent": { + "version": "0.4.3", + "from": "tunnel-agent@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" + }, "tweetnacl": { "version": "0.13.3", "from": "tweetnacl@>=0.13.0 <0.14.0", @@ -1789,11 +1794,6 @@ "from": "uid-number@>=0.0.6 <0.1.0", "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" }, - "tunnel-agent": { - "version": "0.4.3", - "from": "tunnel-agent@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" - }, "util-deprecate": { "version": "1.0.2", "from": "util-deprecate@>=1.0.1 <1.1.0", @@ -1804,33 +1804,21 @@ "from": "verror@1.3.6", "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" }, - "wrappy": { - "version": "1.0.2", - "from": "wrappy@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - }, "wide-align": { "version": "1.1.0", "from": "wide-align@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz" }, + "wrappy": { + "version": "1.0.2", + "from": "wrappy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + }, "xtend": { "version": "4.0.1", "from": "xtend@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" }, - "bl": { - "version": "1.1.2", - "from": "bl@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.5 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - } - } - }, "dashdash": { "version": "1.14.0", "from": "dashdash@>=1.12.0 <2.0.0", @@ -1843,15 +1831,15 @@ } } }, - "getpass": { - "version": "0.1.6", - "from": "getpass@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "bl": { + "version": "1.1.2", + "from": "bl@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@>=2.0.5 <2.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" } } }, @@ -1878,6 +1866,18 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" } } + }, + "getpass": { + "version": "0.1.6", + "from": "getpass@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } } } } @@ -4426,7 +4426,7 @@ "dependencies": { "chalk": { "version": "1.1.3", - "from": "chalk@>=1.0.0 <2.0.0", + "from": "chalk@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "dependencies": { "ansi-styles": { @@ -4471,9 +4471,9 @@ } }, "concat-stream": { - "version": "1.5.2", + "version": "1.6.0", "from": "concat-stream@>=1.5.0 <2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", "dependencies": { "inherits": { "version": "2.0.3", @@ -4482,14 +4482,19 @@ }, "typedarray": { "version": "0.0.6", - "from": "typedarray@>=0.0.5 <0.1.0", + "from": "typedarray@>=0.0.6 <0.0.7", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" }, "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "version": "2.2.2", + "from": "readable-stream@>=2.2.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", "dependencies": { + "buffer-shims": { + "version": "1.0.0", + "from": "buffer-shims@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" + }, "core-util-is": { "version": "1.0.2", "from": "core-util-is@>=1.0.0 <1.1.0", @@ -4931,9 +4936,9 @@ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-1.3.0.tgz", "dependencies": { "JSONStream": { - "version": "1.2.1", + "version": "1.3.0", "from": "JSONStream@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.0.tgz", "dependencies": { "jsonparse": { "version": "1.2.0", @@ -5960,7 +5965,7 @@ "dependencies": { "chalk": { "version": "1.1.3", - "from": "chalk@>=1.0.0 <2.0.0", + "from": "chalk@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "dependencies": { "ansi-styles": { @@ -6005,9 +6010,9 @@ } }, "eslint": { - "version": "3.12.1", + "version": "3.12.2", "from": "eslint@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.12.1.tgz", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.12.2.tgz", "dependencies": { "babel-code-frame": { "version": "6.20.0", @@ -6022,25 +6027,30 @@ } }, "concat-stream": { - "version": "1.5.2", + "version": "1.6.0", "from": "concat-stream@>=1.4.6 <2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", "dependencies": { "inherits": { "version": "2.0.3", - "from": "inherits@>=2.0.1 <2.1.0", + "from": "inherits@>=2.0.3 <3.0.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" }, "typedarray": { "version": "0.0.6", - "from": "typedarray@>=0.0.5 <0.1.0", + "from": "typedarray@>=0.0.6 <0.0.7", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" }, "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "version": "2.2.2", + "from": "readable-stream@>=2.2.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", "dependencies": { + "buffer-shims": { + "version": "1.0.0", + "from": "buffer-shims@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" + }, "core-util-is": { "version": "1.0.2", "from": "core-util-is@>=1.0.0 <1.1.0", @@ -6071,9 +6081,9 @@ } }, "debug": { - "version": "2.3.3", + "version": "2.4.5", "from": "debug@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.4.5.tgz", "dependencies": { "ms": { "version": "0.7.2", @@ -6188,9 +6198,9 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-3.3.2.tgz", "dependencies": { "acorn": { - "version": "4.0.3", + "version": "4.0.4", "from": "acorn@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.3.tgz" + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.4.tgz" }, "acorn-jsx": { "version": "3.0.1", @@ -6222,13 +6232,13 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", "dependencies": { "flat-cache": { - "version": "1.2.1", + "version": "1.2.2", "from": "flat-cache@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", "dependencies": { "circular-json": { "version": "0.3.1", - "from": "circular-json@>=0.3.0 <0.4.0", + "from": "circular-json@>=0.3.1 <0.4.0", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz" }, "del": { @@ -6758,9 +6768,9 @@ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "dependencies": { "resolve": { - "version": "1.1.7", + "version": "1.2.0", "from": "resolve@>=1.1.6 <2.0.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.2.0.tgz" } } } @@ -6971,7 +6981,7 @@ "dependencies": { "ansi-regex": { "version": "2.0.0", - "from": "ansi-regex@2.0.0", + "from": "ansi-regex@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" } } @@ -6983,7 +6993,7 @@ "dependencies": { "ansi-regex": { "version": "2.0.0", - "from": "ansi-regex@2.0.0", + "from": "ansi-regex@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" } } @@ -7224,9 +7234,9 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-4.2.0.tgz" }, "joi": { - "version": "10.0.5", + "version": "10.0.6", "from": "joi@>=10.0.0 <11.0.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-10.0.5.tgz", + "resolved": "https://registry.npmjs.org/joi/-/joi-10.0.6.tgz", "dependencies": { "isemail": { "version": "2.2.1", @@ -7265,9 +7275,9 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-4.2.0.tgz" }, "joi": { - "version": "10.0.5", + "version": "10.0.6", "from": "joi@>=10.0.0 <11.0.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-10.0.5.tgz", + "resolved": "https://registry.npmjs.org/joi/-/joi-10.0.6.tgz", "dependencies": { "isemail": { "version": "2.2.1", @@ -7328,9 +7338,9 @@ "resolved": "https://registry.npmjs.org/shot/-/shot-3.4.0.tgz", "dependencies": { "joi": { - "version": "10.0.5", + "version": "10.0.6", "from": "joi@>=10.0.0 <11.0.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-10.0.5.tgz", + "resolved": "https://registry.npmjs.org/joi/-/joi-10.0.6.tgz", "dependencies": { "isemail": { "version": "2.2.1", @@ -8275,9 +8285,9 @@ } }, "debug": { - "version": "2.3.3", + "version": "2.4.5", "from": "debug@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.4.5.tgz", "dependencies": { "ms": { "version": "0.7.2", @@ -8643,16 +8653,16 @@ "from": "babel-generator@>=6.18.0 <7.0.0", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.18.0.tgz" }, - "babel-runtime": { - "version": "6.18.0", - "from": "babel-runtime@>=6.9.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.18.0.tgz" - }, "babel-messages": { "version": "6.8.0", "from": "babel-messages@>=6.8.0 <7.0.0", "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.8.0.tgz" }, + "babel-runtime": { + "version": "6.18.0", + "from": "babel-runtime@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.18.0.tgz" + }, "babel-template": { "version": "6.16.0", "from": "babel-template@>=6.16.0 <7.0.0", @@ -8673,16 +8683,16 @@ "from": "babylon@>=6.13.0 <7.0.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.13.1.tgz" }, - "brace-expansion": { - "version": "1.1.6", - "from": "brace-expansion@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" - }, "balanced-match": { "version": "0.4.2", "from": "balanced-match@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" }, + "brace-expansion": { + "version": "1.1.6", + "from": "brace-expansion@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" + }, "braces": { "version": "1.8.5", "from": "braces@>=1.8.2 <2.0.0", @@ -8813,16 +8823,16 @@ "from": "glob-base@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz" }, - "glob-parent": { - "version": "2.0.0", - "from": "glob-parent@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" - }, "globals": { "version": "9.12.0", "from": "globals@>=9.0.0 <10.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.12.0.tgz" }, + "glob-parent": { + "version": "2.0.0", + "from": "glob-parent@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" + }, "graceful-fs": { "version": "4.1.9", "from": "graceful-fs@>=4.1.2 <5.0.0", @@ -8848,16 +8858,16 @@ "from": "imurmurhash@>=0.1.4 <0.2.0", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" }, - "inflight": { - "version": "1.0.6", - "from": "inflight@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - }, "inherits": { "version": "2.0.3", "from": "inherits@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" }, + "inflight": { + "version": "1.0.6", + "from": "inflight@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + }, "invariant": { "version": "2.2.1", "from": "invariant@>=2.2.0 <3.0.0", @@ -8868,16 +8878,16 @@ "from": "invert-kv@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz" }, - "is-arrayish": { - "version": "0.2.1", - "from": "is-arrayish@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" - }, "is-buffer": { "version": "1.1.4", "from": "is-buffer@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.4.tgz" }, + "is-arrayish": { + "version": "0.2.1", + "from": "is-arrayish@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + }, "is-builtin-module": { "version": "1.0.0", "from": "is-builtin-module@>=1.0.0 <2.0.0", @@ -8903,16 +8913,16 @@ "from": "is-extglob@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" }, - "is-finite": { - "version": "1.0.2", - "from": "is-finite@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz" - }, "is-fullwidth-code-point": { "version": "1.0.0", "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" }, + "is-finite": { + "version": "1.0.2", + "from": "is-finite@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz" + }, "is-glob": { "version": "2.0.1", "from": "is-glob@>=2.0.1 <3.0.0", @@ -8958,26 +8968,26 @@ "from": "js-tokens@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz" }, - "jsesc": { - "version": "1.3.0", - "from": "jsesc@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz" - }, "kind-of": { "version": "3.0.4", "from": "kind-of@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.4.tgz" }, - "lazy-cache": { - "version": "1.0.4", - "from": "lazy-cache@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz" + "jsesc": { + "version": "1.3.0", + "from": "jsesc@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz" }, "lcid": { "version": "1.0.0", "from": "lcid@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz" }, + "lazy-cache": { + "version": "1.0.4", + "from": "lazy-cache@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz" + }, "load-json-file": { "version": "1.1.0", "from": "load-json-file@>=1.0.0 <2.0.0", @@ -8993,16 +9003,16 @@ "from": "longest@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz" }, - "loose-envify": { - "version": "1.3.0", - "from": "loose-envify@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.0.tgz" - }, "lru-cache": { "version": "4.0.1", "from": "lru-cache@>=4.0.1 <5.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz" }, + "loose-envify": { + "version": "1.3.0", + "from": "loose-envify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.0.tgz" + }, "md5-o-matic": { "version": "0.1.1", "from": "md5-o-matic@>=0.1.1 <0.2.0", @@ -9048,31 +9058,31 @@ "from": "object.omit@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz" }, - "once": { - "version": "1.4.0", - "from": "once@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - }, "optimist": { "version": "0.6.1", "from": "optimist@>=0.6.1 <0.7.0", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz" }, + "once": { + "version": "1.4.0", + "from": "once@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + }, "os-homedir": { "version": "1.0.2", "from": "os-homedir@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz" }, - "os-locale": { - "version": "1.4.0", - "from": "os-locale@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz" - }, "parse-glob": { "version": "3.0.4", "from": "parse-glob@>=3.0.4 <4.0.0", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz" }, + "os-locale": { + "version": "1.4.0", + "from": "os-locale@>=1.4.0 <2.0.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz" + }, "parse-json": { "version": "2.2.0", "from": "parse-json@>=2.2.0 <3.0.0", @@ -9098,11 +9108,6 @@ "from": "path-type@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz" }, - "pify": { - "version": "2.3.0", - "from": "pify@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" - }, "pinkie": { "version": "2.0.4", "from": "pinkie@>=2.0.0 <3.0.0", @@ -9113,6 +9118,11 @@ "from": "pinkie-promise@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" }, + "pify": { + "version": "2.3.0", + "from": "pify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + }, "pkg-dir": { "version": "1.0.0", "from": "pkg-dir@>=1.0.0 <2.0.0", @@ -9123,16 +9133,16 @@ "from": "preserve@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" }, - "randomatic": { - "version": "1.1.5", - "from": "randomatic@>=1.1.3 <2.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.5.tgz" - }, "pseudomap": { "version": "1.0.2", "from": "pseudomap@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" }, + "randomatic": { + "version": "1.1.5", + "from": "randomatic@>=1.1.3 <2.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.5.tgz" + }, "read-pkg": { "version": "1.1.0", "from": "read-pkg@>=1.0.0 <2.0.0", @@ -9148,16 +9158,16 @@ "from": "regenerator-runtime@>=0.9.5 <0.10.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.9.5.tgz" }, - "regex-cache": { - "version": "0.4.3", - "from": "regex-cache@>=0.4.2 <0.5.0", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" - }, "repeat-element": { "version": "1.1.2", "from": "repeat-element@>=1.1.2 <2.0.0", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz" }, + "regex-cache": { + "version": "0.4.3", + "from": "regex-cache@>=0.4.2 <0.5.0", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" + }, "repeat-string": { "version": "1.6.1", "from": "repeat-string@>=1.5.2 <2.0.0", @@ -9263,16 +9273,16 @@ "from": "which-module@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz" }, - "window-size": { - "version": "0.1.0", - "from": "window-size@0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" - }, "wordwrap": { "version": "0.0.3", "from": "wordwrap@>=0.0.2 <0.1.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz" }, + "window-size": { + "version": "0.1.0", + "from": "window-size@0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" + }, "wrap-ansi": { "version": "2.0.0", "from": "wrap-ansi@>=2.0.0 <3.0.0", diff --git a/test/client/api.js b/test/client/api.js index 815cee76..c2e85672 100644 --- a/test/client/api.js +++ b/test/client/api.js @@ -419,7 +419,8 @@ module.exports = config => { email: email, service: options.service || undefined, redirectTo: options.redirectTo || undefined, - resume: options.resume || undefined + resume: options.resume || undefined, + metricsContext: options.metricsContext || undefined }, headers ) @@ -445,7 +446,11 @@ module.exports = config => { ) } - ClientApi.prototype.passwordForgotVerifyCode = function (passwordForgotTokenHex, code, headers) { + ClientApi.prototype.passwordForgotVerifyCode = function (passwordForgotTokenHex, code, headers, options) { + if (!options) { + options = {} + } + return tokens.PasswordForgotToken.fromHex(passwordForgotTokenHex) .then( function (token) { @@ -454,7 +459,8 @@ module.exports = config => { this.baseURL + '/password/forgot/verify_code', token, { - code: code + code: code, + metricsContext: options.metricsContext || undefined }, headers ) diff --git a/test/client/index.js b/test/client/index.js index 507f9cd9..6270bbfb 100644 --- a/test/client/index.js +++ b/test/client/index.js @@ -393,8 +393,8 @@ module.exports = config => { return this.api.passwordForgotResendCode(this.passwordForgotToken, this.email) } - Client.prototype.verifyPasswordResetCode = function (code, headers) { - return this.api.passwordForgotVerifyCode(this.passwordForgotToken, code, headers) + Client.prototype.verifyPasswordResetCode = function (code, headers, options) { + return this.api.passwordForgotVerifyCode(this.passwordForgotToken, code, headers, options) .then( function (result) { this.accountResetToken = result.accountResetToken diff --git a/test/local/bounce_tests.js b/test/local/bounce_tests.js index 20639d04..ff3d7a66 100644 --- a/test/local/bounce_tests.js +++ b/test/local/bounce_tests.js @@ -323,4 +323,61 @@ describe('bounce messages', () => { }) } ) + + it( + 'should emit flow metrics', + () => { + var mockLog = spyLog() + var mockDB = { + emailRecord: sinon.spy(function (email) { + return P.resolve({ + uid: '123456', + email: email, + emailVerified: false + }) + }), + deleteAccount: sinon.spy(function () { + return P.resolve({ }) + }) + } + var mockMsg = mockMessage({ + bounce: { + bounceType: 'Permanent', + bounceSubType: 'General', + bouncedRecipients: [ + {emailAddress: 'test@example.com'} + ] + }, + mail: { + headers: [ + { + name: 'X-Template-Name', + value: 'verifyLoginEmail' + }, + { + name: 'X-Flow-Id', + value: 'someFlowId' + }, + { + name: 'X-Flow-Begin-Time', + value: 1234 + } + ] + } + }) + + return mockedBounces(mockLog, mockDB).handleBounce(mockMsg).then(function () { + assert.equal(mockDB.emailRecord.callCount, 1) + assert.equal(mockDB.emailRecord.args[0][0], 'test@example.com') + assert.equal(mockDB.deleteAccount.callCount, 1) + assert.equal(mockDB.deleteAccount.args[0][0].email, 'test@example.com') + assert.equal(mockLog.messages.length, 4) + assert.equal(mockLog.messages[0].args[0], 'flowEvent') + assert.equal(mockLog.messages[0].args[1]['event'], 'email.verifyLoginEmail.bounced') + assert.equal(mockLog.messages[0].args[1]['flow_id'], 'someFlowId') + assert.equal(mockLog.messages[0].args[1]['flow_time'] > 0, true) + assert.equal(mockLog.messages[0].args[1]['time'] > 0, true) + }) + } + ) }) diff --git a/test/mocks.js b/test/mocks.js index 90ea5b08..f9a501f0 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -10,6 +10,7 @@ const sinon = require('sinon') const extend = require('util')._extend const P = require('../lib/promise') const crypto = require('crypto') +const config = require('../config').getProperties() const CUSTOMS_METHOD_NAMES = [ 'check', @@ -89,6 +90,7 @@ const PUSH_METHOD_NAMES = [ ] module.exports = { + generateMetricsContext: generateMetricsContext, mockCustoms: mockObject(CUSTOMS_METHOD_NAMES), mockDB: mockDB, mockDevices: mockDevices, @@ -281,6 +283,24 @@ function spyLog (methods) { return mockLog(methods) } +function generateMetricsContext(){ + const randomBytes = crypto.randomBytes(16).toString('hex') + const flowBeginTime = Date.now() + const flowSignature = crypto.createHmac('sha256', config.metrics.flow_id_key) + .update([ + randomBytes, + flowBeginTime.toString(16), + undefined + ].join('\n')) + .digest('hex') + .substr(0, 32) + + return { + flowBeginTime: flowBeginTime, + flowId: randomBytes + flowSignature + } +} + function mockRequest (data) { const events = require('../lib/metrics/events')(data.log || module.exports.mockLog()) const metricsContext = data.metricsContext || module.exports.mockMetricsContext() diff --git a/test/remote/account_create_tests.js b/test/remote/account_create_tests.js index 9d9bdbad..f61b715c 100644 --- a/test/remote/account_create_tests.js +++ b/test/remote/account_create_tests.js @@ -9,6 +9,7 @@ var TestServer = require('../test_server') var crypto = require('crypto') const Client = require('../client')() var config = require('../../config').getProperties() +const mocks = require('../mocks') describe('remote account create', function() { this.timeout(15000) @@ -483,13 +484,16 @@ describe('remote account create', function() { 'account creation works with maximal metricsContext metadata', () => { var email = server.uniqueEmail() - return Client.create(config.publicUrl, email, 'foo', { - metricsContext: { - flowId: 'deadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00d', - flowBeginTime: 1 - } - }).then(function (client) { + var opts = { + metricsContext: mocks.generateMetricsContext() + } + return Client.create(config.publicUrl, email, 'foo', opts).then(function (client) { assert.ok(client, 'created account') + return server.mailbox.waitForEmail(email) + }) + .then(function (emailData) { + assert.equal(emailData.headers['x-flow-begin-time'], opts.metricsContext.flowBeginTime, 'flow begin time set') + assert.equal(emailData.headers['x-flow-id'], opts.metricsContext.flowId, 'flow id set') }) } ) diff --git a/test/remote/account_signin_verification_tests.js b/test/remote/account_signin_verification_tests.js index 3795516a..52d0bfbb 100644 --- a/test/remote/account_signin_verification_tests.js +++ b/test/remote/account_signin_verification_tests.js @@ -18,6 +18,8 @@ var publicKey = { 'e': '65537' } +const mocks = require('../mocks') + describe('remote account signin verification', function() { this.timeout(30000) let server @@ -114,6 +116,10 @@ describe('remote account signin verification', function() { var client = null var uid var code + var loginOpts = { + keys: true, + metricsContext: mocks.generateMetricsContext() + } return Client.createAndVerify(config.publicUrl, email, password, server.mailbox) .then( function (x) { @@ -133,7 +139,7 @@ describe('remote account signin verification', function() { ) .then( function () { - return client.login({keys:true}) + return client.login(loginOpts) } ) .then( @@ -155,6 +161,9 @@ describe('remote account signin verification', function() { assert.equal(emailData.subject, 'Confirm new sign-in to Firefox') assert.ok(uid, 'sent uid') assert.ok(code, 'sent verify code') + + assert.equal(emailData.headers['x-flow-begin-time'], loginOpts.metricsContext.flowBeginTime, 'flow begin time set') + assert.equal(emailData.headers['x-flow-id'], loginOpts.metricsContext.flowId, 'flow id set') } ) .then( diff --git a/test/remote/password_forgot_tests.js b/test/remote/password_forgot_tests.js index 98486f01..f3a0d53c 100644 --- a/test/remote/password_forgot_tests.js +++ b/test/remote/password_forgot_tests.js @@ -12,6 +12,7 @@ var crypto = require('crypto') var base64url = require('base64url') var config = require('../../config').getProperties() +const mocks = require('../mocks') describe('remote password forgot', function() { this.timeout(15000) @@ -32,7 +33,11 @@ describe('remote password forgot', function() { var wrapKb = null var kA = null var client = null - return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys:true}) + var opts = { + keys: true, + metricsContext: mocks.generateMetricsContext() + } + return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, opts) .then( function (x) { client = x @@ -54,13 +59,15 @@ describe('remote password forgot', function() { .then( function (emailData) { assert.equal(emailData.html.indexOf('IP address') > -1, true, 'contains ip location data') + assert.equal(emailData.headers['x-flow-begin-time'], opts.metricsContext.flowBeginTime, 'flow begin time set') + assert.equal(emailData.headers['x-flow-id'], opts.metricsContext.flowId, 'flow id set') return emailData.headers['x-recovery-code'] } ) .then( function (code) { assert.throws(function() { client.resetPassword(newPassword) }) - return resetPassword(client, code, newPassword) + return resetPassword(client, code, newPassword, undefined, opts) } ) .then( @@ -73,6 +80,9 @@ describe('remote password forgot', function() { var link = emailData.headers['x-link'] var query = url.parse(link, true).query assert.ok(query.email, 'email is in the link') + + assert.equal(emailData.headers['x-flow-begin-time'], opts.metricsContext.flowBeginTime, 'flow begin time set') + assert.equal(emailData.headers['x-flow-id'], opts.metricsContext.flowId, 'flow id set') } ) .then( @@ -431,8 +441,8 @@ describe('remote password forgot', function() { return TestServer.stop(server) }) - function resetPassword(client, code, newPassword, options) { - return client.verifyPasswordResetCode(code) + function resetPassword(client, code, newPassword, headers, options) { + return client.verifyPasswordResetCode(code, headers, options) .then(function() { return client.resetPassword(newPassword, {}, options) })