Changeset 9e27d64 in Klonkt for src/server.js


Ignore:
Timestamp:
06/14/2026 07:31:23 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
6351545
Parents:
32cc601
Message:

auth: password admin + per-instance Google for listeners (no broker)

Robin's choice: every self-hoster has their own password admin account,
and can optionally let listeners log in to comment using their OWN Google
client. No central broker (that would tie every customer site to Robin's
Google Cloud -> systemic risk on abuse).

  • Admin = username/password (bcrypt). First-time setup via /auth/register (only when there are 0 users); closed afterwards. No public registration.
  • Forgot password: /auth/reset-request -> email (if SMTP configured) with reset link; CLI break-glass npm run reset-admin always works (no email needed).
  • Change password (logged in) restored in /account.
  • Google = per-instance own credentials, OPTIONAL, listeners only -> always role member, never admin (god/admin email is rejected; google_sub mismatch too).
  • config/google.js back to direct Google OAuth; config/mailer.js new (nodemailer).
  • jose removed from deps; nodemailer added.

Security review (workflow) incorporated:

  • Reset token no longer in production logs (dev only).
  • Reset link from PUBLIC_BASE_URL instead of X-Forwarded-Host (host poisoning).
  • Reset tokens stored SHA-256-hashed in the DB.
  • Same-origin check on all state-modifying POSTs (CSRF layer on top of sameSite-lax).
  • Login always runs one bcrypt comparison (no timing enumeration).

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/server.js

    r32cc601 r9e27d64  
    148148app.use(loadTheme);
    149149
     150// Lichtgewicht CSRF-defense: weiger cross-origin state-wijzigende requests.
     151// Same-origin forms + HTMX sturen een matchende Origin; ontbreekt Origin dan
     152// laten we door (non-browser clients). sameSite:'lax' op de sessiecookie is de
     153// tweede laag. (Geldt niet voor GET/HEAD/OPTIONS.)
     154app.use((req, res, next) => {
     155  if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') return next();
     156  const origin = req.get('origin');
     157  if (!origin) return next(); // geen Origin -> geen browser-CSRF-vector
     158  let originHost;
     159  try { originHost = new URL(origin).host; } catch { return res.status(403).send('Ongeldige origin'); }
     160  if (originHost !== req.get('host')) return res.status(403).send('Cross-origin request geweigerd');
     161  next();
     162});
     163
    150164app.use('/auth', authRoutes);
    151165app.use('/account', accountRoutes);
     
    328342  console.log(`   ✓ Privacy:  Self-hosted fonts, no third-party requests`);
    329343  console.log(`   ✓ Layout:   v9 editorial feel (top nav, profile header)`);
    330   console.log(`   ✓ Auth:     Google login / logout`);
     344  console.log(`   ✓ Auth:     wachtwoord (beheer) + Google (luisteraars) / logout`);
    331345  console.log(`   ✓ Posts:    create / edit / view / archive`);
    332346  console.log(`   ✓ Realtime: WebSocket server ready (Prutter)`);
Note: See TracChangeset for help on using the changeset viewer.