Changeset 8f2f97c in Klonkt


Ignore:
Timestamp:
06/23/2026 02:24:46 PM (3 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
cb01666
Parents:
24cdcc6
Message:

Prutter (DM feature) fully removed

Reason: redundant feature + unnecessary attack surface (real-time WebSocket +
storing private messages = privacy/abuse risk). Removed: routes/prutter.js,
PrutterService, the WebSocket server in server.js, both DM views, all nav links
(topnav/bottom-tab), the DM button on profiles, the per-site enable_prutter
toggle and column. Existing (empty) DM tables in old DBs remain untouched but
are no longer referenced anywhere. No WebSocket left in the app.

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

Location:
src
Files:
4 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • src/config/database.js

    r24cdcc6 r8f2f97c  
    4747  // Site-level moderation toggle. 'trust' = auto-approve, 'moderate' = pending until reviewed.
    4848  ensureColumn('sites', 'comments_moderation_mode', "TEXT DEFAULT 'moderate'");
    49   // Per-site Prutter toggle: when off, DM endpoints/UI are hidden for that site.
    50   ensureColumn('sites', 'enable_prutter', 'INTEGER DEFAULT 1');
    5149  // Cirkels: mag deze site in cirkels van anderen verschijnen (surfacing opt-out).
    5250  ensureColumn('sites', 'allow_circle', 'INTEGER DEFAULT 1');
  • src/routes/admin-sites.js

    r24cdcc6 r8f2f97c  
    9898const RESERVED_SITE_SLUGS = new Set([
    9999  'auth', 'admin', 'login', 'register', 'logout', 'archive', 'search',
    100   'account', 'sites', 'comments', 'posts', 'media', 'audio', 'prutter',
     100  'account', 'sites', 'comments', 'posts', 'media', 'audio',
    101101  'forum', 'tag', 'user', 'users', 'artiesten', 'leden', 'feed.xml', 'atom.xml', 'sitemap.xml',
    102102  'manifest.webmanifest', 'sw.js', 'favicon.ico', 'favicon.svg', 'assets',
     
    119119    require_login_to_comment: 1,
    120120    enable_audio_player: 1,
    121     enable_prutter: 1,
    122121    comments_moderation_mode: 'moderate',
    123122    feed_view_default: 'grid',
     
    300299      profile_links = ?,
    301300      is_public = ?, robots_index = ?, require_login_to_comment = ?,
    302       enable_audio_player = ?, enable_prutter = ?,
     301      enable_audio_player = ?,
    303302      comments_moderation_mode = ?,
    304303      feed_view_default = ?, feed_view_switch = ?,
     
    322321    f.require_login_to_comment ? 1 : 0,
    323322    f.enable_audio_player ? 1 : 0,
    324     f.enable_prutter ? 1 : 0,
    325323    moderationMode,
    326324    feedViewDef,
  • src/routes/posts.js

    r24cdcc6 r8f2f97c  
    8181  'auth', 'admin', 'login', 'register', 'logout',
    8282  'archive', 'search', 'account', 'sites', 'comments',
    83   'posts', 'media', 'audio', 'prutter', 'forum',
     83  'posts', 'media', 'audio', 'forum',
    8484  'tag', 'type', 'user', 'users', 'artiesten', 'leden', 'favorieten', 'feed.xml', 'atom.xml', 'sitemap.xml',
    8585  'manifest.webmanifest', 'sw.js', 'favicon.ico', 'favicon.svg', 'assets',
  • src/server.js

    r24cdcc6 r8f2f97c  
    1919import { SqliteSessionStore } from './services/SqliteSessionStore.js';
    2020import { ensurePrimarySite } from './services/ensurePrimarySite.js';
    21 import PrutterService from './services/PrutterService.js';
    22 import { WebSocketServer } from 'ws';
    23 
    2421import { resolveSite, loadAudioTracks, loadTheme } from './middleware/site.js';
    2522import { isViewer } from './middleware/auth.js';
     
    3633import adminSettingsRoutes from './routes/admin-settings.js';
    3734import adminSeoRoutes from './routes/admin-seo.js';
    38 import prutterRoutes from './routes/prutter.js';
    3935import audioRoutes from './routes/audio.js';
    4036import searchRoutes from './routes/search.js';
     
    206202})();
    207203
    208 // Singleton PrutterService — routes get it via req.app.locals.prutter.
    209 const prutter = new PrutterService(db);
    210 app.locals.prutter = prutter;
    211 
    212204// Cirkels-federatie: publieke, site-agnostische endpoints (/.klonkt/*).
    213205// Vóór resolveSite/theme — ze hebben geen site-context nodig.
     
    274266app.use('/admin/epk', adminEpkRoutes);
    275267app.use('/admin', adminRoutes);
    276 app.use('/prutter', prutterRoutes);
    277268app.use('/audio', audioRoutes);
    278269app.use('/search', searchRoutes);
     
    422413});
    423414
    424 // ==================== WebSocket: Prutter real-time ====================
    425 // Authenticate via the existing session cookie. We reuse sessionMiddleware
    426 // during the HTTP upgrade so req.session is populated; if no user, abort.
    427 const wss = new WebSocketServer({ noServer: true });
    428 
    429 server.on('upgrade', (req, socket, head) => {
    430   if (req.url !== '/ws/prutter') {
    431     socket.destroy();
    432     return;
    433   }
    434   // Run session middleware on the upgrade request.
    435   // (Express's middleware accepts (req, res, next); we pass a stub res.)
    436   const stubRes = { setHeader: () => {}, getHeader: () => undefined, on: () => {}, end: () => {} };
    437   sessionMiddleware(req, stubRes, () => {
    438     if (!req.session?.user) {
    439       socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
    440       socket.destroy();
    441       return;
    442     }
    443     // Kijker-accounts zijn alleen-lezen: weiger de WS-upgrade. De HTTP-guard
    444     // dekt geen WS, dus dit is de plek om schrijven via een (toekomstige)
    445     // message-handler te voorkomen.
    446     if (isViewer(req.session.user)) {
    447       socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
    448       socket.destroy();
    449       return;
    450     }
    451     wss.handleUpgrade(req, socket, head, (ws) => {
    452       ws.userId = req.session.user.id;
    453       wss.emit('connection', ws, req);
    454     });
    455   });
    456 });
    457 
    458 wss.on('connection', (ws) => {
    459   prutter.addConnection(ws.userId, ws);
    460   ws.on('close', () => prutter.removeConnection(ws.userId, ws));
    461   ws.on('error', () => prutter.removeConnection(ws.userId, ws));
    462   // Optional: ping every 30s to keep connections alive through proxies
    463   ws.isAlive = true;
    464   ws.on('pong', () => { ws.isAlive = true; });
    465 });
    466 const wsPing = setInterval(() => {
    467   for (const ws of wss.clients) {
    468     if (ws.isAlive === false) { ws.terminate(); continue; }
    469     ws.isAlive = false;
    470     try { ws.ping(); } catch {}
    471   }
    472 }, 30000);
    473 if (wsPing.unref) wsPing.unref();
    474 
    475415server.listen(PORT, () => {
    476416  console.log('');
     
    483423  console.log(`   ✓ Auth:     wachtwoord (beheer) + Google (luisteraars) / logout`);
    484424  console.log(`   ✓ Posts:    create / edit / view / archive`);
    485   console.log(`   ✓ Realtime: WebSocket server ready (Prutter)`);
    486425  console.log('');
    487426  console.log(`   Mode: ${isDev ? 'development' : 'PRODUCTION'}`);
  • src/views/pages/admin-site-edit.ejs

    r24cdcc6 r8f2f97c  
    163163        <span><%= t('asite.enable_audio') %></span>
    164164      </label>
    165       <%# Prutter is een Hub-only premium-functie — toggle alleen tonen in Hub-modus. %>
    166       <% if (typeof tenancy !== 'undefined' && tenancy === 'hub') { %>
    167       <label class="cb">
    168         <input type="checkbox" name="enable_prutter" value="1" <%= site.enable_prutter ? 'checked' : '' %>>
    169         <span><%= t('asite.enable_prutter') %> <small class="form-hint-inline"><%= t('asite.enable_prutter_hint') %></small></span>
    170       </label>
    171       <% } %>
    172165    </fieldset>
    173166
  • src/views/pages/user.ejs

    r24cdcc6 r8f2f97c  
    2727        <% } %>
    2828      </p>
    29       <% if (user && user.id !== author.id && site && site.enable_prutter && (typeof tenancy !== 'undefined' && tenancy === 'hub') && (typeof premiumUnlocked === 'undefined' || premiumUnlocked)) { %>
    30         <p class="user-actions">
    31           <a href="<%= siteUrlBase %>/prutter/new?to=<%= encodeURIComponent(author.username) %>" class="btn btn-primary">💬 <%= t('pusr.send_dm') %></a>
    32         </p>
    33       <% } %>
    3429    </div>
    3530  </header>
  • src/views/partials/bottom-tab.ejs

    r24cdcc6 r8f2f97c  
    1313const _canPost     = !!(user && (typeof canMutate === 'undefined' || canMutate)
    1414                        && permissions && permissions.canCreatePost && permissions.canCreatePost(user, site));
    15 const _hasPrutter  = !!(user && site && site.enable_prutter && (typeof tenancy !== 'undefined' && tenancy === 'hub') && (typeof premiumUnlocked === 'undefined' || premiumUnlocked));
    1615const _ownProfile  = user ? ('/users/' + user.username) : null;
    1716const _isHub       = (typeof tenancy !== 'undefined' && tenancy === 'hub');
     
    3231else if (_p.indexOf('/search') === 0 || _p.indexOf('/tag/') === 0 || _p.indexOf('/type/') === 0) _active = 'search';
    3332else if (_p === '/posts/new' || /^\/posts\/[^/]+\/edit$/.test(_p)) _active = 'create';
    34 else if (_p.indexOf('/prutter') === 0) _active = 'prutter';
    3533else if (_p.indexOf('/account') === 0 || (_ownProfile && _p.indexOf(_ownProfile) === 0)) _active = 'profile';
    3634else if (_p.indexOf('/auth/login') === 0) _active = 'login';
     
    8078  <% } %>
    8179
    82   <!-- Prutter (DMs) -->
    83   <% if (_hasPrutter) { %>
    84     <a class="bottom-tab-item<%= _active === 'prutter' ? ' is-active' : '' %>"
    85        href="<%= _siteUrlBase %>/prutter"
    86        aria-label="Prutter berichten" <%= _active === 'prutter' ? 'aria-current="page"' : '' %>>
    87       <svg class="bottom-tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
    88         <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
    89       </svg>
    90       <span class="bottom-tab-label">Prutter</span>
    91       <% if (typeof unreadDmCount !== 'undefined' && unreadDmCount > 0) { %>
    92         <span class="bottom-tab-badge" aria-label="<%= unreadDmCount %> ongelezen"><%= unreadDmCount > 99 ? '99+' : unreadDmCount %></span>
    93       <% } %>
    94     </a>
    95   <% } %>
    9680
    9781  <!-- Profiel / Inloggen -->
  • src/views/partials/topnav.ejs

    r24cdcc6 r8f2f97c  
    1515const _isAdmin     = typeof bodyClass !== 'undefined' && bodyClass.indexOf('on-admin') >= 0;
    1616const _hasSearch   = !site || site.show_search === undefined || site.show_search;
    17 const _hasPrutter  = user && site && site.enable_prutter && (typeof tenancy !== 'undefined' && tenancy === 'hub') && (typeof premiumUnlocked === 'undefined' || premiumUnlocked);
    1817const _canPost     = user && (typeof canMutate === 'undefined' || canMutate)
    1918                     && permissions && permissions.canCreatePost && permissions.canCreatePost(user, site);
     
    9897          </div>
    9998        </details>
    100       <% } %>
    101 
    102       <% if (_hasPrutter) { %>
    103         <a class="nav-btn" href="<%= _siteUrlBase %>/prutter" aria-label="Prutter (DMs)" title="Prutter">
    104           <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
    105         </a>
    10699      <% } %>
    107100
  • src/views/shell.ejs

    r24cdcc6 r8f2f97c  
    3333// Listing pages (search/tag/type/archive) shouldn't be indexed (dupe content)
    3434if (currentPath) {
    35   if (/^\/(?:search|tag|type|archive|users|prutter|account|admin)(?:$|\/)/.test(currentPath)) {
     35  if (/^\/(?:search|tag|type|archive|users|account|admin)(?:$|\/)/.test(currentPath)) {
    3636    _shouldIndex = false;
    3737  }
Note: See TracChangeset for help on using the changeset viewer.