Skip to content

feat: Web Bot Auth verifier + authenticated frictionless flow - #3145

Open
forgetso wants to merge 5 commits into
mainfrom
feat/web-bot-auth-authenticated-flow
Open

feat: Web Bot Auth verifier + authenticated frictionless flow#3145
forgetso wants to merge 5 commits into
mainfrom
feat/web-bot-auth-authenticated-flow

Conversation

@forgetso

Copy link
Copy Markdown
Member

Summary

  • New @prosopo/web-bot-auth package: RFC 9421 HTTP Message Signatures verifier built on @noble/curves/ed25519 (no Cloudflare dep). Parses Signature-Agent (bare-string + dictionary forms), resolves JWKS at /.well-known/http-message-signatures-directory with cache-control TTL, verifies Ed25519 over the RFC 9421 signature base.
  • Frictionless authenticated fast-path in the provider: when a Web Bot Auth verified request matches an Allow policy at /captcha/frictionless, returns captchaType: authenticated with a session record (serverChecked: false, agent: true, webBotAuthAgent, IP frozen for verify-time binding). Skips decrypt / bot-score / decision machine.
  • New /client/authenticated/verify router with mandatory IP-binding (API.AUTHENTICATED_IP_REQUIRED / API.AUTHENTICATED_IP_MISMATCH), single-use via serverChecked, captchaType gate against replaying other token types.
  • AccessPolicyType.Allow and CaptchaType.authenticated added. Session.agent / Session.webBotAuthAgent fields for the "pre-verified pass" analytics filter.
  • ClientSessionId + email support on the authenticated verify path: submitAuthenticatedCaptchaVerify on ProviderApi, CaptchaType.authenticated branch in @prosopo/server.verifyProvider, verifyAuthenticatedSession uses the shared isClientSessionMismatch helper for parity with pow/image/puzzle.
  • procaptcha-frictionless gets AuthenticatedBadge + a dispatch branch for CaptchaType.authenticated.

Test plan

  • npm run -w @prosopo/web-bot-auth test — signature-base, structured-fields, Signature-Agent parsing, JWKS resolver, verify happy path
  • npm run -w @prosopo/provider build:tsc — provider type-checks
  • Frictionless entry with a signed request matching an Allow rule → captchaType: authenticated, session persisted with agent: true
  • /authenticated/verify — missing IP → API.AUTHENTICATED_IP_REQUIRED; wrong IP → API.AUTHENTICATED_IP_MISMATCH; wrong clientSessionId → API.CLIENT_SESSION_MISMATCH; second use → API.USER_ALREADY_VERIFIED
  • @prosopo/server.isVerified on an authenticated token dispatches to submitAuthenticatedCaptchaVerify (not the legacy image fallback)

🤖 Generated with Claude Code

forgetso and others added 4 commits August 25, 2026 16:38
New package @prosopo/web-bot-auth: RFC 9421 HTTP Message Signatures verifier
using @noble/curves/ed25519 (no Cloudflare dep). Handles both bare-string
(OpenAI) and dictionary (Google) Signature-Agent header forms. 17 unit tests
including full Ed25519 round-trip against golden vectors and replay defence.

Frictionless flow gains an authenticated fast-path — any non-deferToVerify
Allow rule matching the userScope (webBotAuthAgent, ip_cidr, ja4, ua, asn,
country) mints an `authenticated` session, skipping decrypt/detect/DM. Web
Bot Auth is one of many qualifiers, not the only one.

New endpoint POST /v1/prosopo/provider/client/authenticated/verify enforces
IP binding: the operator must forward the client IP and it must equal the
session's captured IP. serverChecked flow gives single-use. ipMatchesSession
extracted to a pure function with 13 unit tests.

Schema:
- CaptchaType.authenticated added
- AccessPolicyType.Allow added
- Session record gains `agent: boolean` and `webBotAuthAgent: string`
- user-access-policy schema gains webBotAuthAgent scope field (rule.ts,
  ruleRecord.ts, ruleInput/userScopeInput.ts with URL normalisation,
  Mongoose + Redis TAG index)
- ClientSettings gains `allowAgents?: boolean` (opt-in gate)
- GetFrictionlessCaptchaResponse carries `agent` on authenticated responses

Widget:
- AuthenticatedBadge component: on mount encodes ProcaptchaToken with
  sessionId as commitmentId, fires events.onHuman. Renders compact
  "Verified agent: chatgpt.com" or "Trusted request" fallback.
- ProcaptchaFrictionless dispatch branch for CaptchaType.authenticated

Fixes:
- dev/config vite.esm.config.ts: @ts-expect-error on Rolldown polyfillRequire
- dev/config configs.test.ts: narrow cast for Vitest custom-provider coverage
- provider/util.ts: Mongoose 8 Document._id cast to Types.ObjectId
Upstream Vite's Rollup output types now include polyfillRequire, so the
directive is unused and TSC flags it as such. Drop the suppression.
…icated captcha type

Extend the Web Bot Auth authenticated fast-path so it participates in the
render-time correlation the pow/image/puzzle flows enforce.

- `GetFrictionlessCaptchaChallengeRequestBody` gains optional
  `clientSessionId` — captured at issuance and persisted onto the session
  record's `clientMetaData`, mirroring how the other captcha types store it.
- `verifyAuthenticatedSession` compares via the shared
  `isClientSessionMismatch` helper — same asymmetric semantics as
  pow/image/puzzle: verify-side opts in; a real mismatch fails with
  `API.CLIENT_SESSION_MISMATCH`.
- `ProviderApi.submitAuthenticatedCaptchaVerify(token, sig, user, ip?,
  email?, clientSessionId?)` — dedicated endpoint call so
  `@prosopo/server` can dispatch on captchaType without a token-shape
  branch. Email is a wire-parity passthrough (no email correlation on
  authenticated — Web Bot Auth doesn't carry one).
- `verifyProvider` gets a `CaptchaType.authenticated` branch that routes
  to the new method with ip + email + clientSessionId forwarded.

Net effect: a customer's siteverify Lambda passes an authenticated token
to `@prosopo/server.isVerified` and it dispatches correctly instead of
falling through to the legacy image path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

No updates since 2026-08-27T15:36:37Z (over 1 days), so this PR has been converted to draft. That stops it holding CI runners.

Nothing is lost — gh pr ready 3145, or the "Ready for review" button, picks it straight back up. Add the keep-ready label to exempt it permanently.

Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely.

@github-actions

Copy link
Copy Markdown
Contributor

No updates since 2026-08-28T15:57:57Z (over 1 days), so this PR has been converted to draft. That stops it holding CI runners.

Nothing is lost — gh pr ready 3145, or the "Ready for review" button, picks it straight back up. Add the keep-ready label to exempt it permanently.

Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely.

@github-actions

Copy link
Copy Markdown
Contributor

No updates since 2026-08-29T18:01:00Z (over 1 days), so this PR has been converted to draft. That stops it holding CI runners.

Nothing is lost — gh pr ready 3145, or the "Ready for review" button, picks it straight back up. Add the keep-ready label to exempt it permanently.

Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely.

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.

1 participant