@@ -117,7 +117,10 @@ import {
117117 getWorkspacesInfoWithStatusByIds ,
118118 doMergePersons ,
119119 getWorkspaceJoinInfo ,
120- signUpByGrant
120+ signUpByGrant ,
121+ isAccountPasswordLocked ,
122+ recordFailedLoginAttempt ,
123+ resetFailedLoginAttempts
121124} from './utils'
122125
123126// Note: it is IMPORTANT to always destructure params passed here to avoid sending extra params
@@ -152,6 +155,8 @@ export async function loginAsGuest (
152155
153156/**
154157 * Given an email and password, logs the user in and returns the account information and token.
158+ * If the account has too many failed login attempts, password login is blocked.
159+ * The user must use an alternative method (e.g., OTP) to unlock the account.
155160 */
156161export async function login (
157162 ctx : MeasureContext ,
@@ -184,15 +189,34 @@ export async function login (
184189 throw new PlatformError ( new Status ( Severity . ERROR , platform . status . AccountNotFound , { } ) )
185190 }
186191
192+ // Check if account is locked due to too many failed login attempts
193+ if ( isAccountPasswordLocked ( existingAccount ) ) {
194+ ctx . warn ( 'Login attempt on locked account - password login locked' , {
195+ email : normalizedEmail ,
196+ failedAttempts : existingAccount . failedLoginAttempts
197+ } )
198+ throw new PlatformError (
199+ new Status ( Severity . ERROR , platform . status . PasswordLoginLocked , { account : normalizedEmail } )
200+ )
201+ }
202+
187203 const person = await db . person . findOne ( { uuid : emailSocialId . personUuid } )
188204 if ( person == null ) {
189205 throw new PlatformError ( new Status ( Severity . ERROR , platform . status . InternalServerError , { } ) )
190206 }
191207
192208 if ( ! verifyPassword ( password , existingAccount . hash , existingAccount . salt ) ) {
209+ try {
210+ await recordFailedLoginAttempt ( db , existingAccount . uuid )
211+ } catch ( err ) {
212+ ctx . warn ( 'Failed to record failed login attempt' , { error : err , account : existingAccount . uuid } )
213+ }
193214 throw new PlatformError ( new Status ( Severity . ERROR , platform . status . AccountNotFound , { } ) )
194215 }
195216
217+ // Successful login - reset failed attempts counter
218+ await resetFailedLoginAttempts ( db , existingAccount . uuid )
219+
196220 const isConfirmed = emailSocialId . verifiedOn != null
197221
198222 const extraToken : Record < string , string > = isAdminEmail ( normalizedEmail ) ? { admin : 'true' } : { }
@@ -477,6 +501,8 @@ export async function validateOtp (
477501 throw new PlatformError ( new Status ( Severity . ERROR , platform . status . InternalServerError , { } ) )
478502 }
479503
504+ await resetFailedLoginAttempts ( db , emailSocialId . personUuid as AccountUuid )
505+
480506 const extraToken : Record < string , string > = isAdminEmail ( normalizedEmail ) ? { admin : 'true' } : { }
481507
482508 return {
0 commit comments