diff --git a/files/__main__.py b/files/__main__.py index 0ce9a5c85..099957a9e 100644 --- a/files/__main__.py +++ b/files/__main__.py @@ -57,7 +57,7 @@ app.config["CACHE_SOURCE_CHECK"] = True if SITE == 'watchpeopledie.tv': app.config["SESSION_COOKIE_DOMAIN"] = SITE -def get_CF(): +def get_IP(): with app.app_context(): x = request.headers.get('CF-Connecting-IP') if not x: @@ -66,7 +66,7 @@ def get_CF(): limiter = Limiter( app=app, - key_func=get_CF, + key_func=get_IP, default_limits=[DEFAULT_RATELIMIT], application_limits=["10/second;200/minute;5000/hour;30000/day"], storage_uri=app.config["CACHE_REDIS_URL"], diff --git a/files/classes/__init__.py b/files/classes/__init__.py index 1f3ae0901..88f8ab9fc 100644 --- a/files/classes/__init__.py +++ b/files/classes/__init__.py @@ -34,3 +34,6 @@ from .push_subscriptions import * from .group import * from .group_membership import * from .orgy import * + +if FEATURES['IP_LOGGING']: + from .ip_logs import * diff --git a/files/classes/ip_logs.py b/files/classes/ip_logs.py new file mode 100644 index 000000000..09ea519fe --- /dev/null +++ b/files/classes/ip_logs.py @@ -0,0 +1,22 @@ +import time + +from sqlalchemy import Column, ForeignKey +from sqlalchemy.sql.sqltypes import * + +from files.classes import Base + +class IPLog(Base): + __tablename__ = "ip_logs" + user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) + ip = Column(String, primary_key=True) + created_utc = Column(Integer) + last_used = Column(Integer) + + def __init__(self, *args, **kwargs): + if "created_utc" not in kwargs: + kwargs["created_utc"] = int(time.time()) + kwargs["last_used"] = kwargs["created_utc"] + super().__init__(*args, **kwargs) + + def __repr__(self): + return f"<{self.__class__.__name__}(id={self.id})>" diff --git a/files/helpers/config/const.py b/files/helpers/config/const.py index 147052801..c4606c089 100644 --- a/files/helpers/config/const.py +++ b/files/helpers/config/const.py @@ -178,6 +178,7 @@ PERMS = { # Minimum admin_level to perform action. 'NOTIFICATIONS_HOLE_CREATION': 1, 'NOTIFICATIONS_GROUP_CREATION': 1, 'NOTIFICATIONS_MODERATOR_ACTIONS': 1, + 'EXEMPT_FROM_IP_LOGGING': 1, 'IS_PERMA_PROGSTACKED': 2, 'USER_BADGES': 2, @@ -436,6 +437,7 @@ FEATURES = { 'NSFW_MARKING': True, 'PING_GROUPS': True, 'BOTS': True, + 'IP_LOGGING': False, } HOUSES = ["Furry","Femboy","Vampire","Racist","Edgy"] @@ -737,6 +739,7 @@ elif SITE == 'watchpeopledie.tv': FEATURES['NSFW_MARKING'] = False FEATURES['BOTS'] = False FEATURES['HAT_SUBMISSIONS'] = False + FEATURES['IP_LOGGING'] = True HOUSES = ["Furry","Femboy","Vampire","Edgy"] PERMS['POST_COMMENT_EDITING'] = 3 @@ -839,6 +842,7 @@ elif SITE == 'devrama.net': else: # localhost or testing environment implied FEATURES['PRONOUNS'] = True FEATURES['USERS_PERMANENT_WORD_FILTERS'] = True + FEATURES['IP_LOGGING'] = True HOLE_BANNER_LIMIT = 69420 BOT_IDs = {AUTOJANNY_ID, SNAPPY_ID, LONGPOSTBOT_ID, ZOZBOT_ID} diff --git a/files/routes/allroutes.py b/files/routes/allroutes.py index f6f94f684..b7b1c25ee 100644 --- a/files/routes/allroutes.py +++ b/files/routes/allroutes.py @@ -5,7 +5,10 @@ from files.helpers.config.const import * from files.helpers.settings import get_setting from files.helpers.cloudflare import CLOUDFLARE_AVAILABLE from files.routes.wrappers import * -from files.__main__ import app, limiter, get_CF, redis_instance +from files.__main__ import app, limiter, get_IP, redis_instance + +if FEATURES['IP_LOGGING']: + from files.classes.ip_logs import * @app.before_request def before_request(): @@ -66,10 +69,24 @@ def after_request(response): g.v.last_active = timestamp g.db.add(g.v) + if FEATURES['IP_LOGGING']: + if g.v.admin_level < PERMS['EXEMPT_FROM_IP_LOGGING'] and user_id != CARP_ID: + ip = get_IP() + existing = g.db.query(IPLog).filter_by(user_id=user_id, ip=ip).one_or_none() + if existing: + existing.last_used = time.time() + g.db.add(existing) + else: + ip_log = IPLog( + user_id=user_id, + ip=ip, + ) + g.db.add(ip_log) + _commit_and_close_db() if request.method == "POST": - redis_instance.delete(f'LIMITER/{get_CF()}/{request.endpoint}:{request.path}/1/1/second') + redis_instance.delete(f'LIMITER/{get_IP()}/{request.endpoint}:{request.path}/1/1/second') if user_id: redis_instance.delete(f'LIMITER/{SITE}-{user_id}/{request.endpoint}:{request.path}/1/1/second') diff --git a/files/routes/login.py b/files/routes/login.py index f715023f4..757530ce6 100644 --- a/files/routes/login.py +++ b/files/routes/login.py @@ -2,7 +2,7 @@ import secrets import requests -from files.__main__ import app, cache, get_CF, limiter +from files.__main__ import app, cache, get_IP, limiter from files.classes.follows import Follow from files.helpers.actions import * from files.helpers.config.const import * @@ -106,7 +106,7 @@ def login_post(v): def log_failed_admin_login_attempt(account, type): if not account or account.admin_level < PERMS['WARN_ON_FAILED_LOGIN']: return - ip = get_CF() + ip = get_IP() print(f"A site admin from {ip} failed to login to account @{account.user_name} (invalid {type})") t = time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(time.time())) log_file(f"{t}, {ip}, {account.username}, {type}", "admin_failed_logins.log") diff --git a/migrations/20240206-add-ip-logging-to-wpd.sql b/migrations/20240206-add-ip-logging-to-wpd.sql new file mode 100644 index 000000000..db64c641a --- /dev/null +++ b/migrations/20240206-add-ip-logging-to-wpd.sql @@ -0,0 +1,12 @@ +create table ip_logs ( + user_id integer not null, + ip varchar(39) not null, + created_utc integer not null, + last_used integer not null +); + +alter table only ip_logs + add constraint ip_logs_pkey primary key (user_id, ip); + +alter table only ip_logs + add constraint ip_logs_user_fkey foreign key (user_id) references public.users(id);