Let only admins change the global settings

BINDERY_ADMIN_USERS lists the admins of the cwa and proxy modes. Only an
admin can change the global settings and the global rules, and read or
change personal.ini. Without a list, each user is an admin.
This commit is contained in:
tim
2026-10-02 23:37:33 +02:00
parent 751b52da77
commit bb6c87cdd5
11 changed files with 140 additions and 8 deletions
+16 -4
View File
@@ -11,7 +11,7 @@ const ROUTES = [
[/^\/$/, storiesPage, []],
[/^\/story\/(\d+)$/, storyPage, ['storyId']],
[/^\/story\/(\d+)\/chapter\/(\d+)$/, chapterPage, ['storyId', 'chapterId']],
[/^\/settings$/, settingsPage, []],
[/^\/settings$/, settingsPage, [], true],
];
const app = document.getElementById('bindery-app');
@@ -23,6 +23,9 @@ let nav = null;
let storyTab = null;
let storyTabShown = false;
// The server refuses the changes of a user who is not an admin. The interface only hides what that user cannot use.
const isAdmin = () => auth.admin !== false;
const currentPath = () => window.location.hash.replace(/^#/, '') || '/';
// The story of #/story/3 and of its chapter pages, such as #/story/3/chapter/5.
const currentStoryId = () => currentPath().match(/^\/story\/(\d+)(?:\/|$)/)?.[1];
@@ -44,6 +47,14 @@ function notFound(main) {
fill(main, h('section', { class: 'card' }, h('h1', {}, 'Page not found'), h('p', {}, h('a', { href: '#/' }, 'Go to the story list'))));
}
function adminOnly(main) {
document.title = 'Settings · Bindery';
fill(
main,
h('section', { class: 'card' }, h('h1', {}, 'Settings'), h('p', {}, 'Only an admin can open the settings. BINDERY_ADMIN_USERS lists the admins.')),
);
}
function route() {
const main = document.getElementById('main');
if (!main) return;
@@ -54,9 +65,10 @@ function route() {
if (storyTab?.getAttribute('href') !== `#/story/${currentStoryId()}`) hideStoryTab();
const found = ROUTES.find(([pattern]) => pattern.test(path));
if (found) {
const [pattern, page, names] = found;
const [pattern, page, names, admin] = found;
const match = path.match(pattern);
cleanup = page(main, Object.fromEntries(names.map((name, i) => [name, match[i + 1]])), config) || null;
if (admin && !isAdmin()) adminOnly(main);
else cleanup = page(main, Object.fromEntries(names.map((name, i) => [name, match[i + 1]])), config) || null;
} else {
notFound(main);
}
@@ -81,7 +93,7 @@ function shell() {
(auth.mode === 'cwa' || auth.mode === 'local' || auth.logout_url) &&
h('button', { type: 'button', onclick: () => logout().catch(showError) }, 'Log out'),
];
nav = h('nav', { 'aria-label': 'Main navigation' }, h('a', { href: '#/' }, 'Stories'), h('a', { href: '#/settings' }, 'Settings'));
nav = h('nav', { 'aria-label': 'Main navigation' }, h('a', { href: '#/' }, 'Stories'), isAdmin() && h('a', { href: '#/settings' }, 'Settings'));
storyTab = h('a', { class: 'story-tab' });
storyTabShown = false;
fill(