{"record":{"id":"d684263d056d0594","repo":"hcengineering/platform","slug":"passwordloginlocked","errorCode":"PasswordLoginLocked","errorMessage":"Login attempt on locked account - password login locked","messagePattern":"Login attempt on locked account - password login locked","errorType":"exception","errorClass":"PlatformError","httpStatus":null,"severity":"error","filePath":"server/account/src/operations.ts","lineNumber":210,"sourceCode":"\n  const normalizedEmail = cleanEmail(email)\n\n  try {\n    const emailSocialId = await getEmailSocialId(db, normalizedEmail)\n\n    if (emailSocialId == null) {\n      throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))\n    }\n\n    const existingAccount = await db.account.findOne({ uuid: emailSocialId.personUuid as AccountUuid })\n\n    if (existingAccount == null) {\n      throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))\n    }\n\n    // Check if account is locked due to too many failed login attempts\n    if (isAccountPasswordLocked(existingAccount)) {\n      ctx.warn('Login attempt on locked account - password login locked', {\n        email: normalizedEmail,\n        failedAttempts: existingAccount.failedLoginAttempts\n      })\n      throw new PlatformError(\n        new Status(Severity.ERROR, platform.status.PasswordLoginLocked, { account: normalizedEmail })\n      )\n    }\n\n    const person = await db.person.findOne({ uuid: emailSocialId.personUuid })\n    if (person == null) {\n      throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))\n    }\n\n    if (!verifyPassword(password, existingAccount.hash, existingAccount.salt)) {\n      try {\n        await recordFailedLoginAttempt(db, existingAccount.uuid)\n      } catch (err) {\n        ctx.warn('Failed to record failed login attempt', { error: err, account: existingAccount.uuid })","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/server/account/src/operations.ts#L192-L228","documentation":"During login, the account exists but is password-locked because of too many failed login attempts (isAccountPasswordLocked). The server throws PasswordLoginLocked, carrying the account email, before any password check is performed.","triggerScenarios":"A user calls login (or restorePassword triggers a login flow) with the correct or incorrect password for an account whose failedLoginAttempts exceeded the lock threshold.","commonSituations":"Users mistyping passwords repeatedly; credential-stuffing bots hammering a login endpoint; a shared service account rotated without updating all consumers; password manager autofill with stale credentials.","solutions":["Wait for the lock window to expire, then log in with correct credentials.","Use the password reset / restorePassword flow to reset credentials and clear the lock state.","An admin can clear failedLoginAttempts / the lock flag on the account record.","For service accounts, update the stored secret wherever it is embedded after rotation."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"null","typeGuard":"function isPasswordLoginLocked(e: unknown): boolean {\n  return e instanceof PlatformError && e.status.code === platform.status.PasswordLoginLocked\n}","tryCatchPattern":"try {\n  await client.login(email, password)\n} catch (e) {\n  if (isPasswordLoginLocked(e)) {\n    showAccountLockedMessage(email)\n    return\n  }\n  throw e\n}","preventionTips":["Implement client-side backoff after failed login attempts instead of retrying immediately.","Never store service-account passwords in multiple places; use a secret manager.","Proactively check lock status via the password reset flow when users report lockouts."],"tags":["account","login","account-locked","rate-limit"],"backgroundTag":"account-locked-too-many-failed-logins","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}