ghost rider
This commit is contained in:
@@ -518,5 +518,15 @@ export default new class {
|
||||
const hostNoPort = firstHost.split(':')[0].trim().toLowerCase();
|
||||
return hostNoPort.endsWith('.onion');
|
||||
}
|
||||
|
||||
isLocalhostRequest(req) {
|
||||
if (!req) return false;
|
||||
const rawHost = req.headers?.['x-forwarded-host'] || req.headers?.['host'] || req.headers?.['x-forwarded-server'] || '';
|
||||
if (!rawHost) return false;
|
||||
const hostStr = Array.isArray(rawHost) ? rawHost[0] : String(rawHost);
|
||||
const firstHost = hostStr.split(',')[0].trim();
|
||||
const hostNoPort = firstHost.split(':')[0].trim().toLowerCase();
|
||||
return hostNoPort === 'localhost' || hostNoPort === '127.0.0.1' || hostNoPort === '::1';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -643,6 +643,8 @@
|
||||
"reason_required": "Grund ist erforderlich.",
|
||||
"reason_optional": "Grund (optional)",
|
||||
"reason_required_label": "Grund (erforderlich)",
|
||||
"captcha_required": "Bitte füllen Sie das CAPTCHA aus.",
|
||||
"captcha_loading": "CAPTCHA wird noch geladen. Bitte kurz warten.",
|
||||
"processing": "Wird verarbeitet...",
|
||||
"yes": "Ja",
|
||||
"no": "Nein",
|
||||
|
||||
@@ -647,6 +647,8 @@
|
||||
"reason_required": "Reason is required.",
|
||||
"reason_optional": "Reason (optional)",
|
||||
"reason_required_label": "Reason (required)",
|
||||
"captcha_required": "Please complete the CAPTCHA.",
|
||||
"captcha_loading": "CAPTCHA is still loading. Please wait a moment.",
|
||||
"processing": "Processing...",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
|
||||
@@ -641,6 +641,8 @@
|
||||
"reason_required": "Reden is vereist.",
|
||||
"reason_optional": "Reden (optioneel)",
|
||||
"reason_required_label": "Reden (vereist)",
|
||||
"captcha_required": "Vul alstublieft de CAPTCHA in.",
|
||||
"captcha_loading": "CAPTCHA is nog aan het laden. Even geduld a.u.b.",
|
||||
"processing": "Verwerken...",
|
||||
"yes": "Ja",
|
||||
"no": "Nee",
|
||||
|
||||
@@ -642,6 +642,8 @@
|
||||
"reason_required": "Grund ist erforderlich.",
|
||||
"reason_optional": "Grund (optional)",
|
||||
"reason_required_label": "Grund (erforderlich)",
|
||||
"captcha_required": "Bitte füllen Sie das CAPTCHA aus.",
|
||||
"captcha_loading": "CAPTCHA wird noch geladen. Bitte kurz warten.",
|
||||
"processing": "Verarbeitung wird durchgeführt...",
|
||||
"yes": "Ja",
|
||||
"no": "Nein",
|
||||
|
||||
+84
-19
@@ -3,32 +3,44 @@ import db from "./sql.mjs";
|
||||
// Maps for fast O(1) in-memory lookups:
|
||||
// dest (string) -> owner username (lowercase string)
|
||||
const _privateDests = new Map();
|
||||
// id (number) -> owner username (lowercase string)
|
||||
const _privateIds = new Map();
|
||||
|
||||
// Unavailable items (visibility === 3, serves HTTP 451 for non-logged in users):
|
||||
const _unavailableDests = new Map();
|
||||
const _unavailableIds = new Map();
|
||||
|
||||
let _initialized = false;
|
||||
let _initPromise = null;
|
||||
|
||||
/**
|
||||
* Load all active private items into memory cache.
|
||||
* Load all active private and unavailable items into memory cache.
|
||||
*/
|
||||
export async function initPrivateItems() {
|
||||
try {
|
||||
const rows = await db`
|
||||
SELECT id, dest, LOWER(username) as username
|
||||
SELECT id, dest, LOWER(username) as username, visibility
|
||||
FROM items
|
||||
WHERE visibility = 2 AND is_deleted = false
|
||||
WHERE visibility IN (2, 3) AND is_deleted = false
|
||||
`;
|
||||
_privateDests.clear();
|
||||
_privateIds.clear();
|
||||
_unavailableDests.clear();
|
||||
_unavailableIds.clear();
|
||||
|
||||
for (const r of rows) {
|
||||
if (r.dest) _privateDests.set(r.dest, r.username || '');
|
||||
if (r.id) _privateIds.set(Number(r.id), r.username || '');
|
||||
const u = r.username || '';
|
||||
if (r.visibility === 2) {
|
||||
if (r.dest) _privateDests.set(r.dest, u);
|
||||
if (r.id) _privateIds.set(Number(r.id), u);
|
||||
} else if (r.visibility === 3) {
|
||||
if (r.dest) _unavailableDests.set(r.dest, u);
|
||||
if (r.id) _unavailableIds.set(Number(r.id), u);
|
||||
}
|
||||
}
|
||||
_initialized = true;
|
||||
console.log(`[BOOT] Loaded ${_privateDests.size} private item(s) into memory cache`);
|
||||
console.log(`[BOOT] Loaded ${_privateDests.size} private item(s) and ${_unavailableDests.size} unavailable item(s) into memory cache`);
|
||||
} catch (err) {
|
||||
console.error('[BOOT] Failed to load private items into cache:', err.message);
|
||||
console.error('[BOOT] Failed to load private/unavailable items into cache:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +60,8 @@ export function addPrivateItem(id, dest, username) {
|
||||
const u = (username || '').toLowerCase();
|
||||
if (dest) _privateDests.set(dest, u);
|
||||
if (id) _privateIds.set(Number(id), u);
|
||||
// Ensure not in unavailable
|
||||
removeUnavailableItem(id, dest);
|
||||
}
|
||||
|
||||
export function removePrivateItem(id, dest) {
|
||||
@@ -55,9 +69,22 @@ export function removePrivateItem(id, dest) {
|
||||
if (id) _privateIds.delete(Number(id));
|
||||
}
|
||||
|
||||
export function addUnavailableItem(id, dest, username) {
|
||||
const u = (username || '').toLowerCase();
|
||||
if (dest) _unavailableDests.set(dest, u);
|
||||
if (id) _unavailableIds.set(Number(id), u);
|
||||
// Ensure not in private
|
||||
removePrivateItem(id, dest);
|
||||
}
|
||||
|
||||
export function removeUnavailableItem(id, dest) {
|
||||
if (dest) _unavailableDests.delete(dest);
|
||||
if (id) _unavailableIds.delete(Number(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given pathname (/b/<dest>, /t/<id>..., /ca/<id>...) is a private item.
|
||||
* Returns { isPrivate: boolean, owner: string } or null if not private.
|
||||
* Checks if a given pathname (/b/<dest>, /t/<id>..., /ca/<id>...) is a private or unavailable item.
|
||||
* Returns { isPrivate: boolean, isUnavailable: boolean, owner: string } or null if normal public.
|
||||
*/
|
||||
export function getPrivateItemFromPath(pathname) {
|
||||
if (!pathname || typeof pathname !== 'string') return null;
|
||||
@@ -70,9 +97,13 @@ export function getPrivateItemFromPath(pathname) {
|
||||
dest = pathname.slice(3);
|
||||
}
|
||||
dest = dest.split('?')[0].split('#')[0];
|
||||
const owner = _privateDests.get(dest);
|
||||
if (owner !== undefined) {
|
||||
return { isPrivate: true, owner };
|
||||
const privOwner = _privateDests.get(dest);
|
||||
if (privOwner !== undefined) {
|
||||
return { isPrivate: true, isUnavailable: false, owner: privOwner };
|
||||
}
|
||||
const unavOwner = _unavailableDests.get(dest);
|
||||
if (unavOwner !== undefined) {
|
||||
return { isPrivate: false, isUnavailable: true, owner: unavOwner };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -88,9 +119,13 @@ export function getPrivateItemFromPath(pathname) {
|
||||
const match = filename.match(/^(\d+)/);
|
||||
if (match) {
|
||||
const id = parseInt(match[1], 10);
|
||||
const owner = _privateIds.get(id);
|
||||
if (owner !== undefined) {
|
||||
return { isPrivate: true, owner };
|
||||
const privOwner = _privateIds.get(id);
|
||||
if (privOwner !== undefined) {
|
||||
return { isPrivate: true, isUnavailable: false, owner: privOwner };
|
||||
}
|
||||
const unavOwner = _unavailableIds.get(id);
|
||||
if (unavOwner !== undefined) {
|
||||
return { isPrivate: false, isUnavailable: true, owner: unavOwner };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -107,9 +142,13 @@ export function getPrivateItemFromPath(pathname) {
|
||||
const match = filename.match(/^(\d+)/);
|
||||
if (match) {
|
||||
const id = parseInt(match[1], 10);
|
||||
const owner = _privateIds.get(id);
|
||||
if (owner !== undefined) {
|
||||
return { isPrivate: true, owner };
|
||||
const privOwner = _privateIds.get(id);
|
||||
if (privOwner !== undefined) {
|
||||
return { isPrivate: true, isUnavailable: false, owner: privOwner };
|
||||
}
|
||||
const unavOwner = _unavailableIds.get(id);
|
||||
if (unavOwner !== undefined) {
|
||||
return { isPrivate: false, isUnavailable: true, owner: unavOwner };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -141,3 +180,29 @@ export function render502(req, res) {
|
||||
}).end(body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render HTTP 451 Unavailable For Legal Reasons response.
|
||||
*/
|
||||
export function render451(req, res) {
|
||||
if (req.headers && (req.headers['x-requested-with'] === 'XMLHttpRequest' || req.headers.accept?.includes('application/json'))) {
|
||||
res.writeHead(451, {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate'
|
||||
}).end(JSON.stringify({ success: false, is_unavailable: true, msg: '451 - Unavailable For Legal Reasons' }));
|
||||
} else {
|
||||
const body = `<html>\r
|
||||
<head><title>451 Unavailable For Legal Reasons</title></head>\r
|
||||
<body>\r
|
||||
<center><h1>451 Unavailable For Legal Reasons</h1></center>\r
|
||||
<hr><center>nginx</center>\r
|
||||
</body>\r
|
||||
</html>\r
|
||||
`;
|
||||
res.writeHead(451, {
|
||||
'Content-Type': 'text/html',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate'
|
||||
}).end(body);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -371,11 +371,12 @@ const buildFeedFilters = async ({
|
||||
userHallFilter = db`and items.id in (select uha.item_id from user_halls_assign uha where uha.hall_id = ${userHallObj.id})`;
|
||||
}
|
||||
|
||||
const isAdmin = !!session?.admin;
|
||||
const isOwnerOrAdmin = (session && user && typeof user === 'string' && session.user && session.user.toLowerCase() === user.toLowerCase()) || (session && (session.admin || session.is_moderator));
|
||||
const visibilityFilter = isOwnerOrAdmin
|
||||
const visibilityFilter = isAdmin
|
||||
? db``
|
||||
: (session && session.user
|
||||
? db`and (coalesce(items.visibility, 0) = 0 or lower(items.username) = ${session.user.toLowerCase()})`
|
||||
? db`and (coalesce(items.visibility, 0) = 0 or (lower(items.username) = ${session.user.toLowerCase()} and items.visibility != 3))`
|
||||
: db`and coalesce(items.visibility, 0) = 0`);
|
||||
|
||||
return {
|
||||
@@ -881,10 +882,11 @@ export default {
|
||||
|
||||
// Helper to construct shared filter conditions
|
||||
const buildConditions = () => {
|
||||
const visibilityFilter = isOwnerOrAdmin
|
||||
const isAdmin = !!session?.admin;
|
||||
const visibilityFilter = isAdmin
|
||||
? db``
|
||||
: (session && session.user
|
||||
? db`and (coalesce(items.visibility, 0) = 0 or lower(items.username) = ${session.user.toLowerCase()})`
|
||||
? db`and (coalesce(items.visibility, 0) = 0 or (lower(items.username) = ${session.user.toLowerCase()} and items.visibility != 3))`
|
||||
: db`and coalesce(items.visibility, 0) = 0`);
|
||||
|
||||
return db`
|
||||
@@ -964,9 +966,18 @@ export default {
|
||||
};
|
||||
}
|
||||
|
||||
// If request was by sequential numeric ID (/123) and item visibility > 0 (unlisted):
|
||||
// If item is Unavailable (visibility === 3):
|
||||
// Only viewable by admins, not regular users or mods (renders normal post not found)
|
||||
if (actitem.visibility === 3 && !session?.admin) {
|
||||
return {
|
||||
success: false,
|
||||
message: "404 - upload not found"
|
||||
};
|
||||
}
|
||||
|
||||
// If request was by sequential numeric ID (/123) and item visibility > 0 (unlisted/private):
|
||||
// Block numeric enumeration unless viewer is owner/admin
|
||||
if (isNumeric && actitem.visibility > 0 && !isOwnerOrAdmin) {
|
||||
if (isNumeric && (actitem.visibility === 1 || actitem.visibility === 2) && !isOwnerOrAdmin) {
|
||||
return {
|
||||
success: false,
|
||||
message: "404 - upload not found"
|
||||
|
||||
@@ -137,6 +137,25 @@ export default (router, tpl) => {
|
||||
if (data.item) {
|
||||
const session = data.session;
|
||||
const item = data.item;
|
||||
// When guest anonymization is active, suppress uploader identity, banner, avatar, and source URL
|
||||
if (cfg.main.guest_anonymize && !req.session) {
|
||||
if (item.src) item.src = null;
|
||||
item.username = 'anonymous';
|
||||
item.author_banner_file = null;
|
||||
item.author_banner_position = null;
|
||||
item.author_banner_size = null;
|
||||
item.author_avatar = null;
|
||||
item.author_avatar_file = null;
|
||||
item.author_color = null;
|
||||
item.author_description = null;
|
||||
item.author_display_name = null;
|
||||
item.author_id = null;
|
||||
if (data.uploader) {
|
||||
data.uploader.name = 'anonymous';
|
||||
data.uploader.id = null;
|
||||
data.uploader.color = null;
|
||||
}
|
||||
}
|
||||
data.is_mod_or_admin = !!(session && (session.admin || session.is_moderator));
|
||||
data.can_manage_item = !!(session && (session.admin || session.is_moderator || (session.user && item.username && session.user.toLowerCase() === item.username.toLowerCase())));
|
||||
data.can_extract_meta = !!(item.mime && item.mime.indexOf('flash') === -1 && !(item.mime.startsWith('application/') && cfg.mimes[item.mime] && !['swf', 'pdf'].includes(cfg.mimes[item.mime])));
|
||||
|
||||
@@ -11,7 +11,7 @@ import audit from '../../audit.mjs';
|
||||
import { parseMultipart, collectBody } from '../../multipart.mjs';
|
||||
import { purgeExpiredUploads } from '../../lib_delete.mjs';
|
||||
import { calculateExpiresAt } from './upload.mjs';
|
||||
import { addPrivateItem, removePrivateItem } from '../../private_items.mjs';
|
||||
import { addPrivateItem, removePrivateItem, addUnavailableItem, removeUnavailableItem } from '../../private_items.mjs';
|
||||
|
||||
const allowedMimes = ["audio", "image", "video", "%"];
|
||||
const getGlobalfilter = () => {
|
||||
@@ -1067,7 +1067,11 @@ export default router => {
|
||||
if (!isOwner && !isMod) return res.json({ success: false, msg: 'Forbidden' }, 403);
|
||||
|
||||
// Accept title from JSON or URL-encoded body
|
||||
let rawTitle = req.post?.title ?? req.body?.title ?? null;
|
||||
let body = req.post || req.body || {};
|
||||
if (typeof body === 'string') {
|
||||
try { body = JSON.parse(body); } catch (_) {}
|
||||
}
|
||||
let rawTitle = body?.title ?? null;
|
||||
if (rawTitle !== null) rawTitle = String(rawTitle).trim();
|
||||
// Empty string → null (clears the title)
|
||||
const title = (rawTitle === '' || rawTitle === null) ? null : rawTitle.substring(0, 500);
|
||||
@@ -1317,7 +1321,7 @@ export default router => {
|
||||
}
|
||||
const postid = req.post?.postid || req.post?.id || req.body?.postid || req.body?.id;
|
||||
const visibility = parseInt(req.post?.visibility ?? req.body?.visibility, 10);
|
||||
if (!postid || isNaN(visibility) || ![0, 1, 2].includes(visibility)) {
|
||||
if (!postid || isNaN(visibility) || ![0, 1, 2, 3].includes(visibility)) {
|
||||
return res.json({ success: false, msg: 'Invalid parameters' }, 400);
|
||||
}
|
||||
|
||||
@@ -1340,12 +1344,21 @@ export default router => {
|
||||
return res.json({ success: false, msg: 'Unauthorized' }, 403);
|
||||
}
|
||||
|
||||
if (visibility === 3 && !isAdmin) {
|
||||
return res.json({ success: false, msg: 'Only moderators or administrators can make an item unavailable' }, 403);
|
||||
}
|
||||
|
||||
await db`UPDATE items SET visibility = ${visibility} WHERE id = ${item[0].id}`;
|
||||
|
||||
if (visibility === 2) {
|
||||
addPrivateItem(item[0].id, item[0].dest, item[0].username);
|
||||
removeUnavailableItem(item[0].id, item[0].dest);
|
||||
} else if (visibility === 3) {
|
||||
addUnavailableItem(item[0].id, item[0].dest, item[0].username);
|
||||
removePrivateItem(item[0].id, item[0].dest);
|
||||
} else {
|
||||
removePrivateItem(item[0].id, item[0].dest);
|
||||
removeUnavailableItem(item[0].id, item[0].dest);
|
||||
}
|
||||
|
||||
f0cklib.clearCountCache();
|
||||
|
||||
@@ -398,9 +398,24 @@ export default (router, tpl) => {
|
||||
// Hall columns for display
|
||||
data.halls_slugs = Array.isArray(item.halls) ? item.halls.map(h => h.slug).join(',') : '';
|
||||
data.user_halls_slugs = Array.isArray(item.user_halls) ? item.user_halls.map(h => h.slug).join(',') : '';
|
||||
// When guest anonymization is active, suppress source URL from the info modal
|
||||
if (cfg.main.guest_anonymize && !req.session && item.src) {
|
||||
item.src = null;
|
||||
// When guest anonymization is active, suppress uploader identity, banner, avatar, and source URL
|
||||
if (cfg.main.guest_anonymize && !req.session) {
|
||||
if (item.src) item.src = null;
|
||||
item.username = 'anonymous';
|
||||
item.author_banner_file = null;
|
||||
item.author_banner_position = null;
|
||||
item.author_banner_size = null;
|
||||
item.author_avatar = null;
|
||||
item.author_avatar_file = null;
|
||||
item.author_color = null;
|
||||
item.author_description = null;
|
||||
item.author_display_name = null;
|
||||
item.author_id = null;
|
||||
if (data.uploader) {
|
||||
data.uploader.name = 'anonymous';
|
||||
data.uploader.id = null;
|
||||
data.uploader.color = null;
|
||||
}
|
||||
}
|
||||
// Precomputed for template engine compatibility (avoids nested { } inside {{ }})
|
||||
data.item_rating_class = item.is_nsfl ? 'is-nsfl' : (item.is_nsfw ? 'is-nsfw' : (item.is_sfw ? 'is-sfw' : 'is-untagged'));
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import db from "../sql.mjs";
|
||||
import lib from "../lib.mjs";
|
||||
import audit from "../audit.mjs";
|
||||
import cfg from "../config.mjs";
|
||||
import security from "../security.mjs";
|
||||
|
||||
export default (router, tpl) => {
|
||||
|
||||
// User: Submit a new report
|
||||
router.post(/^\/api\/v2\/report\/?$/, lib.loggedin, async (req, res) => {
|
||||
// Submit a new report (Users & Guests)
|
||||
router.post(/^\/api\/v2\/report\/?$/, async (req, res) => {
|
||||
try {
|
||||
const { item_id, comment_id, reported_user_id, reason } = req.post;
|
||||
|
||||
@@ -18,10 +20,40 @@ export default (router, tpl) => {
|
||||
return res.json({ success: false, msg: "Must specify an item, comment, or user to report." }, 400);
|
||||
}
|
||||
|
||||
const ip = security.getRealIP(req);
|
||||
const isGuest = !req.session;
|
||||
|
||||
if (isGuest) {
|
||||
// CAPTCHA verification for guest reports (bypassed for .onion and localhost)
|
||||
const isOnion = lib.isOnionRequest(req);
|
||||
const isLocalhost = lib.isLocalhostRequest(req);
|
||||
if (!isOnion && !isLocalhost && cfg.recaptcha?.enabled && cfg.recaptcha?.secret_key) {
|
||||
const rcToken = req.post['g-recaptcha-response'];
|
||||
if (!rcToken) {
|
||||
return res.json({ success: false, msg: "Please complete the CAPTCHA." }, 400);
|
||||
}
|
||||
try {
|
||||
const verifyRes = await fetch('https://www.google.com/recaptcha/api/siteverify', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ secret: cfg.recaptcha.secret_key, response: rcToken, remoteip: ip })
|
||||
});
|
||||
const { success } = await verifyRes.json();
|
||||
if (!success) {
|
||||
return res.json({ success: false, msg: "CAPTCHA verification failed. Please try again." }, 400);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[REPORT] reCAPTCHA error:', e.message);
|
||||
return res.json({ success: false, msg: "CAPTCHA verification error. Please try again." }, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reportRes = await db`
|
||||
INSERT INTO reports (reporter_id, item_id, comment_id, user_id, reason)
|
||||
INSERT INTO reports (reporter_id, reporter_ip, item_id, comment_id, user_id, reason)
|
||||
VALUES (
|
||||
${req.session.id},
|
||||
${req.session ? req.session.id : null},
|
||||
${ip},
|
||||
${item_id ? +item_id : null},
|
||||
${comment_id ? +comment_id : null},
|
||||
${reported_user_id ? +reported_user_id : null},
|
||||
@@ -86,7 +118,8 @@ export default (router, tpl) => {
|
||||
c.content AS comment_body,
|
||||
COALESCE(r.item_id, c.item_id) AS resolved_item_id,
|
||||
COALESCE(i.dest, ci.dest) AS resolved_item_dest,
|
||||
COALESCE(i.mime, ci.mime) AS resolved_item_mime
|
||||
COALESCE(i.mime, ci.mime) AS resolved_item_mime,
|
||||
COALESCE(i.visibility, ci.visibility, 0) AS resolved_item_visibility
|
||||
FROM reports r
|
||||
LEFT JOIN "user" rep ON r.reporter_id = rep.id
|
||||
LEFT JOIN "user" tgt_u ON r.user_id = tgt_u.id
|
||||
|
||||
@@ -185,9 +185,24 @@ export default (router, tpl) => {
|
||||
data.current_user_hall_slug = (data.tmp && data.tmp.userHall && typeof data.tmp.userHall === 'object') ? data.tmp.userHall.slug : (data.tmp && data.tmp.userHall ? data.tmp.userHall : '');
|
||||
data.current_user_hall_owner = (data.tmp && data.tmp.userHallOwner) ? data.tmp.userHallOwner : '';
|
||||
data.item_has_dimensions = !!(item.width && item.height);
|
||||
// When guest anonymization is active, suppress source URL from the info modal
|
||||
if (cfg.main.guest_anonymize && !req.session && item.src) {
|
||||
item.src = null;
|
||||
// When guest anonymization is active, suppress uploader identity, banner, avatar, and source URL
|
||||
if (cfg.main.guest_anonymize && !req.session) {
|
||||
if (item.src) item.src = null;
|
||||
item.username = 'anonymous';
|
||||
item.author_banner_file = null;
|
||||
item.author_banner_position = null;
|
||||
item.author_banner_size = null;
|
||||
item.author_avatar = null;
|
||||
item.author_avatar_file = null;
|
||||
item.author_color = null;
|
||||
item.author_description = null;
|
||||
item.author_display_name = null;
|
||||
item.author_id = null;
|
||||
if (data.uploader) {
|
||||
data.uploader.name = 'anonymous';
|
||||
data.uploader.id = null;
|
||||
data.uploader.color = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+38
-3
@@ -26,7 +26,7 @@ import { createI18n } from "./inc/i18n.mjs";
|
||||
import { safeDeleteMediaFile, purgeExpiredUploads } from "./inc/lib_delete.mjs";
|
||||
|
||||
import security from "./inc/security.mjs";
|
||||
import { initPrivateItems, getPrivateItemFromPath, isPrivateItemPath, render502 } from "./inc/private_items.mjs";
|
||||
import { initPrivateItems, getPrivateItemFromPath, isPrivateItemPath, render502, render451 } from "./inc/private_items.mjs";
|
||||
|
||||
import { createRequire } from 'module';
|
||||
const _require = createRequire(import.meta.url);
|
||||
@@ -87,7 +87,7 @@ const _rcEnabled = !!(cfg.recaptcha && cfg.recaptcha.enabled && cfg.recaptcha.si
|
||||
const _rcSiteKey = (cfg.recaptcha && cfg.recaptcha.site_key) || '';
|
||||
|
||||
function getGateLoginInjection(req) {
|
||||
const rcEnabled = _rcEnabled && !lib.isOnionRequest(req);
|
||||
const rcEnabled = _rcEnabled && !lib.isOnionRequest(req) && !lib.isLocalhostRequest(req);
|
||||
return `
|
||||
<div id="hot-corner" style="position:fixed;bottom:0;left:0;width:20px;height:20px;z-index:9999;"></div>
|
||||
|
||||
@@ -726,6 +726,41 @@ process.on('uncaughtException', err => {
|
||||
if (req.url.pathname.match(/^\/(b|c|t|ca|a|memes)\//) || req.url.pathname.startsWith('/s/emojis/')) {
|
||||
const privItem = getPrivateItemFromPath(req.url.pathname);
|
||||
if (privItem) {
|
||||
if (privItem.isUnavailable) {
|
||||
// Unavailable item (visibility === 3):
|
||||
// Direct URLs MUST serve 451 when requested by non-admins (or without a session)
|
||||
let isAdmin = false;
|
||||
if (req.cookies?.session) {
|
||||
const _sessionHash = lib.sha256(req.cookies.session);
|
||||
let user = _scGet(_sessionHash);
|
||||
if (!user) {
|
||||
const urows = await db`
|
||||
select "user".id, "user".user, "user".admin, "user".is_moderator, "user".banned, "user".ban_expires
|
||||
from "user_sessions"
|
||||
left join "user" on "user".id = "user_sessions".user_id
|
||||
where "user_sessions".session = ${_sessionHash}
|
||||
limit 1
|
||||
`;
|
||||
if (urows.length > 0) {
|
||||
user = urows[0];
|
||||
_scSet(_sessionHash, user);
|
||||
}
|
||||
}
|
||||
if (user && !user.banned && user.admin) {
|
||||
isAdmin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAdmin) {
|
||||
render451(req, res);
|
||||
req.url.pathname = '/unavailable_item_bypass';
|
||||
return;
|
||||
}
|
||||
|
||||
res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
|
||||
return;
|
||||
}
|
||||
|
||||
// Private item (visibility === 2):
|
||||
// Direct URLs MUST serve 502 when requested without a session (or by unauthorized users),
|
||||
// regardless of the protect_files setting.
|
||||
@@ -1693,7 +1728,7 @@ process.on('uncaughtException', err => {
|
||||
const defaultRecaptcha = !!(cfg.recaptcha && cfg.recaptcha.enabled && cfg.recaptcha.site_key);
|
||||
let perRequestRecaptcha = defaultRecaptcha;
|
||||
|
||||
if (effectiveReq && lib.isOnionRequest(effectiveReq)) {
|
||||
if (effectiveReq && (lib.isOnionRequest(effectiveReq) || lib.isLocalhostRequest(effectiveReq))) {
|
||||
perRequestRecaptcha = false;
|
||||
} else if (data && typeof data.recaptcha_enabled === 'boolean') {
|
||||
perRequestRecaptcha = data.recaptcha_enabled;
|
||||
|
||||
Reference in New Issue
Block a user