Skip to content
This repository was archived by the owner on Apr 3, 2019. It is now read-only.

Commit 5007b4d

Browse files
committed
feat(email): add verification reminders
Fixes #1081
1 parent 42dc3d1 commit 5007b4d

File tree

10 files changed

+390
-6
lines changed

10 files changed

+390
-6
lines changed

.env.dev

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ LOCKOUT_ENABLED=true
1111
SNS_TOPIC_ARN=disabled
1212
TRUSTED_JKUS=http://127.0.0.1:8080/.well-known/public-keys,http://127.0.0.1:10139/.well-known/public-keys
1313
STATSD_SAMPLE_RATE=1
14+
VERIFICATION_REMINDER_RATE=1

config/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,13 @@ var conf = convict({
281281
default: ''
282282
}
283283
},
284+
verificationReminders: {
285+
rate: {
286+
doc: 'Rate of users getting the verification reminder. If "0" then the feature is disabled. If "1" all users get it.',
287+
default: 0,
288+
env: 'VERIFICATION_REMINDER_RATE'
289+
}
290+
},
284291
useHttps: {
285292
doc: 'set to true to serve directly over https',
286293
env: 'USE_TLS',

lib/db.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,26 @@ module.exports = function (
758758
)
759759
}
760760

761+
// VERIFICATION REMINDERS
762+
763+
DB.prototype.createVerificationReminder = function (reminderData) {
764+
log.trace({
765+
op: 'DB.createVerificationReminder',
766+
reminderData: reminderData
767+
})
768+
769+
return this.pool.post('/verificationReminders', reminderData)
770+
}
771+
772+
DB.prototype.deleteVerificationReminder = function (reminderData) {
773+
log.trace({
774+
op: 'DB.deleteVerificationReminder',
775+
reminderData: reminderData
776+
})
777+
778+
return this.pool.del('/verificationReminders', reminderData)
779+
}
780+
761781
return DB
762782
}
763783

lib/pool.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ Pool.prototype.get = function (path) {
8383
return this.request('GET', path)
8484
}
8585

86-
Pool.prototype.del = function (path) {
87-
return this.request('DELETE', path)
86+
Pool.prototype.del = function (path, data) {
87+
return this.request('DELETE', path, data)
8888
}
8989

9090
Pool.prototype.head = function (path) {

lib/routes/account.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ module.exports = function (
4040
)
4141
]
4242

43+
var verificationReminder = require('../verification-reminders')(log, db)
44+
4345
function isOpenIdProviderAllowed(id) {
4446
if (typeof(id) !== 'string') { return false }
4547
var hostname = url.parse(id).hostname
@@ -223,7 +225,16 @@ module.exports = function (
223225
redirectTo: form.redirectTo,
224226
resume: form.resume,
225227
acceptLanguage: request.app.acceptLanguage
226-
}).catch(function (err) {
228+
})
229+
.then(function () {
230+
// only create reminder if sendVerifyCode succeeds
231+
verificationReminder.create({
232+
uid: account.uid.toString('hex')
233+
}).catch(function (err) {
234+
log.error({ op: 'Account.verificationReminder.create', err: err })
235+
})
236+
})
237+
.catch(function (err) {
227238
log.error({ op: 'mailer.sendVerifyCode.1', err: err })
228239
})
229240
}
@@ -1081,6 +1092,12 @@ module.exports = function (
10811092

10821093
// send a push notification to all devices that the account changed
10831094
push.notifyUpdate(uid, 'accountVerify')
1095+
// remove verification reminders
1096+
verificationReminder.delete({
1097+
uid: account.uid.toString('hex')
1098+
}).catch(function (err) {
1099+
log.error({ op: 'Account.RecoveryEmailVerify', err: err })
1100+
})
10841101

10851102
return db.verifyEmail(account)
10861103
.then(

lib/verification-reminders.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
var config = require('../config')
6+
var P = require('./promise')
7+
var reminderConfig = config.get('verificationReminders')
8+
9+
var LOG_REMINDERS_CREATED = 'verification-reminders.created'
10+
var LOG_REMINDERS_DELETED = 'verification-reminders.deleted'
11+
var LOG_REMINDERS_ERROR_CREATE = 'verification-reminder.create'
12+
var LOG_REMINDERS_ERROR_DELETE = 'verification-reminder.delete'
13+
14+
module.exports = function (log, db) {
15+
/**
16+
* shouldRemind
17+
*
18+
* Determines if we should create a reminder for this user to verify their account.
19+
*
20+
* @returns {boolean}
21+
*/
22+
function shouldRemind() {
23+
// random between 0 and 100, inclusive
24+
var rand = Math.floor(Math.random() * (100 + 1))
25+
return rand < (reminderConfig.rate * 100)
26+
}
27+
28+
return {
29+
/**
30+
* Create a new reminder
31+
* @param reminderData
32+
* @param {string} reminderData.uid - The uid to remind.
33+
*/
34+
create: function createReminder(reminderData) {
35+
if (! shouldRemind()) {
36+
// resolves if not part of the verification roll out
37+
return P.resolve(false)
38+
}
39+
40+
reminderData.type = 'first'
41+
var firstReminder = db.createVerificationReminder(reminderData)
42+
reminderData.type = 'second'
43+
var secondReminder = db.createVerificationReminder(reminderData)
44+
45+
return P.all([firstReminder, secondReminder])
46+
.then(
47+
function () {
48+
log.increment(LOG_REMINDERS_CREATED)
49+
},
50+
function (err) {
51+
log.error({ op: LOG_REMINDERS_ERROR_CREATE, err: err })
52+
}
53+
)
54+
},
55+
/**
56+
* Delete the reminder. Used if the user verifies their account.
57+
*
58+
* @param reminderData
59+
* @param {string} reminderData.uid - The uid for the reminder.
60+
*/
61+
'delete': function deleteReminder(reminderData) {
62+
reminderData.type = 'first'
63+
var firstReminder = db.deleteVerificationReminder(reminderData)
64+
reminderData.type = 'second'
65+
var secondReminder = db.deleteVerificationReminder(reminderData)
66+
67+
return P.all([firstReminder, secondReminder])
68+
.then(
69+
function () {
70+
log.increment(LOG_REMINDERS_DELETED)
71+
},
72+
function (err) {
73+
log.error({ op: LOG_REMINDERS_ERROR_DELETE, err: err })
74+
}
75+
)
76+
}
77+
}
78+
}

npm-shrinkwrap.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/local/pool_tests.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,14 +273,15 @@ test(
273273

274274
var pool = new Pool('http://example.com/')
275275
sinon.stub(pool, 'request', function () {})
276-
pool.del('foo')
276+
pool.del('foo', 'bar')
277277

278278
t.equal(pool.request.callCount, 1, 'pool.request was called once')
279279

280280
var args = pool.request.getCall(0).args
281-
t.equal(args.length, 2, 'pool.request was passed three arguments')
281+
t.equal(args.length, 3, 'pool.request was passed three arguments')
282282
t.equal(args[0], 'DELETE', 'first argument to pool.request was POST')
283283
t.equal(args[1], 'foo', 'second argument to pool.request was correct')
284+
t.equal(args[2], 'bar', 'second argument can be data')
284285

285286
t.end()
286287
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
require('ass')
6+
var tap = require('tap')
7+
var test = tap.test
8+
var uuid = require('uuid')
9+
var log = { trace: console.log, info: console.log }
10+
11+
var config = require('../../config').getProperties()
12+
var TestServer = require('../test_server')
13+
var Token = require('../../lib/tokens')(log)
14+
var DB = require('../../lib/db')(
15+
config.db.backend,
16+
log,
17+
Token.error,
18+
Token.SessionToken,
19+
Token.KeyFetchToken,
20+
Token.AccountResetToken,
21+
Token.PasswordForgotToken,
22+
Token.PasswordChangeToken
23+
)
24+
25+
var zeroBuffer16 = Buffer('00000000000000000000000000000000', 'hex')
26+
var zeroBuffer32 = Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex')
27+
28+
function createTestAccount() {
29+
return {
30+
uid: uuid.v4('binary'),
31+
email: 'reminder' + Math.random() + '@bar.com',
32+
emailCode: zeroBuffer16,
33+
emailVerified: false,
34+
verifierVersion: 1,
35+
verifyHash: zeroBuffer32,
36+
authSalt: zeroBuffer32,
37+
kA: zeroBuffer32,
38+
wrapWrapKb: zeroBuffer32,
39+
acceptLanguage: 'bg-BG,en-US;q=0.7,ar-BH;q=0.3'
40+
}
41+
}
42+
43+
var mockLog = require('../mocks').mockLog
44+
45+
var dbServer, reminderConfig
46+
var dbConn = TestServer.start(config)
47+
.then(
48+
function (server) {
49+
dbServer = server
50+
reminderConfig = process.env.VERIFICATION_REMINDER_RATE
51+
process.env.VERIFICATION_REMINDER_RATE = 1
52+
return DB.connect(config[config.db.backend])
53+
}
54+
)
55+
56+
test(
57+
'create',
58+
function (t) {
59+
var thisMockLog = mockLog({
60+
increment: function (name) {
61+
t.equal(name, 'verification-reminders.created')
62+
}
63+
})
64+
65+
dbConn.then(function (db) {
66+
var account = createTestAccount()
67+
var reminder = { uid: account.uid.toString('hex') }
68+
69+
var verificationReminder = require('../../lib/verification-reminders')(thisMockLog, db)
70+
return verificationReminder.create(reminder).then(
71+
function () {
72+
t.end()
73+
},
74+
function () {
75+
t.fail()
76+
}
77+
)
78+
})
79+
}
80+
)
81+
82+
test(
83+
'delete',
84+
function (t) {
85+
var thisMockLog = mockLog({
86+
increment: function (name) {
87+
if (name === 'verification-reminders.deleted') {
88+
t.ok(true, 'correct log message')
89+
}
90+
}
91+
})
92+
93+
dbConn.then(function (db) {
94+
var verificationReminder = require('../../lib/verification-reminders')(thisMockLog, db)
95+
var account = createTestAccount()
96+
var reminder = { uid: account.uid.toString('hex') }
97+
98+
return verificationReminder.create(reminder)
99+
.then(function () {
100+
return verificationReminder.delete(reminder)
101+
})
102+
.then(function () {
103+
t.end()
104+
}, function (err) {
105+
t.notOk(err)
106+
})
107+
})
108+
}
109+
)
110+
111+
test(
112+
'teardown',
113+
function (t) {
114+
return dbConn.then(function(db) {
115+
return db.close()
116+
}).then(function() {
117+
return dbServer.stop()
118+
}).then(function () {
119+
process.env.VERIFICATION_REMINDER_RATE = reminderConfig
120+
t.end()
121+
})
122+
}
123+
)

0 commit comments

Comments
 (0)