@@ -13,6 +13,10 @@ var PUSH_PAYLOADS_SCHEMA_PATH = '../../docs/pushpayloads.schema.json'
13
13
// Currently only for metrics purposes, not enforced.
14
14
var MAX_ACTIVE_SESSIONS = 200
15
15
16
+ var MS_ONE_DAY = 1000 * 60 * 60 * 24
17
+ var MS_ONE_WEEK = MS_ONE_DAY * 7
18
+ var MS_ONE_MONTH = MS_ONE_DAY * 30
19
+
16
20
var path = require ( 'path' )
17
21
var ajv = require ( 'ajv' ) ( )
18
22
var fs = require ( 'fs' )
@@ -51,6 +55,8 @@ module.exports = function (
51
55
defaultLanguage : config . i18n . defaultLanguage
52
56
} )
53
57
58
+ var securityHistoryEnabled = config . securityHistory && config . securityHistory . enabled
59
+
54
60
var routes = [
55
61
{
56
62
method : 'POST' ,
@@ -103,6 +109,7 @@ module.exports = function (
103
109
. then ( createSessionToken )
104
110
. then ( sendVerifyCode )
105
111
. then ( createKeyFetchToken )
112
+ . then ( recordSecurityEvent )
106
113
. then ( createResponse )
107
114
. done ( reply , reply )
108
115
@@ -307,6 +314,18 @@ module.exports = function (
307
314
}
308
315
}
309
316
317
+ function recordSecurityEvent ( ) {
318
+ if ( securityHistoryEnabled ) {
319
+ // don't block response recording db event
320
+ db . securityEvent ( {
321
+ name : 'account.create' ,
322
+ uid : account . uid ,
323
+ ipAddr : request . app . clientAddress ,
324
+ sessionTokenId : sessionToken . tokenId
325
+ } )
326
+ }
327
+ }
328
+
310
329
function createResponse ( ) {
311
330
var response = {
312
331
uid : account . uid . toString ( 'hex' ) ,
@@ -377,13 +396,15 @@ module.exports = function (
377
396
378
397
customs . check ( request , email , 'accountLogin' )
379
398
. then ( readEmailRecord )
399
+ . then ( checkSecurityHistory )
380
400
. then ( checkNumberOfActiveSessions )
381
401
. then ( createSessionToken )
382
402
. then ( createKeyFetchToken )
383
403
. then ( emitSyncLoginEvent )
384
404
. then ( sendVerifyAccountEmail )
385
405
. then ( sendNewDeviceLoginNotification )
386
406
. then ( sendVerifyLoginEmail )
407
+ . then ( recordSecurityEvent )
387
408
. then ( createResponse )
388
409
. done ( reply , reply )
389
410
@@ -444,6 +465,72 @@ module.exports = function (
444
465
)
445
466
}
446
467
468
+ function checkSecurityHistory ( ) {
469
+ if ( ! securityHistoryEnabled ) {
470
+ return
471
+ }
472
+ return db . securityEvents ( {
473
+ uid : emailRecord . uid ,
474
+ ipAddr : request . app . clientAddress
475
+ } )
476
+ . then (
477
+ function ( events ) {
478
+ // if we've seen this address for this user before, we
479
+ // can skip signin confirmation
480
+ //
481
+ // for now, just log that we *could* have done so
482
+ if ( events . length > 0 ) {
483
+ var latest = 0
484
+ var verified = false
485
+
486
+ events . forEach ( function ( ev ) {
487
+ if ( ev . verified ) {
488
+ verified = true
489
+ if ( ev . createdAt > latest ) {
490
+ latest = ev . createdAt
491
+ }
492
+ }
493
+ } )
494
+ if ( verified ) {
495
+ var since = Date . now ( ) - latest
496
+ var recency
497
+ if ( since < MS_ONE_DAY ) {
498
+ recency = 'day'
499
+ } else if ( since < MS_ONE_WEEK ) {
500
+ recency = 'week'
501
+ } else if ( since < MS_ONE_MONTH ) {
502
+ recency = 'month'
503
+ } else {
504
+ recency = 'old'
505
+ }
506
+
507
+ log . info ( {
508
+ op : 'Account.history.verified' ,
509
+ uid : emailRecord . uid . toString ( 'hex' ) ,
510
+ events : events . length ,
511
+ recency : recency
512
+ } )
513
+ } else {
514
+ log . info ( {
515
+ op : 'Account.history.unverified' ,
516
+ uid : emailRecord . uid . toString ( 'hex' ) ,
517
+ events : events . length
518
+ } )
519
+ }
520
+ }
521
+ } ,
522
+ function ( err ) {
523
+ // for now, security events are purely for metrics
524
+ // so errors shouldn't stop the login attempt
525
+ log . error ( {
526
+ op : 'Account.history.error' ,
527
+ err : err ,
528
+ uid : emailRecord . uid . toString ( 'hex' )
529
+ } )
530
+ }
531
+ )
532
+ }
533
+
447
534
function checkNumberOfActiveSessions ( ) {
448
535
return db . sessions ( emailRecord . uid )
449
536
. then (
@@ -619,6 +706,18 @@ module.exports = function (
619
706
}
620
707
}
621
708
709
+ function recordSecurityEvent ( ) {
710
+ if ( securityHistoryEnabled ) {
711
+ // don't block response recording db event
712
+ db . securityEvent ( {
713
+ name : 'account.login' ,
714
+ uid : emailRecord . uid ,
715
+ ipAddr : request . app . clientAddress ,
716
+ sessionTokenId : sessionToken && sessionToken . tokenId
717
+ } )
718
+ }
719
+ }
720
+
622
721
function createResponse ( ) {
623
722
var response = {
624
723
uid : sessionToken . uid . toString ( 'hex' ) ,
@@ -644,7 +743,6 @@ module.exports = function (
644
743
response . verificationMethod = 'email'
645
744
response . verificationReason = 'login'
646
745
}
647
-
648
746
return P . resolve ( response )
649
747
}
650
748
}
@@ -1458,6 +1556,7 @@ module.exports = function (
1458
1556
. then ( resetAccountData )
1459
1557
. then ( createSessionToken )
1460
1558
. then ( createKeyFetchToken )
1559
+ . then ( recordSecurityEvent )
1461
1560
. then ( createResponse )
1462
1561
. done ( reply , reply )
1463
1562
@@ -1572,13 +1671,26 @@ module.exports = function (
1572
1671
}
1573
1672
}
1574
1673
1674
+ function recordSecurityEvent ( ) {
1675
+ if ( securityHistoryEnabled ) {
1676
+ // don't block response recording db event
1677
+ db . securityEvent ( {
1678
+ name : 'account.reset' ,
1679
+ uid : account . uid ,
1680
+ ipAddr : request . app . clientAddress ,
1681
+ sessionTokenId : sessionToken && sessionToken . tokenId
1682
+ } )
1683
+ }
1684
+ }
1685
+
1575
1686
function createResponse ( ) {
1576
1687
// If no sessionToken, this could be a legacy client
1577
1688
// attempting to reset an account password, return legacy response.
1578
1689
if ( ! hasSessionToken ) {
1579
1690
return { }
1580
1691
}
1581
1692
1693
+
1582
1694
var response = {
1583
1695
uid : sessionToken . uid . toString ( 'hex' ) ,
1584
1696
sessionToken : sessionToken . data . toString ( 'hex' ) ,
0 commit comments