@@ -23,37 +23,7 @@ export class AccountLockout {
2323 }
2424
2525 /**
26- * check if the _failed_login_count field has been set
27- */
28- _isFailedLoginCountSet ( ) {
29- const query = {
30- username : this . _user . username ,
31- _failed_login_count : { $exists : true } ,
32- } ;
33-
34- return this . _config . database . find ( '_User' , query ) . then ( users => {
35- if ( Array . isArray ( users ) && users . length > 0 ) {
36- return true ;
37- } else {
38- return false ;
39- }
40- } ) ;
41- }
42-
43- /**
44- * if _failed_login_count is NOT set then set it to 0
45- * else do nothing
46- */
47- _initFailedLoginCount ( ) {
48- return this . _isFailedLoginCountSet ( ) . then ( failedLoginCountIsSet => {
49- if ( ! failedLoginCountIsSet ) {
50- return this . _setFailedLoginCount ( 0 ) ;
51- }
52- } ) ;
53- }
54-
55- /**
56- * increment _failed_login_count by 1
26+ * increment _failed_login_count by 1 and return the updated document
5727 */
5828 _incrementFailedLoginCount ( ) {
5929 const query = {
@@ -127,20 +97,26 @@ export class AccountLockout {
12797 }
12898
12999 /**
130- * set and/or increment _failed_login_count
131- * if _failed_login_count > threshold
132- * set the _account_lockout_expires_at to current_time + accountPolicy.duration
133- * else
134- * do nothing
100+ * Atomically increment _failed_login_count and enforce lockout threshold.
101+ * Uses the atomic increment result to determine the exact post-increment
102+ * count, eliminating the TOCTOU race between checking and updating.
135103 */
136104 _handleFailedLoginAttempt ( ) {
137- return this . _initFailedLoginCount ( )
138- . then ( ( ) => {
139- return this . _incrementFailedLoginCount ( ) ;
140- } )
141- . then ( ( ) => {
142- return this . _setLockoutExpiration ( ) ;
143- } ) ;
105+ return this . _incrementFailedLoginCount ( ) . then ( result => {
106+ const count = result . _failed_login_count ;
107+ if ( count >= this . _config . accountLockout . threshold ) {
108+ return this . _setLockoutExpiration ( ) . then ( ( ) => {
109+ if ( count > this . _config . accountLockout . threshold ) {
110+ throw new Parse . Error (
111+ Parse . Error . OBJECT_NOT_FOUND ,
112+ 'Your account is locked due to multiple failed login attempts. Please try again after ' +
113+ this . _config . accountLockout . duration +
114+ ' minute(s)'
115+ ) ;
116+ }
117+ } ) ;
118+ }
119+ } ) ;
144120 }
145121
146122 /**
0 commit comments