Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,40 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- The components under the package prefix: `<x-laranail-captcha::captcha />`,
`<x-laranail-captcha::js />` and `<x-laranail-captcha::container />`.
- The `laranail.captcha` container alias for `CaptchaService`.
- The `laranail_captcha` string validation rule, implicit like the bare one, reporting the rule's own
translated message on failure (`CaptchaServiceProvider::VALIDATION_RULE`).
- The canonical `laranail/captcha` view namespace, registered by package-tools over the same paths
as `laranail-captcha`.
- A live-registry naming test (`tests/Feature/NamingConventionTest.php`) built on package-tools'
`AssertsRegisteredNames`.

### Changed

- The components render through `laranail/captcha::components.*`. `laranail-captcha::` still
resolves the same files.
- Docs, the README and the install command lead with the scoped tags and rule.
- Requires `laranail/package-tools ^0.1.3`.
- `laranail::captcha.install` now extends laranail/package-tools' `InstallCommand` and takes
laranail/console's display API and run lifecycle from its `InteractsWithConsoleServices` and
`InteractsWithConsoleWriter` traits instead of its `Command` base. Name, option, description,
listing visibility, output and exit codes are unchanged and pinned by a new contract test. The
command is bound in the container because the new base takes the `Package` in its constructor.

### Deprecated

- The bare `<x-captcha />`, `<x-captcha-js />` and `<x-captcha-container />` tags. They render the
same components and raise one `E_USER_DEPRECATED` notice when a template using them compiles.
- The bare `captcha` validation rule. It validates as before, implicit included, with one
`E_USER_DEPRECATED` notice per process.
- The bare `captcha` container alias. It resolves the same `CaptchaService`; it cannot raise a notice.

Each is removed no earlier than the next minor after 0.1.

## [0.1.0] - 2026-08-15

### Added
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ To switch provider, set `CAPTCHA_PROVIDER`, `CAPTCHA_SITE_KEY` and `CAPTCHA_SECR
<form method="post" action="/register">
@csrf

<x-captcha />
<x-laranail-captcha::captcha />

<button type="submit">Create account</button>
</form>
Expand All @@ -54,7 +54,7 @@ To switch provider, set `CAPTCHA_PROVIDER`, `CAPTCHA_SITE_KEY` and `CAPTCHA_SECR
```php
$request->validate([
'email' => ['required', 'email'],
'captcha' => ['captcha'],
'captcha' => ['laranail_captcha'],
]);
```

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"laranail/console": "^0.1",
"laranail/db-tools": "^0.1",
"laranail/enumerator": "^0.1",
"laranail/package-tools": "^0.1",
"laranail/package-tools": "^0.1.3",
"psr/clock": "^1.0",
"psr/log": "^3.0"
},
Expand Down
18 changes: 18 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@ very different setting from a missing one.
One consequence to know: changing captcha config at runtime needs the service forgotten from the
container, because the policy was already read.


## Public names carry the vendor

Laravel keeps Blade component aliases, container aliases, validation rules and view namespaces in
flat, host-owned maps, where a second claimant silently replaces the first. So:

| Surface | Name | Deprecated alias (removed no earlier than the next minor after 0.1) |
|---|---|---|
| Blade components | `<x-laranail-captcha::captcha />`, `::js`, `::container` | `<x-captcha />`, `<x-captcha-js />`, `<x-captcha-container />` |
| Container alias | `laranail.captcha` (and `CaptchaService::class`) | `captcha` |
| Validation rule | `laranail_captcha` | `captcha` |
| View namespace | `laranail/captcha` (canonical), `laranail-captcha` (kept, not deprecated) | |
| Route name | `laranail.captcha.challenge` | |

Each deprecated alias still works. The tags and the rule raise one `E_USER_DEPRECATED` notice; the
container alias cannot, because the container offers no hook on alias resolution.
`tests/Feature/NamingConventionTest.php` asserts all of it against the live registries.

---

[← Docs index](../README.md#documentation)
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Leave `hmac_key` null on the self-hosted providers and a key is derived from `AP
## `widget`

`theme`, `size`, `language` and `nonce`, applied to whichever provider is active. Set `nonce` and
pass one to `<x-captcha :nonce="$nonce" />` to keep a strict CSP without `unsafe-inline`.
pass one to `<x-laranail-captcha::captcha :nonce="$nonce" />` to keep a strict CSP without `unsafe-inline`.

## `bot_management`

Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ A protected form and a verified submission, in one page.
```blade
<form method="post" action="/register">
@csrf
<x-captcha />
<x-laranail-captcha::captcha />
<button type="submit">Create account</button>
</form>
```

```php
$request->validate([
'email' => ['required', 'email'],
'captcha' => ['captcha'],
'captcha' => ['laranail_captcha'],
]);
```

Expand Down
5 changes: 4 additions & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ From `rahul900day/laravel-captcha`, and from the `laranail/toolkit` captcha modu
```

`Rahul900day\Captcha\` becomes `Simtabi\Laranail\Captcha\`. The `Captcha` facade alias and the
`<x-captcha-js />` / `<x-captcha-container />` tags are unchanged, so your Blade needs no edits.
`<x-captcha-js />` / `<x-captcha-container />` tags still work, so your Blade needs no edits on day
one. The tags and the `captcha` string rule are deprecated aliases: move to
`<x-laranail-captcha::js />`, `<x-laranail-captcha::container />` and `laranail_captcha` (see
[Blade components](tools/blade-components.md) and [the validation rule](tools/validation-rules.md)).

### Read these before deploying

Expand Down
4 changes: 2 additions & 2 deletions docs/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ to choose — so no provider fact lives in two places.

- **reCAPTCHA v3 / Enterprise** need an action name (`providers.recaptcha.action`), and Enterprise
needs a `project_id` alongside an API key as the secret.
- **reCAPTCHA v3 and v2-invisible have nothing to click.** `<x-captcha />` wires the execution for
- **reCAPTCHA v3 and v2-invisible have nothing to click.** `<x-laranail-captcha::captcha />` wires the execution for
you: it intercepts the enclosing form's submit once, mints the token and replays the submit. If
you place `<x-captcha-container />` by hand instead, you have to call `grecaptcha.execute()`
you place `<x-laranail-captcha::container />` by hand instead, you have to call `grecaptcha.execute()`
yourself, or the form submits with no token at all.
- **Arkose** needs a `client` extra — its verify endpoint is per-customer
(`{client}-verify.arkoselabs.com`).
Expand Down
4 changes: 2 additions & 2 deletions docs/recipes/protect-a-livewire-form.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The most-asked question about the package this replaced, and the one it never an
<form wire:submit="register">
<input type="email" wire:model="email">

<x-captcha />
<x-laranail-captcha::captcha />

<button type="submit">Create account</button>
</form>
Expand Down Expand Up @@ -58,7 +58,7 @@ new class extends Component {
?>

<form wire:submit="register">
<x-captcha />
<x-laranail-captcha::captcha />
<button type="submit">Create account</button>
</form>
```
Expand Down
33 changes: 19 additions & 14 deletions docs/tools/blade-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ Three components. Most forms need only the first.

| Tag | Renders |
|---|---|
| `<x-captcha />` | Everything — script and widget, or a server-rendered question |
| `<x-captcha-js />` | The active provider's script tag alone |
| `<x-captcha-container />` | The widget mount point alone |
| `<x-laranail-captcha::captcha />` | Everything — script and widget, or a server-rendered question |
| `<x-laranail-captcha::js />` | The active provider's script tag alone |
| `<x-laranail-captcha::container />` | The widget mount point alone |

## `<x-captcha />`
## `<x-laranail-captcha::captcha />`

```blade
<form method="post" action="/register">
@csrf
<x-captcha />
<x-laranail-captcha::captcha />
<button type="submit">Create account</button>
</form>
```
Expand All @@ -27,33 +27,38 @@ and widget div.
The split exists because asking someone to place two tags correctly is the difference between a
package that gets used and one that gets copied wrong from Stack Overflow.

## `<x-captcha-js />` and `<x-captcha-container />`
## `<x-laranail-captcha::js />` and `<x-laranail-captcha::container />`

For layouts that want the script in `<head>` and the widget further down:

```blade
<head>
<x-captcha-js lang="fr" nonce="{{ $nonce }}" />
<x-laranail-captcha::js lang="fr" nonce="{{ $nonce }}" />
</head>
<body>
<form method="post">
@csrf
<x-captcha-container theme="dark" size="compact" />
<x-laranail-captcha::container theme="dark" size="compact" />
</form>
</body>
```

These are the tags the package has always documented, so markup written against the original
integration keeps working — the migration is a namespace change rather than a sweep through every
Blade file.
## Deprecated bare tags

`<x-captcha />`, `<x-captcha-js />` and `<x-captcha-container />` are the tags the package
documented before 0.1, and they still render the same components. They are deprecated aliases,
removed no earlier than the next minor after 0.1: Blade's component aliases are one flat,
host-owned map, so a bare `captcha` tag is one sibling package away from being silently replaced.
Each raises one `E_USER_DEPRECATED` notice, when a template using it compiles. Replace them with
the `laranail-captcha::` tags above.

## Providers with nothing to click

reCAPTCHA v3 and v2-invisible mint their token from `grecaptcha.execute()` rather than from a
checkbox. `<x-captcha />` handles that: it intercepts the enclosing form's submit once, mints the
checkbox. `<x-laranail-captcha::captcha />` handles that: it intercepts the enclosing form's submit once, mints the
token into a hidden `captcha` field and replays the submit.

That is why the all-in-one tag is worth preferring. `<x-captcha-container />` alone renders an empty
That is why the all-in-one tag is worth preferring. `<x-laranail-captcha::container />` alone renders an empty
div for those two providers, and the form submits with no token — the failure looks like the captcha
simply not working, with nothing in the logs.

Expand All @@ -73,7 +78,7 @@ and a JavaScript identifier.
## Content Security Policy

```blade
<x-captcha :nonce="$nonce" />
<x-laranail-captcha::captcha :nonce="$nonce" />
```

Emitted on the script tag, so a strict CSP does not need `unsafe-inline`.
Expand Down
4 changes: 2 additions & 2 deletions docs/tools/recaptcha.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ reading `tokenProperties.valid` and `riskAnalysis.score`.

## v3 and v2-invisible have nothing to click

Their token only exists once `grecaptcha.execute()` has run. `<x-captcha />` wires that: it
Their token only exists once `grecaptcha.execute()` has run. `<x-laranail-captcha::captcha />` wires that: it
intercepts the enclosing form's submit once, mints the token and replays the submit. Place
`<x-captcha-container />` by hand instead and the form submits with no token at all — a failure
`<x-laranail-captcha::container />` by hand instead and the form submits with no token at all — a failure
that looks like the captcha simply not working, with nothing in any log.

## The score is enforced, not reported
Expand Down
2 changes: 1 addition & 1 deletion docs/tools/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ while the entire suite stays green. Nothing else here can see that.
`.github/workflows/install.yml` builds the dist with `git archive` (exactly how Composer builds one
from a tag), asserts both directions — that nothing the runtime needs was stripped, and that
`tests/`, `docs/` and `.github/` did not leak into it — then installs the result into a real Laravel
application, runs `laranail::captcha.doctor`, renders `<x-captcha />` and publishes every advertised
application, runs `laranail::captcha.doctor`, renders `<x-laranail-captcha::captcha />` and publishes every advertised
tag, checking the files landed. `vendor:publish` exits zero for a tag that does not exist, so each
tag is verified by its output rather than its exit code.

Expand Down
2 changes: 1 addition & 1 deletion docs/tools/turnstile.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ works here the day they ship it without a release from us.
| `theme` | `auto` · `light` · `dark` | |

**For a full-width widget set `size` to `flexible`** — via `CAPTCHA_SIZE=flexible`, the
`captcha.widget.size` config key, or per-widget with `<x-captcha-container size="flexible" />`.
`captcha.widget.size` config key, or per-widget with `<x-laranail-captcha::container size="flexible" />`.
Turnstile sizes itself to the container, so the container also needs a width; it is not a fixed
`100%` on the widget itself.

Expand Down
13 changes: 11 additions & 2 deletions docs/tools/validation-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
One rule, two forms, and one property that matters more than the rest.

```php
$request->validate(['captcha' => ['captcha']]);
$request->validate(['captcha' => ['laranail_captcha']]);
```

```php
Expand All @@ -13,6 +13,15 @@ $request->validate(['captcha' => [new Captcha]]);
$request->validate(['captcha' => [Captcha::for('login')]]);
```

The string rule is `laranail_captcha`, spelled the way `laranail/validation` spells its rules
(`laranail_iban`): the validator's rule map is flat and host-owned, and an underscore survives
Laravel's studly/snake round trip, so the message key is the rule name as written. On failure it
reports the rule's own translated message.

**The bare `captcha` rule is a deprecated alias**, removed no earlier than the next minor after
0.1. It validates exactly as before, implicit included, and raises one `E_USER_DEPRECATED` notice
per process.

## It is implicit

A non-implicit rule is **skipped entirely when the field is absent from the request**. Omitting the
Expand All @@ -26,7 +35,7 @@ because an application can reach the rule either way.
## Pairing with `required`

Harmless. Laravel stops validating an attribute once an implicit rule on it has failed, so
`['required', 'captcha']` on a missing field produces one message rather than two.
`['required', 'laranail_captcha']` on a missing field produces one message rather than two.

## Binding to an action

Expand Down
2 changes: 1 addition & 1 deletion src/Commands/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function handle(): int
}

$this->services->display()->info(sprintf(
'Active provider: %s. Drop <x-captcha /> in a form and add \'captcha\' => \'captcha\' to its rules.',
'Active provider: %s. Drop <x-laranail-captcha::captcha /> in a form and add \'captcha\' => \'laranail_captcha\' to its rules.',
$this->activeProvider(),
));

Expand Down
Loading
Loading