Simplify the WebAuthn adoption: an error branch that could not reach a screen.
`postJson` caught a non-JSON response and threw a plain `Error` carrying "Resposta inválida do servidor.", but all three islands read `message` only off a `CampaignWebAuthnError` — so the sentence was written for the person and then thrown down the channel that discards messages. Deleted rather than re-typed: the islands' own fallback is better copy for a field worker, which is the same reason the five list-cell controls substitute their own. Nothing user-visible changes, because the string was already unreachable; the comment now records why the branch is absent, so it does not come back. The login failure sentence was also spelled twice for one slot on one screen — the route's `genericMessage` and the button's fallback — so it became `CAMPAIGN_BIOMETRIC_LOGIN_ERROR_MESSAGE`, twin of the enroll constant the register half already shares between its route and its card. Also widened two doc comments this branch made stale: `campaignJsonMutationRoute` and `campaignJsonRequest` both claimed to serve only the list cells' quick edits, and four of nine callers are now WebAuthn ceremonies.
This commit is contained in:
parent
4d56a32ed2
commit
2cfb7d5e67
6 changed files with 38 additions and 26 deletions
|
|
@ -2,6 +2,7 @@ import config from '@payload-config'
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getPayload } from 'payload'
|
||||
|
||||
import { CAMPAIGN_BIOMETRIC_LOGIN_ERROR_MESSAGE } from '@/lib/campaignAuthCopy'
|
||||
import type { CampaignWebAuthnLoginResponse } from '@/lib/campaignWebAuthn'
|
||||
import { campaignWebAuthnLoginSchema } from '@/lib/schemas/campaignWebAuthn'
|
||||
import { setCampaignAuthCookie } from '@/utilities/campaignAuth'
|
||||
|
|
@ -32,7 +33,7 @@ export const POST = campaignJsonMutationRoute(
|
|||
CAMPAIGN_WEBAUTHN_ACCOUNT_UNAVAILABLE_MESSAGE,
|
||||
CAMPAIGN_WEBAUTHN_ACCOUNT_LOCKED_MESSAGE,
|
||||
],
|
||||
genericMessage: 'Não foi possível entrar com a biometria. Use sua senha.',
|
||||
genericMessage: CAMPAIGN_BIOMETRIC_LOGIN_ERROR_MESSAGE,
|
||||
},
|
||||
async ({ credential }): Promise<NextResponse<CampaignWebAuthnLoginResponse>> => {
|
||||
const payload = await getPayload({ config })
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ import { useCampaignBiometricsAvailable } from '@/components/campaign/auth/useCa
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { FieldError } from '@/components/ui/field'
|
||||
import { Spinner } from '@/components/ui/Spinner'
|
||||
import { CAMPAIGN_BIOMETRIC_LOGIN_LABEL } from '@/lib/campaignAuthCopy'
|
||||
import {
|
||||
CAMPAIGN_BIOMETRIC_LOGIN_ERROR_MESSAGE,
|
||||
CAMPAIGN_BIOMETRIC_LOGIN_LABEL,
|
||||
} from '@/lib/campaignAuthCopy'
|
||||
import { CampaignWebAuthnError } from '@/lib/campaignWebAuthnSupport'
|
||||
|
||||
const FALLBACK_ERROR_MESSAGE = 'Não foi possível entrar com a biometria. Use sua senha.'
|
||||
|
||||
/**
|
||||
* Password-less sign-in on the login screen. It is mounted only when the server
|
||||
* says the relying party is configured for this origin (Vercel previews are
|
||||
|
|
@ -47,7 +48,9 @@ export const CampaignBiometricLoginButton = () => {
|
|||
// A cancelled prompt is a decision, not a failure: say it plainly and
|
||||
// leave the password form untouched.
|
||||
setErrorMessage(
|
||||
error instanceof CampaignWebAuthnError ? error.message : FALLBACK_ERROR_MESSAGE,
|
||||
error instanceof CampaignWebAuthnError
|
||||
? error.message
|
||||
: CAMPAIGN_BIOMETRIC_LOGIN_ERROR_MESSAGE,
|
||||
)
|
||||
}
|
||||
setPending(false)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,14 @@ export const CAMPAIGN_BIOMETRIC_ENROLLED_MESSAGE =
|
|||
export const CAMPAIGN_BIOMETRIC_ENROLL_ERROR_MESSAGE =
|
||||
'Não foi possível cadastrar este aparelho. Tente novamente.'
|
||||
|
||||
/**
|
||||
* Twin of the one above for the sign-in half: the route's `genericMessage` and
|
||||
* the login button's fallback are the same sentence in the same slot, reached
|
||||
* depending on whether the server answered an envelope or the network broke.
|
||||
*/
|
||||
export const CAMPAIGN_BIOMETRIC_LOGIN_ERROR_MESSAGE =
|
||||
'Não foi possível entrar com a biometria. Use sua senha.'
|
||||
|
||||
export const CAMPAIGN_BIOMETRIC_REMOVED_MESSAGE =
|
||||
'Aparelho removido. Ele não entra mais com biometria.'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
/**
|
||||
* Transport half of the list cells' quick edits: the preamble every control
|
||||
* repeated around `fetch` (same-origin credentials, JSON headers, the cast of
|
||||
* the discriminated response). Deliberately knows nothing about revert, retry
|
||||
* or debounce — that is `useCampaignCellAutosave`'s, or the caller's.
|
||||
* Transport half of every `/campanha` JSON POST — the list cells' quick edits
|
||||
* and the WebAuthn ceremonies: the preamble each one repeated around `fetch`
|
||||
* (same-origin credentials, JSON headers, the cast of the discriminated
|
||||
* response). Deliberately knows nothing about revert, retry or debounce — that
|
||||
* is `useCampaignCellAutosave`'s, or the caller's.
|
||||
*
|
||||
* `ok` travels next to the payload because a route can answer a real body with
|
||||
* a non-200 status (`engagement-level` returns its `blocked` state as 409), so
|
||||
|
|
|
|||
|
|
@ -32,22 +32,20 @@ import {
|
|||
import { CampaignWebAuthnError } from '@/lib/campaignWebAuthnSupport'
|
||||
|
||||
/**
|
||||
* Transport is `postCampaignJson`; this adds the two things the ceremonies need
|
||||
* on top of it. The status is dropped on purpose — a 403/409/500 from these
|
||||
* routes still carries the typed envelope, so the discriminant decides — and a
|
||||
* response that is not JSON at all (proxy error page, offline) surfaces as one
|
||||
* sentence instead of a `SyntaxError` about position 0. A rejection that is not
|
||||
* a parse failure is a network failure and is rethrown untouched.
|
||||
* Transport is `postCampaignJson`; the only thing added here is dropping `ok`,
|
||||
* because a 403/409/500 from these routes still carries the typed envelope and
|
||||
* the discriminant is what decides. The `{}` default is load-bearing: the route
|
||||
* shell always parses a body, so omitting it would answer 400.
|
||||
*
|
||||
* Nothing is caught. A transport failure — offline, or a proxy answering HTML
|
||||
* that `response.json()` rejects — bubbles as a plain `Error`, which is the
|
||||
* channel `CampaignWebAuthnError` reserves for "the island shows its own copy".
|
||||
* An earlier version rewrote that case as "Resposta inválida do servidor." and
|
||||
* the sentence never reached a screen, because all three islands read `message`
|
||||
* only off a `CampaignWebAuthnError`.
|
||||
*/
|
||||
const postJson = async <Response>(path: string, body?: unknown): Promise<Response> => {
|
||||
try {
|
||||
const { payload } = await postCampaignJson<Response>(path, body ?? {})
|
||||
return payload
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) throw new Error('Resposta inválida do servidor.')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
const postJson = async <Response>(path: string, body?: unknown): Promise<Response> =>
|
||||
(await postCampaignJson<Response>(path, body ?? {})).payload
|
||||
|
||||
/**
|
||||
* `navigator.credentials` rejects with DOM exceptions whose names are the only
|
||||
|
|
|
|||
|
|
@ -73,8 +73,9 @@ const campaignJsonMutationErrorResponse = (
|
|||
|
||||
/**
|
||||
* Builds the `POST` of a `/campanha/**` JSON mutation route (the list cells'
|
||||
* quick edits): same-origin check, body parse, schema parse and error mapping,
|
||||
* leaving each route with its schema, its action call and its success payload.
|
||||
* quick edits, and the WebAuthn ceremonies): same-origin check, body parse,
|
||||
* schema parse and error mapping, leaving each route with its schema, its
|
||||
* action call and its success payload.
|
||||
*
|
||||
* It is a wrapper and not a `guard(request)` helper because the origin check is
|
||||
* the kind of line a sixth route forgets, and forgetting it fails OPEN — a
|
||||
|
|
|
|||
Loading…
Reference in a new issue