source: Klonkt/src/middleware/auth.js@ b94c08e

main
Last change on this file since b94c08e was 8cb1dc7, checked in by roboburr <roboburr@…>, 3 months ago

hub: artist self-management — owner manages their own subsite (posts/appearance/comments)

A site owner (member) now gets a "My PrutFolio" dashboard and can manage their
OWN site; cross-site access is restricted.

  • auth.js: requireSiteManager (owner of res.locals.site or god) + requireSiteManagerBySlug (owner of :slug or god).
  • admin.js: /admin role-aware -> god=hub management, owner=my-site dashboard, member without site=403.
  • admin-sites.js: /:slug/edit + /save owner-or-god (was god-only); upload-photo requireAuth. list/new/create/delete remain god-only.
  • admin-comments.js: requireSiteManager + redirects/form actions via siteUrlBase so the artist context (/user/<slug>/admin/comments) is correct.
  • render.js: userOwnsSite flag; topnav + overview show "Admin" for owners; topnav "New post" uses siteUrlBase.
  • pages/my-site.ejs: artist dashboard.

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

  • Property mode set to 100644
File size: 2.4 KB
Line 
1/**
2 * Auth middleware
3 */
4
5import db from '../config/database.js';
6
7/**
8 * Validate a "next" URL for safe redirect after login.
9 * Returns the URL if safe, otherwise null.
10 *
11 * Rules:
12 * - Must be a string, max 256 chars (prevent abuse).
13 * - Must start with "/" but NOT "//" or "/\" (no protocol-relative open redirects).
14 * - Must not point back at /auth/* (prevents login → login loop).
15 */
16export function safeNext(raw) {
17 if (typeof raw !== 'string' || !raw.length || raw.length > 256) return null;
18 if (raw[0] !== '/' || raw[1] === '/' || raw[1] === '\\') return null;
19 if (/^\/auth(\/|$)/i.test(raw)) return null;
20 return raw;
21}
22
23function loginRedirect(req, res) {
24 // Preserve the originally-requested URL so login can return us there.
25 const next = encodeURIComponent(req.originalUrl || req.url || '/');
26 const target = `/auth/login?next=${next}`;
27 if (req.headers['hx-request'] === 'true') {
28 res.setHeader('HX-Redirect', target);
29 return res.status(401).send('Login required');
30 }
31 return res.redirect(target);
32}
33
34export function requireAuth(req, res, next) {
35 if (!req.session?.user) return loginRedirect(req, res);
36 next();
37}
38
39export function requireGod(req, res, next) {
40 if (!req.session?.user) return loginRedirect(req, res);
41 if (req.session.user.role !== 'god') {
42 return res.status(403).send('God role required');
43 }
44 next();
45}
46
47// Mag de ingelogde user de HUIDIGE site (res.locals.site) beheren? god altijd;
48// anders alleen de owner van die site. Gebruikt voor site-gescopete beheerroutes
49// die een artiest via /user/<eigen-slug>/admin/... bereikt (res.locals.site is dan
50// z'n eigen site; een vreemde slug levert een andere site -> 403).
51export function requireSiteManager(req, res, next) {
52 if (!req.session?.user) return loginRedirect(req, res);
53 const u = req.session.user;
54 if (u.role === 'god') return next();
55 const site = res.locals.site;
56 if (site && site.owner_id === u.id) return next();
57 return res.status(403).send('Geen toegang tot deze site.');
58}
59
60// Idem, maar de site wordt bepaald door de :slug-parameter (bv. site-edit).
61export function requireSiteManagerBySlug(req, res, next) {
62 if (!req.session?.user) return loginRedirect(req, res);
63 const u = req.session.user;
64 if (u.role === 'god') return next();
65 const site = db.prepare('SELECT owner_id FROM sites WHERE slug = ?').get(req.params.slug);
66 if (site && site.owner_id === u.id) return next();
67 return res.status(403).send('Geen toegang tot deze site.');
68}
Note: See TracBrowser for help on using the repository browser.