MishkaCmsCore.Captcha (Mishka CMS Core v0.0.1)

Copy Markdown View Source

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.

Summary

Functions

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

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.

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.

Whether site_id's forms ask a captcha now.

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).

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.

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.

Types

answer()

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

form()

request()

@type request() :: %{optional(:remote_ip) => String.t() | nil}

site_id()

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

Functions

browser(site_id, form, language)

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

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

check(token, site_id, form, request)

@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(site_ids)

@spec csp_sources([site_id()] | :all) :: %{optional(atom()) => [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?(site_id)

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

Whether site_id's forms ask a captcha now.

provider(site_id)

@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()

@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(site_id, token, request)

@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.