Show a distinct message when campaign login is temporarily locked.

Users hitting Payload maxLoginAttempts now see lockout guidance instead of the generic invalid-credentials copy.
This commit is contained in:
Francisco Ignácio Fontoura Solla 2026-07-20 18:23:23 -03:00
parent 87c36e4856
commit bbd9f627f5
3 changed files with 38 additions and 3 deletions

View file

@ -5,6 +5,10 @@ import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
import {
CAMPAIGN_ACCOUNT_LOCKED_MESSAGE,
CAMPAIGN_LOGIN_INVALID_CREDENTIALS_MESSAGE,
} from '@/lib/campaignAuthCopy'
import { requiredFormSecret, requiredFormText } from '@/lib/formData'
import { campaignLoginSchema, type CampaignLoginInput } from '@/lib/schemas/campaign-login'
import {
@ -15,6 +19,12 @@ import {
export type LoginResult = { error?: string }
const isLockedAuthError = (error: unknown): boolean =>
typeof error === 'object' &&
error !== null &&
'name' in error &&
(error as { name: unknown }).name === 'LockedAuth'
export const loginCampaign = async (input: CampaignLoginInput): Promise<LoginResult> => {
const parsed = campaignLoginSchema.safeParse(input)
@ -38,12 +48,15 @@ export const loginCampaign = async (input: CampaignLoginInput): Promise<LoginRes
})
token = result.token
} catch {
return { error: 'E-mail, celular ou senha inválidos.' }
} catch (error) {
if (isLockedAuthError(error)) {
return { error: CAMPAIGN_ACCOUNT_LOCKED_MESSAGE }
}
return { error: CAMPAIGN_LOGIN_INVALID_CREDENTIALS_MESSAGE }
}
if (!token) {
return { error: 'E-mail, celular ou senha inválidos.' }
return { error: CAMPAIGN_LOGIN_INVALID_CREDENTIALS_MESSAGE }
}
await setCampaignAuthCookie(token, payload)

View file

@ -11,6 +11,12 @@ export const CAMPAIGN_FIRST_ACCESS_HINT =
export const CAMPAIGN_LOGIN_SUBTITLE = 'Use o e-mail ou o celular cadastrado na sua conta.'
/** Shown when Payload locks the account after maxLoginAttempts (default: 5 / 10 min). */
export const CAMPAIGN_ACCOUNT_LOCKED_MESSAGE =
'Conta temporariamente bloqueada após várias tentativas. Aguarde alguns minutos e tente de novo.'
export const CAMPAIGN_LOGIN_INVALID_CREDENTIALS_MESSAGE = 'E-mail, celular ou senha inválidos.'
/**
* Card titles on public campaign auth screens (login / forgot / reset).
* `!text-center` beats `[data-theme='campaign'] h1 { text-align: left }` (app shell).

View file

@ -123,4 +123,20 @@ describe('loginCampaign credential mapping', () => {
error: 'E-mail, celular ou senha inválidos.',
})
})
it('returns a distinct message when Payload locks the account', async () => {
const locked = new Error('This user is locked due to having too many failed login attempts.')
locked.name = 'LockedAuth'
mocks.login.mockRejectedValueOnce(locked)
await expect(
loginCampaign({
identifier: 'staff@example.com',
password: 'any-password',
}),
).resolves.toEqual({
error:
'Conta temporariamente bloqueada após várias tentativas. Aguarde alguns minutos e tente de novo.',
})
})
})