Add email verification for user accounts

Every account created after the first must confirm a 24-hour link
(Lib\Mailer::sendMail(), log-file or MailJet) before AdminAuth::attempt()
allows login, folded into the same generic pass/fail as a disabled account
or wrong password. First user and pre-migration rows are grandfathered;
create-admin-user.php also auto-verifies for lockout recovery.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
code
2026-07-15 19:23:20 +00:00
parent c0455241ea
commit e699027b4b
13 changed files with 396 additions and 41 deletions
+9
View File
@@ -51,4 +51,13 @@ return [
// `php novaconium/bin/index-content.php` (e.g. from a deploy step):
// 'content_index_enabled' => true,
// 'content_index_auto' => false,
// Docs: /admin/docs/admin-auth — email verification. Set all four to
// switch Lib\Mailer's transactional mail (verification links, not the
// contact form) from the zero-dependency log fallback to MailJet:
// 'mail_driver' => 'mailjet',
// 'mail_from_email' => 'noreply@example.com',
// 'mail_from_name' => 'Example Site',
// 'mailjet_api_key' => '...',
// 'mailjet_api_secret' => '...',
];
@@ -32,7 +32,7 @@
<li><strong>Layout inheritance</strong> — <code>_layout/layout.twig</code> directories are resolved by walking upward from the matched page, so you can override the layout for a whole subtree.</li>
<li><strong>SEO boilerplate out of the box</strong> — the default layout ships meta description, canonical link, robots, Open Graph, and Twitter Card tags, all overridable per-page via Twig blocks.</li>
<li><strong>Built-in Matomo analytics</strong> — set <code>matomo_url</code> and <code>matomo_site_id</code> in <code>App/config.php</code> to enable tracking site-wide, including automatic 404 tracking. Off by default.</li>
<li><strong>Admin authentication</strong> — gate every <code>/admin/*</code> route behind a session login with multi-user management: a SQLite-backed <code>users</code> table, <code>/admin/login</code>/<code>/admin/logout</code>, and an <code>/admin/users</code> page to create, disable, delete, group, promote/demote, and change the email or password of accounts (plus a <code>novaconium/bin/create-admin-user.php</code> CLI for the first user or deploy scripts). Two roles: the first user created is the admin; everyone after is a registered user with an optional group. Enabled with a single <code>admin_auth_enabled</code> flag in <code>App/config.php</code>; off by default.</li>
<li><strong>Admin authentication</strong> — gate every <code>/admin/*</code> route behind a session login with multi-user management: a SQLite-backed <code>users</code> table, <code>/admin/login</code>/<code>/admin/logout</code>, and an <code>/admin/users</code> page to create, disable, delete, group, promote/demote, and change the email or password of accounts (plus a <code>novaconium/bin/create-admin-user.php</code> CLI for the first user or deploy scripts). Two roles: the first user created is the admin; everyone after is a registered user with an optional group. Every account after the first must verify its email (a link sent via <code>Lib\Mailer</code>, logged to a file by default or sent through MailJet if configured) before it can log in. Enabled with a single <code>admin_auth_enabled</code> flag in <code>App/config.php</code>; off by default.</li>
<li><strong>Access control</strong> — assign a page (or a section, one line per page) to a user or group from its sidecar: <code>Access::require('group:members')</code> returns <code>null</code> or a ready-made <code>Response</code> (login redirect with a return path, or a 404 for the wrong account). Public is the default — a sidecar that never calls it is untouched, and static (sidecar-less, cached) pages are always public by construction.</li>
<li><strong>Draft pages</strong> — list a route under <code>draft_routes</code> in <code>App/config.php</code> to make it visible only to an authenticated admin; anyone else gets a plain 404, not a login prompt.</li>
<li><strong>Media manager</strong> — <code>/admin/media</code>, an upload/browse/delete UI for files under <code>public/uploads/</code>, covered by the existing <code>/admin/*</code> auth gate with no separate flag needed. Extension allowlist and max upload size are configurable.</li>