4862526fa1
Lets a page under App/pages/ be previewed by an admin before the public can see it: list its route in draft_routes (App/config.php), checked in bootstrap.php alongside the existing /admin/* gate. Not authenticated -> same plain 404 an unmatched route gets, not a login prompt, so a draft's existence isn't revealed. Authenticated -> renders normally. No separate login flow needed - Basic Auth credentials are scoped to the whole origin/realm, so authenticating once at /admin covers draft URLs too. AdminAuth::isAuthenticated() is extracted out of requireLogin() so the draft gate can reuse the same credential check with a different failure response (404 vs. a 401 challenge). Renderer::render() gains an $excludeFromCache param so a draft without its own sidecar can't get written to the static HTML cache - .htaccess serves a cached file before PHP, and therefore any auth check, ever runs again, so an uncached exception is required, not just the auth gate. While testing this, found the same bug already existed for /admin itself: novaconium/pages/admin/index.twig has no sidecar, so it was already being cached - meaning any admin visiting /admin once caused the panel to be served to everyone, unauthenticated, straight from public/cache/admin/. Fixed in this change by excluding every /admin/* route from the cache the same way, and documented as a standing rule in AGENTS.md: any future mechanism that conditionally hides page content from the public has to make the same check, not just gate the initial request. Closes the "Draft pages (admin-only preview)" backlog item in novaconium/ISSUES.md.
77 lines
3.9 KiB
PHP
77 lines
3.9 KiB
PHP
<?php
|
|
|
|
// These are the framework defaults. A project overrides any subset of them
|
|
// by creating App/config.php returning an array of just the keys it wants
|
|
// to change — novaconium/bootstrap.php shallow-merges it over this file, the
|
|
// same App-over-novaconium override pattern used for pages/ and lib/. This
|
|
// file itself is not meant to be edited per-project.
|
|
return [
|
|
// Ordered override roots: App/pages is checked first so a project can
|
|
// override any page, sidecar, or layout by placing one at the same
|
|
// relative path there; novaconium/pages supplies the framework defaults.
|
|
'pages_dirs' => [
|
|
__DIR__ . '/../App/pages',
|
|
__DIR__ . '/pages',
|
|
],
|
|
'cache_dir' => __DIR__ . '/../public/cache',
|
|
'debug' => true,
|
|
|
|
// Site name used as the default page title, og:site_name, and the
|
|
// footer copyright line in novaconium/pages/_layout/layout.twig.
|
|
'site_name' => 'Novaconium Website',
|
|
|
|
// Matomo analytics. Leave both empty (the default) to disable tracking
|
|
// entirely — the layout emits no tracking script at all in that case.
|
|
// Set both via App/config.php to enable, e.g.:
|
|
// 'matomo_url' => 'https://matomo.example.com/',
|
|
// 'matomo_site_id' => '1',
|
|
'matomo_url' => '',
|
|
'matomo_site_id' => '',
|
|
|
|
// Gates every /admin/* route (clear-cache, docs, and any future admin
|
|
// page) behind HTTP Basic Auth. Leave admin_password_hash empty (the
|
|
// default) to disable the gate entirely — matches this project's
|
|
// existing wide-open behavior until a project opts in. Generate a hash
|
|
// with: php -r "echo password_hash('yourpassword', PASSWORD_DEFAULT), PHP_EOL;"
|
|
// and set both via App/config.php, e.g.:
|
|
// 'admin_username' => 'admin',
|
|
// 'admin_password_hash' => '$2y$10$...',
|
|
'admin_username' => 'admin',
|
|
'admin_password_hash' => '',
|
|
|
|
// Lib\Db (see /admin/docs/database) — named, simultaneously-usable
|
|
// connections, keyed by name; 'default' is the only one required. A
|
|
// sidecar can use more than one at once, e.g. Db::query(...) (default)
|
|
// alongside Db::query(..., 'legacy'). Supported drivers: 'sqlite',
|
|
// 'mysql'. The default connection's path deliberately lives outside
|
|
// both public/ (must never be web-accessible) and novaconium/ (gets
|
|
// wholly replaced on a framework update — see
|
|
// /admin/docs/getting-started's "Updating the framework" section) — a
|
|
// top-level data/ directory, project-owned like App/, is the only safe
|
|
// place for it. migrations_dir is optional per connection; omit it to
|
|
// never run migrations against that connection (e.g. a read-only
|
|
// legacy database). NOTE: unlike every other key here, App/config.php
|
|
// merges into db_connections one level deeper than a normal shallow
|
|
// override — see the comment on Lib\Db::config() — so adding a second
|
|
// connection there doesn't require repeating 'default'.
|
|
'db_connections' => [
|
|
'default' => [
|
|
'driver' => 'sqlite',
|
|
'path' => __DIR__ . '/../data/novaconium.sqlite',
|
|
'migrations_dir' => __DIR__ . '/../App/migrations',
|
|
],
|
|
],
|
|
|
|
// Routes an admin can preview before the public can see them (see
|
|
// /admin/docs/drafts) — a list of Route::$dir-format paths, no leading
|
|
// slash, e.g. 'blog/upcoming-post'. Not authenticated as admin (per
|
|
// AdminAuth::isAuthenticated()) → 404, same as a route that doesn't
|
|
// exist at all, so a draft's existence isn't revealed to anyone
|
|
// poking at the URL. Authenticated → renders normally, and — critically
|
|
// — is never written to the static HTML cache regardless of whether
|
|
// the page has a sidecar (see Renderer::render()'s $isDraft param),
|
|
// since a world-readable cached copy would otherwise permanently leak
|
|
// the draft the first time an admin previewed it.
|
|
'draft_routes' => [],
|
|
];
|