The challenge interstitial, and how to make it your own.
The page has no inline scripts or styles: it loads solver.js and
challenge.css from the challenge path, reads the challenge from data-
attributes, and is served with its own strict Content-Security-Policy
(content_security_policy/0).
Your own words
The page is in English unless the instance's :page (an option of the
:challenge configuration) is a module of yours implementing this
behaviour. text/2 is called for every page served, with the request, so
it can speak the visitor's language:
defmodule MyAppWeb.ChallengePage do
use Limen.Challenge.Page
use Gettext, backend: MyAppWeb.Gettext
@impl true
def text(conn, vars) do
Gettext.put_locale(MyAppWeb.Gettext, MyAppWeb.Locale.from_conn(conn))
%{
lang: Gettext.get_locale(MyAppWeb.Gettext),
title: gettext("Checking your browser"),
heading: gettext("Checking your browser"),
message: gettext("This takes a moment and only happens once in a while."),
no_js: no_js(vars)
}
end
defp no_js(%{seconds: nil}), do: gettext("Please enable JavaScript to continue.")
defp no_js(%{seconds: seconds}),
do: gettext("You will be taken to the page in %{seconds} seconds.", seconds: seconds)
end
config :my_app, Limen, challenge: [page: MyAppWeb.ChallengePage]It returns any of these keys, as plain text; the rest keep their English
defaults (see default_text/1):
:lang- the page's language, for<html lang>;:title,:headingand:message- what the visitor reads while the check runs;:no_js- what visitors without JavaScript read: how long they wait whenvars.secondsis an integer (no_js: {:meta_refresh, seconds}), or that they need JavaScript when it isnil(no_js: :deny);:solved,:failedand:unsupported- what the script shows when the check succeeds, cannot run, or the browser has no Web Workers.
text/2 runs on the request path of every challenged navigation: no
calls to processes, and nothing slow.
Your own page
render/1 returns the whole page. Override it to change the markup, for
example to add your own stylesheet, which the page's
Content-Security-Policy allows as long as it comes from your own origin:
@impl true
def render(assigns) do
Limen.Challenge.Page.template(%{assigns | stylesheets: assigns.stylesheets ++ ["/assets/challenge.css"]})
endA page of your own must keep the contract with solver.js, and use no
inline scripts or styles (the Content-Security-Policy blocks them): an
element with the id limen-challenge and data-token, data-difficulty
and data-worker attributes (and optionally data-text-solved,
data-text-failed and data-text-unsupported), an element with the id
limen-status for messages, a form with the id limen-form posting to
verify the hidden fields token, nonce (empty), return_to and
instance, and a <script src> for solver. template/1 is the
reference. Assigns are not escaped: escape them, as template/1 does.
Assigns:
:text- the texts above, merged over the defaults;:token,:difficulty,:return_toand:instance- the challenge;:solver,:workerand:verify- the URLs of the script, its worker and the verification endpoint;:stylesheets- stylesheet URLs, Limen's by default;:refresh- the content of the no-JavaScript meta refresh, ornil.
Summary
Callbacks
The page, from the assigns described in the module documentation.
The page's texts for the request, in any subset: see the module documentation for the keys.
Functions
The Content-Security-Policy the page is served with.
The English texts, for vars.
Renders Limen's page from assigns, escaping them.
Types
@type vars() :: %{seconds: non_neg_integer() | nil}
What text/2 knows about the page: the no-JavaScript wait, if any.
Callbacks
The page, from the assigns described in the module documentation.
@callback text(conn :: Plug.Conn.t(), vars()) :: map() | keyword()
The page's texts for the request, in any subset: see the module documentation for the keys.
Functions
@spec content_security_policy() :: String.t()
The Content-Security-Policy the page is served with.
The English texts, for vars.
Renders Limen's page from assigns, escaping them.