# crystal.stg.net/request-access — the only way a new person gets an account.
#
# Public (listed under public: in _auth.yaml), because someone with no account
# cannot get past the login gate to ask for one.
#
# Deliberately, this sends no email and grants nothing. It records the address
# as 'pending', and an administrator approves it on the Users page; only then can
# that address request a login code. Doing it the other way round — mail a code
# first, approve afterwards — would let any stranger make this server send
# "your login code" mail to an address of their choosing, from a host whose SPF
# already fails. Approval alone still grants nothing: the person must also pass
# the emailed code, so control of the mailbox is proven either way.
#
# The reply is identical whether or not the address is new, so this cannot be
# used to find out who already has an account.
title: Request access
main:
- php: |
if (!function_exists('h')) {
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
}
$root = $_SERVER['DOCUMENT_ROOT'] ?? '.';
require_once $root . '/users.php';
$done = '';
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
$email = strtolower(trim($_POST['email'] ?? ''));
// Same loose syntax check bserver applies before mailing: enough to
// reject junk, without trying to out-guess RFC 5322.
$ok = $email !== '' && strlen($email) <= 254
&& preg_match('/^[^\s@]+@[^\s@]+\.[^\s@]+$/', $email);
if ($ok) {
try {
$db = new PDO('sqlite:' . rc_db_path());
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// A crude per-address ceiling: repeated requests just update the
// same row, so the pending list cannot be flooded by one person
// hammering the form with the same address.
$db->prepare('INSERT INTO users (email, status, superuser, created, note)
VALUES (?, ?, 0, date("now"), ?)
ON CONFLICT(email) DO NOTHING')
->execute([$email, 'pending', 'requested access']);
} catch (Exception $e) {
// Fall through to the same message: a database hiccup should not
// tell a stranger anything about who does or does not have an account.
}
}
$done = '<div class="alert alert-success">Thanks — your request has been recorded. '
. 'You will be able to sign in once the site owner approves it. '
. 'Nothing has been emailed to you yet.</div>';
}
echo $done;
if ($done === '') {
echo '<p class="text-muted">This cookbook is private. Leave your email address and the '
. 'owner can approve you; after that, signing in emails you a one-time code.</p>';
}
echo '<form method="post" action="/request-access?go" class="mt-3" style="max-width:22rem">'
. '<label class="form-label" for="email">Email address</label>'
. '<input class="form-control" id="email" name="email" type="email" required '
. 'placeholder="you@example.com" autocomplete="email">'
. '<button class="btn btn-primary mt-3" type="submit">Request access</button>'
. '</form>';
echo '<p class="mt-3"><a href="/">← Back to home</a> · <a href="/auth/login">Already approved? Sign in</a></p>';
View Source (request-access.yaml) Powered by bserver - YAML-driven web serving