hcengineering/platform · error · PlatformError
PasswordLoginLocked
PasswordLoginLocked
Error message
Login attempt on locked account - password login locked
What it means
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.
Source
Thrown at server/account/src/operations.ts:210
const normalizedEmail = cleanEmail(email)
try {
const emailSocialId = await getEmailSocialId(db, normalizedEmail)
if (emailSocialId == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
}
const existingAccount = await db.account.findOne({ uuid: emailSocialId.personUuid as AccountUuid })
if (existingAccount == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, {}))
}
// Check if account is locked due to too many failed login attempts
if (isAccountPasswordLocked(existingAccount)) {
ctx.warn('Login attempt on locked account - password login locked', {
email: normalizedEmail,
failedAttempts: existingAccount.failedLoginAttempts
})
throw new PlatformError(
new Status(Severity.ERROR, platform.status.PasswordLoginLocked, { account: normalizedEmail })
)
}
const person = await db.person.findOne({ uuid: emailSocialId.personUuid })
if (person == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
}
if (!verifyPassword(password, existingAccount.hash, existingAccount.salt)) {
try {
await recordFailedLoginAttempt(db, existingAccount.uuid)
} catch (err) {
ctx.warn('Failed to record failed login attempt', { error: err, account: existingAccount.uuid })View on GitHub (pinned to 63e28dc964)
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.
Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
function isPasswordLoginLocked(e: unknown): boolean {
return e instanceof PlatformError && e.status.code === platform.status.PasswordLoginLocked
} Try / catch
try {
await client.login(email, password)
} catch (e) {
if (isPasswordLoginLocked(e)) {
showAccountLockedMessage(email)
return
}
throw e
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- rate-limit
- Login failed
- platform.status.AccountNotFound
- platform.status.WorkspaceRateLimit
- AccountNotFound
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/d684263d056d0594.
Report an issue: GitHub.