Skip to content

fix(captcha): scope the booking refresh to its form and make the ids unique - #1057

Merged
rpgmem merged 2 commits into
developfrom
claude/altcha-captcha-integration-4vvuer
Sep 5, 2026
Merged

fix(captcha): scope the booking refresh to its form and make the ids unique#1057
rpgmem merged 2 commits into
developfrom
claude/altcha-captcha-integration-4vvuer

Conversation

@rpgmem

@rpgmem rpgmem commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Fecha #1056. Intercalado entre o PR2 e o PR3 do épico #1053, porque o PR3 mexe no mesmo markup e no mesmo caminho de refresh.

Summary

  • O bug funcional. ffcCalendarFrontend.refreshCaptcha() era global: reescrevia a pergunta em toda .ffc-captcha-row da página e depois gravava o token novo via $('#ffc_captcha_hash'), que por definição casa só com o primeiro elemento. Com dois formulários montados, uma rejeição em qualquer um deixava o segundo exibindo uma pergunta que seu token não respondia — o visitante respondia o que estava na tela e ouvia que a resposta matemática estava incorreta. Verdade, e inútil como diagnóstico.
  • A correção. Passa a escopar ao formulário submetido e a casar por name, que é o que o ffc-frontend-helpers.js já fazia no caminho do certificado. O $form já estava em escopo no ponto de chamada, então nada precisou ser reestruturado.
  • Os ids. Um censo confirmou que todo o resto — ffc-dynamic-fragments, ffc-frontend, ffc-frontend-helpers — casa por name, e nenhum CSS os referencia. Com o calendário convertido, os ids passam a existir só para o par <label for>. Ainda assim, duplicados quebravam a associação que um leitor de tela usa para anunciar um campo obrigatório, então MathCaptcha os sufixa por render. Os name são o contrato com o servidor e ficam intocados.

O cenário não é hipotético: DynamicFragments tem um ramo dedicado a múltiplos formulários, emitindo um desafio distinto para cada um.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would alter existing behavior)
  • Refactor / chore (no functional change)
  • Documentation only

Test plan

  • composer test7507 testes, 21650 asserções, verde
  • vendor/bin/phpcs --standard=phpcs.xml.dist includes/ templates/ — limpo
  • vendor/bin/phpcs --standard=phpcs-tests.xml.dist tests/ — limpo
  • npm run lint:js — 0 erros
  • npm run test:js:coverage — 116 arquivos, 1731 testes
  • npm run build:js — só ffc-calendar-frontend.min.js mudou
  • composer lint (PHPStan) — não executado (o pacote é dist-only e o proxy deste ambiente dá 403 na API do GitHub). Fica para o CI.
  • Smoke manual — pendente

Os testes novos foram verificados contra o código antigo. Revertendo o corpo do refreshCaptcha para a versão global, 3 dos 4 falham; com a correção, passam. Eles fixam o defeito, não descrevem a correção. No lado PHP, três casos cobrem ids distintos entre renders, o for apontando para o input do mesmo render, e a estabilidade dos name.

Checklist

  • JS re-minificado e commitado
  • CHANGELOG.md atualizado sob [Unreleased] / Fixed
  • Nenhuma entrada nova no baseline do PHPStan
  • Sem segredos, tokens ou PII no diff

O commit extra

193955a é um ajuste de @covers que não alcançou a janela do merge do #1055 — meu push saiu segundos depois de você mergear, e a branch já tinha sido deletada. Recuperei por cherry-pick sobre o novo develop. Ele adiciona SecurityService ao @covers do CaptchaProviderTest: o Coveralls reportava render_security_fields() como descoberto porque @covers filtra a atribuição, não porque faltasse teste. Vai de 67/73 para 72/73; a linha que sobra é o exit do guard ABSPATH.

Sobre a varredura de timers

A issue lista um segundo item: varrer os timers longos por outras instâncias da corrida de teardown corrigida no #1055. Fiz a medição e não fiz a mudança, deliberadamente.

O inventário estático são 16 timers de 1000ms ou mais, e vários vivem em arquivos carregados por testes que não usam fake timers:

Asset ms Testes sem fakeTimers
ffc-pdf-generator.js:353 10000 frontend-offline-and-overlay-hint, pdf-overlay-a11y
ffc-pdf-generator.js:607 6000 idem
ffc-admin.js:118 5000 5 arquivos
ffc-csv-download.js:88 4000 csv-and-rereg-frontend, csv-download-open-early
ffc-reregistration-frontend.js:382 3000 csv-and-rereg-frontend
ffc-frontend-helpers.js:646,651 1000 4 arquivos

Mas "carrega o arquivo sem fake timers" não é o mesmo que "vaza um timer" — o teste precisa alcançar o caminho que o agenda. E três rodadas da suíte JS completa produziram zero erros não tratados. Nada além da instância já corrigida está vazando de forma observável.

Adicionar fake timers a cinco arquivos que passam, com base numa heurística estática, seria churn com risco de quebrar testes que funcionam. O inventário fica registrado na issue para quem tiver uma reprodução; o checkbox permanece aberto com a medição anexada, não fechado em falso.

Refs #1056, #1053

🤖 Generated with Claude Code

https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU


Generated by Claude Code

Coveralls reported `SecurityService` at 25% patch coverage — 2 of the 8 changed
lines — with the whole body of `render_security_fields()` uncovered, even
though `test_render_security_fields_composes_honeypot_and_challenge` calls it
and asserts on its output.

The method was never untested; the report was filtered. `@covers` restricts
attribution to the classes it names, and this test class named only the two
captcha ones, so everything the composition tests executed inside
`SecurityService` was discarded. Adding it to `@covers` — with the
`class_exists()` preload CLAUDE.md prescribes for the pcov gotcha — takes the
file from 67/73 to 72/73; the one line left is the `exit` in the ABSPATH guard,
unreachable by construction.

No assertion changed. This makes the coverage report describe what the suite
actually does, so a later reader does not "add a test" for a covered method or
read it as dead.

Refs #1053

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU
…unique

The plugin supports several forms on one page on purpose — DynamicFragments
has a branch that mints a distinct challenge per form — but the captcha did not
hold up under it.

`ffcCalendarFrontend.refreshCaptcha()` was page-global. It rewrote the question
in *every* `.ffc-captcha-row` on the page, then wrote the new token through
`$('#ffc_captcha_hash')`, which by definition matches only the first element.
With two forms up, a rejection in either one left the second displaying a
question its token did not answer: the visitor answered what was on screen and
was told the math answer is incorrect — true, and useless as a diagnosis.

It now scopes to the submitted form and matches by `name`, which is what
`ffc-frontend-helpers.js` already did on the certificate path. `$form` was
already in scope at the call site, so nothing had to be restructured.

That leaves the ids used only for the `<label for>` pair, and a census
confirmed it: every other consumer — ffc-dynamic-fragments, ffc-frontend,
ffc-frontend-helpers — matches by `name`, and no CSS references them. Duplicate
ids still broke the label association a screen reader needs to announce a
required field, so `MathCaptcha` now suffixes them per render. The `name`
attributes are the contract with the server and are untouched.

Three of the four new JS tests fail against the previous implementation and
pass against this one, so they pin the defect rather than describing the fix.

Also carries a test-attribution fix that missed the #1055 merge window: that
class `@covers` SecurityService, taking the file from 67/73 to 72/73 — the
method was always tested, the report was filtered.

On the timer sweep this issue also lists: the inventory is 16 timers of 1000ms
or more, several loaded by tests that do not fake timers. But three runs of the
full JS suite produced zero unhandled errors, so nothing beyond the instance
already fixed is observably leaking. Adding fake timers to five passing test
files on a static heuristic would be churn; the inventory is recorded on the
issue for whoever has a reproduction.

Refs #1056, #1053

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU
@rpgmem
rpgmem marked this pull request as ready for review September 5, 2026 03:57
@rpgmem
rpgmem enabled auto-merge (squash) September 5, 2026 03:57
@rpgmem
rpgmem merged commit 9f62702 into develop Sep 5, 2026
19 checks passed
@rpgmem
rpgmem deleted the claude/altcha-captcha-integration-4vvuer branch September 5, 2026 04:05
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 33942993551

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Warning

No base build found for commit c1f9085 on develop.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 89.934%

Details

  • Patch coverage: 7 of 7 lines across 1 file are fully covered (100%).

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 53469
Covered Lines: 48087
Line Coverage: 89.93%
Coverage Strength: 4.83 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants