openvibers/openvibe.live Commits
Ban screen: 'Please leave me alone' as the headline; the ban sticks to the browser via cookie so every visit (signed in or not) lands on it
Changes
@@@ -11,11 +11,14 @@* { box-sizing: border-box; }body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 24px 16px; font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; color: #e8e6e3;background: radial-gradient(120% 90% at 50% 110%, #3b0a0a 0%, #0d0d0f 60%); }-- .card { max-width: 640px; width: 100%; text-align: center; padding: 36px 28px; border-radius: 20px; background: rgba(20,12,14,0.85); border: 1px solid rgba(231,76,60,0.45);++ .card { max-width: 820px; width: 100%; text-align: center; padding: 36px 28px; border-radius: 20px; background: rgba(20,12,14,0.85); border: 1px solid rgba(231,76,60,0.45);box-shadow: 0 30px 80px rgba(0,0,0,0.6), 0 0 0 1px rgba(231,76,60,0.2), 0 0 60px rgba(231,76,60,0.15); }.stamp { display: inline-block; padding: 8px 22px; border: 4px solid #e74c3c; color: #e74c3c; border-radius: 10px; font-weight: 900; font-size: clamp(2rem, 8vw, 3.4rem); letter-spacing: 0.18em;transform: rotate(-4deg); text-shadow: 0 0 20px rgba(231,76,60,0.5); }h1 { margin: 22px 0 8px; font-size: clamp(1.3rem, 3.6vw, 1.9rem); }++ .plea { margin: 26px 0 10px; font-size: clamp(2.2rem, 8.5vw, 4.6rem); line-height: 1.02; font-weight: 900; letter-spacing: -0.02em; color: #fff; text-shadow: 0 6px 30px rgba(0,0,0,0.6); }++ .plea-body { font-size: clamp(1.25rem, 3.6vw, 1.9rem); line-height: 1.35; color: #fff; font-weight: 600; margin: 0 0 18px; }++ .who { font-size: 1.05rem; color: #cfc9c4; margin: 0 0 6px; }p { margin: 0 0 12px; font-size: 1.02rem; line-height: 1.55; color: #cfc9c4; }p b { color: #fff; }.reason { margin: 18px 0; padding: 12px 14px; border-radius: 12px; background: rgba(231,76,60,0.1); border-left: 3px solid #e74c3c; text-align: left; color: #f5d5d0; }@@@ -27,11 +30,12 @@<body><div class="card"><div class="stamp">BANNED</div>-- <h1>{{HEADLINE}}</h1>-- <p>{{BODY}}</p>++ <h1 class="plea">Please leave me alone.</h1>++ <p class="plea-body">I'm trying to move forward with my life, and you have been nothing but disrespectful to me.</p>++ <p class="who">{{HEADLINE}}</p><div class="reason"><small>Reason on record</small>{{REASON}}</div><p><b>This ban is permanent.</b> Don't put any effort into coming back — there is nothing here for you, and this decision won't change.</p>-- <p class="fine"><span class="logo">OpenVibe.Live</span> · this page is shown to you every time you open the site.</p>++ <p class="fine"><span class="logo">OpenVibe.Live</span> · you will see this page every time you open the site.</p></div></body></html>@@@ -361,6 +361,12 @@ app.use((req, res, next) => {return res.status(404).send('<!DOCTYPE html><html><head><title>404</title></head><body><h1>404 Not Found</h1></body></html>');}} catch (e) { /* DB error — let request through rather than block everyone */ }++ // A browser that has been shown the ban screen keeps seeing it (cookie set by GET /banned),++ // signed in or not. Assets still load so the page itself can render.++ if (req.cookies && req.cookies.ov_banned === '1' && !req.path.startsWith('/banned') && !req.path.startsWith('/assets/') && req.path !== '/api/health') {++ if (req.path.startsWith('/api/') || req.path.startsWith('/ws/')) return res.status(403).json({ error: 'Account is banned' });++ return res.redirect(302, '/banned');++ }next();});@@@ -735,14 +741,20 @@ app.get('/banned', (req, res) => {const token = extractToken(req);if (token) { user = authenticateApiToken(token) || (() => { const d = verifyToken(token); return d ? resolveNetworkUser(d) : null; })(); }} catch { user = null; }-- if (user && !user.is_banned) return res.redirect('/');-- const name = user ? (user.display_name || user.username) : null;-- const reason = (user && user.ban_reason) || 'Banned by the site owner.';++ if (user && !user.is_banned) { res.clearCookie('ov_banned'); res.clearCookie('ov_banned_name'); return res.redirect('/'); }++ // Make the ban stick to this browser: from now on every visit — signed in or not — lands here.++ const isSecure = String(config.baseUrl || '').startsWith('https');++ const tenYears = 10 * 365 * 24 * 3600 * 1000;++ if (user && user.is_banned) {++ res.cookie('ov_banned', '1', { httpOnly: true, maxAge: tenYears, sameSite: 'Lax', secure: isSecure });++ res.cookie('ov_banned_name', String(user.display_name || user.username).slice(0, 60), { httpOnly: true, maxAge: tenYears, sameSite: 'Lax', secure: isSecure });++ }++ const name = user ? (user.display_name || user.username) : (req.cookies && req.cookies.ov_banned_name ? String(req.cookies.ov_banned_name) : null);++ const reason = (user && user.ban_reason) || 'Repeated disrespect toward the owner of this site. Permanent.';let html = '';try { html = require('fs').readFileSync(path.join(__dirname, '../public/banned.html'), 'utf8'); } catch { html = '<h1>Banned</h1>'; }html = html-- .replace('{{HEADLINE}}', name ? `${escHtml(name)}, you are banned from OpenVibe.Live.` : 'This account is banned from OpenVibe.Live.')-- .replace('{{BODY}}', name ? `You were disrespectful to the person who built and runs this site, and you hurt him. That's the whole story, and it's enough.` : 'The account you signed in with has been banned by the site owner.')++ .replace('{{HEADLINE}}', name ? `${escHtml(name)}, you are banned from OpenVibe.Live.` : 'You are banned from OpenVibe.Live.').replace('{{REASON}}', escHtml(reason));res.status(403).set('Cache-Control', 'no-store').type('html').send(html);});
@@ -11,11 +11,14 @@
@@ -11,11 +11,14 @@
* { box-sizing: border-box; }
* { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 24px 16px; font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; color: #e8e6e3;
body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 24px 16px; font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; color: #e8e6e3;
background: radial-gradient(120% 90% at 50% 110%, #3b0a0a 0%, #0d0d0f 60%); }
background: radial-gradient(120% 90% at 50% 110%, #3b0a0a 0%, #0d0d0f 60%); }
- .card { max-width: 640px; width: 100%; text-align: center; padding: 36px 28px; border-radius: 20px; background: rgba(20,12,14,0.85); border: 1px solid rgba(231,76,60,0.45);
+ .card { max-width: 820px; width: 100%; text-align: center; padding: 36px 28px; border-radius: 20px; background: rgba(20,12,14,0.85); border: 1px solid rgba(231,76,60,0.45);
box-shadow: 0 30px 80px rgba(0,0,0,0.6), 0 0 0 1px rgba(231,76,60,0.2), 0 0 60px rgba(231,76,60,0.15); }
box-shadow: 0 30px 80px rgba(0,0,0,0.6), 0 0 0 1px rgba(231,76,60,0.2), 0 0 60px rgba(231,76,60,0.15); }
.stamp { display: inline-block; padding: 8px 22px; border: 4px solid #e74c3c; color: #e74c3c; border-radius: 10px; font-weight: 900; font-size: clamp(2rem, 8vw, 3.4rem); letter-spacing: 0.18em;
.stamp { display: inline-block; padding: 8px 22px; border: 4px solid #e74c3c; color: #e74c3c; border-radius: 10px; font-weight: 900; font-size: clamp(2rem, 8vw, 3.4rem); letter-spacing: 0.18em;
transform: rotate(-4deg); text-shadow: 0 0 20px rgba(231,76,60,0.5); }
transform: rotate(-4deg); text-shadow: 0 0 20px rgba(231,76,60,0.5); }
h1 { margin: 22px 0 8px; font-size: clamp(1.3rem, 3.6vw, 1.9rem); }
h1 { margin: 22px 0 8px; font-size: clamp(1.3rem, 3.6vw, 1.9rem); }
+ .plea { margin: 26px 0 10px; font-size: clamp(2.2rem, 8.5vw, 4.6rem); line-height: 1.02; font-weight: 900; letter-spacing: -0.02em; color: #fff; text-shadow: 0 6px 30px rgba(0,0,0,0.6); }
+ .plea-body { font-size: clamp(1.25rem, 3.6vw, 1.9rem); line-height: 1.35; color: #fff; font-weight: 600; margin: 0 0 18px; }
+ .who { font-size: 1.05rem; color: #cfc9c4; margin: 0 0 6px; }
p { margin: 0 0 12px; font-size: 1.02rem; line-height: 1.55; color: #cfc9c4; }
p { margin: 0 0 12px; font-size: 1.02rem; line-height: 1.55; color: #cfc9c4; }
p b { color: #fff; }
p b { color: #fff; }
.reason { margin: 18px 0; padding: 12px 14px; border-radius: 12px; background: rgba(231,76,60,0.1); border-left: 3px solid #e74c3c; text-align: left; color: #f5d5d0; }
.reason { margin: 18px 0; padding: 12px 14px; border-radius: 12px; background: rgba(231,76,60,0.1); border-left: 3px solid #e74c3c; text-align: left; color: #f5d5d0; }
@@ -27,11 +30,12 @@
@@ -27,11 +30,12 @@
<body>
<body>
<div class="card">
<div class="card">
<div class="stamp">BANNED</div>
<div class="stamp">BANNED</div>
- <h1>{{HEADLINE}}</h1>
- <p>{{BODY}}</p>
+ <h1 class="plea">Please leave me alone.</h1>
+ <p class="plea-body">I'm trying to move forward with my life, and you have been nothing but disrespectful to me.</p>
+ <p class="who">{{HEADLINE}}</p>
<div class="reason"><small>Reason on record</small>{{REASON}}</div>
<div class="reason"><small>Reason on record</small>{{REASON}}</div>
<p><b>This ban is permanent.</b> Don't put any effort into coming back — there is nothing here for you, and this decision won't change.</p>
<p><b>This ban is permanent.</b> Don't put any effort into coming back — there is nothing here for you, and this decision won't change.</p>
- <p class="fine"><span class="logo">OpenVibe.Live</span> · this page is shown to you every time you open the site.</p>
+ <p class="fine"><span class="logo">OpenVibe.Live</span> · you will see this page every time you open the site.</p>
</div>
</div>
</body>
</body>
</html>
</html>
@@ -361,6 +361,12 @@ app.use((req, res, next) => {
@@ -361,6 +361,12 @@ app.use((req, res, next) => {
return res.status(404).send('<!DOCTYPE html><html><head><title>404</title></head><body><h1>404 Not Found</h1></body></html>');
return res.status(404).send('<!DOCTYPE html><html><head><title>404</title></head><body><h1>404 Not Found</h1></body></html>');
}
}
} catch (e) { /* DB error — let request through rather than block everyone */ }
} catch (e) { /* DB error — let request through rather than block everyone */ }
+ // A browser that has been shown the ban screen keeps seeing it (cookie set by GET /banned),
+ // signed in or not. Assets still load so the page itself can render.
+ if (req.cookies && req.cookies.ov_banned === '1' && !req.path.startsWith('/banned') && !req.path.startsWith('/assets/') && req.path !== '/api/health') {
+ if (req.path.startsWith('/api/') || req.path.startsWith('/ws/')) return res.status(403).json({ error: 'Account is banned' });
+ return res.redirect(302, '/banned');
+ }
next();
next();
});
});
@@ -735,14 +741,20 @@ app.get('/banned', (req, res) => {
@@ -735,14 +741,20 @@ app.get('/banned', (req, res) => {
const token = extractToken(req);
const token = extractToken(req);
if (token) { user = authenticateApiToken(token) || (() => { const d = verifyToken(token); return d ? resolveNetworkUser(d) : null; })(); }
if (token) { user = authenticateApiToken(token) || (() => { const d = verifyToken(token); return d ? resolveNetworkUser(d) : null; })(); }
} catch { user = null; }
} catch { user = null; }
- if (user && !user.is_banned) return res.redirect('/');
- const name = user ? (user.display_name || user.username) : null;
- const reason = (user && user.ban_reason) || 'Banned by the site owner.';
+ if (user && !user.is_banned) { res.clearCookie('ov_banned'); res.clearCookie('ov_banned_name'); return res.redirect('/'); }
+ // Make the ban stick to this browser: from now on every visit — signed in or not — lands here.
+ const isSecure = String(config.baseUrl || '').startsWith('https');
+ const tenYears = 10 * 365 * 24 * 3600 * 1000;
+ if (user && user.is_banned) {
+ res.cookie('ov_banned', '1', { httpOnly: true, maxAge: tenYears, sameSite: 'Lax', secure: isSecure });
+ res.cookie('ov_banned_name', String(user.display_name || user.username).slice(0, 60), { httpOnly: true, maxAge: tenYears, sameSite: 'Lax', secure: isSecure });
+ }
+ const name = user ? (user.display_name || user.username) : (req.cookies && req.cookies.ov_banned_name ? String(req.cookies.ov_banned_name) : null);
+ const reason = (user && user.ban_reason) || 'Repeated disrespect toward the owner of this site. Permanent.';
let html = '';
let html = '';
try { html = require('fs').readFileSync(path.join(__dirname, '../public/banned.html'), 'utf8'); } catch { html = '<h1>Banned</h1>'; }
try { html = require('fs').readFileSync(path.join(__dirname, '../public/banned.html'), 'utf8'); } catch { html = '<h1>Banned</h1>'; }
html = html
html = html
- .replace('{{HEADLINE}}', name ? `${escHtml(name)}, you are banned from OpenVibe.Live.` : 'This account is banned from OpenVibe.Live.')
- .replace('{{BODY}}', name ? `You were disrespectful to the person who built and runs this site, and you hurt him. That's the whole story, and it's enough.` : 'The account you signed in with has been banned by the site owner.')
+ .replace('{{HEADLINE}}', name ? `${escHtml(name)}, you are banned from OpenVibe.Live.` : 'You are banned from OpenVibe.Live.')
.replace('{{REASON}}', escHtml(reason));
.replace('{{REASON}}', escHtml(reason));
res.status(403).set('Cache-Control', 'no-store').type('html').send(html);
res.status(403).set('Cache-Control', 'no-store').type('html').send(html);
});
});