Added georestriction with SFW mode.

This commit is contained in:
2026-02-04 05:39:14 +01:00
parent ce107e6b65
commit 1a84f899e7
35 changed files with 777 additions and 112 deletions

View File

@@ -1,6 +1,7 @@
/* eslint-disable no-param-reassign */
import { stringify } from '@brillout/json-serializer/stringify'; /* eslint-disable-line import/extensions */
import IPCIDR from 'ip-cidr';
import argv from '../argv.js';
import {
login,
@@ -14,6 +15,10 @@ import {
import { fetchUser } from '../users.js';
function getIp(req) {
if (argv.ip) {
return argv.ip;
}
const ip = req.headers['x-forwarded-for']?.split(',')[0] || req.connection.remoteAddress;
const unmappedIp = ip?.includes('.')

View File

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

80
src/web/restrictions.js Normal file
View File

@@ -0,0 +1,80 @@
import config from 'config';
import path from 'path';
import { Reader } from '@maxmind/geoip2-node';
import initLogger from '../logger.js';
const logger = initLogger();
const regions = config.restrictions.regions;
export default async function initRestrictionHandler() {
const reader = await Reader.open('assets/GeoLite2-City.mmdb');
function getRestriction(req) {
if (req.session.restriction && req.session.country && req.session.restrictionIp === req.userIp) {
return {
restriction: req.session.restriction,
country: req.session.country,
};
}
const location = reader.city(req.userIp);
const country = location.country.isoCode;
const subdivision = location.subdivisions?.[0]?.isoCode;
if (regions[country]?.[subdivision]) {
// state or province restriction
return {
restriction: config.restrictions.modes[regions[country][subdivision]],
country,
};
}
if (regions[country]) {
// country restriction
return {
restriction: config.restrictions.modes[regions[country]],
country,
};
}
return {
restriction: null,
country,
};
}
function restrictionHandler(req, res, next) {
if (!config.restrictions.enabled) {
next();
return;
}
try {
const { restriction, country } = getRestriction(req);
if (restriction === 'block' || req.path === '/sfw/') {
res.render(path.join(import.meta.dirname, '../../assets/sfw.ejs'), {
noVpn: config.restrictions.noVpn.includes(country),
});
return;
}
if (req.session.restriction !== restriction) {
req.session.restrictionIp = req.userIp;
req.session.restriction = restriction;
req.session.country = country;
}
req.restriction = restriction;
req.country = country;
} catch (error) {
logger.error(`Failed Maxmind IP lookup for ${req.ip}: ${error.message}`);
}
next();
}
return restrictionHandler;
}

View File

@@ -12,6 +12,7 @@ import redis from '../redis.js';
import errorHandler from './error.js';
import consentHandler from './consent.js';
import initRestrictionHandler from './restrictions.js';
import { scenesRouter } from './scenes.js';
import { actorsRouter } from './actors.js';
@@ -48,9 +49,11 @@ const isProduction = process.env.NODE_ENV === 'production';
export default async function initServer() {
const app = express();
const router = Router();
const restrictionHandler = await initRestrictionHandler();
app.use(compression());
app.disable('x-powered-by');
app.set('view engine', 'ejs');
router.use(boolParser());
@@ -58,7 +61,7 @@ export default async function initServer() {
router.use('/', express.static('static'));
router.use('/media', express.static(config.media.path));
router.use((req, res, next) => {
router.use((req, _res, next) => {
if (req.headers.cookie) {
const cookies = cookie.parse(req.headers.cookie);
@@ -109,11 +112,13 @@ export default async function initServer() {
router.use(viteDevMiddleware);
}
router.get('/consent', (req, res) => {
router.use(restrictionHandler);
router.get('/consent', (_req, res) => {
res.sendFile(path.join(import.meta.dirname, '../../assets/consent.html'));
});
router.use('/api/*', async (req, res, next) => {
router.use('/api/*', async (req, _res, next) => {
if (req.headers['api-user']) {
await verifyKey(req.headers['api-user'], req.headers['api-key'], req);
@@ -159,7 +164,7 @@ export default async function initServer() {
router.use(consentHandler);
router.use((req, res, next) => {
router.use((_req, res, next) => {
/* eslint-disable no-param-reassign */
res.set('Accept-CH', 'Sec-CH-Prefers-Color-Scheme');
res.set('Vary', 'Sec-CH-Prefers-Color-Scheme');