Changeset 8afbdd6 in Klonkt for src/server.js


Ignore:
Timestamp:
06/14/2026 06:07:51 PM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
c869272
Parents:
b94c08e
Message:

feat: viewer role + artists directory + Klonkt Hub Beta rebrand

Viewer role (replaces the separate view-mode/readonly toggle):

  • 'kijker' is now a real role (VALID_ROLES) selectable in Admin. May view EVERYTHING including Admin, but cannot modify anything.
  • isViewer(user) (= role kijker or legacy readonly flag) is the source; requireGod/requireSiteManager(BySlug) let a viewer through (viewing), the global guard 403s every write. Existing readonly accounts are migrated on each role change (readonly=0).
  • Write leaks via GET patched: /prutter/new (INSERT) blocks for viewers, /prutter/:id skips markAsRead (UPDATE); WS upgrade rejects viewers (the HTTP guard doesn't cover WebSockets).
  • Clean "Viewer mode" page (viewer-blocked) instead of raw 403 text; styled sticky banner; account page shows read-only UI instead of an upload button that silently 403s. canMutate hides write buttons.

Scalability (>50 artists):

  • Hub home shows max 24 (most active first) + "All N artists ->".
  • New searchable, paginated /artiesten directory (hub only).
  • 'user' + 'artiesten' reserved as slugs.

Rebrand PrutFolio v1 -> Klonkt Hub Beta (footer, PWA manifest, account/
admin texts, default page title, startup, README; internal package +
PWA id 'prutfolio' remain for stability).

Co-Authored-By: Claude <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/server.js

    rb94c08e r8afbdd6  
    11/**
    2  * PrutFolio v1 — server bootstrap
     2 * Klonkt Hub Beta — server bootstrap
    33 *
    44 * Persoonlijk multi-site platform forked van PrutCMS v9 (PHP, file-based).
     
    2121
    2222import { resolveSite, loadAudioTracks, loadTheme } from './middleware/site.js';
     23import { isViewer } from './middleware/auth.js';
     24import { renderPage } from './middleware/render.js';
    2325import authRoutes from './routes/auth.js';
    2426import accountRoutes from './routes/account.js';
     
    3941import feedRoutes from './routes/feed.js';
    4042import hubRoutes from './routes/hub.js';
     43import artistsRoutes from './routes/artists.js';
    4144import postsRoutes from './routes/posts.js';
    4245
     
    164167});
    165168
    166 // Read-only/kijk-accounts: alles bekijken mag, niets wijzigen. Blokkeert elke
    167 // state-wijzigende methode (de login-POST zelf zet de sessie pas, dus die valt
    168 // hier nog niet onder).
     169// Kijker-accounts: alles bekijken mag (incl. Beheer), niets wijzigen. Dit is de
     170// ENIGE schrijf-blokkade — fail-closed, vóór alle route-handlers. Elke state-
     171// wijzigende methode wordt geweigerd (de login-POST zet de sessie pas ná deze
     172// guard, dus die valt er niet onder). I.p.v. rauwe 403-tekst tonen we een nette
     173// pagina (of, bij HTMX, een ingeswapte melding).
    169174app.use((req, res, next) => {
    170   if (req.session?.user?.readonly && req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') {
    171     return res.status(403).send('Demo-account: alleen-lezen — wijzigen is uitgeschakeld.');
     175  const mutating = req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS';
     176  if (mutating && isViewer(req.session?.user)) {
     177    if (req.headers['hx-request'] === 'true') {
     178      // htmx swapt niet op 4xx; stuur 200 + retarget zodat de melding in #pcms-main verschijnt.
     179      res.setHeader('HX-Retarget', '#pcms-main');
     180      res.setHeader('HX-Reswap', 'innerHTML');
     181      res.status(200);
     182    } else {
     183      res.status(403);
     184    }
     185    return renderPage(req, res, 'pages/viewer-blocked', {
     186      pageTitle: 'Kijker-modus',
     187      bodyClass: 'on-special',
     188    });
    172189  }
    173190  next();
     
    192209// Feed/sitemap routes are mounted at root because they're at well-known paths
    193210app.use('/', feedRoutes);
     211app.use('/artiesten', artistsRoutes); // doorzoekbare artiesten-directory (alleen hub; solo: next())
    194212app.use('/', hubRoutes); // hub-overview op '/' (solo: next() -> postsRoutes)
    195213app.use('/', postsRoutes);
     
    215233  res.json({
    216234    id: idBase,
    217     name: site?.title || 'PrutFolio',
    218     short_name: (site?.title || 'PrutFolio').slice(0, 12),
     235    name: site?.title || 'Klonkt Hub Beta',
     236    short_name: (site?.title || 'Klonkt').slice(0, 12),
    219237    description: site?.description || site?.tagline || '',
    220238    scope,
     
    324342      return;
    325343    }
     344    // Kijker-accounts zijn alleen-lezen: weiger de WS-upgrade. De HTTP-guard
     345    // dekt geen WS, dus dit is de plek om schrijven via een (toekomstige)
     346    // message-handler te voorkomen.
     347    if (isViewer(req.session.user)) {
     348      socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
     349      socket.destroy();
     350      return;
     351    }
    326352    wss.handleUpgrade(req, socket, head, (ws) => {
    327353      ws.userId = req.session.user.id;
     
    350376server.listen(PORT, () => {
    351377  console.log('');
    352   console.log('🪶 PrutFolio v1 — alpha');
     378  console.log('🪶 Klonkt Hub Beta');
    353379  console.log(`   http://localhost:${PORT}`);
    354380  console.log('');
Note: See TracChangeset for help on using the changeset viewer.