Determining location with restrictions disabled, cleaned up.

This commit is contained in:
2026-07-23 05:34:28 +02:00
parent 4c48700086
commit 77b27a56f7
28 changed files with 590 additions and 71 deletions

View File

@@ -423,7 +423,7 @@ async function updatePassword(password, reqUser, reqSession) {
.where('id', reqUser.id)
.update('password', storedPassword);
const country = reqSession.country && await knex('countries').where('alpha2', reqSession.country).first();
const country = reqSession.geo?.country && await knex('countries').where('alpha2', reqSession.geo.country).first();
await sendEmails([{
template: 'password',

View File

@@ -1,7 +1,7 @@
import { pageContext } from '../renderer/usePageContext.js';
function getBasePath(media, options) {
if (pageContext.restriction) {
if (pageContext.geo.restriction) {
return pageContext.env.media.assetPath;
}
@@ -17,11 +17,11 @@ function getBasePath(media, options) {
}
function getFilename(media, type, options) {
if (pageContext.restriction && type && !options?.original) {
if (pageContext.geo.restriction && type && !options?.original) {
return media.sfw?.[type];
}
if (pageContext.restriction) {
if (pageContext.geo.restriction) {
return media.sfw?.path;
}

View File

@@ -1,6 +1,7 @@
import crypto from 'node:crypto';
import { Router } from 'express';
import IPCIDR from 'ip-cidr';
import runtimeId from '../../utils/runtime-id.js';
import argv from '../argv.js';
import {
@@ -24,7 +25,6 @@ import {
// changes every time the process restarts, so a deploy invalidates all cached session users
// even if they're within the staleness window below
const runtimeId = crypto.randomUUID();
const sessionStaleThreshold = 5 * 60 * 1000;
// spread into a new object rather than mutating the user we're about to hand back to the

View File

@@ -60,7 +60,7 @@ export default async function mainHandler(req, res, next) {
siteKey: config.auth.captcha.siteKey,
},
},
restriction: req.restriction,
geo: req.geo,
meta: {
now: new Date().toISOString(),
},

View File

@@ -31,7 +31,7 @@ export async function fetchMoviesApi(req, res) {
} = await fetchMovies(await curateMoviesQuery(req.query), {
page: Number(req.query.page) || 1,
limit: Number(req.query.limit) || 30,
}, req.user, { restriction: req.restriction });
}, req.user, { restriction: req.geo.restriction });
res.send({
movies,
@@ -45,7 +45,7 @@ export async function fetchMoviesApi(req, res) {
}
export async function fetchMovieApi(req, res) {
const [movie] = await fetchMoviesById([Number(req.params.movieId)], { reqUser: req.user }, { restriction: req.restriction });
const [movie] = await fetchMoviesById([Number(req.params.movieId)], { reqUser: req.user }, { restriction: req.geo.restriction });
if (!movie) {
throw new HttpError(`No movie with ID ${req.params.movieId} found`, 404);
@@ -135,7 +135,7 @@ export async function fetchMoviesGraphql(query, req) {
page: query.page || 1,
limit: query.limit || 30,
aggregate: false,
}, req.user, { restriction: req.restriction });
}, req.user, { restriction: req.geo.restriction });
return {
nodes: movies,

View File

@@ -2,6 +2,7 @@ import path from 'node:path';
import { Reader } from '@maxmind/geoip2-node';
import config from 'config';
import runtimeId from '../../utils/runtime-id.js';
import initLogger from '../logger.js';
const logger = initLogger();
@@ -11,22 +12,47 @@ export default async function initRestrictionHandler() {
const reader = await Reader.open('assets/GeoLite2-City.mmdb');
function getRestriction(req) {
if (Object.hasOwn(req.session, 'restriction') && Object.hasOwn(req.session, 'country') && req.session.restrictionIp === req.userIp) {
return {
restriction: req.session.restriction,
country: req.session.country,
};
if (req.session.geo
&& Object.hasOwn(req.session.geo, 'country')
&& req.session.geo.ip === req.userIp
&& req.session.geo.runtimeId === runtimeId) {
const { country, subdivision } = req.session.geo;
if (!config.restrictions.enabled) {
return {
restriction: 0,
country,
subdivision,
};
}
if (Object.hasOwn(req.session.geo, 'restriction')) {
return {
restriction: req.session.geo.restriction,
country,
subdivision,
};
}
}
const location = reader.city(req.userIp);
const country = location.country.isoCode;
const subdivision = location.subdivisions?.[0]?.isoCode;
if (!config.restrictions.enabled) {
return {
restriction: 0,
country,
subdivision,
};
}
if (regions[country]?.[subdivision]) {
// state or province restriction
return {
restriction: config.restrictions.modes[regions[country][subdivision]],
country,
subdivision,
};
}
@@ -35,23 +61,24 @@ export default async function initRestrictionHandler() {
return {
restriction: config.restrictions.modes[regions[country]],
country,
subdivision,
};
}
return {
restriction: null,
country,
subdivision,
};
}
function restrictionHandler(req, res, next) {
if (!config.restrictions.enabled) {
next();
return;
}
try {
const { restriction, country } = getRestriction(req);
const {
restriction,
country,
subdivision,
} = getRestriction(req);
if (restriction === 'block' || req.path === '/sfw/') {
res.render(path.join(import.meta.dirname, '../../assets/sfw.ejs'), {
@@ -61,21 +88,38 @@ export default async function initRestrictionHandler() {
return;
}
if (req.session.restriction !== restriction) {
req.session.restrictionIp = req.userIp;
req.session.restriction = restriction;
req.session.country = country;
if (req.session.geo?.restriction !== restriction) {
req.session.geo = {
ip: req.userIp,
runtimeId,
restriction,
country,
subdivision,
};
}
req.restriction = restriction;
req.country = country;
req.geo = {
restriction,
country,
subdivision,
};
}
catch (error) {
logger.error(`Failed Maxmind IP lookup for ${req.ip}: ${error.message}`);
req.session.restrictionIp = req.userIp;
req.session.restriction = 0;
req.session.country = null;
req.session.geo = {
ip: req.userIp,
runtimeId,
restriction: 0,
country: null,
subdivision: null,
};
req.geo = {
restriction: 0,
country: null,
subdivision: null,
};
}
next();

View File

@@ -70,7 +70,7 @@ async function fetchScenesApi(req, res) {
page: Number(req.query.page) || 1,
limit: Number(req.query.limit) || 30,
}, req.user, {
restriction: req.restriction,
restriction: req.geo.restriction,
});
res.send({
@@ -254,7 +254,7 @@ export async function fetchScenesGraphql(query, req) {
}
async function fetchSceneApi(req, res) {
const [scene] = await fetchScenesById([Number(req.params.sceneId)], { reqUser: req.user }, { restriction: req.restriction });
const [scene] = await fetchScenesById([Number(req.params.sceneId)], { reqUser: req.user }, { restriction: req.geo.restriction });
if (!scene) {
throw new HttpError(`No scene with ID ${req.params.sceneId} found`, 404);
@@ -267,7 +267,7 @@ export async function fetchScenesByIdGraphql(query, req) {
const scenes = await fetchScenesById([].concat(query.id, query.ids).filter(Boolean), {
reqUser: req.user,
includePartOf: true,
restriction: req.restriction,
restriction: req.geo.restriction,
});
if (query.ids) {

View File

@@ -4,7 +4,7 @@ export async function fetchTagsApi(req, res) {
const tags = await fetchTags({
query: req.query.query,
}, {
restriction: req.restriction,
restriction: req.geo.restriction,
});
res.send(tags);