@@ -13,6 +13,10 @@ var PUSH_PAYLOADS_SCHEMA_PATH = '../../docs/pushpayloads.schema.json'
1313// Currently only for metrics purposes, not enforced.
1414var MAX_ACTIVE_SESSIONS = 200
1515
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+
1620var path = require ( 'path' )
1721var ajv = require ( 'ajv' ) ( )
1822var fs = require ( 'fs' )
@@ -51,6 +55,8 @@ module.exports = function (
5155 defaultLanguage : config . i18n . defaultLanguage
5256 } )
5357
58+ var securityHistoryEnabled = config . securityHistory && config . securityHistory . enabled
59+
5460 var routes = [
5561 {
5662 method : 'POST' ,
@@ -103,6 +109,7 @@ module.exports = function (
103109 . then ( createSessionToken )
104110 . then ( sendVerifyCode )
105111 . then ( createKeyFetchToken )
112+ . then ( recordSecurityEvent )
106113 . then ( createResponse )
107114 . done ( reply , reply )
108115
@@ -307,6 +314,18 @@ module.exports = function (
307314 }
308315 }
309316
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+
310329 function createResponse ( ) {
311330 var response = {
312331 uid : account . uid . toString ( 'hex' ) ,
@@ -377,13 +396,15 @@ module.exports = function (
377396
378397 customs . check ( request , email , 'accountLogin' )
379398 . then ( readEmailRecord )
399+ . then ( checkSecurityHistory )
380400 . then ( checkNumberOfActiveSessions )
381401 . then ( createSessionToken )
382402 . then ( createKeyFetchToken )
383403 . then ( emitSyncLoginEvent )
384404 . then ( sendVerifyAccountEmail )
385405 . then ( sendNewDeviceLoginNotification )
386406 . then ( sendVerifyLoginEmail )
407+ . then ( recordSecurityEvent )
387408 . then ( createResponse )
388409 . done ( reply , reply )
389410
@@ -444,6 +465,72 @@ module.exports = function (
444465 )
445466 }
446467
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+
447534 function checkNumberOfActiveSessions ( ) {
448535 return db . sessions ( emailRecord . uid )
449536 . then (
@@ -619,6 +706,18 @@ module.exports = function (
619706 }
620707 }
621708
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+
622721 function createResponse ( ) {
623722 var response = {
624723 uid : sessionToken . uid . toString ( 'hex' ) ,
@@ -644,7 +743,6 @@ module.exports = function (
644743 response . verificationMethod = 'email'
645744 response . verificationReason = 'login'
646745 }
647-
648746 return P . resolve ( response )
649747 }
650748 }
@@ -1458,6 +1556,7 @@ module.exports = function (
14581556 . then ( resetAccountData )
14591557 . then ( createSessionToken )
14601558 . then ( createKeyFetchToken )
1559+ . then ( recordSecurityEvent )
14611560 . then ( createResponse )
14621561 . done ( reply , reply )
14631562
@@ -1572,13 +1671,26 @@ module.exports = function (
15721671 }
15731672 }
15741673
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+
15751686 function createResponse ( ) {
15761687 // If no sessionToken, this could be a legacy client
15771688 // attempting to reset an account password, return legacy response.
15781689 if ( ! hasSessionToken ) {
15791690 return { }
15801691 }
15811692
1693+
15821694 var response = {
15831695 uid : sessionToken . uid . toString ( 'hex' ) ,
15841696 sessionToken : sessionToken . data . toString ( 'hex' ) ,
0 commit comments