# `MishkaCmsCore.Captcha`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/captcha.ex#L5)

The captcha a site's sign-in, registration, forgot-password and sign-in-link forms ask, through
the provider the site picked.

Each scope picks a provider on the settings page's Security tab (`security.captcha.provider`) and
sets that provider's keys there, for itself alone (`resolve: :own`): Global is the masters' own
sign-in. Providers are `mishka_installer` plugins (`MishkaCmsCore.Captcha.Provider`), the core's
own — Cloudflare Turnstile, Google reCAPTCHA and hCaptcha — included. A scope with no provider
(None) or with one missing a key asks nothing. A scope whose chosen provider does not answer — its
plugin is gone, raised, or cannot run (`MishkaCmsCore.ProviderRegistry`) — refuses every check
until it chooses another or None.

## Where

A form draws the check with `<.captcha>` (`MishkaCmsCore.Captcha.Widget`), which `browser/3`
describes, and its action checks the token (`MishkaCmsCore.Accounts.CaptchaCheck`), so the sign-in
pages and the `/api/auth` routes ask alike. An extension's form asks the same captcha under a form
of its own, an atom its action and its `<.captcha for={…}>` both name, and may ask it only once the
form has been sent (`deferred`, see `MishkaCmsCore.Captcha.Widget`). The security headers let the
browser load the provider on the sites that use it (`csp_sources/1`).

## One check

`check/4` refuses a missing token, and a token it saw accepted in the last five minutes — each is
good for one check — without asking anybody, then asks the provider, then holds its answer to the
site's own address (`security.captcha.check_domain`). A provider that cannot be asked — down, slow,
refusing the keys — is answered `{:error, {:unavailable, detail}}`, and the action lets the form
through: its rate limits still hold.

# `answer`

```elixir
@type answer() ::
  :ok
  | {:error, :missing}
  | {:error, {:failed, String.t()}}
  | {:error, {:unavailable, String.t()}}
```

# `form`

```elixir
@type form() :: MishkaCmsCore.Captcha.Provider.form()
```

# `request`

```elixir
@type request() :: %{optional(:remote_ip) =&gt; String.t() | nil}
```

# `site_id`

```elixir
@type site_id() :: String.t() | nil
```

# `browser`

```elixir
@spec browser(site_id(), form(), String.t()) :: map() | nil
```

How the browser draws `site_id`'s check for `form` in `language` — the provider's
`c:MishkaCmsCore.Captcha.Provider.browser/3` with its `:provider` id — or `nil` when it asks none.

# `check`

```elixir
@spec check(String.t() | nil, site_id(), form(), request()) :: answer()
```

Checks `token`, sent with `form` to `site_id`: `:ok` when it passed or the scope asks none;
`{:error, :missing}` without one; `{:error, {:failed, detail}}` when it was used before, the
provider refused it, it was solved on another address, or the provider the scope chose does not
answer; `{:error, {:unavailable, detail}}` when the provider could not be asked.

# `csp_sources`

```elixir
@spec csp_sources([site_id()] | :all) :: %{optional(atom()) =&gt; [String.t()]}
```

The origins, by CSP directive, the browser loads the captcha of any of `site_ids` from — none for
a scope that asks none. `:all` names every offered provider's, for the settings page, where any of
them may be tried.

# `on?`

```elixir
@spec on?(site_id()) :: boolean()
```

Whether `site_id`'s forms ask a captcha now.

# `provider`

```elixir
@spec provider(site_id()) ::
  {:ok, module()}
  | {:error, :off | :unknown_provider | :cannot_run | :not_ready}
```

The provider `site_id` checks with, ready to check: `{:ok, module}`, or `{:error, reason}` —
`:off` (none chosen), `:unknown_provider` (chosen, but its plugin is gone), `:cannot_run` (chosen,
while the providers' plugins cannot run) or `:not_ready` (chosen, but missing a key).

# `settings`

```elixir
@spec settings() :: [MishkaCmsCore.Settings.definition()]
```

The settings the Security tab shows under "Captcha": which provider a scope uses, whether its
answer is held to the site's address, then every offered provider's own — each drawn only while
its provider is the one chosen (`MishkaCmsCore.Settings.shown_when/3`). Part of
`MishkaCmsCore.Settings`' core definitions.

# `test`

```elixir
@spec test(site_id(), String.t() | nil, request()) ::
  {:ok, String.t()} | {:error, String.t()}
```

The settings page's test of `site_id`'s keys with a `token` solved on it: what to tell the admin,
`{:ok, text}` or `{:error, text}`. The same check a form gets, so a passed test spends the token.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
